LLM 工具调用与 MCP 机制
2026/7/23 11:56:37 网站建设 项目流程

主题:LLM 如何被提供可用工具、工具调用协议、MCP 工具发现机制、以及多模型参数差异的抹平方式。

一、工具调用整体流程

工具调用(Function Calling / Tool Use)本质是一套「声明 → 决策 → 执行 → 回填」的循环:

  1. 在请求里声明「有哪些工具可用」(tools 定义)
  2. LLM 根据用户问题决定「是否调用 / 调用哪个 / 传什么参数」
  3. 应用程序真正执行工具,拿到结果
  4. 把结果回填给 LLM,LLM 生成最终自然语言回答

关键认知:LLM 本身不执行工具,它只输出「我想调用某工具 + 参数」的结构化意图,真正的执行由应用代码负责。


二、如何提供可用工具(工具声明)

主流做法:在 API 请求中传入一个tools数组,每个工具用 JSON Schema 描述。

OpenAI 格式

{"model":"gpt-4","messages":[],"tools":[{"type":"function","function":{"name":"get_weather","description":"查询指定城市的实时天气","parameters":{"type":"object","properties":{"city":{"type":"string","description":"城市名,如 杭州"},"unit":{"type":"string","enum":["celsius","fahrenheit"]}},"required":["city"]}}}],"tool_choice":"auto"}

Anthropic (Claude) 格式

{"model":"claude-sonnet-4","messages":[],"tools":[{"name":"get_weather","description":"查询指定城市的实时天气","input_schema":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}]}

关键点:

  • name:工具唯一标识
  • description:最重要,模型靠它判断何时调用,要写清楚用途/触发条件
  • parameters/input_schema:用 JSON Schema 约束参数类型、枚举、必填项

三、工具调用协议(关键要素)

1. 调用控制字段 tool_choice

  • auto:模型自行决定是否调用
  • none:禁止调用
  • required/any:强制必须调用某工具
  • 指定具体工具名:强制调用该工具

2. 模型返回工具调用意图

OpenAI:

{"role":"assistant","tool_calls":[{"id":"call_abc123","type":"function","function":{"name":"get_weather","arguments":"{\"city\": \"杭州\"}"}}]}

Anthropic:

{"role":"assistant","content":[{"type":"tool_use","id":"toolu_abc123","name":"get_weather","input":{"city":"杭州"}}],"stop_reason":"tool_use"}

3. 回填执行结果(协议核心)

执行完工具后,必须用特定角色/类型把结果塞回对话历史,并通过 id 关联:

OpenAI —— 用role: "tool"+tool_call_id

{"role":"tool","tool_call_id":"call_abc123","content":"{\"temp\": 28, \"desc\": \"晴\"}"}

Anthropic —— 用role: "user"里的tool_result+tool_use_id

{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_abc123","content":"{\"temp\": 28, \"desc\": \"晴\"}"}]}

4. 多轮循环

回填后再次请求模型,模型可能继续调用下一个工具(多步/链式调用),或生成最终回答(stop_reason: end_turn/finish_reason: stop)。

5. 并行工具调用

现代模型支持一次返回多个 tool_call。协议要求:

  • 每个 tool_call 有独立 id
  • 回填时每个结果按 id 一一对应返回,缺一不可
  • 应用可并行执行这些工具以提速

6. 常见协议注意事项

事项说明
arguments 是字符串OpenAI 的 arguments 是 JSON 字符串需二次解析;Anthropic 的 input 已是对象
id 必须匹配回填结果 id 与调用 id 不匹配会报错
完整对话历史每轮请求需带上包含 tool_call + tool_result 的完整 messages
schema 越清晰越准description 和 enum 约束能显著降低幻觉/传错参
错误也要回填工具执行失败时,把错误信息作为 tool_result 返回,让模型决定重试或换方案

四、MCP:支持哪些工具是怎么告诉 LLM 的

核心结论:LLM 本身并不直接"知道"有哪些 MCP,它只认标准的 tools 定义。中间的 MCP Client(宿主程序)负责去各 MCP Server 发现工具,再翻译成 LLM 的工具协议喂给它。

整体链路

MCP Server(提供工具) ↑ MCP 协议 (tools/list, tools/call) MCP Client / Host(如 Claude Desktop、IDE、Agent 框架) ↑ 把 MCP 工具翻译成 LLM 的 tools 定义 LLM API 请求 (tools: [...])

第一步:宿主如何知道"支持哪些 MCP"—— 靠配置

{"mcpServers":{"filesystem":{"command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","/path"]},"github":{"command":"npx","args":["-y","@modelcontextprotocol/server-github"],"env":{"GITHUB_TOKEN":"xxx"}},"my-remote":{"url":"https://example.com/mcp","transport":"sse"}}}

宿主启动时按配置逐个连接(本地用 stdio 启子进程,远程用 SSE / HTTP)。

第二步:MCP 协议的"工具发现"(底层 JSON-RPC 2.0)

握手初始化:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}

列出工具 tools/list:

// Client → Server{"jsonrpc":"2.0","id":2,"method":"tools/list"}// Server → Client{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"read_file","description":"读取指定路径的文件内容","inputSchema":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}}]}}

MCP Server 除了 tools,还能暴露 resources(resources/list)和 prompts(prompts/list),发现机制类似。

第三步:翻译成 LLM 的 tools 定义

宿主把所有 MCP Server 返回的工具聚合,转换成 LLM 工具格式注入 API 请求。MCP 的 inputSchema 与 LLM 的 input_schema/parameters 几乎同构(都是 JSON Schema)。

命名冲突处理:多 Server 可能有同名工具,宿主通常加前缀区分,例如filesystem__read_filegithub__create_issue

第四步:调用回路

1. LLM 返回 tool_use: { name: "filesystem__read_file", input: {...} } 2. 宿主识别前缀,路由到对应 MCP Server 3. 宿主向该 Server 发 tools/call 4. Server 执行,返回 result 5. 宿主把 result 作为 tool_result 回填给 LLM

tools/call 示例:

// Client → Server{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"read_file","arguments":{"path":"/a.txt"}}}// Server → Client{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"文件内容..."}]}}

动态更新:工具列表变化通知

{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}

宿主收到后重新 tools/list,并在下一轮请求里更新给 LLM 的 tools 定义。

完整图景

配置文件 → 宿主知道「连哪些 MCP Server」 tools/list → 宿主知道「每个 Server 有哪些工具」 Schema 翻译+聚合 → 拼成 LLM 的 tools 定义 API 请求 → 这一刻 LLM 才「知道」有哪些工具可用 tool_use → LLM 决定调用 tools/call → 宿主路由回对应 Server 执行 tool_result 回填 → LLM 生成最终回答

一句话总结:支持哪些 MCP 由宿主的配置决定;宿主通过 MCP 的 tools/list 发现工具;再翻译聚合成标准 tools 定义,在每次 API 请求里告诉 LLM。LLM 全程只跟标准工具协议打交道,对 MCP 本身无感知。


五、不同模型参数格式差异的抹平

核心结论:差异主要在「客户端 / Agent 框架层」通过适配器(Adapter / Provider 抽象)抹平。抹平的是「协议格式」,抹不平的是「模型能力和行为」。

抹平发生在哪一层

业务代码 ↓ 统一接口(抹平层) ┌─────────────────────────────┐ │ Provider / Adapter 抽象层 │ │ OpenAIAdapter / ClaudeAdapter / GeminiAdapter │ └─────────────────────────────┘ ↓ 各自原生 API 格式 OpenAI API Claude API Gemini API

两种常见实现方式:

  1. Agent / SDK 框架内置适配:LangChain、LlamaIndex、Vercel AI SDK、Spring AI 等,定义统一的 Tool / Message 抽象,内部为每个厂商写 adapter。
  2. 网关 / 代理服务:如 LiteLLM、OneAPI,对外统一暴露 OpenAI 格式,内部转译成各厂商格式。

具体抹平了哪些差异(以工具调用为例)

差异点OpenAIAnthropicGemini抹平方式
工具定义键名function.parametersinput_schemafunctionDeclarations.parametersadapter 改字段名,schema 本体同构
调用意图tool_calls[]content[].tool_usefunctionCall统一解析成内部 ToolCall
参数载荷arguments(JSON 字符串)input(对象)args(对象)adapter 统一 parse 成对象
结果回填role:“tool” + tool_call_idrole:“user” 里 tool_result + tool_use_idfunctionResponse统一封装成 ToolResult 按厂商拼回
system 提示messages 里 role:“system”顶层独立 system 字段systemInstructionadapter 搬运到对应位置
强制调用tool_choicetool_choicetoolConfig.mode统一枚举映射

本质:一套内部中间表示(IR)

业务定义统一 Tool │ ▼ 内部 IR(中立表示:Message / Tool / ToolCall / ToolResult) │ serialize(按 provider 出站翻译) ▼ 各厂商原生请求格式 │ 调用 API ▼ 各厂商原生响应 │ parse(入站翻译回 IR) ▼ 内部 IR → 业务统一处理

伪代码:

classToolCall:# 中立表示id:strname:strargs:dict# 统一是对象,不管厂商用字符串还是对象classOpenAIAdapter:defto_request(self,tools,messages):...defparse_response(self,resp)->list[ToolCall]:# OpenAI 的 arguments 是字符串,这里 json.loads 抹平成 dict...classClaudeAdapter:defto_request(self,tools,messages):...defparse_response(self,resp)->list[ToolCall]:...

能抹平 vs 抹不平

能抹平(格式/协议层):

  • 字段名、消息结构、system 位置
  • 参数是字符串还是对象
  • 工具定义、调用、回填的封装形式
  • 流式事件格式

抹不平(能力/行为层):

  • 是否支持并行工具调用
  • 是否支持工具调用本身(老模型/小模型可能不支持,只能降级为提示词模拟 ReAct)
  • JSON Schema 支持程度(enum、嵌套对象、$ref 遵守度不同)
  • 指令遵循度 / 幻觉率
  • 上下文窗口、token 计费、stop_reason 语义差异
  • 多模态、思维链(thinking)等特有能力

对能力差异,框架只能做「能力探测 + 降级策略」,而非真正抹平。

结论

  • 格式差异:在 Agent / 框架 / 网关的适配层根据 provider 自动抹平,业务代码通常无感。
  • 能力差异:抹不平,只能靠能力标记 + 降级 / 兼容策略处理。
  • 这也是很多框架有「provider」或「model capability」概念的原因——既做格式翻译,也记录每个模型支持什么,运行时决定走原生工具协议还是模拟方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询