Skip to content

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 实现了强大的工具调用功能:

  1. 标准化接口 - 支持 OpenAI Functions 格式的工具定义和调用
  2. 参数解析 - 自动解析和验证工具调用参数
  3. 工具执行 - 安全执行工具并处理结果
  4. 循环调用 - 支持多轮工具调用和对话
  5. 链式集成 - 可以与 LCEL 链式调用无缝集成
  6. 动态注册 - 支持动态加载和注册工具

工具调用机制使得 LLM 能够与外部系统进行交互,大大扩展了 AI 应用的能力和实用性。

在下一章中,我们将探讨 LangGraph:用有向图构建状态化 Agent,了解如何使用图形结构构建更复杂的 AI 代理。