feat: 添加 web_search 工具和权限管控
- 新增 web_search 工具,使用 Tavily SDK 进行网络搜索 - 支持搜索深度(basic/advanced)和主题(general/news/finance)配置 - 新增 WebPermissionChecker 权限检查器 - 搜索操作默认需要用户确认,支持会话级权限记忆 - 配置文件支持 tavilyApiKey 存储
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
搜索网络获取最新信息。使用 Tavily API 进行智能搜索,返回相关网页内容和 AI 摘要。
|
||||
|
||||
这是进行网络搜索的首选工具,不要使用 curl 或 bash 命令来搜索网络。
|
||||
|
||||
适用场景:
|
||||
- 查询最新新闻、事件、游戏更新
|
||||
- 搜索技术文档、API 参考
|
||||
- 获取实时数据(股价、天气等)
|
||||
- 查找开源项目、库的信息
|
||||
- 了解最新的技术趋势
|
||||
|
||||
参数说明:
|
||||
- query: 搜索关键词(必填)
|
||||
- max_results: 返回结果数量,1-20,默认 5
|
||||
- search_depth: "basic" 快速搜索 / "advanced" 深度搜索
|
||||
- topic: "general" 通用 / "news" 新闻 / "finance" 财经
|
||||
- include_answer: 是否包含 AI 摘要,默认 true
|
||||
|
||||
返回内容:
|
||||
- AI 生成的摘要答案
|
||||
- 相关网页列表(标题、链接、内容摘要)
|
||||
@@ -23,6 +23,9 @@ import {
|
||||
deleteFileTool,
|
||||
} from './filesystem/index.js';
|
||||
|
||||
// Web 工具
|
||||
import { webSearchTool } from './web/index.js';
|
||||
|
||||
// 所有工具列表(用于注册)
|
||||
const allToolsWithMetadata: ToolWithMetadata[] = [
|
||||
// 核心工具 (deferLoading: false)
|
||||
@@ -43,6 +46,9 @@ const allToolsWithMetadata: ToolWithMetadata[] = [
|
||||
moveFileTool,
|
||||
copyFileTool,
|
||||
deleteFileTool,
|
||||
|
||||
// Web 工具 (deferLoading: true)
|
||||
webSearchTool,
|
||||
];
|
||||
|
||||
// 注册所有工具到 registry
|
||||
|
||||
@@ -62,7 +62,7 @@ export const toolSearchTool: ToolWithMetadata = {
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: `找到 ${results.length} 个相关工具:\n\n${toolList}\n\n这些工具现在可以使用了。请选择合适的工具来完成任务。`,
|
||||
output: `找到 ${results.length} 个相关工具:\n\n${toolList}\n\n重要:这些工具现在已经可以直接调用了。请立即使用合适的工具(如 web_search)来完成任务,不要使用 bash 命令代替。`,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { webSearchTool } from './web_search.js';
|
||||
@@ -0,0 +1,138 @@
|
||||
import { tavily } from '@tavily/core';
|
||||
import type { ToolResult } from '../../types/index.js';
|
||||
import type { ToolWithMetadata } from '../types.js';
|
||||
import { loadDescription } from '../load_description.js';
|
||||
import { getConfig } from '../../utils/config.js';
|
||||
import { getPermissionManager } from '../../permission/index.js';
|
||||
|
||||
export const webSearchTool: ToolWithMetadata = {
|
||||
name: 'web_search',
|
||||
description: loadDescription('web_search'),
|
||||
metadata: {
|
||||
name: 'web_search',
|
||||
category: 'web',
|
||||
description: '搜索网络获取最新信息',
|
||||
keywords: ['search', 'web', 'internet', 'google', 'query', '搜索', '网络', '查询', '互联网'],
|
||||
deferLoading: false, // 核心工具,始终可用
|
||||
},
|
||||
parameters: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: '搜索查询关键词',
|
||||
required: true,
|
||||
},
|
||||
max_results: {
|
||||
type: 'number',
|
||||
description: '返回结果数量(默认 5,最大 20)',
|
||||
required: false,
|
||||
},
|
||||
search_depth: {
|
||||
type: 'string',
|
||||
description: '搜索深度: "basic" 快速搜索,"advanced" 深度搜索(默认 basic)',
|
||||
required: false,
|
||||
},
|
||||
topic: {
|
||||
type: 'string',
|
||||
description: '搜索主题: "general" 通用,"news" 新闻,"finance" 财经(默认 general)',
|
||||
required: false,
|
||||
},
|
||||
include_answer: {
|
||||
type: 'boolean',
|
||||
description: '是否包含 AI 生成的摘要答案(默认 true)',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const query = params.query as string;
|
||||
const maxResults = Math.min((params.max_results as number) || 5, 20);
|
||||
const searchDepth = (params.search_depth as 'basic' | 'advanced') || 'basic';
|
||||
const topic = (params.topic as 'general' | 'news' | 'finance') || 'general';
|
||||
const includeAnswer = params.include_answer !== false;
|
||||
|
||||
// 权限检查
|
||||
const permissionManager = getPermissionManager();
|
||||
const permResult = await permissionManager.checkWebPermission({
|
||||
query,
|
||||
searchDepth,
|
||||
topic,
|
||||
maxResults,
|
||||
});
|
||||
|
||||
if (!permResult.allowed) {
|
||||
// 如果需要用户确认但没有设置回调,返回等待确认的状态
|
||||
if (permResult.needsConfirmation) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `需要用户确认网络搜索: "${query}"\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `网络搜索权限被拒绝: ${permResult.reason || '搜索不被允许'}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取 Tavily API Key
|
||||
const config = getConfig();
|
||||
const apiKey = process.env.TAVILY_API_KEY || config.tavilyApiKey;
|
||||
|
||||
if (!apiKey) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '未配置 Tavily API Key。请设置环境变量 TAVILY_API_KEY 或在配置文件中添加 tavilyApiKey。',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用 Tavily SDK
|
||||
const client = tavily({ apiKey });
|
||||
const response = await client.search(query, {
|
||||
searchDepth,
|
||||
topic,
|
||||
maxResults,
|
||||
includeAnswer,
|
||||
});
|
||||
|
||||
// 格式化输出
|
||||
let output = `## 搜索结果: "${query}"\n\n`;
|
||||
|
||||
// 如果有 AI 摘要答案
|
||||
if (response.answer) {
|
||||
output += `### 摘要\n${response.answer}\n\n`;
|
||||
}
|
||||
|
||||
// 搜索结果列表
|
||||
if (response.results && response.results.length > 0) {
|
||||
output += `### 相关链接 (${response.results.length} 条)\n\n`;
|
||||
|
||||
for (let i = 0; i < response.results.length; i++) {
|
||||
const result = response.results[i];
|
||||
output += `**${i + 1}. ${result.title}**\n`;
|
||||
output += `链接: ${result.url}\n`;
|
||||
// 截断过长的内容
|
||||
const content = result.content.length > 300
|
||||
? result.content.substring(0, 300) + '...'
|
||||
: result.content;
|
||||
output += `${content}\n\n`;
|
||||
}
|
||||
} else {
|
||||
output += '未找到相关结果。\n';
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `搜索失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user