diff --git a/apps/prompts.js b/apps/prompts.js index cd9c85b..52ee5bd 100644 --- a/apps/prompts.js +++ b/apps/prompts.js @@ -81,7 +81,7 @@ export class help extends plugin { prompts.push(...[defaultPrompt, defaultSydneyPrompt]) prompts.push(...readPrompts()) console.log(prompts) - e.reply(await makeForwardMsg(e, prompts.map(p => `《${p.name}》\n${limitString(p.content, 500)}`), '设定列表')) + e.reply(await makeForwardMsg(e, prompts.map(p => `《${p.name}》\n${limitString(p.content, 100)}`), '设定列表')) } async detailPrompt (e) { diff --git a/client/CustomGoogleGeminiClient.js b/client/CustomGoogleGeminiClient.js index 623afbc..cfc2215 100644 --- a/client/CustomGoogleGeminiClient.js +++ b/client/CustomGoogleGeminiClient.js @@ -112,9 +112,10 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { * search: boolean, * codeExecution: boolean, * }} opt + * @param {number} retryTime 重试次数 * @returns {Promise<{conversationId: string?, parentMessageId: string, text: string, id: string}>} */ - async sendMessage(text, opt = {}) { + async sendMessage (text, opt = {}, retryTime = 3) { let history = await this.getHistory(opt.parentMessageId) let systemMessage = opt.system // if (systemMessage) { @@ -216,7 +217,7 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { } ], generationConfig: { - maxOutputTokens: opt.maxOutputTokens || 1000, + maxOutputTokens: opt.maxOutputTokens || 4096, temperature: opt.temperature || 0.9, topP: opt.topP || 0.95, topK: opt.tokK || 16 @@ -285,7 +286,7 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { */ let responseContent /** - * @type {{candidates: Array<{content: Content, groundingMetadata: GroundingMetadata}>}} + * @type {{candidates: Array<{content: Content, groundingMetadata: GroundingMetadata, finishReason: string}>}} */ let response = await result.json() if (this.debug) { @@ -293,14 +294,19 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient { } responseContent = response.candidates[0].content let groundingMetadata = response.candidates[0].groundingMetadata + if (response.candidates[0].finishReason === 'MALFORMED_FUNCTION_CALL') { + logger.warn('遇到MALFORMED_FUNCTION_CALL,进行重试。') + return this.sendMessage(text, opt, retryTime--) + } + // todo 空回复也可以重试 if (responseContent.parts.filter(i => i.functionCall).length > 0) { // functionCall const functionCall = responseContent.parts.filter(i => i.functionCall).map(i => i.functionCall) const text = responseContent.parts.find(i => i.text)?.text - if (text) { + if (text && text.trim()) { // send reply first - logger.info('send message: ' + text) - opt.replyPureTextCallback && await opt.replyPureTextCallback(text) + logger.info('send message: ' + text.trim()) + opt.replyPureTextCallback && await opt.replyPureTextCallback(text.trim()) } let /** @type {FunctionResponse[]} **/ fcResults = [] for (let fc of functionCall) { diff --git a/guoba.support.js b/guoba.support.js index 739d0df..8a80706 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -1198,6 +1198,12 @@ export function supportGuoba () { label: '额外工具url', bottomHelpMessage: '(测试期间提供一个公益接口,一段时间后撤掉)参考搭建:https://github.com/ikechan8370/chatgpt-plugin-extras', component: 'Input' + }, + { + field: 'githubAPIKey', + label: 'github Access Token', + bottomHelpMessage: '去https://github.com/settings/personal-access-tokens生成。用于提高AI调用github工具的Rate Limit', + component: 'Input' } ], // 获取配置数据方法(用于前端填充显示数据) diff --git a/model/core.js b/model/core.js index be8306a..eb1415b 100644 --- a/model/core.js +++ b/model/core.js @@ -54,6 +54,7 @@ import { QwenApi } from '../utils/alibaba/qwen-api.js' import { BingAIClient } from '../client/CopilotAIClient.js' import Keyv from 'keyv' import crypto from 'crypto' +import {GithubAPITool} from '../utils/tools/GithubTool.js' export const roleMap = { owner: 'group owner', @@ -774,7 +775,8 @@ async function collectTools (e) { new SendMessageToSpecificGroupOrUserTool(), new SendDiceTool(), new QueryGenshinTool(), - new SetTitleTool() + new SetTitleTool(), + new GithubAPITool() ] // todo 3.0再重构tool的插拔和管理 let /** @type{AbstractTool[]} **/ tools = [ @@ -796,7 +798,8 @@ async function collectTools (e) { new APTool(), // new HandleMessageMsgTool(), serpTool, - new QueryUserinfoTool() + new QueryUserinfoTool(), + new GithubAPITool() ] let systemAddition = '' if (e.isGroup) { diff --git a/utils/config.js b/utils/config.js index edba6a4..4c9a9eb 100644 --- a/utils/config.js +++ b/utils/config.js @@ -229,6 +229,8 @@ const defaultConfig = { apiMaxToken: 4096, enableToolPrivateSend: true, // 是否允许智能模式下私聊骚扰其他群友。主人不受影响。 geminiForceToolKeywords: [], + githubAPI: 'https://api.github.com', + githubAPIKey: '', version: 'v2.8.4' } const _path = process.cwd() diff --git a/utils/tools/GithubTool.js b/utils/tools/GithubTool.js new file mode 100644 index 0000000..50fd8f4 --- /dev/null +++ b/utils/tools/GithubTool.js @@ -0,0 +1,59 @@ +import { AbstractTool } from './AbstractTool.js' +import { Config } from '../config.js' + +export class GithubAPITool extends AbstractTool { + name = 'github' + + parameters = { + properties: { + q: { + type: 'string', + description: 'search keyword. you should build it. If you want to find from specified repo, please must use repo:ORG/REPO as part of the keyword. For example, if you want to find the oldest unresolved Python bugs on Windows. Your query might look something like this: q=windows+label:bug+language:python+state:open&sort=created&order=asc' + }, + type: { + type: 'string', + enum: ['repositories', 'issues', 'users', 'code', 'custom'], + description: 'search type. If custom is chosen, you must provide full github api url path.' + }, + num: { + type: 'number', + description: 'search results limit number, default is 5' + }, + fullUrl: { + type: 'string', + description: 'if type is custom, you need provide this, such as /repos/OWNER/REPO/actions/artifacts?name=NAME&page=2&per_page=1. if type is not custom, is will be ignored' + } + }, + required: ['q', 'type'] + } + + func = async function (opts) { + let { q, type, num = 5, fullUrl = '' } = opts + let headers = { + 'X-From-Library': 'ikechan8370', + Accept: 'application/vnd.github+json' + } + if (Config.githubAPIKey) { + headers.Authorization = `Bearer ${Config.githubAPIKey}` + } + let res + if (type !== 'custom') { + let serpRes = await fetch(`${Config.githubAPI}/search/${type}?q=${encodeURIComponent(q)}&per_page=${num}`, { + headers + }) + serpRes = await serpRes.json() + + res = serpRes + } else { + let serpRes = await fetch(`${Config.githubAPI}${fullUrl}`, { + headers + }) + serpRes = await serpRes.json() + res = serpRes + } + + return `the search results are here in json format:\n${JSON.stringify(res)} \n(Notice that these information are only available for you, the user cannot see them, you next answer should consider about the information)` + } + + description = 'Useful when you want to search something from api.github.com. You can use preset search types or build your own url path with order, per_page, page and other params. Automatically adjust the query and params if any error messages return.' +} diff --git a/utils/tools/SendPictureTool.js b/utils/tools/SendPictureTool.js index 1c27687..1ff6407 100644 --- a/utils/tools/SendPictureTool.js +++ b/utils/tools/SendPictureTool.js @@ -51,7 +51,7 @@ export class SendPictureTool extends AbstractTool { try { await group.sendMsg(pic) } catch (err) { - errs.push(pic.url) + errs.push(pic) } } // await group.sendMsg(pictures) diff --git a/utils/tools/WebsiteTool.js b/utils/tools/WebsiteTool.js index 88b383c..0f509d6 100644 --- a/utils/tools/WebsiteTool.js +++ b/utils/tools/WebsiteTool.js @@ -6,6 +6,58 @@ import proxy from 'https-proxy-agent' import { getMaxModelTokens } from '../common.js' import { ChatGPTPuppeteer } from '../browser.js' import { CustomGoogleGeminiClient } from '../../client/CustomGoogleGeminiClient.js' + +/** + * Generated by GPT-4o + * @param html + * @returns {*} + */ +function cleanHTML (html) { + // 1. 移除