feat: 实现 Tool Search Tool 动态工具发现机制

- 新增 ToolRegistry 工具注册表,支持核心工具和延迟加载工具分离
- 新增 tool_search 元工具,支持关键词搜索发现可用工具
- 新增基于关键词的搜索算法,按相关度评分排序
- 为所有工具添加 metadata(分类、关键词、延迟加载标识)
- 修改 Agent 支持动态工具注入,tool_search 结果自动添加到可用工具
- 核心工具(tool_search, bash)始终加载,其他工具按需发现
This commit is contained in:
2025-12-10 19:51:25 +08:00
parent e435b2f8f8
commit bc1ece3dad
19 changed files with 569 additions and 66 deletions
+122 -23
View File
@@ -1,8 +1,16 @@
import { createAnthropic } from '@ai-sdk/anthropic';
import { createDeepSeek } from '@ai-sdk/deepseek';
import { generateText, streamText, stepCountIs, type ModelMessage, type Tool as AITool, type LanguageModel } from 'ai';
import {
generateText,
streamText,
stepCountIs,
type ModelMessage,
type Tool as AITool,
type LanguageModel,
} from 'ai';
import type { Tool, ToolResult, Message, AgentConfig, ProviderType } from '../types/index.js';
import { buildZodSchema } from '../types/index.js';
import { ToolRegistry } from '../tools/registry.js';
// Provider 工厂函数类型
type ProviderFactory = (apiKey: string) => (model: string) => LanguageModel;
@@ -22,9 +30,17 @@ const providers: Record<ProviderType, ProviderFactory> = {
export class Agent {
private getModel: (model: string) => LanguageModel;
private config: AgentConfig;
private tools: Map<string, Tool> = new Map();
private conversationHistory: ModelMessage[] = [];
// 工具注册表
private registry: ToolRegistry | null = null;
// 已发现的工具(通过 tool_search 发现的)
private discoveredTools: Set<string> = new Set();
// 兼容旧模式:直接注册的工具
private legacyTools: Map<string, Tool> = new Map();
constructor(config: AgentConfig) {
this.config = config;
@@ -35,30 +51,67 @@ export class Agent {
this.getModel = providerFactory(config.apiKey);
}
// 注册单个工具
registerTool(customTool: Tool): void {
this.tools.set(customTool.name, customTool);
/**
* 设置工具注册表(新模式:支持动态工具发现)
*/
setRegistry(registry: ToolRegistry): void {
this.registry = registry;
}
// 批量注册工具
/**
* 注册单个工具(兼容旧代码)
*/
registerTool(customTool: Tool): void {
this.legacyTools.set(customTool.name, customTool);
}
/**
* 批量注册工具(兼容旧代码)
*/
registerTools(tools: Tool[]): void {
for (const tool of tools) {
this.tools.set(tool.name, tool);
this.legacyTools.set(tool.name, tool);
}
}
// 将自定义工具转换为 Vercel AI SDK 的工具格式
/**
* 获取当前可用的工具
* - 如果使用 registry 模式:返回核心工具 + 已发现的工具
* - 如果使用旧模式:返回所有注册的工具
*/
private getAvailableTools(): Tool[] {
if (this.registry) {
// 新模式:核心工具 + 已发现的工具
const coreTools = this.registry.getCoreTools();
const discoveredTools = this.registry.getTools([...this.discoveredTools]);
return [...coreTools, ...discoveredTools];
} else {
// 旧模式:返回所有注册的工具
return [...this.legacyTools.values()];
}
}
/**
* 将工具转换为 Vercel AI SDK 的工具格式
*/
private getVercelTools(): Record<string, AITool> {
const vercelTools: Record<string, AITool> = {};
const availableTools = this.getAvailableTools();
for (const [name, customTool] of this.tools) {
const schema = buildZodSchema(customTool.parameters);
for (const tool of availableTools) {
const schema = buildZodSchema(tool.parameters);
vercelTools[name] = {
description: customTool.description,
vercelTools[tool.name] = {
description: tool.description,
inputSchema: schema,
execute: async (params) => {
const result = await customTool.execute(params as Record<string, unknown>);
const result = await tool.execute(params as Record<string, unknown>);
// 如果是 tool_search 调用,解析结果并注入发现的工具
if (tool.name === 'tool_search' && result.success) {
this.handleToolSearchResult(result.output);
}
return result;
},
} as AITool;
@@ -67,11 +120,25 @@ export class Agent {
return vercelTools;
}
// 发送消息并处理响应(流式)
async chat(
userMessage: string,
onStream?: (text: string) => void
): Promise<string> {
/**
* 处理 tool_search 的结果,将发现的工具添加到可用列表
*/
private handleToolSearchResult(output: string): void {
// 解析输出,提取工具名称
// 格式: "- tool_name: description [category]"
const matches = output.matchAll(/^- (\w+):/gm);
for (const match of matches) {
const toolName = match[1];
if (this.registry?.has(toolName)) {
this.discoveredTools.add(toolName);
}
}
}
/**
* 发送消息并处理响应(流式)
*/
async chat(userMessage: string, onStream?: (text: string) => void): Promise<string> {
// 添加用户消息到历史
this.conversationHistory.push({
role: 'user',
@@ -97,7 +164,12 @@ export class Agent {
const output = (chunk as { output?: ToolResult }).output;
if (output && typeof output === 'object') {
if (output.success) {
onStream(`[结果: ${output.output}]\n`);
// 截断过长的输出
const displayOutput =
output.output.length > 500
? output.output.substring(0, 500) + '...(截断)'
: output.output;
onStream(`[结果: ${displayOutput}]\n`);
} else {
onStream(`[错误: ${output.error}]\n`);
}
@@ -137,20 +209,47 @@ export class Agent {
return fullResponse;
}
// 清空对话历史
/**
* 清空对话历史和发现的工具
*/
clearHistory(): void {
this.conversationHistory = [];
this.discoveredTools.clear();
}
// 获取对话历史
/**
* 获取对话历史
*/
getHistory(): Message[] {
return this.conversationHistory
.filter((msg): msg is ModelMessage & { role: 'user' | 'assistant' } =>
msg.role === 'user' || msg.role === 'assistant'
.filter(
(msg): msg is ModelMessage & { role: 'user' | 'assistant' } =>
msg.role === 'user' || msg.role === 'assistant'
)
.map((msg) => ({
role: msg.role,
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
}));
}
/**
* 获取当前可用工具的数量
*/
getToolCount(): { core: number; discovered: number; total: number } {
if (this.registry) {
const coreCount = this.registry.getCoreTools().length;
const discoveredCount = this.discoveredTools.size;
return {
core: coreCount,
discovered: discoveredCount,
total: coreCount + discoveredCount,
};
} else {
return {
core: this.legacyTools.size,
discovered: 0,
total: this.legacyTools.size,
};
}
}
}