From c2a906773f1c0308b3f788a0ebb5a8a4c93a44f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 5 Jun 2026 17:16:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E7=94=A8=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 32 ++++++++++++++++++++++++++++++-- src/llm.ts | 6 +++++- src/prompts/system.ts | 2 ++ src/tools/calculator.ts | 22 ++++++++++++++++++++++ src/tools/registry.ts | 24 ++++++++++++++++++++++++ src/tools/weather.ts | 19 +++++++++++++++++++ src/types/index.ts | 14 ++++++++++++++ 7 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 src/tools/calculator.ts create mode 100644 src/tools/registry.ts create mode 100644 src/tools/weather.ts create mode 100644 src/types/index.ts diff --git a/src/index.ts b/src/index.ts index 57b7c01..9931b36 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ import { ContextManager } from './context.js'; import { chat } from './llm.js'; import { PROMPTS } from './prompts/system.js'; +import { getOpenAITools, findTool } from './tools/registry.js'; import * as readline from 'node:readline/promises'; -const promptName = process.argv[2] || 'default'; +const promptName = process.argv[2] || 'agent'; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; if (!systemPrompt) { @@ -26,7 +27,34 @@ while (true) { if (userInput.toLowerCase() === 'exit') break; context.add({ role: 'user', content: userInput }); - const reply = await chat(context.getMessages()); + + // 第一轮:带工具定义调用 + const tools = getOpenAITools(); + let reply = await chat(context.getMessages(), tools); + + // 如果模型要求调用工具,执行并在上下文中构造 tool 消息 + if (reply.tool_calls && reply.tool_calls.length > 0) { + // 先把 assistant 消息(含 tool_calls)加入上下文 + context.add({ role: 'assistant', content: '', tool_calls: reply.tool_calls } as any); + + for (const tc of reply.tool_calls) { + const tool = findTool(tc.function.name); + const args = JSON.parse(tc.function.arguments); + const result = await tool!.execute(args); + console.log(` [工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); + + // tool 消息:必须回传 tool_call_id + context.add({ + role: 'tool', + tool_call_id: tc.id, + content: result, + } as any); + } + + // 继续调用,看模型还需要不需要更多工具 + reply = await chat(context.getMessages(), tools); + } + console.log('助手:', reply.content); context.add({ role: 'assistant', content: reply.content! }); } diff --git a/src/llm.ts b/src/llm.ts index bc444df..8409ffa 100644 --- a/src/llm.ts +++ b/src/llm.ts @@ -6,11 +6,15 @@ const client = new OpenAI({ baseURL: process.env.OPENAI_BASE_URL, }); -export async function chat(messages: { role: string; content: string }[]) { +export async function chat( + messages: { role: string; content: string }[], + tools?: any[] // OpenAI 格式的工具定义数组 +) { const response = await client.chat.completions.create({ model: 'deepseek-v4-pro', messages: messages as any, temperature: 0.7, + ...(tools && { tools }), }); return response.choices[0]!.message!; } \ No newline at end of file diff --git a/src/prompts/system.ts b/src/prompts/system.ts index 5d40546..d99bef1 100644 --- a/src/prompts/system.ts +++ b/src/prompts/system.ts @@ -6,4 +6,6 @@ export const PROMPTS = { "answer": "你的回答", "confidence": 0.0-1.0 之间的数字 }`, + agent: `你是一个有工具调用能力的助手。当需要查询信息或执行计算时,请使用提供的工具。 +如果不需要工具,直接回答即可。回答简洁。`, } as const; \ No newline at end of file diff --git a/src/tools/calculator.ts b/src/tools/calculator.ts new file mode 100644 index 0000000..fb22ec0 --- /dev/null +++ b/src/tools/calculator.ts @@ -0,0 +1,22 @@ +import { Tool } from '../types/index.js'; + +export const calculatorTool: Tool = { + name: 'calculate', + description: '执行数学计算,支持加减乘除和括号', + parameters: { + type: 'object', + properties: { + expression: { type: 'string', description: '数学表达式,如"2+3*4"' }, + }, + required: ['expression'], + }, + execute: async (args) => { + try { + // 安全警告:生产环境绝不可以用 eval + const result = eval(args.expression); + return `${args.expression} = ${result}`; + } catch (e: any) { + return `计算错误: ${e.message}`; + } + }, +}; \ No newline at end of file diff --git a/src/tools/registry.ts b/src/tools/registry.ts new file mode 100644 index 0000000..c8d5a75 --- /dev/null +++ b/src/tools/registry.ts @@ -0,0 +1,24 @@ +import { weatherTool } from './weather.js'; +import { calculatorTool } from './calculator.js'; +import { Tool } from '../types/index.js'; + +const tools: Tool[] = [weatherTool, calculatorTool]; + +export function getTools(): Tool[] { + return tools; +} + +export function getOpenAITools() { + return tools.map((t) => ({ + type: 'function' as const, + function: { + name: t.name, + description: t.description, + parameters: t.parameters, + }, + })); +} + +export function findTool(name: string): Tool | undefined { + return tools.find((t) => t.name === name); +} \ No newline at end of file diff --git a/src/tools/weather.ts b/src/tools/weather.ts new file mode 100644 index 0000000..3dcf11b --- /dev/null +++ b/src/tools/weather.ts @@ -0,0 +1,19 @@ +import { Tool } from '../types/index.js'; + +export const weatherTool: Tool = { + name: 'get_weather', + description: '获取指定城市的当前天气信息', + parameters: { + type: 'object', + properties: { + city: { type: 'string', description: '城市名称,如"北京"、"上海"' }, + }, + required: ['city'], + }, + execute: async (args) => { + // 模拟异步 API 调用 + const weathers = ['晴', '多云', '小雨', '阴天']; + const picked = weathers[Math.floor(Math.random() * weathers.length)]; + return `城市:${args.city},天气:${picked},温度:${Math.floor(Math.random() * 15 + 15)}°C`; + }, +}; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..1c30214 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,14 @@ +export interface Tool { + name: string; + description: string; + parameters: { + type: 'object'; + properties: Record; + required: string[]; + }; + execute: (args: Record) => Promise | string; +} \ No newline at end of file