feat: 调用工具

This commit is contained in:
李岩岩 2026-06-05 17:16:33 +08:00
parent aa6ab0d379
commit c2a906773f
7 changed files with 116 additions and 3 deletions

View File

@ -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! });
}

View File

@ -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!;
}

View File

@ -6,4 +6,6 @@ export const PROMPTS = {
"answer": "你的回答",
"confidence": 0.0-1.0
}`,
agent: `你是一个有工具调用能力的助手。当需要查询信息或执行计算时,请使用提供的工具。
`,
} as const;

22
src/tools/calculator.ts Normal file
View File

@ -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}`;
}
},
};

24
src/tools/registry.ts Normal file
View File

@ -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);
}

19
src/tools/weather.ts Normal file
View File

@ -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`;
},
};

14
src/types/index.ts Normal file
View File

@ -0,0 +1,14 @@
export interface Tool {
name: string;
description: string;
parameters: {
type: 'object';
properties: Record<string, {
type: string;
description: string;
enum?: string[];
}>;
required: string[];
};
execute: (args: Record<string, any>) => Promise<string> | string;
}