From 5f6c4e5abb5ff22851577bf789b6046e42e6e36d Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Thu, 12 Dec 2024 20:31:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20support=20search=20for=20gemini2:=20#ch?= =?UTF-8?q?atgpt=E5=BC=80=E5=90=AFgemini=E6=90=9C=E7=B4=A2=20#chatgpt?= =?UTF-8?q?=E5=BC=80=E5=90=AF=E4=BB=A3=E7=A0=81=E6=89=A7=E8=A1=8C=20?= =?UTF-8?q?=E6=9A=82=E6=97=B6=E4=B8=8D=E5=8F=AF=E5=B9=B6=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/management.js | 18 ++++++ client/CustomGoogleGeminiClient.js | 99 ++++++++++++++++++++++++++---- model/core.js | 4 +- utils/chat.js | 6 ++ utils/config.js | 4 +- 5 files changed, 118 insertions(+), 13 deletions(-) diff --git a/apps/management.js b/apps/management.js index 4ea163eb..3e63c0c6 100644 --- a/apps/management.js +++ b/apps/management.js @@ -341,6 +341,11 @@ export class ChatgptManagement extends plugin { reg: '^#chatgpt(开启|关闭)(工具箱|后台服务)$', fnc: 'switchToolbox', permission: 'master' + }, + { + reg: '^#chatgpt(开启|关闭)gemini(搜索|代码执行)$', + fnc: 'geminiOpenSearchCE', + permission: 'master' } ] }) @@ -1827,4 +1832,17 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, await this.reply('好的,已经关闭工具箱') } } + + async geminiOpenSearchCE (e) { + let msg = e.msg + let open = msg.includes('开启') + if (msg.includes('搜索')) { + Config.geminiEnableGoogleSearch = open + open && (Config.geminiEnableCodeExecution = !open) + } else { + Config.geminiEnableCodeExecution = open + open && (Config.geminiEnableGoogleSearch = !open) + } + await e.reply('操作成功') + } } diff --git a/client/CustomGoogleGeminiClient.js b/client/CustomGoogleGeminiClient.js index a950e232..dc33ef7b 100644 --- a/client/CustomGoogleGeminiClient.js +++ b/client/CustomGoogleGeminiClient.js @@ -27,13 +27,37 @@ export const HarmBlockThreshold = { * parts: Array<{ * text?: string, * functionCall?: FunctionCall, - * functionResponse?: FunctionResponse + * functionResponse?: FunctionResponse, + * executableCode?: { + * language: string, + * code: string + * }, + * codeExecutionResult?: { + * outcome: string, + * output: string + * } * }> * }} Content * * Gemini消息的基本格式 */ +/** + * @typedef {{ + * searchEntryPoint: { + * renderedContent: string, + * }, + * groundingChunks: Array<{ + * web: { + * uri: string, + * title: string + * } + * }>, + * webSearchQueries: Array + * }} GroundingMetadata + * 搜索结果的元数据 + */ + /** * @typedef {{ * name: string, @@ -80,7 +104,9 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { * temperature: number?, * topP: number?, * tokK: number?, - * replyPureTextCallback: Function + * replyPureTextCallback: Function, + * search: boolean, + * codeExecution: boolean * }} opt * @returns {Promise<{conversationId: string?, parentMessageId: string, text: string, id: string}>} */ @@ -163,15 +189,15 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { temperature: opt.temperature || 0.9, topP: opt.topP || 0.95, topK: opt.tokK || 16 - } + }, + tools: [] } if (this.tools?.length > 0) { - body.tools = [ - { - function_declarations: this.tools.map(tool => tool.function()) - // codeExecution: {} - } - ] + body.tools.push({ + function_declarations: this.tools.map(tool => tool.function()) + // codeExecution: {} + }) + // ANY要笑死人的效果 body.tool_config = { function_calling_config: { @@ -179,6 +205,12 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { } } } + if (opt.search) { + body.tools.push({ google_search: {} }) + } + if (opt.codeExecution) { + body.tools.push({ code_execution: {} }) + } if (opt.image) { delete body.tools } @@ -202,13 +234,14 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { */ let responseContent /** - * @type {{candidates: Array<{content: Content}>}} + * @type {{candidates: Array<{content: Content, groundingMetadata: GroundingMetadata}>}} */ let response = await result.json() if (this.debug) { console.log(JSON.stringify(response)) } responseContent = response.candidates[0].content + let groundingMetadata = response.candidates[0].groundingMetadata if (responseContent.parts.find(i => i.functionCall)) { // functionCall const functionCall = responseContent.parts.find(i => i.functionCall).functionCall @@ -265,6 +298,7 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { // 递归直到返回text // 先把这轮的消息存下来 await this.upsertMessage(thisMessage) + responseContent = handleSearchResponse(responseContent).responseContent const respMessage = Object.assign(responseContent, { id: idModel, parentMessageId: idThis @@ -290,11 +324,54 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { }) await this.upsertMessage(respMessage) } + let { final } = handleSearchResponse(responseContent) + try { + if (groundingMetadata?.groundingChunks) { + final += '\n参考资料\n' + groundingMetadata.groundingChunks.forEach(chunk => { + // final += `[${chunk.web.title}](${chunk.web.uri})\n` + final += `[${chunk.web.title}]\n` + }) + groundingMetadata.webSearchQueries.forEach(q => { + logger.info('search query: ' + q) + }) + } + } catch (err) { + logger.warn(err) + } + return { - text: responseContent.parts[0].text.trim(), + text: final, conversationId: '', parentMessageId: idThis, id: idModel } } } + +/** + * 处理成单独的text + * @param {Content} responseContent + * @returns {{final: string, responseContent}} + */ +function handleSearchResponse (responseContent) { + let final = '' + for (let part of responseContent.parts) { + if (part.text) { + final += part.text + } + if (part.executableCode) { + final += '\n执行代码:\n' + '```' + part.executableCode.language + '\n' + part.executableCode.code.trim() + '\n```\n\n' + } + if (part.codeExecutionResult) { + final += `\n执行结果(${part.codeExecutionResult.outcome}):\n` + '```\n' + part.codeExecutionResult.output + '\n```\n\n' + } + } + responseContent.parts = [{ + text: final + }] + return { + final, + responseContent + } +} diff --git a/model/core.js b/model/core.js index 9a2547f8..a215914c 100644 --- a/model/core.js +++ b/model/core.js @@ -734,7 +734,9 @@ class Core { } }, parentMessageId: conversation.parentMessageId, - conversationId: conversation.conversationId + conversationId: conversation.conversationId, + search: Config.geminiEnableGoogleSearch, + codeExecution: Config.geminiEnableCodeExecution } const image = await getImg(e) let imageUrl = image ? image[0] : undefined diff --git a/utils/chat.js b/utils/chat.js index 94ad34f4..ae2a206a 100644 --- a/utils/chat.js +++ b/utils/chat.js @@ -13,7 +13,13 @@ export async function getChatHistoryGroup (e, num) { let chats = [] while (chats.length < num) { let chatHistory = await e.group.getChatHistory(seq, 20) + if (!chatHistory || chatHistory.length === 0) { + break + } chats.push(...chatHistory) + if (seq === chatHistory[0].seq || seq === chatHistory[0].message_id) { + break + } seq = chatHistory[0].seq || chatHistory[0].message_id } chats = chats.slice(0, num) diff --git a/utils/config.js b/utils/config.js index 165605c0..35d32f9a 100644 --- a/utils/config.js +++ b/utils/config.js @@ -197,7 +197,9 @@ const defaultConfig = { translateSource: 'openai', enableMd: false, // 第三方md,非QQBot。需要适配器实现segment.markdown和segment.button方可使用,否则不建议开启,会造成各种错误 enableToolbox: true, // 默认关闭工具箱节省占用和加速启动 - version: 'v2.8.1' + geminiEnableGoogleSearch: false, + geminiEnableCodeExecution: false, + version: 'v2.8.2' } const _path = process.cwd() let config = {}