Logo
热心市民王先生

关键代码验证

AI研究 Tool Calling 代码实现

提供跨模型Tool Calling兼容的关键代码示例和实现方案

统一工具调用接口设计

TypeScript 接口定义

// 统一的工具定义接口
interface UnifiedToolDefinition {
  name: string;
  description: string;
  parameters: JSONSchema;
}

// 统一的工具调用请求
interface UnifiedToolCall {
  id: string;
  name: string;
  arguments: Record<string, unknown>;
}

// 统一的工具调用结果
interface UnifiedToolResult {
  callId: string;
  content: string;
  isError?: boolean;
}

模型适配器实现

OpenAI 适配器

class OpenAIAdapter implements ToolCallingAdapter {
  // 转换工具定义
  toModelFormat(tools: UnifiedToolDefinition[]): OpenAITool[] {
    return tools.map(tool => ({
      type: 'function',
      function: {
        name: tool.name,
        description: tool.description,
        parameters: tool.parameters
      }
    }));
  }

  // 解析模型响应
  parseToolCalls(response: OpenAIResponse): UnifiedToolCall[] {
    if (!response.tool_calls) return [];

    return response.tool_calls.map(tc => ({
      id: tc.id,
      name: tc.function.name,
      // 关键:OpenAI 返回的是 JSON 字符串,需要解析
      arguments: JSON.parse(tc.function.arguments)
    }));
  }

  // 构建结果消息
  buildToolResultMessage(result: UnifiedToolResult): OpenAIMessage {
    return {
      role: 'tool',
      tool_call_id: result.callId,
      content: result.content
    };
  }
}

Anthropic 适配器

class AnthropicAdapter implements ToolCallingAdapter {
  // 转换工具定义
  toModelFormat(tools: UnifiedToolDefinition[]): AnthropicTool[] {
    return tools.map(tool => ({
      name: tool.name,
      description: tool.description,
      // 关键差异:使用 input_schema 而非 parameters
      input_schema: tool.parameters
    }));
  }

  // 解析模型响应
  parseToolCalls(response: AnthropicResponse): UnifiedToolCall[] {
    const toolUseBlocks = response.content.filter(
      block => block.type === 'tool_use'
    );

    return toolUseBlocks.map(block => ({
      id: block.id,
      name: block.name,
      // 关键:Anthropic 直接返回对象,无需解析
      arguments: block.input
    }));
  }

  // 构建结果消息
  buildToolResultMessage(result: UnifiedToolResult): AnthropicMessage {
    return {
      role: 'user',
      content: [{
        type: 'tool_result',
        tool_use_id: result.callId,
        content: result.content
      }]
    };
  }
}

MCP 兼容转换

// MCP 工具转换为 Claude 格式
function mcpToClaudeTools(mcpTools: MCPTool[]): ClaudeTool[] {
  return mcpTools.tools.map(tool => ({
    name: tool.name,
    description: tool.description || '',
    // 关键转换:inputSchema -> input_schema
    input_schema: tool.inputSchema
  }));
}

// 处理 MCP 工具调用结果
async function handleMCPToolCall(
  mcpClient: MCPClient,
  toolCall: UnifiedToolCall
): Promise<UnifiedToolResult> {
  const result = await mcpClient.callTool({
    name: toolCall.name,
    arguments: toolCall.arguments
  });

  return {
    callId: toolCall.id,
    content: result.content
      .filter(c => c.type === 'text')
      .map(c => c.text)
      .join('\n'),
    isError: result.isError
  };
}

错误恢复机制

参数解析容错

function safeParseArguments(
  rawArgs: string | Record<string, unknown>
): Record<string, unknown> {
  // 如果已经是对象,直接返回
  if (typeof rawArgs === 'object') {
    return rawArgs;
  }

  // 如果是字符串,尝试解析
  if (typeof rawArgs === 'string') {
    try {
      return JSON.parse(rawArgs);
    } catch (e) {
      // 解析失败,返回空对象而非抛出异常
      console.error('Failed to parse tool arguments:', e);
      return {};
    }
  }

  return {};
}

响应格式验证

interface ToolCallValidator {
  validate(response: unknown): ToolCallValidationResult;
}

class GenericToolCallValidator implements ToolCallValidator {
  validate(response: unknown): ToolCallValidationResult {
    // 检测 OpenAI 格式
    if (this.isOpenAIFormat(response)) {
      return { format: 'openai', toolCalls: response.tool_calls };
    }

    // 检测 Anthropic 格式
    if (this.isAnthropicFormat(response)) {
      const toolUseBlocks = response.content?.filter(
        (b: { type: string }) => b.type === 'tool_use'
      ) || [];
      return { format: 'anthropic', toolCalls: toolUseBlocks };
    }

    // 检测 Gemini 格式
    if (this.isGeminiFormat(response)) {
      const functionCalls = response.candidates?.[0]?.content?.parts
        ?.filter((p: { functionCall?: unknown }) => p.functionCall)
        .map((p: { functionCall: { name: string; args: Record<string, unknown> } }) => ({
          name: p.functionCall.name,
          arguments: p.functionCall.args
        })) || [];
      return { format: 'gemini', toolCalls: functionCalls };
    }

    return { format: 'unknown', toolCalls: [] };
  }

  private isOpenAIFormat(r: unknown): r is { tool_calls: unknown[] } {
    return typeof r === 'object' && r !== null && 'tool_calls' in r;
  }

  private isAnthropicFormat(r: unknown): r is { content: unknown[] } {
    return typeof r === 'object' && r !== null && 'content' in r &&
           Array.isArray((r as { content: unknown[] }).content);
  }

  private isGeminiFormat(r: unknown): r is { candidates: unknown[] } {
    return typeof r === 'object' && r !== null && 'candidates' in r;
  }
}

OpenCode 集成建议

配置层设计

# opencode.yaml
providers:
  - name: openai
    adapter: openai
    models:
      - gpt-4
      - gpt-3.5-turbo

  - name: anthropic
    adapter: anthropic
    models:
      - claude-opus-4-6
      - claude-sonnet-4-6

  - name: glm
    adapter: openai  # GLM 兼容 OpenAI 格式
    models:
      - glm-5
    config:
      # 特殊处理配置
      strictArgumentParsing: false
      retryOnFormatError: true

运行时错误处理

async function executeToolCall(
  model: string,
  toolCall: UnifiedToolCall,
  tools: Map<string, ToolExecutor>
): Promise<UnifiedToolResult> {
  const executor = tools.get(toolCall.name);

  if (!executor) {
    return {
      callId: toolCall.id,
      content: `Error: Unknown tool "${toolCall.name}"`,
      isError: true
    };
  }

  try {
    const result = await executor.execute(toolCall.arguments);
    return {
      callId: toolCall.id,
      content: JSON.stringify(result)
    };
  } catch (error) {
    // 捕获执行异常,避免中断整个流程
    return {
      callId: toolCall.id,
      content: `Error: ${error.message}`,
      isError: true
    };
  }
}

最佳实践总结

1. 格式转换三原则

  1. 输入统一 —— 无论模型如何,工具定义使用统一格式
  2. 输出适配 —— 根据模型特性转换响应
  3. 错误隔离 —— 工具调用失败不应导致整体崩溃

2. GLM 模型特殊处理

// GLM 模型的已知问题和解决方案
const glmConfig = {
  // 问题:有时返回非标准 JSON
  // 解决:宽松解析 + 默认值
  argumentParsing: 'lenient',

  // 问题:复杂 Schema 可能导致异常
  // 解决:简化 Schema 定义
  schemaSimplification: true,

  // 问题:并行调用支持不稳定
  // 解决:串行执行工具调用
  parallelCalls: false
};

3. 调试技巧

// 启用详细日志
const DEBUG = process.env.TOOL_CALL_DEBUG === 'true';

function logToolCall(model: string, direction: 'in' | 'out', data: unknown) {
  if (DEBUG) {
    console.error(`[ToolCall][${model}][${direction}]`, JSON.stringify(data, null, 2));
  }
}

参考资料