Tool Calling:如何让 LLM 调用函数并解析参数?
OpenAI Functions / Tools 格式生成
在现代 LLM 应用中,工具调用(Tool Calling)是一项关键技术,它允许语言模型识别何时需要调用外部函数或工具,并生成相应的参数。LangChain V3 通过支持 OpenAI Functions/Tools 格式,提供了强大的工具调用能力。本章将深入探讨如何实现和使用工具调用功能。
Tool Calling 的基本概念
工具调用允许 LLM 在需要时请求调用外部工具:
typescript
// 工具调用接口
interface ToolCall {
id: string;
type: 'function';
function: {
name: string;
arguments: string; // JSON 格式的参数字符串
};
}
// 工具调用结果
interface ToolCallResult {
tool_call_id: string;
role: 'tool';
name: string;
content: string;
}
// 支持工具调用的 LLM 接口
interface ToolCallingLLM extends BaseLanguageModel {
bindTools(tools: Tool[]): this;
invokeWithTools(
messages: BaseMessage[],
tools: Tool[]
): Promise<{
message: BaseMessage;
tool_calls?: ToolCall[]
}>;
}工具定义和注册
定义和注册可被 LLM 调用的工具:
typescript
// 工具参数定义
interface ToolParameter {
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
description: string;
required?: boolean;
enum?: string[];
items?: ToolParameter; // 用于数组类型
properties?: Record<string, ToolParameter>; // 用于对象类型
}
// 工具接口
interface Tool {
name: string;
description: string;
parameters?: Record<string, ToolParameter>;
execute(args: any): Promise<any>;
}
// 工具注册器
class ToolRegistry {
private tools: Map<string, Tool>;
constructor() {
this.tools = new Map();
}
register(tool: Tool): void {
if (this.tools.has(tool.name)) {
throw new Error(`工具 "${tool.name}" 已存在`);
}
this.tools.set(tool.name, tool);
console.log(`工具已注册: ${tool.name}`);
}
get(name: string): Tool | undefined {
return this.tools.get(name);
}
getAll(): Tool[] {
return Array.from(this.tools.values());
}
getToolDefinitions(): any[] {
return Array.from(this.tools.values()).map(tool => ({
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.parameters ? {
type: 'object',
properties: tool.parameters,
required: Object.keys(tool.parameters).filter(
key => tool.parameters![key].required
)
} : undefined
}
}));
}
}OpenAI Functions 格式实现
实现对 OpenAI Functions 格式的支持:
typescript
// OpenAI 工具调用 LLM
class OpenAIFunctionsLLM extends BaseChatModel {
private modelName: string;
private apiKey: string;
constructor(modelName: string, apiKey: string) {
super();
this.modelName = modelName;
this.apiKey = apiKey;
}
bindTools(tools: Tool[]): this {
// 在 OpenAI 中,工具绑定是通过 API 调用参数实现的
// 这里返回 this 以支持链式调用
return this;
}
async _call(messages: BaseMessage[]): Promise<string> {
// 传统调用方式(不使用工具)
const response = await this.makeApiCall(messages, undefined);
return response.choices[0].message.content || '';
}
async invokeWithTools(
messages: BaseMessage[],
tools: Tool[]
): Promise<{
message: BaseMessage;
tool_calls?: ToolCall[]
}> {
const toolDefinitions = tools.map(tool => ({
type: 'function' as const,
function: {
name: tool.name,
description: tool.description,
parameters: tool.parameters ? {
type: 'object',
properties: tool.parameters,
required: Object.keys(tool.parameters).filter(
key => tool.parameters![key].required
)
} : undefined
}
}));
const response = await this.makeApiCall(messages, toolDefinitions);
const choice = response.choices[0];
if (choice.message.tool_calls) {
return {
message: new AIMessage(choice.message.content || ''),
tool_calls: choice.message.tool_calls
};
} else {
return {
message: new AIMessage(choice.message.content || '')
};
}
}
private async makeApiCall(
messages: BaseMessage[],
tools?: any[]
): Promise<any> {
const openaiMessages = messages.map(msg => ({
role: msg.role,
content: msg.content
}));
const requestBody: any = {
model: this.modelName,
messages: openaiMessages
};
if (tools && tools.length > 0) {
requestBody.tools = tools;
requestBody.tool_choice = 'auto';
}
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
throw new Error(`OpenAI API 调用失败: ${response.statusText}`);
}
return await response.json();
}
}工具参数解析和验证
实现工具参数的解析和验证:
typescript
// 参数解析器
class ParameterParser {
static parseArguments(argsString: string, parameters?: Record<string, ToolParameter>): any {
try {
const args = JSON.parse(argsString);
if (parameters) {
// 验证和转换参数
return this.validateAndConvert(args, parameters);
}
return args;
} catch (error) {
throw new Error(`参数解析失败: ${error.message}`);
}
}
private static validateAndConvert(
args: any,
parameters: Record<string, ToolParameter>
): any {
const result: any = {};
for (const [paramName, paramDef] of Object.entries(parameters)) {
const value = args[paramName];
// 检查必需参数
if (paramDef.required && (value === undefined || value === null)) {
throw new Error(`缺少必需参数: ${paramName}`);
}
// 如果参数不存在且非必需,跳过
if (value === undefined || value === null) {
continue;
}
// 类型验证和转换
result[paramName] = this.validateAndConvertValue(value, paramDef, paramName);
}
return result;
}
private static validateAndConvertValue(
value: any,
paramDef: ToolParameter,
paramName: string
): any {
switch (paramDef.type) {
case 'string':
if (typeof value !== 'string') {
throw new Error(`参数 ${paramName} 必须是字符串`);
}
if (paramDef.enum && !paramDef.enum.includes(value)) {
throw new Error(`参数 ${paramName} 必须是以下值之一: ${paramDef.enum.join(', ')}`);
}
return value;
case 'number':
if (typeof value !== 'number') {
const numValue = Number(value);
if (isNaN(numValue)) {
throw new Error(`参数 ${paramName} 必须是数字`);
}
return numValue;
}
return value;
case 'boolean':
if (typeof value !== 'boolean') {
if (value === 'true') return true;
if (value === 'false') return false;
throw new Error(`参数 ${paramName} 必须是布尔值`);
}
return value;
case 'array':
if (!Array.isArray(value)) {
throw new Error(`参数 ${paramName} 必须是数组`);
}
if (paramDef.items) {
return value.map((item, index) =>
this.validateAndConvertValue(item, paramDef.items!, `${paramName}[${index}]`)
);
}
return value;
case 'object':
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new Error(`参数 ${paramName} 必须是对象`);
}
if (paramDef.properties) {
return this.validateAndConvert(value, paramDef.properties);
}
return value;
default:
return value;
}
}
}工具调用执行器
实现工具调用的执行和结果处理:
typescript
// 工具调用执行器
class ToolExecutor {
private toolRegistry: ToolRegistry;
constructor(toolRegistry: ToolRegistry) {
this.toolRegistry = toolRegistry;
}
async executeToolCall(toolCall: ToolCall): Promise<ToolCallResult> {
const tool = this.toolRegistry.get(toolCall.function.name);
if (!tool) {
return {
tool_call_id: toolCall.id,
role: 'tool',
name: toolCall.function.name,
content: `错误: 未知工具 "${toolCall.function.name}"`
};
}
try {
// 解析参数
const args = ParameterParser.parseArguments(
toolCall.function.arguments,
tool.parameters
);
// 执行工具
const result = await tool.execute(args);
// 格式化结果
const resultString = typeof result === 'string'
? result
: JSON.stringify(result, null, 2);
return {
tool_call_id: toolCall.id,
role: 'tool',
name: toolCall.function.name,
content: resultString
};
} catch (error) {
return {
tool_call_id: toolCall.id,
role: 'tool',
name: toolCall.function.name,
content: `工具执行错误: ${error.message}`
};
}
}
async executeToolCalls(toolCalls: ToolCall[]): Promise<ToolCallResult[]> {
return await Promise.all(
toolCalls.map(toolCall => this.executeToolCall(toolCall))
);
}
}完整的工具调用代理
实现一个完整的支持工具调用的代理:
typescript
// 工具调用代理
class ToolCallingAgent extends BaseChatModel {
private llm: ToolCallingLLM;
private toolRegistry: ToolRegistry;
private toolExecutor: ToolExecutor;
private maxIterations: number;
constructor(
llm: ToolCallingLLM,
toolRegistry: ToolRegistry,
maxIterations: number = 5
) {
super();
this.llm = llm;
this.toolRegistry = toolRegistry;
this.toolExecutor = new ToolExecutor(toolRegistry);
this.maxIterations = maxIterations;
}
async _call(messages: BaseMessage[]): Promise<string> {
let conversation = [...messages];
for (let i = 0; i < this.maxIterations; i++) {
// 调用 LLM 并获取工具调用
const { message, tool_calls } = await this.llm.invokeWithTools(
conversation,
this.toolRegistry.getAll()
);
// 将 LLM 的回复添加到对话中
conversation.push(message);
// 如果没有工具调用,返回结果
if (!tool_calls || tool_calls.length === 0) {
return message.content;
}
// 执行工具调用
console.log(`执行 ${tool_calls.length} 个工具调用:`);
const toolResults = await this.toolExecutor.executeToolCalls(tool_calls);
// 将工具结果添加到对话中
toolResults.forEach(result => {
conversation.push(new ToolMessage(
result.content,
result.tool_call_id,
result.name
));
});
}
return '达到最大迭代次数,工具调用未完成';
}
async *_streamResponseChunks(
messages: BaseMessage[]
): AsyncGenerator<ChatGeneration> {
// 简化实现,实际应用中需要处理流式工具调用
const result = await this._call(messages);
yield {
text: result,
message: new AIMessage(result)
};
}
}
// 工具消息类
class ToolMessage implements BaseMessage {
role = 'tool';
constructor(
public content: string,
public tool_call_id: string,
public name: string
) {}
}实际应用示例
让我们看一个完整的实际应用示例,展示如何使用工具调用功能:
typescript
// 实用工具实现
class WeatherTool implements Tool {
name = 'get_weather';
description = '获取指定城市的天气信息';
parameters = {
city: {
type: 'string',
description: '城市名称',
required: true
},
country: {
type: 'string',
description: '国家代码(可选)',
required: false
}
};
async execute(args: { city: string; country?: string }): Promise<string> {
// 模拟天气 API 调用
const weatherData = {
city: args.city,
temperature: Math.floor(Math.random() * 30) + 5, // 5-35°C
condition: ['晴天', '多云', '雨天', '雪天'][Math.floor(Math.random() * 4)],
humidity: Math.floor(Math.random() * 50) + 30, // 30-80%
};
return JSON.stringify(weatherData, null, 2);
}
}
class CalculatorTool implements Tool {
name = 'calculate';
description = '执行数学计算';
parameters = {
expression: {
type: 'string',
description: '数学表达式,支持 +, -, *, /, ^, sqrt 等',
required: true
}
};
async execute(args: { expression: string }): Promise<number> {
try {
// 安全的数学表达式求值
const result = Function(`"use strict"; return (${args.expression})`)();
return result;
} catch (error) {
throw new Error(`计算错误: ${error.message}`);
}
}
}
class SearchTool implements Tool {
name = 'search';
description = '搜索网络信息';
parameters = {
query: {
type: 'string',
description: '搜索查询',
required: true
},
count: {
type: 'number',
description: '返回结果数量(默认为 5)',
required: false
}
};
async execute(args: { query: string; count?: number }): Promise<string> {
// 模拟搜索功能
const results = [
`关于 "${args.query}" 的搜索结果 1`,
`关于 "${args.query}" 的搜索结果 2`,
`关于 "${args.query}" 的搜索结果 3`
];
return results.slice(0, args.count || 5).join('\n');
}
}
class DateTimeTool implements Tool {
name = 'get_current_datetime';
description = '获取当前日期和时间';
parameters = {};
async execute(args: {}): Promise<string> {
return new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
weekday: 'long'
});
}
}
// 工具调用演示
class ToolCallingDemo {
private toolRegistry: ToolRegistry;
private agent: ToolCallingAgent;
constructor() {
// 创建工具注册器并注册工具
this.toolRegistry = new ToolRegistry();
this.toolRegistry.register(new WeatherTool());
this.toolRegistry.register(new CalculatorTool());
this.toolRegistry.register(new SearchTool());
this.toolRegistry.register(new DateTimeTool());
// 创建支持工具调用的 LLM
const llm = new OpenAIFunctionsLLM(
'gpt-3.5-turbo', // 实际使用时替换为 process.env.OPENAI_API_KEY
process.env.OPENAI_API_KEY || 'your-api-key-here'
);
// 创建工具调用代理
this.agent = new ToolCallingAgent(llm, this.toolRegistry, 5);
}
async demonstrateWeatherQuery(): Promise<void> {
console.log('=== 天气查询演示 ===\n');
const messages: BaseMessage[] = [
new SystemMessage('你是一个 helpful 的助手,可以调用工具来获取信息。'),
new HumanMessage('北京的天气怎么样?')
];
const result = await this.agent._call(messages);
console.log(`用户: 北京的天气怎么样?`);
console.log(`助手: ${result}\n`);
}
async demonstrateCalculation(): Promise<void> {
console.log('=== 数学计算演示 ===\n');
const messages: BaseMessage[] = [
new SystemMessage('你是一个 helpful 的助手,可以调用工具来获取信息。'),
new HumanMessage('计算 (25 + 17) * 3 - 8 的值')
];
const result = await this.agent._call(messages);
console.log(`用户: 计算 (25 + 17) * 3 - 8 的值`);
console.log(`助手: ${result}\n`);
}
async demonstrateMultiTool(): Promise<void> {
console.log('=== 多工具调用演示 ===\n');
const messages: BaseMessage[] = [
new SystemMessage('你是一个 helpful 的助手,可以调用工具来获取信息。'),
new HumanMessage('现在是什么时间?根据当前时间,计算 100 天后是星期几?')
];
const result = await this.agent._call(messages);
console.log(`用户: 现在是什么时间?根据当前时间,计算 100 天后是星期几?`);
console.log(`助手: ${result}\n`);
}
async demonstrateSearch(): Promise<void> {
console.log('=== 搜索功能演示 ===\n');
const messages: BaseMessage[] = [
new SystemMessage('你是一个 helpful 的助手,可以调用工具来获取信息。'),
new HumanMessage('搜索关于人工智能的最新发展')
];
const result = await this.agent._call(messages);
console.log(`用户: 搜索关于人工智能的最新发展`);
console.log(`助手: ${result}\n`);
}
async runAllDemos(): Promise<void> {
// 注意:这些演示需要有效的 OpenAI API 密钥
try {
// await this.demonstrateWeatherQuery();
// await this.demonstrateCalculation();
// await this.demonstrateMultiTool();
// await this.demonstrateSearch();
console.log('工具调用演示完成');
console.log('\n注意: 实际演示需要有效的 OpenAI API 密钥');
console.log('请在环境变量中设置 OPENAI_API_KEY');
} catch (error) {
console.error('演示过程中出现错误:', error.message);
}
}
}
// 工具调用链
class ToolCallingChain extends Runnable<string, string> {
private agent: ToolCallingAgent;
constructor(agent: ToolCallingAgent) {
super();
this.agent = agent;
}
async invoke(input: string): Promise<string> {
const messages: BaseMessage[] = [
new SystemMessage('你是一个 helpful 的助手,可以调用工具来获取信息。'),
new HumanMessage(input)
];
return await this.agent._call(messages);
}
}
// 使用工具调用链
async function demonstrateToolCallingChain() {
console.log('=== 工具调用链演示 ===\n');
// 创建工具和代理(同上)
const toolRegistry = new ToolRegistry();
toolRegistry.register(new WeatherTool());
toolRegistry.register(new CalculatorTool());
toolRegistry.register(new DateTimeTool());
// 创建链
const chain = new ToolCallingChain(
new ToolCallingAgent(
new OpenAIFunctionsLLM('gpt-3.5-turbo', process.env.OPENAI_API_KEY || 'your-api-key'),
toolRegistry,
3
)
);
// 使用链
try {
const result = await chain.invoke('今天是星期几?计算 50 天后是星期几?');
console.log(`问题: 今天是星期几?计算 50 天后是星期几?`);
console.log(`答案: ${result}`);
} catch (error) {
console.error('链式调用错误:', error.message);
}
}
// 动态工具注册
class DynamicToolRegistry extends ToolRegistry {
async loadToolsFromConfig(config: any[]): Promise<void> {
for (const toolConfig of config) {
const tool = this.createToolFromConfig(toolConfig);
if (tool) {
this.register(tool);
}
}
}
private createToolFromConfig(config: any): Tool | null {
switch (config.type) {
case 'weather':
return new WeatherTool();
case 'calculator':
return new CalculatorTool();
case 'search':
return new SearchTool();
case 'datetime':
return new DateTimeTool();
default:
console.warn(`未知工具类型: ${config.type}`);
return null;
}
}
}
// 配置驱动的工具调用
async function demonstrateConfigDrivenTools() {
console.log('=== 配置驱动的工具调用演示 ===\n');
const toolConfig = [
{ type: 'weather' },
{ type: 'calculator' },
{ type: 'datetime' }
];
const dynamicRegistry = new DynamicToolRegistry();
await dynamicRegistry.loadToolsFromConfig(toolConfig);
console.log(`动态注册了 ${dynamicRegistry.getAll().length} 个工具:`);
dynamicRegistry.getAll().forEach(tool => {
console.log(`- ${tool.name}: ${tool.description}`);
});
}
// 运行演示
async function runToolCallingDemos() {
console.log('🚀 LangChain Tool Calling 演示\n');
// 基础演示
const demo = new ToolCallingDemo();
await demo.runAllDemos();
console.log('\n' + '='.repeat(50) + '\n');
// 链式调用演示
await demonstrateToolCallingChain();
console.log('\n' + '='.repeat(50) + '\n');
// 配置驱动演示
await demonstrateConfigDrivenTools();
}
// 如果直接运行此文件,则执行演示
if (require.main === module) {
runToolCallingDemos().catch(console.error);
}总结
通过 OpenAI Functions/Tools 格式,LangChain V3 实现了强大的工具调用功能:
- 标准化接口 - 支持 OpenAI Functions 格式的工具定义和调用
- 参数解析 - 自动解析和验证工具调用参数
- 工具执行 - 安全执行工具并处理结果
- 循环调用 - 支持多轮工具调用和对话
- 链式集成 - 可以与 LCEL 链式调用无缝集成
- 动态注册 - 支持动态加载和注册工具
工具调用机制使得 LLM 能够与外部系统进行交互,大大扩展了 AI 应用的能力和实用性。
在下一章中,我们将探讨 LangGraph:用有向图构建状态化 Agent,了解如何使用图形结构构建更复杂的 AI 代理。