feat: 实现 Tool Search Tool 动态工具发现机制
- 新增 ToolRegistry 工具注册表,支持核心工具和延迟加载工具分离 - 新增 tool_search 元工具,支持关键词搜索发现可用工具 - 新增基于关键词的搜索算法,按相关度评分排序 - 为所有工具添加 metadata(分类、关键词、延迟加载标识) - 修改 Agent 支持动态工具注入,tool_search 结果自动添加到可用工具 - 核心工具(tool_search, bash)始终加载,其他工具按需发现
This commit is contained in:
+122
-23
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user