diff --git a/apps/bym.js b/apps/bym.js index 337524c..c860f6d 100644 --- a/apps/bym.js +++ b/apps/bym.js @@ -18,7 +18,7 @@ import { KickOutTool } from '../utils/tools/KickOutTool.js' import { SetTitleTool } from '../utils/tools/SetTitleTool.js' import {SerpTool} from '../utils/tools/SerpTool.js' import { APTool } from '../utils/tools/APTool.js' -import { CustomSearchTool } from '../utils/tools/CustomSearchTool.js' +import { ContentSearchTool } from '../utils/tools/ContentSearchTool.js' import { UrlExtractionTool } from '../utils/tools/UrlExtractionTool.js' // 角色映射表 @@ -170,7 +170,7 @@ ${Object.values(faceMap).map(face => `[/${face}]`).join(',')} new APTool(), new WebsiteTool(), new UrlExtractionTool(), - new CustomSearchTool(), + new ContentSearchTool(), new WeatherTool() ] if (Config.azSerpKey) { diff --git a/utils/tools/ContentSearchTool.js b/utils/tools/ContentSearchTool.js new file mode 100644 index 0000000..f457664 --- /dev/null +++ b/utils/tools/ContentSearchTool.js @@ -0,0 +1,160 @@ +import { AbstractTool } from './AbstractTool.js'; +import fetch from 'node-fetch'; +import { Config } from '../config.js'; + +/** + * 内容搜索和分析工具类 - 使用 Gemini API + * @class ContentSearchTool + * @extends {AbstractTool} + */ +export class ContentSearchTool extends AbstractTool { + name = 'ContentSearchTool'; + + parameters = { + properties: { + content: { // 改为 content 参数 + type: 'string', + description: '需要分析的文本内容', + }, + task: { // 新增 task 参数 + type: 'string', + description: '分析任务类型(如:总结、分析、问答等)', + default: 'summarize' + }, + length: { + type: 'integer', + description: '期望的输出长度(句子数),默认为3', + }, + }, + required: ['content'], + }; + + description = '使用 Gemini API 进行内容分析,支持文本总结、深度分析、问答等功能。'; + + /** + * 工具执行函数 + * @param {Object} opt - 工具参数 + * @param {string} opt.content - 需要分析的内容 + * @param {string} [opt.task='summarize'] - 分析任务类型 + * @param {number} [opt.length=3] - 输出长度 + * @returns {Promise} - 包含答案和分析的对象 + */ + func = async function (opt) { + const { content, task = 'summarize', length = 3 } = opt; + + if (!content?.trim()) { + throw new Error('分析内容不能为空'); + } + + try { + const result = await this.analyzeWithGemini(content, task, length); + console.log(`分析结果: ${JSON.stringify(result)}`); + return result; + } catch (error) { + console.error('内容分析失败:', error); + throw new Error(`内容分析失败: ${error.message}`); + } + }; + + /** + * 使用 Gemini API 进行内容分析 + * @param {string} content - 需要分析的内容 + * @param {string} task - 分析任务类型 + * @param {number} length - 输出长度 + * @returns {Promise} - 分析结果 + * @private + */ + async analyzeWithGemini(content, task, length) { + const apiKey = Config.geminiKey; + const apiBaseUrl = Config.geminiBaseUrl; + const apiUrl = `${apiBaseUrl}/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`; + + if (!apiKey || !apiBaseUrl) { + throw new Error('Gemini API 配置缺失'); + } + + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + contents: [{ + parts: [{ + text: this.constructPrompt(content, task, length) + }] + }], + tools: [{ + googleSearch: {} + }], + generationConfig: { + temperature: 0.7, + topP: 0.8, + topK: 40, + } + }) + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(`API 请求失败: ${data.error?.message || '未知错误'}`); + } + + return this.processGeminiResponse(data); + } + + /** + * 构建分析提示词 + * @param {string} content - 需要分析的内容 + * @param {string} task - 分析任务类型 + * @param {number} length - 输出长度 + * @returns {string} - 格式化的提示词 + * @private + */ + constructPrompt(content, task, length) { + const taskPrompts = { + summarize: `Please provide a ${length} sentence summary of the following content:`, + analyze: `Please provide a ${length} point analysis of the following content:`, + qa: 'Please answer questions based on the following content:', + extract: 'Please extract key information from the following content:', + }; + + const prompt = taskPrompts[task] || taskPrompts.summarize; + return `${prompt}\n\nContent: ${content}`; + } + + /** + * 处理 Gemini API 响应 + * @param {Object} data - API 响应数据 + * @returns {Object} - 处理后的结果对象 + * @private + */ + processGeminiResponse(data) { + if (!data?.candidates?.[0]?.content?.parts?.[0]?.text) { + throw new Error('无效的 API 响应'); + } + + const analysis = data.candidates[0].content.parts[0].text; + + // 提取参考信息(如果有) + const references = data.candidates?.[0]?.groundingMetadata?.groundingChunks + ?.filter(chunk => chunk.web) + ?.map(chunk => ({ + title: chunk.web.title, + url: chunk.web.uri + })) + ?.filter((v, i, a) => + a.findIndex(t => (t.title === v.title && t.url === v.url)) === i + ) || []; + + return { + analysis, + references, + metadata: { + timestamp: new Date().toISOString(), + model: 'gemini-2.0-flash-exp' + } + }; + } +} \ No newline at end of file diff --git a/utils/tools/CustomSearchTool.js b/utils/tools/CustomSearchTool.js deleted file mode 100644 index dccbbbb..0000000 --- a/utils/tools/CustomSearchTool.js +++ /dev/null @@ -1,137 +0,0 @@ -import { AbstractTool } from './AbstractTool.js'; -import fetch from 'node-fetch'; -import { Config } from '../config.js'; - -/** - * 自定义搜索工具类 - 使用 Gemini API - * @class CustomSearchTool - * @extends {AbstractTool} - */ -export class CustomSearchTool extends AbstractTool { - name = 'CustomSearchTool'; - - parameters = { - properties: { - query: { - type: 'string', - description: '搜索关键词', - }, - length: { - type: 'integer', - description: '期望的摘要长度(句子数),默认为3', - }, - }, - required: ['query'], - }; - - description = '使用 Gemini API 进行智能搜索,提供全面的搜索结果和摘要。支持自定义摘要长度。'; - - /** - * 工具执行函数 - * @param {Object} opt - 工具参数 - * @param {string} opt.query - 搜索关键词 - * @param {number} [opt.length=3] - 摘要长度 - * @returns {Promise} - 包含答案和来源的对象 - */ - func = async function (opt) { - const { query, length = 3 } = opt; - - if (!query?.trim()) { - throw new Error('搜索关键词不能为空'); - } - - try { - const result = await this.searchWithGemini(query, length); - console.log(`搜索结果: ${JSON.stringify(result)}`); - return result; - } catch (error) { - console.error('搜索失败:', error); - throw new Error(`搜索失败: ${error.message}`); - } - }; - - /** - * 使用 Gemini API 进行搜索 - * @param {string} query - 搜索关键词 - * @param {number} length - 摘要长度 - * @returns {Promise} - 包含答案和来源的对象 - * @private - */ - async searchWithGemini(query, length) { - const apiKey = Config.geminiKey; - const apiBaseUrl = Config.geminiBaseUrl; - const apiUrl = `${apiBaseUrl}/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`; - - if (!apiKey || !apiBaseUrl) { - throw new Error('Gemini API 配置缺失'); - } - - const response = await fetch(apiUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - contents: [{ - parts: [{ - text: this.constructPrompt(query, length) - }] - }], - tools: [{ - googleSearch: {} - }] - }) - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(`API 请求失败: ${data.error?.message || '未知错误'}`); - } - - return this.processGeminiResponse(data); - } - - /** - * 构建提示词 - * @param {string} query - 搜索关键词 - * @param {number} length - 摘要长度 - * @returns {string} - 格式化的提示词 - * @private - */ - constructPrompt(query, length) { - return `Please provide a comprehensive ${length} sentence summary for the following query. - Include relevant facts and information. - Query: ${query}`; - } - - /** - * 处理 Gemini API 响应 - * @param {Object} data - API 响应数据 - * @returns {Object} - 处理后的结果对象 - * @private - */ - processGeminiResponse(data) { - if (!data?.candidates?.[0]?.content?.parts?.[0]?.text) { - throw new Error('无效的 API 响应'); - } - - const answer = data.candidates[0].content.parts[0].text; - - // 提取来源信息 - const sources = data.candidates?.[0]?.groundingMetadata?.groundingChunks - ?.filter(chunk => chunk.web) - ?.map(chunk => ({ - title: chunk.web.title, - url: chunk.web.uri - })) - ?.filter((v, i, a) => - a.findIndex(t => (t.title === v.title && t.url === v.url)) === i - ) || []; - - return { - answer, - sources, - }; - } -} \ No newline at end of file