<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>工具调用 | 林子杨的个人网站</title><link>https://ziyanglin.netlify.app/zh/tags/%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8/</link><atom:link href="https://ziyanglin.netlify.app/zh/tags/%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8/index.xml" rel="self" type="application/rss+xml"/><description>工具调用</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh-Hans</language><lastBuildDate>Mon, 30 Jun 2025 08:00:00 +0000</lastBuildDate><image><url>https://ziyanglin.netlify.app/img/icon-192.png</url><title>工具调用</title><link>https://ziyanglin.netlify.app/zh/tags/%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8/</link></image><item><title>模型上下文协议(MCP)：AI能力扩展的标准化框架</title><link>https://ziyanglin.netlify.app/zh/post/mcp-documentation/</link><pubDate>Mon, 30 Jun 2025 08:00:00 +0000</pubDate><guid>https://ziyanglin.netlify.app/zh/post/mcp-documentation/</guid><description>&lt;h2 id="1-mcp">1. 宏观介绍：在工具调用之上，我们为什么需要MCP？&lt;/h2>
&lt;p>在上一篇关于通用LLM工具调用的文档中，我们揭示了LLM如何通过调用外部函数来打破其知识边界。这是一种强大的&lt;strong>编程范式&lt;/strong>，但它本身并未定义一套&lt;strong>标准化的通信规则&lt;/strong>。每个开发者在实现时，都需要自行决定如何组织API、如何管理工具、如何处理数据格式，这导致了生态的碎片化。&lt;/p>
&lt;p>&lt;strong>模型上下文协议（Model Context Protocol, MCP）&lt;/strong> 正是为了解决这个问题而生。它不是要取代通用的工具调用概念，而是在其之上构建了一层&lt;strong>标准化的、可插拔的、面向服务的协议&lt;/strong>。&lt;/p>
&lt;p>如果说&amp;quot;工具调用&amp;quot;是让汽车学会了&amp;quot;加油&amp;rdquo;（使用外部能力）这个动作，那么MCP就是为世界建立了&lt;strong>统一标准的加油站和加油枪接口&lt;/strong>。无论你开的是什么车（不同的LLM），无论你要加什么油（不同的工具），只要遵循MCP这套标准，就能无缝对接，即插即用。&lt;/p>
&lt;p>MCP的核心价值在于：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>标准化 (Standardization)&lt;/strong>：定义了模型与外部工具服务之间通信的统一消息格式和交互模式。开发者不再需要为每个模型或应用定制工具集成方案。&lt;/li>
&lt;li>&lt;strong>解耦 (Decoupling)&lt;/strong>：将工具的&lt;strong>实现&lt;/strong>（运行在MCP服务器上）与模型的&lt;strong>使用&lt;/strong>（由LLM发起调用）彻底分离。模型不需要知道工具的内部代码，只需要知道如何通过协议与其对话。&lt;/li>
&lt;li>&lt;strong>可复用性 (Reusability)&lt;/strong>：一旦一个工具或数据源被封装成一个MCP服务器，它就可以被任何支持MCP协议的模型或应用轻松复用，极大地提高了开发效率。&lt;/li>
&lt;li>&lt;strong>可发现性 (Discoverability)&lt;/strong>：MCP使得工具服务化，为未来构建工具市场、实现工具的自动发现和编排奠定了基础。&lt;/li>
&lt;/ul>
&lt;p>简而言之，MCP将零散的&amp;quot;函数调用&amp;quot;提升到了&amp;quot;分布式服务调用&amp;quot;的层面，是构建可扩展、可互操作的AI Agent生态系统的关键基础设施。&lt;/p>
&lt;h2 id="2-mcp">2. MCP核心架构：三位一体的协同模式&lt;/h2>
&lt;p>MCP的架构由三个核心组件构成，它们之间通过清晰定义的协议进行交互，形成了一个稳固的&amp;quot;三位一体&amp;quot;协同模式。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>模型/智能体 (Model/Agent)&lt;/strong>：决策核心。它负责理解用户意图，并生成遵循MCP格式的请求，以调用外部工具或访问外部资源。&lt;/li>
&lt;li>&lt;strong>MCP客户端 (MCP Client)&lt;/strong>：通信枢纽。它作为模型与MCP服务器之间的桥梁，负责解析模型生成的MCP请求，通过标准化的传输方式（如Stdio、HTTP SSE）与相应的MCP服务器通信，并处理返回结果。&lt;/li>
&lt;li>&lt;strong>MCP服务器 (MCP Server)&lt;/strong>：能力提供方。这是一个独立的进程或服务，它将一个或多个工具（Tools）或数据源（Resources）封装起来，并通过MCP协议对外提供标准化的访问接口。&lt;/li>
&lt;/ol>
&lt;p>下面是这个架构的可视化解释：&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
subgraph Agent [模型/智能体]
A[LLM] -- 生成请求 --&amp;gt; B(MCP XML Request);
end
subgraph Client [MCP客户端]
C{请求解析器};
B -- 解析请求 --&amp;gt; C;
end
subgraph LocalServer [MCP服务器 - 本地]
D[Stdio通信];
end
subgraph RemoteServer [MCP服务器 - 远程]
E[HTTP SSE通信];
end
subgraph ServerCore [MCP服务器内部]
F[协议处理器] -- 执行工具 --&amp;gt; G[工具/资源实现];
end
C -- 路由到本地 --&amp;gt; D;
C -- 路由到远程 --&amp;gt; E;
D -- 本地传输 --&amp;gt; F;
E -- 远程传输 --&amp;gt; F;
G -- 返回结果 --&amp;gt; F;
F -- 协议返回 --&amp;gt; C;
C -- 提交结果 --&amp;gt; A;
style A fill:#cde4ff,stroke:#333;
style B fill:#e6ffc2,stroke:#333;
style C fill:#fce8b2,stroke:#333;
style D fill:#f9c5b4,stroke:#333;
style E fill:#f9c5b4,stroke:#333;
style F fill:#d4a8e3,stroke:#333;
style G fill:#b4f9f2,stroke:#333;
&lt;/code>&lt;/pre>
&lt;h3 id="heading">架构职责详解：&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>模型生成请求&lt;/strong>：当LLM需要外部能力时，它不再生成特定API的JSON，而是生成一个符合MCP规范的XML消息，例如&lt;code>&amp;lt;use_mcp_tool&amp;gt;&lt;/code>。这个消息清晰地指明了要与哪个&lt;code>server_name&lt;/code>通信，调用哪个&lt;code>tool_name&lt;/code>。&lt;/li>
&lt;li>&lt;strong>客户端解析与路由&lt;/strong>：MCP客户端（通常是模型运行环境的一部分）捕获并解析这个XML请求。它根据&lt;code>server_name&lt;/code>查询一个服务注册表，确定目标服务器是本地进程还是远程服务。&lt;/li>
&lt;li>&lt;strong>选择通信信道&lt;/strong>：
&lt;ul>
&lt;li>如果目标是&lt;strong>本地MCP服务器&lt;/strong>（例如，一个本地运行的Python脚本），客户端将通过&lt;strong>标准输入/输出 (stdio)&lt;/strong> 与该服务器进程进行通信。&lt;/li>
&lt;li>如果目标是&lt;strong>远程MCP服务器&lt;/strong>（例如，一个部署在云端的服务），客户端将通过&lt;strong>HTTP服务器发送事件 (SSE)&lt;/strong> 协议与其建立连接。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>服务器处理请求&lt;/strong>：MCP服务器上的协议处理器接收到请求后，根据&lt;code>tool_name&lt;/code>或&lt;code>uri&lt;/code>，调用其内部已经注册好的具体工具函数或资源处理器。&lt;/li>
&lt;li>&lt;strong>执行与返回&lt;/strong>：服务器执行具体的逻辑（调用API、查询数据库等），并将结果封装成MCP标准格式，通过原路返回给客户端。&lt;/li>
&lt;li>&lt;strong>结果反馈给模型&lt;/strong>：客户端接收到服务器的响应后，将其整理并格式化，作为外部工具的执行结果，再次提交给LLM，以供LLM生成最终的自然语言回复，完成整个交互闭环。&lt;/li>
&lt;/ol>
&lt;p>这个架构的精妙之处在于，LLM本身完全与工具的物理位置、网络实现细节解耦。它只需要学会&amp;quot;说&amp;quot;MCP这门&amp;quot;普通话&amp;rdquo;，就可以与整个MCP生态系统中的任何服务进行交互。&lt;/p>
&lt;h2 id="3-mcp">3. 通信协议深潜：MCP的神经网络&lt;/h2>
&lt;p>MCP的强大之处在于其标准化的通信方式。它主要通过两种截然不同的协议来连接客户端和服务器，以适应不同的部署场景。&lt;/p>
&lt;h3 id="31--stdio">3.1. 本地通信：标准输入/输出 (Stdio)&lt;/h3>
&lt;p>当MCP服务器是一个本地可执行文件或脚本时（例如，一个Python脚本、一个Go程序），MCP客户端会采用**标准输入/输出（Stdio）**来进行通信。这是一种经典且高效的进程间通信（IPC）方式。&lt;/p>
&lt;p>&lt;strong>工作流程掰开揉碎看&lt;/strong>:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>启动子进程&lt;/strong>: MCP客户端（如VS Code扩展）以一个&lt;strong>子进程&lt;/strong>的方式启动MCP服务器程序（例如，执行 &lt;code>python mcp_server.py&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>管道建立&lt;/strong>: 操作系统会自动为父进程（客户端）和子进程（服务器）之间建立三个管道：
&lt;ul>
&lt;li>&lt;code>stdin&lt;/code> (标准输入): 客户端向服务器发送数据的通道。&lt;/li>
&lt;li>&lt;code>stdout&lt;/code> (标准输出): 服务器向客户端发送成功结果的通道。&lt;/li>
&lt;li>&lt;code>stderr&lt;/code> (标准错误): 服务器向客户端发送错误信息的通道。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>消息交换&lt;/strong>:
&lt;ul>
&lt;li>客户端将MCP请求（例如 &lt;code>&amp;lt;use_mcp_tool&amp;gt;...&lt;/code> 的XML字符串）写入到服务器进程的&lt;code>stdin&lt;/code>。为了处理粘包问题，消息通常会以特定的分隔符（如换行符&lt;code>\n&lt;/code>）或长度前缀来界定。&lt;/li>
&lt;li>服务器从其&lt;code>stdout&lt;/code>读取并解析该请求，执行相应的逻辑。&lt;/li>
&lt;li>服务器将执行结果（同样是MCP格式的XML字符串）写入到自己的&lt;code>stdout&lt;/code>。&lt;/li>
&lt;li>如果过程中发生任何错误，错误详情会被写入到&lt;code>stderr&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>生命周期管理&lt;/strong>: 客户端负责监控服务器子进程的生命周期，可以在不再需要时终止它。&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>优点&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>极低延迟&lt;/strong>: 因为是本地进程间通信，几乎没有网络开销。&lt;/li>
&lt;li>&lt;strong>简单可靠&lt;/strong>: 实现简单，不依赖于网络堆栈。&lt;/li>
&lt;li>&lt;strong>安全性高&lt;/strong>: 数据不出本机，天然隔离。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>适用场景&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>需要高性能、高频次调用的本地工具。&lt;/li>
&lt;li>直接操作本地文件系统或硬件的工具。&lt;/li>
&lt;li>作为开发和调试环境。&lt;/li>
&lt;/ul>
&lt;h3 id="32--http-sse">3.2. 远程通信：服务器发送事件 (HTTP SSE)&lt;/h3>
&lt;p>当MCP服务器部署在远程主机或云端时，通信则通过基于HTTP的**服务器发送事件（Server-Sent Events, SSE）**协议。SSE是一种允许服务器向客户端单向推送事件的Web技术。&lt;/p>
&lt;p>&lt;strong>工作流程掰开揉碎看&lt;/strong>:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>HTTP连接&lt;/strong>: MCP客户端向MCP服务器的特定端点（例如 &lt;code>https://api.my-mcp-server.com/v1/mcp&lt;/code>）发起一个常规的HTTP GET请求。关键在于，客户端会在请求头中包含 &lt;code>Accept: text/event-stream&lt;/code>，表明它希望建立一个SSE连接。&lt;/li>
&lt;li>&lt;strong>长连接保持&lt;/strong>: 服务器在收到该请求后，不会立即关闭连接，而是保持其打开状态，形成一个&lt;strong>长连接&lt;/strong>。响应的&lt;code>Content-Type&lt;/code>头会被设置为&lt;code>text/event-stream&lt;/code>。&lt;/li>
&lt;li>&lt;strong>事件推送&lt;/strong>:
&lt;ul>
&lt;li>客户端通过这个长连接，将MCP请求（XML字符串）作为HTTP POST请求体的一部分发送到服务器的另一个端点。&lt;/li>
&lt;li>服务器处理请求后，会将响应数据封装成SSE事件的格式，通过之前建立的长连接&lt;strong>推送&lt;/strong>回客户端。每个事件都由&lt;code>event: &amp;lt;event_name&amp;gt;&lt;/code>和&lt;code>data: &amp;lt;event_data&amp;gt;&lt;/code>等字段组成。&lt;/li>
&lt;li>MCP通常会定义不同类型的事件，如&lt;code>result&lt;/code>表示成功，&lt;code>error&lt;/code>表示失败，&lt;code>log&lt;/code>用于传输日志等。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>优点&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>跨网络通信&lt;/strong>: 可以轻松连接到任何地方的服务器。&lt;/li>
&lt;li>&lt;strong>穿透防火墙&lt;/strong>: 基于标准的HTTP(S)协议，具有良好的网络兼容性。&lt;/li>
&lt;li>&lt;strong>服务端推送&lt;/strong>: 适合需要服务器主动通知的场景。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>适用场景&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>封装第三方云服务API（如天气、地图、支付）。&lt;/li>
&lt;li>需要集中管理和部署的共享工具。&lt;/li>
&lt;li>构建可公开访问的工具服务生态。&lt;/li>
&lt;/ul>
&lt;h2 id="4-mcp">4. MCP消息格式拆解：协议的&amp;quot;通用语&amp;rdquo;&lt;/h2>
&lt;p>MCP的核心是其基于XML的、人类可读且机器易解析的消息格式。模型通过生成这些特定格式的XML片段来表达其意图。&lt;/p>
&lt;h3 id="41-usemcptool">4.1. &lt;code>&amp;lt;use_mcp_tool&amp;gt;&lt;/code>：调用一个工具&lt;/h3>
&lt;p>这是最核心的消息，用于请求执行一个已定义的工具。&lt;/p>
&lt;p>&lt;strong>结构示例&lt;/strong>:&lt;/p>
&lt;pre>&lt;code class="language-xml">&amp;lt;use_mcp_tool&amp;gt;
&amp;lt;server_name&amp;gt;weather-server&amp;lt;/server_name&amp;gt;
&amp;lt;tool_name&amp;gt;get_forecast&amp;lt;/tool_name&amp;gt;
&amp;lt;arguments&amp;gt;
{
&amp;quot;city&amp;quot;: &amp;quot;San Francisco&amp;quot;,
&amp;quot;days&amp;quot;: 5
}
&amp;lt;/arguments&amp;gt;
&amp;lt;/use_mcp_tool&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>字段详解&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>&amp;lt;server_name&amp;gt;&lt;/code> (必需)&lt;/strong>:
&lt;ul>
&lt;li>&lt;strong>作用&lt;/strong>: MCP服务器的唯一标识符。&lt;/li>
&lt;li>&lt;strong>底层细节&lt;/strong>: 客户端通过这个名称在其内部的服务注册表中查找对应的服务器信息（是本地进程还是远程URL），决定是使用Stdio还是SSE进行通信。这是实现路由的关键。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>&lt;code>&amp;lt;tool_name&amp;gt;&lt;/code> (必需)&lt;/strong>:
&lt;ul>
&lt;li>&lt;strong>作用&lt;/strong>: 要调用的工具的名称。&lt;/li>
&lt;li>&lt;strong>底层细节&lt;/strong>: MCP服务器接收到请求后，会用这个名称在其内部的工具映射表中找到并执行对应的函数。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>&lt;code>&amp;lt;arguments&amp;gt;&lt;/code> (必需)&lt;/strong>:
&lt;ul>
&lt;li>&lt;strong>作用&lt;/strong>: 调用工具所需的参数。&lt;/li>
&lt;li>&lt;strong>底层细节&lt;/strong>: 内容通常是一个&lt;strong>JSON字符串&lt;/strong>。服务器需要先解析这个字符串，将其转换为语言原生的对象或字典，然后再传递给具体的工具函数。这种设计利用了JSON强大的数据表达能力和跨语言的通用性。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="42-accessmcpresource">4.2. &lt;code>&amp;lt;access_mcp_resource&amp;gt;&lt;/code>：访问一个资源&lt;/h3>
&lt;p>除了主动&amp;quot;执行&amp;quot;工具，MCP还支持被动地&amp;quot;访问&amp;quot;数据源。&lt;/p>
&lt;p>&lt;strong>结构示例&lt;/strong>:&lt;/p>
&lt;pre>&lt;code class="language-xml">&amp;lt;access_mcp_resource&amp;gt;
&amp;lt;server_name&amp;gt;internal-docs&amp;lt;/server_name&amp;gt;
&amp;lt;uri&amp;gt;doc://product/specs/version-3.md&amp;lt;/uri&amp;gt;
&amp;lt;/access_mcp_resource&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>字段详解&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>&amp;lt;server_name&amp;gt;&lt;/code> (必需)&lt;/strong>: 同上，用于路由。&lt;/li>
&lt;li>&lt;strong>&lt;code>&amp;lt;uri&amp;gt;&lt;/code> (必需)&lt;/strong>:
&lt;ul>
&lt;li>&lt;strong>作用&lt;/strong>: 资源的统一资源标识符。&lt;/li>
&lt;li>&lt;strong>底层细节&lt;/strong>: URI的格式 (&lt;code>scheme://path&lt;/code>) 由服务器自行定义和解释。例如：
&lt;ul>
&lt;li>&lt;code>file:///path/to/local/file&lt;/code>: 访问本地文件。&lt;/li>
&lt;li>&lt;code>db://customers/id/123&lt;/code>: 查询数据库。&lt;/li>
&lt;li>&lt;code>api://v1/users?active=true&lt;/code>: 访问某个REST API端点。
服务器需要解析这个URI，并根据其模式和路径执行相应的资源获取逻辑。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="5-mcp">5. 构建一个MCP服务器：从概念到代码骨架&lt;/h2>
&lt;p>为了让概念更具体，下面是一个极简的Python伪代码骨架，展示了如何实现一个响应Stdio通信的MCP服务器。&lt;/p>
&lt;pre>&lt;code class="language-python">import sys
import json
import xml.etree.ElementTree as ET
# 1. 定义具体的工具函数
def get_weather(city: str, days: int = 1):
&amp;quot;&amp;quot;&amp;quot;一个模拟的天气工具&amp;quot;&amp;quot;&amp;quot;
# 在真实世界里，这里会调用一个天气API
return {&amp;quot;city&amp;quot;: city, &amp;quot;forecast&amp;quot;: f&amp;quot;未来 {days} 天天气晴朗&amp;quot;}
# 将工具名映射到函数对象
AVAILABLE_TOOLS = {
&amp;quot;get_weather&amp;quot;: get_weather
}
# 2. MCP协议处理主循环
def main_loop():
&amp;quot;&amp;quot;&amp;quot;从stdin读取请求，处理后将结果写入stdout&amp;quot;&amp;quot;&amp;quot;
for line in sys.stdin:
request_xml = line.strip()
if not request_xml:
continue
try:
# 3. 解析MCP请求
root = ET.fromstring(request_xml)
if root.tag == &amp;quot;use_mcp_tool&amp;quot;:
tool_name = root.find(&amp;quot;tool_name&amp;quot;).text
args_str = root.find(&amp;quot;arguments&amp;quot;).text
args = json.loads(args_str)
# 4. 查找并执行工具
tool_function = AVAILABLE_TOOLS.get(tool_name)
if tool_function:
result = tool_function(**args)
# 5. 将成功结果封装并写回stdout
response = {&amp;quot;status&amp;quot;: &amp;quot;success&amp;quot;, &amp;quot;data&amp;quot;: result}
sys.stdout.write(json.dumps(response) + &amp;quot;\n&amp;quot;)
else:
raise ValueError(f&amp;quot;Tool '{tool_name}' not found.&amp;quot;)
# (此处可以添加对access_mcp_resource的处理逻辑)
except Exception as e:
# 6. 将错误信息写回stderr
error_response = {&amp;quot;status&amp;quot;: &amp;quot;error&amp;quot;, &amp;quot;message&amp;quot;: str(e)}
sys.stderr.write(json.dumps(error_response) + &amp;quot;\n&amp;quot;)
# 实时刷新缓冲区，确保客户端能立即收到
sys.stdout.flush()
sys.stderr.flush()
if __name__ == &amp;quot;__main__&amp;quot;:
main_loop()
&lt;/code>&lt;/pre>
&lt;p>这个骨架清晰地展示了MCP服务器的核心职责：监听输入、解析协议、执行逻辑、返回结果。&lt;/p>
&lt;h2 id="6-mcpcontext7">6. 实战演练：使用MCP驱动的context7服务器解答技术问题&lt;/h2>
&lt;p>理论和骨架之后，让我们通过一个真实的、端到端的例子，看看MCP在实际应用中如何发挥威力。&lt;/p>
&lt;p>&lt;strong>场景&lt;/strong>：我们正在构建一个AI编程助手。当用户问到一个具体的编程问题时，我们希望AI能通过查询最新的官方文档来给出最权威、最准确的回答，而不是依赖其可能过时的内部知识。&lt;/p>
&lt;p>在这个场景中，&lt;code>context7&lt;/code> MCP服务器就是我们的&amp;quot;外部文档库&amp;rdquo;。&lt;/p>
&lt;p>下面是完整的交互流程：&lt;/p>
&lt;pre>&lt;code class="language-mermaid">sequenceDiagram
participant User as 用户
participant Agent as AI编程助手 (模型+客户端)
participant Context7 as context7 MCP服务器
User-&amp;gt;&amp;gt;+Agent: 提问React Hooks区别
Note over Agent: 1. 分析问题, 决定调用工具
Agent--&amp;gt;&amp;gt;+Context7: 2. 发送MCP请求 (get-library-docs)
Note over Context7: 3. 查询文档库
Context7--&amp;gt;&amp;gt;-Agent: 4. 返回文档摘要 (关键差异)
Note over Agent: 5. 理解并总结权威资料
Agent--&amp;gt;&amp;gt;-User: 6. 生成基于文档的最终回答
&lt;/code>&lt;/pre>
&lt;h3 id="mcp">流程拆解与MCP价值体现&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>意图到协议的转换&lt;/strong>：模型 (LLM) 成功地将用户的自然语言问题，转换成了一个结构化、标准化的MCP请求。它不仅识别出需要调用工具，还准确地填充了&lt;code>server_name&lt;/code>、&lt;code>tool_name&lt;/code>和&lt;code>arguments&lt;/code>，这是MCP驱动的Agent的核心能力。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>解耦的优势&lt;/strong>：AI编程助手（客户端）完全不需要知道&lt;code>context7&lt;/code>服务器是如何实现的。它可能是一个复杂的系统，连接了多个数据源。但对于助手来说，它只是一个遵循MCP协议、可以通过&lt;code>context7&lt;/code>这个名字访问的服务端点。这种解耦使得更换或升级文档源变得极其简单，而无需改动Agent的核心逻辑。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>标准化带来的可扩展性&lt;/strong>：现在，如果我们想让这个AI助手再增加查询NPM包依赖关系的能力，我们只需要开发或接入另一个名为&lt;code>npm-analyzer&lt;/code>的MCP服务器。Agent的学习成本几乎为零，因为它只需要学会生成一个新的&lt;code>&amp;lt;use_mcp_tool&amp;gt;&lt;/code>请求，指向新的&lt;code>server_name&lt;/code>即可。整个系统的能力可以像搭乐高一样被无限扩展。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>这个例子清晰地展示了MCP是如何从一个简单的&amp;quot;函数调用&amp;quot;思想，升华为一个强大、可扩展的服务化架构，为构建复杂AI应用提供了坚实的基础。&lt;/p>
&lt;h2 id="7-mcpai">7. 总结：MCP的价值与未来——构建AI的&amp;quot;互联网&amp;rdquo;&lt;/h2>
&lt;p>通用工具调用赋予了LLM&amp;quot;说话&amp;quot;和&amp;quot;行动&amp;quot;的能力，而&lt;strong>模型上下文协议（MCP）则为这些能力定义了语法和交通规则&lt;/strong>。MCP通过标准化、解耦和服务化的设计理念，将孤立的AI应用和工具转变为一个潜在的、可互操作的巨大网络。&lt;/p>
&lt;p>MCP的真正价值不在于它定义了另一种RPC（远程过程调用），而在于它专为&lt;strong>AI Agent与外部世界交互&lt;/strong>这一独特场景量身定制。它足够简单，使得LLM可以轻松生成协议消息；又足够强大，能够支撑起复杂的、分布式的应用生态。&lt;/p>
&lt;p>未来，随着MCP生态的成熟，我们可以预见到一个&amp;quot;AI工具的互联网&amp;rdquo;：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>工具市场&lt;/strong>: 开发者可以发布和销售标准化的MCP服务器，其他应用可以按需购买和集成。&lt;/li>
&lt;li>&lt;strong>Agent的互操作&lt;/strong>: 不同公司开发的、基于不同底层模型的智能体，只要它们都&amp;quot;说&amp;quot;MCP这门语言，就可以互相调用对方的能力，协同完成更复杂的任务。&lt;/li>
&lt;li>&lt;strong>动态服务发现&lt;/strong>: 更高级的Agent或许能够动态发现和学习新的MCP服务，不断扩展自己的能力边界，而无需重新编程。&lt;/li>
&lt;/ul>
&lt;p>因此，理解和掌握MCP，不仅仅是学习一项具体的技术，更是洞察和布局下一代AI应用架构的关键一步。&lt;/p></description></item><item><title>LLM工具调用：打破AI能力边界的关键技术</title><link>https://ziyanglin.netlify.app/zh/post/llm-tool-calling/</link><pubDate>Mon, 30 Jun 2025 07:00:00 +0000</pubDate><guid>https://ziyanglin.netlify.app/zh/post/llm-tool-calling/</guid><description>&lt;h2 id="1-llm">1. 宏观概述：为什么工具调用是LLM的&amp;quot;超级外挂&amp;rdquo;？&lt;/h2>
&lt;p>大型语言模型（LLM）的出现，彻底改变了我们与机器交互的方式。然而，LLM本身存在一个固有的、无法回避的&amp;quot;天花板&amp;rdquo;：它们本质上是基于海量文本数据训练出来的&amp;quot;概率预测机器&amp;rdquo;，其知识被冻结在训练数据截止的那一刻。这意味着，LLM无法得知&amp;quot;今天的天气怎么样？&amp;quot;，也无法访问你公司的内部数据库，更不能帮你预订一张机票。&lt;/p>
&lt;p>&lt;strong>LLM工具调用（Tool Calling / Function Calling）&lt;/strong> 机制的出现，正是为了打破这层天花板。它赋予了LLM一个前所未有的能力：在需要的时候，&lt;strong>调用外部工具（API、函数、数据库等）来获取实时信息、执行特定任务，或与外部世界进行交互&lt;/strong>。&lt;/p>
&lt;p>简而言之，工具调用机制将LLM从一个&amp;quot;博学的对话者&amp;quot;升级为了一个能知能行的&amp;quot;智能代理&amp;rdquo;（Intelligent Agent）。它允许LLM：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>获取实时信息&lt;/strong>：通过调用天气API、新闻API、搜索引擎等，获取模型训练数据之外的最新信息。&lt;/li>
&lt;li>&lt;strong>操作外部系统&lt;/strong>：连接到企业内部的CRM、ERP系统，查询数据；或者连接到IoT设备，控制智能家居。&lt;/li>
&lt;li>&lt;strong>执行复杂任务&lt;/strong>：将用户的复杂指令（如&amp;quot;帮我找找下周去上海的便宜机票并预订&amp;rdquo;）拆解，并通过调用多个API组合来完成。&lt;/li>
&lt;li>&lt;strong>提供更精确、可验证的答案&lt;/strong>：对于需要精确计算或结构化数据的查询，LLM可以调用计算器或数据库，而不是依赖其可能不准确的内部知识。&lt;/li>
&lt;/ul>
&lt;p>因此，工具调用不仅是LLM功能的一个简单扩展，更是通往构建真正强大的、能够与物理和数字世界深度融合的AI应用的核心基石。&lt;/p>
&lt;h2 id="2-llm">2. 核心理念与工作流程：LLM如何&amp;quot;学会&amp;quot;使用工具？&lt;/h2>
&lt;p>要理解工具调用的底层逻辑，我们需要将其看作是一个由三个核心角色协同工作的精妙流程：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>大型语言模型 (LLM)&lt;/strong>：大脑和决策者。&lt;/li>
&lt;li>&lt;strong>工具定义 (Tool Definitions)&lt;/strong>：一本详细的&amp;quot;工具使用说明书&amp;rdquo;。&lt;/li>
&lt;li>&lt;strong>开发者/客户端 (Client-side Code)&lt;/strong>：最终的&amp;quot;执行者&amp;rdquo;。&lt;/li>
&lt;/ol>
&lt;p>LLM本身&lt;strong>永远不会真正地执行任何代码&lt;/strong>。它的唯一任务是，在理解了用户的意图和它所拥有的&amp;quot;工具说明书&amp;quot;后，&lt;strong>生成一段精确描述了应该调用哪个工具、以及使用什么参数的JSON数据&lt;/strong>。&lt;/p>
&lt;p>下面是这个流程的可视化解释：&lt;/p>
&lt;pre>&lt;code class="language-mermaid">sequenceDiagram
participant User as 用户
participant Client as 客户端/应用层
participant LLM as 大型语言模型
participant Tools as 外部工具/API
User-&amp;gt;&amp;gt;+Client: &amp;quot;帮我查一下北京今天的天气&amp;quot;
Client-&amp;gt;&amp;gt;+LLM: 提交用户请求 + 工具定义 (Tool Definitions)
Note over LLM: 1. 理解用户意图&amp;lt;br/&amp;gt;2. 匹配最合适的工具 (get_weather)&amp;lt;br/&amp;gt;3. 提取所需参数 (location: &amp;quot;北京&amp;quot;)
LLM--&amp;gt;&amp;gt;-Client: 返回JSON：{&amp;quot;tool_calls&amp;quot;: [{&amp;quot;function&amp;quot;: {&amp;quot;name&amp;quot;: &amp;quot;get_weather&amp;quot;, &amp;quot;arguments&amp;quot;: &amp;quot;{\&amp;quot;location\&amp;quot;: \&amp;quot;北京\&amp;quot;}&amp;quot;}}]}
Client-&amp;gt;&amp;gt;+Tools: 2. 根据LLM返回的JSON，调用真正的get_weather(&amp;quot;北京&amp;quot;)函数
Tools--&amp;gt;&amp;gt;-Client: 返回天气数据 (例如: {&amp;quot;temperature&amp;quot;: &amp;quot;25°C&amp;quot;, &amp;quot;condition&amp;quot;: &amp;quot;晴&amp;quot;})
Client-&amp;gt;&amp;gt;+LLM: 3. 将工具执行结果提交回LLM
Note over LLM: 4. 理解工具返回的数据
LLM--&amp;gt;&amp;gt;-Client: 5. 生成对用户友好的自然语言回答
Client-&amp;gt;&amp;gt;-User: &amp;quot;北京今天天气晴，温度是25摄氏度。&amp;quot;
&lt;/code>&lt;/pre>
&lt;h3 id="heading">流程详解：&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>定义与描述 (Define &amp;amp; Describe)&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>开发者首先需要用一种结构化的方式（通常是JSON Schema）来定义好可用的工具。这份&amp;quot;说明书&amp;quot;是整个流程的关键，它必须清晰地告诉LLM：
&lt;ul>
&lt;li>&lt;strong>工具名称&lt;/strong> (&lt;code>name&lt;/code>)：例如 &lt;code>get_weather&lt;/code>。&lt;/li>
&lt;li>&lt;strong>工具功能描述&lt;/strong> (&lt;code>description&lt;/code>)：例如&amp;quot;获取指定城市的实时天气信息&amp;rdquo;。这是LLM理解工具用途的最重要依据。&lt;/li>
&lt;li>&lt;strong>工具参数&lt;/strong> (&lt;code>parameters&lt;/code>)：详细定义工具需要哪些输入，每个输入的名称、类型（字符串、数字、布尔等）、是否必需，以及对参数的描述。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>意图识别与参数提取 (Intent Recognition &amp;amp; Parameter Extraction)&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>当用户发出请求时（例如&amp;quot;查查北京天气&amp;rdquo;），开发者的应用会将用户的原始请求&lt;strong>连同第一步中定义的所有工具说明书&lt;/strong>一起发送给LLM。&lt;/li>
&lt;li>LLM的核心任务就是进行两件事：
&lt;ul>
&lt;li>&lt;strong>意图识别&lt;/strong>：在所有可用的工具中，判断用户的请求最符合哪个工具的功能描述。在这个例子中，它会匹配到&lt;code>get_weather&lt;/code>。&lt;/li>
&lt;li>&lt;strong>参数提取&lt;/strong>：从用户的请求中，找出并提取满足工具参数要求的值。在这里，它会识别出&lt;code>location&lt;/code>参数的值是&amp;quot;北京&amp;rdquo;。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>完成这两步后，LLM会生成一个或多个&lt;code>tool_calls&lt;/code>对象，其内容本质上是&amp;quot;我建议你调用名为&lt;code>get_weather&lt;/code>的函数，并传入&lt;code>{ &amp;quot;location&amp;quot;: &amp;quot;北京&amp;quot; }&lt;/code>这个参数&amp;rdquo;。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>执行与观察 (Execute &amp;amp; Observe)&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>开发者的应用层代码接收到LLM返回的JSON后，会解析这个&amp;quot;调用建议&amp;rdquo;。&lt;/li>
&lt;li>应用层代码&lt;strong>在本地或服务器端，实际地执行&lt;/strong>&lt;code>get_weather(&amp;quot;北京&amp;quot;)&lt;/code>这个函数。&lt;/li>
&lt;li>执行后，会得到一个真实的返回结果，例如一个包含天气信息的JSON对象。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>总结与回应 (Summarize &amp;amp; Respond)&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>为了完成闭环，应用层需要将上一步中工具的真实执行结果，再次提交给LLM。&lt;/li>
&lt;li>这一次，LLM的任务是理解这个工具返回的原始数据（例如&lt;code>{&amp;quot;temperature&amp;quot;: &amp;quot;25°C&amp;quot;, &amp;quot;condition&amp;quot;: &amp;quot;晴&amp;quot;}&lt;/code>），并将其转换成一句通顺、自然的、对用户友好的答复。&lt;/li>
&lt;li>最终，用户收到了&amp;quot;北京今天天气晴，温度是25摄氏度&amp;quot;的回复，整个流程结束。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>这个流程精妙地结合了LLM强大的自然语言理解能力和外部工具强大的功能执行能力，实现了1+1&amp;gt;2的效果。&lt;/p>
&lt;h2 id="3--openai-tool-calling">3. 技术深潜：剖析行业标准 (OpenAI Tool Calling)&lt;/h2>
&lt;p>OpenAI的API是目前LLM工具调用领域的事实标准，其设计被广泛借鉴。理解其实现细节对于任何希望在应用中集成LLM工具调用的开发者都至关重要。&lt;/p>
&lt;h3 id="31-api">3.1. 核心API参数&lt;/h3>
&lt;p>在调用OpenAI的Chat Completions API时，与工具调用相关的核心参数主要有两个：&lt;code>tools&lt;/code> 和 &lt;code>tool_choice&lt;/code>。&lt;/p>
&lt;h4 id="tools-">&lt;code>tools&lt;/code> 参数：你的&amp;quot;工具箱&amp;rdquo;&lt;/h4>
&lt;p>&lt;code>tools&lt;/code>参数是一个数组，你可以在其中定义一个或多个工具。每个工具都遵循一个固定的结构，其核心是&lt;code>function&lt;/code>对象，该对象基于&lt;strong>JSON Schema&lt;/strong>规范来定义。&lt;/p>
&lt;p>&lt;strong>示例：定义一个获取天气和一个预订机票的工具&lt;/strong>&lt;/p>
&lt;pre>&lt;code class="language-json">[
{
&amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;,
&amp;quot;function&amp;quot;: {
&amp;quot;name&amp;quot;: &amp;quot;get_current_weather&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;获取指定地点的实时天气信息&amp;quot;,
&amp;quot;parameters&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
&amp;quot;properties&amp;quot;: {
&amp;quot;location&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;城市和省份名称，例如：'北京市'&amp;quot;
},
&amp;quot;unit&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
&amp;quot;enum&amp;quot;: [&amp;quot;celsius&amp;quot;, &amp;quot;fahrenheit&amp;quot;],
&amp;quot;description&amp;quot;: &amp;quot;温度单位&amp;quot;
}
},
&amp;quot;required&amp;quot;: [&amp;quot;location&amp;quot;]
}
}
},
{
&amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;,
&amp;quot;function&amp;quot;: {
&amp;quot;name&amp;quot;: &amp;quot;book_flight&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;为用户预订从出发地到目的地的机票&amp;quot;,
&amp;quot;parameters&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
&amp;quot;properties&amp;quot;: {
&amp;quot;departure&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;出发机场或城市&amp;quot;
},
&amp;quot;destination&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;目的机场或城市&amp;quot;
},
&amp;quot;date&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;希望出发的日期，格式为 YYYY-MM-DD&amp;quot;
}
},
&amp;quot;required&amp;quot;: [&amp;quot;departure&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;date&amp;quot;]
}
}
}
]
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>关键点剖析&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>type&lt;/code>&lt;/strong>: 目前固定为&lt;code>&amp;quot;function&amp;quot;&lt;/code>。&lt;/li>
&lt;li>&lt;strong>&lt;code>function.name&lt;/code>&lt;/strong>: 函数名。必须是字母、数字和下划线的组合，长度不超过64。这是你的代码用来识别调用哪个函数的关键。&lt;/li>
&lt;li>&lt;strong>&lt;code>function.description&lt;/code>&lt;/strong>: &lt;strong>至关重要&lt;/strong>。这是LLM决定是否选择该工具的主要依据。描述应该清晰、准确、无歧义地说明该函数能做什么。好的描述能极大提升LLM的调用准确率。&lt;/li>
&lt;li>&lt;strong>&lt;code>function.parameters&lt;/code>&lt;/strong>: 一个标准的JSON Schema对象。
&lt;ul>
&lt;li>&lt;strong>&lt;code>type&lt;/code>&lt;/strong>: 必须是&lt;code>&amp;quot;object&amp;quot;&lt;/code>。&lt;/li>
&lt;li>&lt;strong>&lt;code>properties&lt;/code>&lt;/strong>: 定义每个参数的名称、类型 (&lt;code>string&lt;/code>, &lt;code>number&lt;/code>, &lt;code>boolean&lt;/code>, &lt;code>array&lt;/code>, &lt;code>object&lt;/code>) 和描述。参数的描述同样重要，它能帮助LLM理解应该从用户输入中提取什么信息来填充这个参数。&lt;/li>
&lt;li>&lt;strong>&lt;code>required&lt;/code>&lt;/strong>: 一个字符串数组，列出哪些参数是必须的。如果用户请求中缺少必要信息，LLM可能会追问用户，或者选择不调用该工具。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="toolchoice-llm">&lt;code>tool_choice&lt;/code> 参数：控制LLM的选择&lt;/h4>
&lt;p>默认情况下，LLM会根据用户的输入自主决定是回答文本，还是调用一个或多个工具。&lt;code>tool_choice&lt;/code>参数允许你更精确地控制这个行为。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>&amp;quot;none&amp;quot;&lt;/code>&lt;/strong>: 强制LLM不调用任何工具，直接返回文本回复。&lt;/li>
&lt;li>&lt;strong>&lt;code>&amp;quot;auto&amp;quot;&lt;/code>&lt;/strong> (默认值): LLM可以自由选择是回复文本还是调用工具。&lt;/li>
&lt;li>&lt;strong>&lt;code>{&amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;, &amp;quot;function&amp;quot;: {&amp;quot;name&amp;quot;: &amp;quot;my_function&amp;quot;}}&lt;/code>&lt;/strong>: 强制LLM必须调用名为&lt;code>my_function&lt;/code>的这个特定工具。&lt;/li>
&lt;/ul>
&lt;p>这个参数在需要固定执行某个流程或限制LLM能力的场景下非常有用。&lt;/p>
&lt;h3 id="32-">3.2. 请求-响应生命周期&lt;/h3>
&lt;p>一次完整的工具调用交互包含至少两次API请求。&lt;/p>
&lt;p>&lt;strong>第一次请求：从用户到LLM&lt;/strong>&lt;/p>
&lt;pre>&lt;code class="language-python"># request
response = client.chat.completions.create(
model=&amp;quot;gpt-4o&amp;quot;,
messages=[{&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: &amp;quot;明天从北京到上海的机票帮我订一张&amp;quot;}],
tools=my_tools, # 上面定义的工具列表
tool_choice=&amp;quot;auto&amp;quot;
)
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>第一次响应：LLM的&amp;quot;调用建议&amp;rdquo;&lt;/strong>&lt;/p>
&lt;p>如果LLM决定调用工具，API的响应中&lt;code>finish_reason&lt;/code>会是&lt;code>tool_calls&lt;/code>，并且&lt;code>message&lt;/code>对象会包含一个&lt;code>tool_calls&lt;/code>数组。&lt;/p>
&lt;pre>&lt;code class="language-json">{
&amp;quot;choices&amp;quot;: [
{
&amp;quot;finish_reason&amp;quot;: &amp;quot;tool_calls&amp;quot;,
&amp;quot;message&amp;quot;: {
&amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,
&amp;quot;content&amp;quot;: null,
&amp;quot;tool_calls&amp;quot;: [
{
&amp;quot;id&amp;quot;: &amp;quot;call_abc123&amp;quot;,
&amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;,
&amp;quot;function&amp;quot;: {
&amp;quot;name&amp;quot;: &amp;quot;book_flight&amp;quot;,
&amp;quot;arguments&amp;quot;: &amp;quot;{\&amp;quot;departure\&amp;quot;:\&amp;quot;北京\&amp;quot;,\&amp;quot;destination\&amp;quot;:\&amp;quot;上海\&amp;quot;,\&amp;quot;date\&amp;quot;:\&amp;quot;2025-07-01\&amp;quot;}&amp;quot;
}
}
]
}
}
],
...
}
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>关键点剖析&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>finish_reason&lt;/code>&lt;/strong>: 值为&lt;code>&amp;quot;tool_calls&amp;quot;&lt;/code>标志着LLM希望你执行工具调用，而不是对话结束。&lt;/li>
&lt;li>&lt;strong>&lt;code>message.role&lt;/code>&lt;/strong>: &lt;code>assistant&lt;/code>。&lt;/li>
&lt;li>&lt;strong>&lt;code>message.tool_calls&lt;/code>&lt;/strong>: 这是一个数组，意味着LLM可以要求一次性调用多个工具。
&lt;ul>
&lt;li>&lt;strong>&lt;code>id&lt;/code>&lt;/strong>: 一个唯一的调用ID。在后续请求中，你需要用这个ID来关联工具的执行结果。&lt;/li>
&lt;li>&lt;strong>&lt;code>function.name&lt;/code>&lt;/strong>: LLM建议调用的函数名。&lt;/li>
&lt;li>&lt;strong>&lt;code>function.arguments&lt;/code>&lt;/strong>: &lt;strong>一个字符串形式的JSON对象&lt;/strong>。你需要解析这个字符串来获取调用函数所需的具体参数。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>第二次请求：将工具结果返回给LLM&lt;/strong>&lt;/p>
&lt;p>在你的代码中执行完工具后，你需要将结果再次发送给LLM以完成对话。这时，你需要构造一个新的&lt;code>messages&lt;/code>列表，其中包含：&lt;/p>
&lt;ol>
&lt;li>原始的用户消息。&lt;/li>
&lt;li>上一步中LLM返回的&lt;code>assistant&lt;/code>消息（包含&lt;code>tool_calls&lt;/code>）。&lt;/li>
&lt;li>一个新的&lt;code>tool&lt;/code>角色的消息，其中包含工具的执行结果。&lt;/li>
&lt;/ol>
&lt;pre>&lt;code class="language-python"># message history
messages = [
{&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: &amp;quot;明天从北京到上海的机票帮我订一张&amp;quot;},
response.choices[0].message, # Assistant's 'tool_calls' message
{
&amp;quot;tool_call_id&amp;quot;: &amp;quot;call_abc123&amp;quot;, # 必须和上一步的ID匹配
&amp;quot;role&amp;quot;: &amp;quot;tool&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;book_flight&amp;quot;,
&amp;quot;content&amp;quot;: &amp;quot;{\&amp;quot;status\&amp;quot;: \&amp;quot;success\&amp;quot;, \&amp;quot;ticket_id\&amp;quot;: \&amp;quot;TICKET-45678\&amp;quot;}&amp;quot; # 工具的真实返回值
}
]
# second request
second_response = client.chat.completions.create(
model=&amp;quot;gpt-4o&amp;quot;,
messages=messages
)
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>第二次响应：LLM的最终回复&lt;/strong>&lt;/p>
&lt;p>这次，LLM会基于工具返回的结果，生成一段自然的语言回复给用户。&lt;/p>
&lt;pre>&lt;code class="language-json">{
&amp;quot;choices&amp;quot;: [
{
&amp;quot;finish_reason&amp;quot;: &amp;quot;stop&amp;quot;,
&amp;quot;message&amp;quot;: {
&amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,
&amp;quot;content&amp;quot;: &amp;quot;好的，已经为您预订了明天从北京到上海的机票，订单号是 TICKET-45678。&amp;quot;
}
}
],
...
}
&lt;/code>&lt;/pre>
&lt;p>至此，一个完整的工具调用周期完成。&lt;/p>
&lt;h2 id="4-python">4. 代码实现：一个完整的Python示例&lt;/h2>
&lt;p>下面是一个端到端的Python示例，使用OpenAI的Python库来演示如何实现一个查询天气的功能。&lt;/p>
&lt;pre>&lt;code class="language-python">import os
import json
from openai import OpenAI
from dotenv import load_dotenv
# --- 1. 初始化设置 ---
load_dotenv() # 加载 .env 文件中的环境变量
client = OpenAI(api_key=os.getenv(&amp;quot;OPENAI_API_KEY&amp;quot;))
# --- 2. 定义我们本地的工具函数 ---
# 这是一个模拟函数，实际应用中它会调用真正的天气API
def get_current_weather(location, unit=&amp;quot;celsius&amp;quot;):
&amp;quot;&amp;quot;&amp;quot;获取指定地点的实时天气信息&amp;quot;&amp;quot;&amp;quot;
if &amp;quot;北京&amp;quot; in location:
return json.dumps({
&amp;quot;location&amp;quot;: &amp;quot;北京&amp;quot;,
&amp;quot;temperature&amp;quot;: &amp;quot;10&amp;quot;,
&amp;quot;unit&amp;quot;: unit,
&amp;quot;forecast&amp;quot;: [&amp;quot;晴&amp;quot;, &amp;quot;微风&amp;quot;]
})
elif &amp;quot;上海&amp;quot; in location:
return json.dumps({
&amp;quot;location&amp;quot;: &amp;quot;上海&amp;quot;,
&amp;quot;temperature&amp;quot;: &amp;quot;15&amp;quot;,
&amp;quot;unit&amp;quot;: unit,
&amp;quot;forecast&amp;quot;: [&amp;quot;小雨&amp;quot;, &amp;quot;东北风&amp;quot;]
})
else:
return json.dumps({&amp;quot;location&amp;quot;: location, &amp;quot;temperature&amp;quot;: &amp;quot;未知&amp;quot;})
# --- 3. 主执行流程 ---
def run_conversation(user_prompt: str):
print(f&amp;quot;👤 用户: {user_prompt}&amp;quot;)
# 步骤1: 将用户的消息和工具定义发送给LLM
messages = [{&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: user_prompt}]
tools = [
{
&amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;,
&amp;quot;function&amp;quot;: {
&amp;quot;name&amp;quot;: &amp;quot;get_current_weather&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;获取指定城市的实时天气信息&amp;quot;,
&amp;quot;parameters&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
&amp;quot;properties&amp;quot;: {
&amp;quot;location&amp;quot;: {
&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;城市名称, e.g., 北京市&amp;quot;,
},
&amp;quot;unit&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;enum&amp;quot;: [&amp;quot;celsius&amp;quot;, &amp;quot;fahrenheit&amp;quot;]},
},
&amp;quot;required&amp;quot;: [&amp;quot;location&amp;quot;],
},
},
}
]
response = client.chat.completions.create(
model=&amp;quot;gpt-4o&amp;quot;,
messages=messages,
tools=tools,
tool_choice=&amp;quot;auto&amp;quot;,
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
# 步骤2: 检查LLM是否决定调用工具
if tool_calls:
print(f&amp;quot;🤖 LLM决定调用工具: {tool_calls[0].function.name}&amp;quot;)
# 将LLM的回复添加到消息历史中
messages.append(response_message)
# 步骤3: 执行工具调用
# 注意: 目前示例仅处理第一个工具调用
tool_call = tool_calls[0]
function_name = tool_call.function.name
function_to_call = globals().get(function_name) # 从全局作用域中获取函数
if not function_to_call:
print(f&amp;quot;❌ 错误: 函数 {function_name} 未定义&amp;quot;)
return
function_args = json.loads(tool_call.function.arguments)
# 调用函数并获取结果
function_response = function_to_call(
location=function_args.get(&amp;quot;location&amp;quot;),
unit=function_args.get(&amp;quot;unit&amp;quot;),
)
print(f&amp;quot;🛠️ 工具 '{function_name}' 返回: {function_response}&amp;quot;)
# 步骤4: 将工具的执行结果返回给LLM
messages.append(
{
&amp;quot;tool_call_id&amp;quot;: tool_call.id,
&amp;quot;role&amp;quot;: &amp;quot;tool&amp;quot;,
&amp;quot;name&amp;quot;: function_name,
&amp;quot;content&amp;quot;: function_response,
}
)
print(&amp;quot;🗣️ 将工具结果提交回LLM，生成最终回复...&amp;quot;)
second_response = client.chat.completions.create(
model=&amp;quot;gpt-4o&amp;quot;,
messages=messages,
)
final_response = second_response.choices[0].message.content
print(f&amp;quot;🤖 LLM最终回复: {final_response}&amp;quot;)
return final_response
else:
# 如果LLM没有调用工具，直接返回其文本内容
final_response = response_message.content
print(f&amp;quot;🤖 LLM直接回复: {final_response}&amp;quot;)
return final_response
# --- 运行示例 ---
if __name__ == &amp;quot;__main__&amp;quot;:
run_conversation(&amp;quot;上海今天天气怎么样？&amp;quot;)
print(&amp;quot;\n&amp;quot; + &amp;quot;=&amp;quot;*50 + &amp;quot;\n&amp;quot;)
run_conversation(&amp;quot;你好吗？&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>这个示例清晰地展示了从定义工具、发送请求、处理&lt;code>tool_calls&lt;/code>、执行本地函数、再到将结果发回给模型以获得最终答案的全过程。&lt;/p>
&lt;h2 id="5-">5. 高级主题与最佳实践&lt;/h2>
&lt;p>掌握了基础流程后，我们还需要了解一些高级用法和设计原则，以构建更健壮、更可靠的工具调用系统。&lt;/p>
&lt;h3 id="51--parallel-tool-calling">5.1. 并行工具调用 (Parallel Tool Calling)&lt;/h3>
&lt;p>较新的模型（如&lt;code>gpt-4o&lt;/code>）支持并行工具调用。这意味着模型可以在一次响应中，要求同时调用多个不同的、独立的工具。&lt;/p>
&lt;p>&lt;strong>场景示例&lt;/strong>: 用户问：&amp;ldquo;北京和上海今天的天气怎么样？&amp;rdquo;&lt;/p>
&lt;p>模型可能会返回一个包含两个&lt;code>tool_calls&lt;/code>的响应：&lt;/p>
&lt;ol>
&lt;li>&lt;code>get_current_weather(location=&amp;quot;北京&amp;quot;)&lt;/code>&lt;/li>
&lt;li>&lt;code>get_current_weather(location=&amp;quot;上海&amp;quot;)&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>你的代码需要能够迭代处理&lt;code>message.tool_calls&lt;/code>数组中的每一个&lt;code>tool_call&lt;/code>对象，分别执行它们，收集所有结果，然后将这些结果在一个新的请求中一并提交给模型。&lt;/p>
&lt;p>&lt;strong>代码处理逻辑&lt;/strong>：&lt;/p>
&lt;pre>&lt;code class="language-python"># ... (接收到包含多个tool_calls的response_message)
messages.append(response_message) # Add assistant's reply to messages
# 为每个工具调用执行函数并收集结果
tool_outputs = []
for tool_call in tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
output = function_to_call(**function_args)
tool_outputs.append({
&amp;quot;tool_call_id&amp;quot;: tool_call.id,
&amp;quot;role&amp;quot;: &amp;quot;tool&amp;quot;,
&amp;quot;name&amp;quot;: function_name,
&amp;quot;content&amp;quot;: output,
})
# 将所有工具的输出都添加到消息历史中
messages.extend(tool_outputs)
# 再次调用模型
second_response = client.chat.completions.create(
model=&amp;quot;gpt-4o&amp;quot;,
messages=messages
)
&lt;/code>&lt;/pre>
&lt;h3 id="52-">5.2. 错误处理&lt;/h3>
&lt;p>工具调用并不总是成功的。API可能会超时，数据库可能无法连接，或者函数执行本身可能抛出异常。优雅地处理这些错误至关重要。&lt;/p>
&lt;p>当工具执行失败时，你应该捕获异常，并将一个描述错误的、结构化的信息作为工具调用的结果返回给LLM。&lt;/p>
&lt;p>&lt;strong>示例&lt;/strong>：&lt;/p>
&lt;pre>&lt;code class="language-python">try:
# 尝试调用API
result = some_flaky_api()
content = json.dumps({&amp;quot;status&amp;quot;: &amp;quot;success&amp;quot;, &amp;quot;data&amp;quot;: result})
except Exception as e:
# 如果失败，返回错误信息
content = json.dumps({&amp;quot;status&amp;quot;: &amp;quot;error&amp;quot;, &amp;quot;message&amp;quot;: f&amp;quot;API调用失败: {str(e)}&amp;quot;})
# 将结果（无论成功或失败）返回给LLM
messages.append({
&amp;quot;tool_call_id&amp;quot;: tool_call.id,
&amp;quot;role&amp;quot;: &amp;quot;tool&amp;quot;,
&amp;quot;name&amp;quot;: function_name,
&amp;quot;content&amp;quot;: content,
})
&lt;/code>&lt;/pre>
&lt;p>LLM在接收到错误信息后，通常会向用户回复一个歉意的、能反映出问题的答案（例如：&amp;ldquo;抱歉，我暂时无法查询到天气信息，请稍后再试。&amp;quot;），而不是让整个应用崩溃。&lt;/p>
&lt;h3 id="53-">5.3. 设计高效的工具描述&lt;/h3>
&lt;p>&lt;strong>工具描述 (&lt;code>description&lt;/code>) 的质量直接决定了LLM的调用准确率。&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>清晰具体&lt;/strong>: 避免使用模糊的词汇。
&lt;ul>
&lt;li>&lt;strong>不好&lt;/strong>: &amp;ldquo;获取数据&amp;rdquo;&lt;/li>
&lt;li>&lt;strong>好&lt;/strong>: &amp;ldquo;从公司的CRM系统中，根据用户ID查询该用户的订单历史记录&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>包含关键信息和限制&lt;/strong>: 如果工具有特定限制，一定要在描述中说明。
&lt;ul>
&lt;li>&lt;strong>示例&lt;/strong>: &amp;ldquo;查询航班信息。注意：本工具只能查询未来30天内的航班，无法查询历史航班。&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>使用动词开头&lt;/strong>: 用一个清晰的动词来描述函数的核心功能。&lt;/li>
&lt;li>&lt;strong>参数描述也要清晰&lt;/strong>: 参数的&lt;code>description&lt;/code>同样重要，它指导LLM如何从用户对话中正确提取信息。
&lt;ul>
&lt;li>&lt;strong>不好&lt;/strong>: &lt;code>&amp;quot;date&amp;quot;: &amp;quot;一个日期&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;strong>好&lt;/strong>: &lt;code>&amp;quot;date&amp;quot;: &amp;quot;预订的日期，必须是YYYY-MM-DD格式的字符串&amp;quot;&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="54-">5.4. 安全性考量&lt;/h3>
&lt;p>赋予LLM调用代码的能力是一把双刃剑，必须谨慎处理其安全性。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>永远不要执行LLM生成的代码&lt;/strong>: LLM的输出是&amp;quot;调用建议&amp;rdquo;，而不是可执行代码。永远不要使用&lt;code>eval()&lt;/code>或类似的方法直接执行LLM生成的字符串。你应该解析它建议的函数名和参数，然后调用你已经预先定义好的、安全可信的本地函数。&lt;/li>
&lt;li>&lt;strong>确认与授权&lt;/strong>: 对于会产生严重后果的操作（如删除数据、发送邮件、进行支付），应该在执行前实现一个确认机制。可以是在代码层面强制要求用户确认，或者让LLM在生成调用建议后，再生成一句向用户确认的话术。&lt;/li>
&lt;li>&lt;strong>最小权限原则&lt;/strong>: 只向LLM提供完成其任务所必需的最少工具。不要暴露整个代码库或不相关的API。&lt;/li>
&lt;/ul>
&lt;h2 id="6-">6. 总结与未来展望&lt;/h2>
&lt;p>LLM工具调用是近年来人工智能领域最具突破性的进展之一。它将LLM从一个封闭的&amp;quot;语言大脑&amp;quot;转变为一个开放的、可扩展的、能够与世界交互的&amp;quot;智能代理&amp;quot;核心。通过将LLM强大的自然语言理解能力与外部工具的无限功能相结合，我们得以构建出前所未有的智能应用。&lt;/p>
&lt;p>从查询天气、预订酒店，到控制智能家居、分析企业财报、自动化软件开发流程，工具调用正在解锁无数的可能性。随着模型能力的不断增强，工具描述的理解会愈发精准，多工具的协同会更加复杂和智能，错误处理和自我修正的能力也会变得更强。&lt;/p>
&lt;p>未来，我们可能会看到更加复杂的Agentic架构，其中LLM不仅调用工具，还能动态地创建、组合甚至优化工具。掌握LLM工具调用的原理与实践，不仅是跟上当前AI技术浪潮的必备技能，更是通往未来智能应用开发的关键钥匙。&lt;/p></description></item></channel></rss>