mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 13:57:10 +00:00
fix: error when gemini multiple functionCall in one response
This commit is contained in:
parent
69ff552dc9
commit
98d129517a
3 changed files with 53 additions and 109 deletions
|
|
@ -99,7 +99,7 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
* parentMessageId: string?,
|
* parentMessageId: string?,
|
||||||
* stream: boolean?,
|
* stream: boolean?,
|
||||||
* onProgress: function?,
|
* onProgress: function?,
|
||||||
* functionResponse: FunctionResponse?,
|
* functionResponse?: FunctionResponse | FunctionResponse[],
|
||||||
* system: string?,
|
* system: string?,
|
||||||
* image: string?,
|
* image: string?,
|
||||||
* maxOutputTokens: number?,
|
* maxOutputTokens: number?,
|
||||||
|
|
@ -109,7 +109,7 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
* replyPureTextCallback: Function,
|
* replyPureTextCallback: Function,
|
||||||
* toolMode: 'AUTO' | 'ANY' | 'NONE'
|
* toolMode: 'AUTO' | 'ANY' | 'NONE'
|
||||||
* search: boolean,
|
* search: boolean,
|
||||||
* codeExecution: boolean
|
* codeExecution: boolean,
|
||||||
* }} opt
|
* }} opt
|
||||||
* @returns {Promise<{conversationId: string?, parentMessageId: string, text: string, id: string}>}
|
* @returns {Promise<{conversationId: string?, parentMessageId: string, text: string, id: string}>}
|
||||||
*/
|
*/
|
||||||
|
|
@ -138,12 +138,20 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
// }
|
// }
|
||||||
const idThis = crypto.randomUUID()
|
const idThis = crypto.randomUUID()
|
||||||
const idModel = crypto.randomUUID()
|
const idModel = crypto.randomUUID()
|
||||||
const thisMessage = opt.functionResponse
|
if (opt.functionResponse && !typeof Array.isArray(opt.functionResponse)) {
|
||||||
|
opt.functionResponse = [opt.functionResponse]
|
||||||
|
}
|
||||||
|
const thisMessage = opt.functionResponse?.length > 0
|
||||||
? {
|
? {
|
||||||
role: 'user',
|
role: 'user',
|
||||||
parts: [{
|
// parts: [{
|
||||||
functionResponse: opt.functionResponse
|
// functionResponse: opt.functionResponse
|
||||||
}],
|
// }],
|
||||||
|
parts: opt.functionResponse.map(i => {
|
||||||
|
return {
|
||||||
|
functionResponse: i
|
||||||
|
}
|
||||||
|
}),
|
||||||
id: idThis,
|
id: idThis,
|
||||||
parentMessageId: opt.parentMessageId || undefined
|
parentMessageId: opt.parentMessageId || undefined
|
||||||
}
|
}
|
||||||
|
|
@ -241,7 +249,9 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
delete content.parentMessageId
|
delete content.parentMessageId
|
||||||
delete content.conversationId
|
delete content.conversationId
|
||||||
})
|
})
|
||||||
// logger.info(JSON.stringify(body))
|
if (this.debug) {
|
||||||
|
logger.info(JSON.stringify(body))
|
||||||
|
}
|
||||||
let result = await newFetch(url, {
|
let result = await newFetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
|
|
@ -265,19 +275,19 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
}
|
}
|
||||||
responseContent = response.candidates[0].content
|
responseContent = response.candidates[0].content
|
||||||
let groundingMetadata = response.candidates[0].groundingMetadata
|
let groundingMetadata = response.candidates[0].groundingMetadata
|
||||||
if (responseContent.parts.find(i => i.functionCall)) {
|
if (responseContent.parts.filter(i => i.functionCall).length > 0) {
|
||||||
// functionCall
|
// functionCall
|
||||||
const functionCall = responseContent.parts.find(i => i.functionCall).functionCall
|
const functionCall = responseContent.parts.filter(i => i.functionCall).map(i => i.functionCall)
|
||||||
const text = responseContent.parts.find(i => i.text)?.text
|
const text = responseContent.parts.find(i => i.text)?.text
|
||||||
if (text) {
|
if (text) {
|
||||||
// send reply first
|
// send reply first
|
||||||
logger.info('send message: ' + text)
|
logger.info('send message: ' + text)
|
||||||
opt.replyPureTextCallback && await opt.replyPureTextCallback(text)
|
opt.replyPureTextCallback && await opt.replyPureTextCallback(text)
|
||||||
}
|
}
|
||||||
// Gemini有时候只回复一个空的functionCall,无语死了
|
let /** @type {FunctionResponse[]} **/ fcResults = []
|
||||||
if (functionCall.name) {
|
for (let fc of functionCall) {
|
||||||
logger.info(JSON.stringify(functionCall))
|
logger.info(JSON.stringify(fc))
|
||||||
const funcName = functionCall.name
|
const funcName = fc.name
|
||||||
let chosenTool = this.tools.find(t => t.name === funcName)
|
let chosenTool = this.tools.find(t => t.name === funcName)
|
||||||
/**
|
/**
|
||||||
* @type {FunctionResponse}
|
* @type {FunctionResponse}
|
||||||
|
|
@ -299,7 +309,7 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
try {
|
try {
|
||||||
let isAdmin = ['admin', 'owner'].includes(this.e.sender.role) || (this.e.group?.is_admin && this.e.isMaster)
|
let isAdmin = ['admin', 'owner'].includes(this.e.sender.role) || (this.e.group?.is_admin && this.e.isMaster)
|
||||||
let isOwner = ['owner'].includes(this.e.sender.role) || (this.e.group?.is_owner && this.e.isMaster)
|
let isOwner = ['owner'].includes(this.e.sender.role) || (this.e.group?.is_owner && this.e.isMaster)
|
||||||
let args = Object.assign(functionCall.args, {
|
let args = Object.assign(fc.args, {
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isOwner,
|
isOwner,
|
||||||
sender: this.e.sender,
|
sender: this.e.sender,
|
||||||
|
|
@ -316,29 +326,21 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let responseOpt = _.cloneDeep(opt)
|
fcResults.push(functionResponse)
|
||||||
responseOpt.parentMessageId = idModel
|
|
||||||
responseOpt.functionResponse = functionResponse
|
|
||||||
// 递归直到返回text
|
|
||||||
// 先把这轮的消息存下来
|
|
||||||
await this.upsertMessage(thisMessage)
|
|
||||||
responseContent = handleSearchResponse(responseContent).responseContent
|
|
||||||
const respMessage = Object.assign(responseContent, {
|
|
||||||
id: idModel,
|
|
||||||
parentMessageId: idThis
|
|
||||||
})
|
|
||||||
await this.upsertMessage(respMessage)
|
|
||||||
return await this.sendMessage('', responseOpt)
|
|
||||||
} else {
|
|
||||||
// 谷歌抽风了,瞎调函数,不保存这轮,直接返回
|
|
||||||
return {
|
|
||||||
text: '',
|
|
||||||
conversationId: '',
|
|
||||||
parentMessageId: opt.parentMessageId,
|
|
||||||
id: '',
|
|
||||||
error: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let responseOpt = _.cloneDeep(opt)
|
||||||
|
responseOpt.parentMessageId = idModel
|
||||||
|
responseOpt.functionResponse = fcResults
|
||||||
|
// 递归直到返回text
|
||||||
|
// 先把这轮的消息存下来
|
||||||
|
await this.upsertMessage(thisMessage)
|
||||||
|
responseContent = handleSearchResponse(responseContent).responseContent
|
||||||
|
const respMessage = Object.assign(responseContent, {
|
||||||
|
id: idModel,
|
||||||
|
parentMessageId: idThis
|
||||||
|
})
|
||||||
|
await this.upsertMessage(respMessage)
|
||||||
|
return await this.sendMessage('', responseOpt)
|
||||||
}
|
}
|
||||||
if (responseContent) {
|
if (responseContent) {
|
||||||
await this.upsertMessage(thisMessage)
|
await this.upsertMessage(thisMessage)
|
||||||
|
|
|
||||||
|
|
@ -790,14 +790,14 @@ async function collectTools (e) {
|
||||||
}
|
}
|
||||||
let fullTools = [
|
let fullTools = [
|
||||||
new EditCardTool(),
|
new EditCardTool(),
|
||||||
new QueryStarRailTool(),
|
// new QueryStarRailTool(),
|
||||||
new WebsiteTool(),
|
new WebsiteTool(),
|
||||||
new JinyanTool(),
|
new JinyanTool(),
|
||||||
new KickOutTool(),
|
new KickOutTool(),
|
||||||
new WeatherTool(),
|
new WeatherTool(),
|
||||||
new SendPictureTool(),
|
new SendPictureTool(),
|
||||||
new SendVideoTool(),
|
new SendVideoTool(),
|
||||||
new ImageCaptionTool(),
|
// new ImageCaptionTool(),
|
||||||
new SearchVideoTool(),
|
new SearchVideoTool(),
|
||||||
new SendAvatarTool(),
|
new SendAvatarTool(),
|
||||||
new SerpImageTool(),
|
new SerpImageTool(),
|
||||||
|
|
@ -805,45 +805,40 @@ async function collectTools (e) {
|
||||||
new SendMusicTool(),
|
new SendMusicTool(),
|
||||||
new SerpIkechan8370Tool(),
|
new SerpIkechan8370Tool(),
|
||||||
new SerpTool(),
|
new SerpTool(),
|
||||||
new SendAudioMessageTool(),
|
// new SendAudioMessageTool(),
|
||||||
new ProcessPictureTool(),
|
// new ProcessPictureTool(),
|
||||||
new APTool(),
|
new APTool(),
|
||||||
new HandleMessageMsgTool(),
|
new HandleMessageMsgTool(),
|
||||||
new QueryUserinfoTool(),
|
new QueryUserinfoTool(),
|
||||||
new EliMusicTool(),
|
// new EliMusicTool(),
|
||||||
new EliMovieTool(),
|
// new EliMovieTool(),
|
||||||
new SendMessageToSpecificGroupOrUserTool(),
|
new SendMessageToSpecificGroupOrUserTool(),
|
||||||
new SendDiceTool(),
|
new SendDiceTool(),
|
||||||
new QueryGenshinTool(),
|
new QueryGenshinTool(),
|
||||||
new SetTitleTool()
|
new SetTitleTool()
|
||||||
]
|
]
|
||||||
// todo 3.0再重构tool的插拔和管理
|
// todo 3.0再重构tool的插拔和管理
|
||||||
let tools = [
|
let /** @type{AbstractTool} **/ tools = [
|
||||||
new SendAvatarTool(),
|
new SendAvatarTool(),
|
||||||
new SendDiceTool(),
|
new SendDiceTool(),
|
||||||
new SendMessageToSpecificGroupOrUserTool(),
|
new SendMessageToSpecificGroupOrUserTool(),
|
||||||
// new EditCardTool(),
|
// new EditCardTool(),
|
||||||
new QueryStarRailTool(),
|
// new QueryStarRailTool(),
|
||||||
new QueryGenshinTool(),
|
new QueryGenshinTool(),
|
||||||
|
new SendMusicTool(),
|
||||||
|
new SearchMusicTool(),
|
||||||
new ProcessPictureTool(),
|
new ProcessPictureTool(),
|
||||||
new WebsiteTool(),
|
new WebsiteTool(),
|
||||||
// new JinyanTool(),
|
// new JinyanTool(),
|
||||||
// new KickOutTool(),
|
// new KickOutTool(),
|
||||||
new WeatherTool(),
|
new WeatherTool(),
|
||||||
new SendPictureTool(),
|
new SendPictureTool(),
|
||||||
new SendAudioMessageTool(),
|
// new SendAudioMessageTool(),
|
||||||
new APTool(),
|
new APTool(),
|
||||||
// new HandleMessageMsgTool(),
|
// new HandleMessageMsgTool(),
|
||||||
serpTool,
|
serpTool,
|
||||||
new QueryUserinfoTool()
|
new QueryUserinfoTool()
|
||||||
]
|
]
|
||||||
try {
|
|
||||||
await import('../../avocado-plugin/apps/avocado.js')
|
|
||||||
tools.push(...[new EliMusicTool(), new EliMovieTool()])
|
|
||||||
} catch (err) {
|
|
||||||
tools.push(...[new SendMusicTool(), new SearchMusicTool()])
|
|
||||||
// logger.debug(logger.green('【ChatGPT-Plugin】插件avocado-plugin未安装') + ',安装后可查看最近热映电影与体验可玩性更高的点歌工具。\n可前往 https://github.com/Qz-Sean/avocado-plugin 获取')
|
|
||||||
}
|
|
||||||
let systemAddition = ''
|
let systemAddition = ''
|
||||||
if (e.isGroup) {
|
if (e.isGroup) {
|
||||||
let botInfo = await e.bot?.pickMember?.(e.group_id, getUin(e), true) || await e.bot?.getGroupMemberInfo?.(e.group_id, getUin(e), true)
|
let botInfo = await e.bot?.pickMember?.(e.group_id, getUin(e), true) || await e.bot?.getGroupMemberInfo?.(e.group_id, getUin(e), true)
|
||||||
|
|
@ -862,8 +857,8 @@ async function collectTools (e) {
|
||||||
let promptAddition = ''
|
let promptAddition = ''
|
||||||
let img = await getImg(e)
|
let img = await getImg(e)
|
||||||
if (img?.length > 0 && Config.extraUrl) {
|
if (img?.length > 0 && Config.extraUrl) {
|
||||||
tools.push(new ImageCaptionTool())
|
// tools.push(new ImageCaptionTool())
|
||||||
tools.push(new ProcessPictureTool())
|
// tools.push(new ProcessPictureTool())
|
||||||
promptAddition += `\nthe url of the picture(s) above: ${img.join(', ')}`
|
promptAddition += `\nthe url of the picture(s) above: ${img.join(', ')}`
|
||||||
} else {
|
} else {
|
||||||
tools.push(new SerpImageTool())
|
tools.push(new SerpImageTool())
|
||||||
|
|
@ -892,57 +887,4 @@ async function collectTools (e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAvailableBingToken (conversation, throttled = []) {
|
|
||||||
let allThrottled = false
|
|
||||||
if (!await redis.get('CHATGPT:BING_TOKENS')) {
|
|
||||||
return {
|
|
||||||
bingToken: null,
|
|
||||||
allThrottled
|
|
||||||
}
|
|
||||||
// throw new Error('未绑定Bing Cookie,请使用#chatgpt设置必应token命令绑定Bing Cookie')
|
|
||||||
}
|
|
||||||
|
|
||||||
let bingToken = ''
|
|
||||||
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
|
||||||
const normal = bingTokens.filter(element => element.State === '正常')
|
|
||||||
const restricted = bingTokens.filter(element => element.State === '受限')
|
|
||||||
|
|
||||||
// 判断受限的token是否已经可以解除
|
|
||||||
for (const restrictedToken of restricted) {
|
|
||||||
const now = new Date()
|
|
||||||
const tk = new Date(restrictedToken.DisactivationTime)
|
|
||||||
if (tk <= now) {
|
|
||||||
const index = bingTokens.findIndex(element => element.Token === restrictedToken.Token)
|
|
||||||
bingTokens[index].Usage = 0
|
|
||||||
bingTokens[index].State = '正常'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (normal.length > 0) {
|
|
||||||
const minElement = normal.reduce((min, current) => {
|
|
||||||
return current.Usage < min.Usage ? current : min
|
|
||||||
})
|
|
||||||
bingToken = minElement.Token
|
|
||||||
} else if (restricted.length > 0 && restricted.some(x => throttled.includes(x.Token))) {
|
|
||||||
allThrottled = true
|
|
||||||
const minElement = restricted.reduce((min, current) => {
|
|
||||||
return current.Usage < min.Usage ? current : min
|
|
||||||
})
|
|
||||||
bingToken = minElement.Token
|
|
||||||
} else {
|
|
||||||
// throw new Error('全部Token均已失效,暂时无法使用')
|
|
||||||
return {
|
|
||||||
bingToken: null,
|
|
||||||
allThrottled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 记录使用情况
|
|
||||||
const index = bingTokens.findIndex(element => element.Token === bingToken)
|
|
||||||
bingTokens[index].Usage += 1
|
|
||||||
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
|
|
||||||
return {
|
|
||||||
bingToken,
|
|
||||||
allThrottled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new Core()
|
export default new Core()
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export class SendPictureTool extends AbstractTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// await group.sendMsg(pictures)
|
// await group.sendMsg(pictures)
|
||||||
return 'picture has been sent to group' + target + errs.length > 0 ? `, but some pictures failed to send (${errs.join('、')})` : ''
|
return 'picture has been sent to group' + target + (errs.length > 0 ? `, but some pictures failed to send (${errs.join('、')})` : '')
|
||||||
} else {
|
} else {
|
||||||
let user = e.bot.pickUser(target)
|
let user = e.bot.pickUser(target)
|
||||||
if (e.group_id) {
|
if (e.group_id) {
|
||||||
|
|
@ -66,7 +66,7 @@ export class SendPictureTool extends AbstractTool {
|
||||||
errs.push(pic.url)
|
errs.push(pic.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 'picture has been sent to user' + target + errs.length > 0 ? `, but some pictures failed to send (${errs.join('、')})` : ''
|
return 'picture has been sent to user' + target + (errs.length > 0 ? `, but some pictures failed to send (${errs.join('、')})` : '')
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return `failed to send pictures, error: ${JSON.stringify(err)}`
|
return `failed to send pictures, error: ${JSON.stringify(err)}`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue