Appearance
2、工具定义与使用
上一章我们知道了Agent的四大组件中,工具(Tools)是Agent与外部世界交互的唯一通道——LLM负责"想",工具负责"做"。没有工具的Agent,就像一个只会说话但没有手脚的人,什么实际操作也完成不了。
因此,构建Agent的第一步,就是定义工具。
2.1 工具的本质
在深入LangChain的工具定义之前,我们需要先理解一个底层机制:工具调用的本质是大模型的Function Calling能力。
当我们给Agent配置工具时,实际上发生的事情是:
① 你定义工具(函数名 + 参数说明 + 功能描述)
↓
② LangChain把工具信息转换成JSON Schema,随提示词一起发给LLM
↓
③ LLM阅读工具描述,根据用户问题决定:
- 需要调用哪个工具
- 传入什么参数
↓
④ LLM返回一个"工具调用指令"(不是直接执行,而是告诉框架"我要调用XX工具")
↓
⑤ LangChain框架接收指令,在本地执行对应的函数
↓
⑥ 执行结果返回给LLM,LLM继续推理关键理解:LLM自身并不能执行任何工具。 它只是根据工具描述"选择"要调用什么、传什么参数。真正执行工具的是你的代码。LLM的角色更像是一个"调度员"。
这意味着:工具的描述写得好不好,直接决定了LLM能不能正确地选择和调用它。 这是工具定义中最重要的事。
2.2 原生Function Calling
为了理解LangChain在背后做了什么,我们先看一下不用LangChain时,直接用OpenAI SDK实现Function Calling是什么样的。这一节是帮你理解原理,实际开发中不需要这样写。
python
from openai import OpenAI
import json
client = OpenAI()
# ===== 第1步:用JSON Schema手动描述工具 =====
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市在指定日期的天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称",
},
"date": {
"type": "string",
"description": "日期,格式为YYYY-MM-DD",
}
},
"required": ["city", "date"],
"additionalProperties": False,
},
"strict": True,
},
},
]
# ===== 第2步:定义工具的实际执行逻辑 =====
def get_weather(city, date):
# 实际项目中这里会调用真实的天气API
return f"{city} 在 {date} 天气多云,有下雨的可能性。"
# ===== 第3步:把用户消息和工具描述一起发给LLM =====
messages = [
{"role": "user", "content": "北京2025-12-25的天气怎么样?"}
]
response = client.chat.completions.create(
model="gpt-4.1",
messages=messages,
tools=tools, # ← 工具描述随请求一起发送
)
# ===== 第4步:LLM返回的不是文字,而是"工具调用指令" =====
# response.choices[0].message.tool_calls 包含了LLM想调用的工具和参数
# ===== 第5步:我们在本地执行工具,把结果喂回给LLM =====
messages.append(response.choices[0].message)
for tool_call in response.choices[0].message.tool_calls or []:
if tool_call.function.name == "get_weather":
args = json.loads(tool_call.function.arguments)
result = get_weather(args["city"], args["date"])
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps({"weather": result}),
})
# ===== 第6步:LLM拿到工具结果后,生成最终的自然语言回复 =====
final_response = client.chat.completions.create(
model="gpt-4.1",
messages=messages,
tools=tools,
)
print(final_response.choices[0].message.content)观察这段代码,你会发现手动实现Function Calling非常繁琐:要手写JSON Schema、要手动解析工具调用指令、要手动把结果喂回去、要手动管理消息列表……而且如果有多个工具、多轮调用,代码量会爆炸式增长。
这就是为什么我们需要LangChain——它把上面所有的脏活都封装好了。
2.3 LangChain定义工具
LangChain提供了@tool装饰器,只需要写一个普通的Python函数,加上装饰器和类型注解,LangChain就会自动帮你:
- 根据函数签名生成JSON Schema
- 根据docstring生成工具描述
- 处理参数的序列化和反序列化
python
from langchain.tools import tool
@tool
def get_weather(city: str, date: str) -> str:
"""获取指定城市在指定日期的天气。
Args:
city: 城市名称,如"北京"、"上海"
date: 日期,格式为YYYY-MM-DD
"""
# 实际项目中调用天气API,这里用模拟数据
return f"{city} 在 {date} 天气多云,有下雨的可能性。"对比一下: 原生方式需要手写十几行JSON Schema来描述一个工具,LangChain只需要一个@tool装饰器加上规范的docstring。效果是一样的——LangChain会在背后自动生成JSON Schema发给LLM。
三个影响LLM调用准确性的关键点:
| 要素 | 作用 | 写法建议 |
|---|---|---|
| 函数名 | LLM根据函数名初步判断工具用途 | 用清晰的动词+名词,如get_weather、search_documents |
| docstring | LLM根据描述理解工具的具体功能 | 写清楚"这个工具做什么",越具体越好 |
| 参数类型注解 | LLM根据类型和描述决定传入什么值 | 每个参数都要有类型注解和说明 |
常见错误: docstring写得太简略(如"查天气"),导致LLM不确定什么时候该用这个工具、该传什么参数。docstring是你和LLM之间的"说明书",写得越清楚,LLM用得越准。
Pydantic定义复杂参数
当工具的参数比较复杂时,可以用Pydantic模型来定义参数结构,提供更精确的约束:
python
from langchain.tools import tool
from pydantic import BaseModel, Field
class GetWeatherArgs(BaseModel):
"""天气查询参数"""
city: str = Field(description="城市名称,如'北京'、'上海'")
date: str = Field(description="查询日期,格式为YYYY-MM-DD")
@tool(args_schema=GetWeatherArgs)
def get_weather(city: str, date: str) -> str:
"""获取指定城市在指定日期的天气预报"""
return f"{city} 在 {date} 天气多云,有下雨的可能性。"2.4 构建Agent
工具定义好之后,就可以用LangChain的create_agent函数来构建一个完整的Agent了。create_agent会帮你处理第2.1节中描述的所有底层细节——消息管理、工具调用解析、结果回传、循环控制。
python
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
# ===== 第1步:初始化LLM =====
llm = init_chat_model(
model="gpt-4o-mini",
model_provider="openai",
)
# ===== 第2步:准备工具列表 =====
# 这里同时使用自定义工具和第三方工具
from langchain_tavily import TavilySearch
search = TavilySearch(max_results=5) # 第三方搜索工具
tools = [get_weather, search] # 把所有工具放进列表
# ===== 第3步:创建Agent =====
agent = create_agent(
model=llm, # 指定LLM作为大脑
tools=tools, # 传入工具列表
system_prompt="你是一个智能助手,请根据用户的需求调用合适的工具来帮助他们。",
)create_agent函数的核心参数:
| 参数 | 作用 | 是否必填 |
|---|---|---|
model | 指定LLM(Agent的大脑) | ✅ 必填 |
tools | 工具列表(Agent的手脚) | ✅ 必填 |
system_prompt | 系统提示词,指导Agent的行为风格 | 可选 |
checkpointer | 记忆存储(下一章详细讲) | 可选 |
middleware | 中间件列表(后续章节详细讲) | 可选 |
2.5 调用方式
Agent创建好之后,有两种调用方式:一次性调用和流式调用。
2.5.1 invoke(一次性调用)
等待Agent完成所有推理和工具调用后,一次性返回最终结果:
python
result = agent.invoke(
{"messages": [{"role": "user", "content": "今天北京的天气怎么样?"}]}
)
# 取出最终回复
print(result["messages"][-1].content)注意:目前 LangChain 最新的推荐模式, 采用LangGraph 架构的 Agent,在这种模式下,Agent 内部是通过一个名为
messages的变量来管理整个对话状态。
2.5.2 stream(流式调用)
实时输出Agent每一步的中间过程,适合需要展示"Agent正在思考/执行"的场景:
python
for step in agent.stream(
{"messages": [{"role": "user", "content": "今天北京的天气怎么样?"}]}
):
print(step, end="\n\n")流式调用的输出会依次展示: 2. LLM推理过程,进行工具调用(调用get_weather工具) 3. 工具返回结果 4. LLM的最终回复
注意:
agent.stream默认吐出的是"当前这一步做完后的完整状态"。实际开发建议: 开发调试阶段用
stream可以观察Agent每一步在干什么,方便排查问题;生产环境中根据产品形态选择——聊天界面适合流式,后台任务适合一次性调用。
2.6 完整实例
下面是一个完整的、可直接运行的例子,把本章所有知识点串起来:
python
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain_tavily import TavilySearch
# ===== 1. 定义自定义工具 =====
@tool
def calculate(expression: str) -> str:
"""计算数学表达式的结果。
Args:
expression: 数学表达式,如 "2 + 3 * 4"、"100 / 7"
"""
try:
result = eval(expression)
return f"计算结果:{expression} = {result}"
except Exception as e:
return f"计算出错:{e}"
@tool
def get_current_date() -> str:
"""获取当前日期和时间,不需要任何参数。"""
from datetime import datetime
return datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
# ===== 2. 使用第三方工具 =====
search = TavilySearch(max_results=3)
# ===== 3. 初始化LLM =====
llm = init_chat_model(model="gpt-4o-mini", model_provider="openai")
# ===== 4. 创建Agent =====
agent = create_agent(
model=llm,
tools=[calculate, get_current_date, search],
system_prompt="""你是一个智能助手,拥有以下能力:
- 计算数学表达式
- 查询当前日期时间
- 搜索网络信息
请根据用户的问题,选择合适的工具来回答。如果不需要工具就能回答,直接回答即可。""",
)
# ===== 5. 运行Agent =====
for i, step in enumerate(agent.stream(
{"messages": [{"role": "user", "content": "今天是几号?帮我算一下距离2026年五一还有多少天"}]}
), start=1):
print(f"=== 第 {i} 步 ===")
print(step, end="\n\n")在这个例子中,Agent会自主完成以下步骤(无需我们编码控制流程):
- 调用
get_current_date获取今天日期 - 自己计算天数差,或调用
calculate来辅助计算 - 组织语言回复用户
2.7 LangSmith调试
Agent的执行过程是动态的,有时候出了问题很难排查——比如LLM选错了工具、传错了参数、或者陷入了无限循环。LangSmith是LangChain官方提供的追踪调试工具,可以可视化Agent每一步的执行细节。
只需要设置环境变量即可启用:
python
import os
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "你的API Key"
os.environ["LANGSMITH_PROJECT"] = "my-agent-project"启用后,每次Agent运行的完整轨迹(每轮推理、每次工具调用、每个参数和返回值)都会记录到LangSmith平台上,方便回溯和分析。
2.8 本章小结
本章的学习路径是:
- 理解原理: 工具调用的本质是Function Calling——LLM只负责"选择调用什么",框架负责"实际执行"。
- 定义工具: 用
@tool装饰器把普通Python函数变成Agent可用的工具,重点是写好docstring和类型注解。 - 构建Agent: 用
create_agent把LLM和工具组装起来,一行代码搞定所有底层细节。 - 调用Agent:
invoke一次性获取结果,stream实时观察执行过程。
到目前为止,我们定义的工具都是本地工具——函数代码写在我们自己的项目里。但实际开发中,你经常需要使用别人已经封装好的工具服务(比如查火车票、操作数据库、读取GitHub仓库)。每个服务的接入方式都不一样,难道要为每个服务单独写适配代码吗?
下一章我们将学习MCP(模型上下文协议)——一个标准化的工具接入协议,让Agent能像"插USB"一样轻松接入各种外部工具服务。
3、MCP工具接入
3.1 为什么需要MCP
上一章我们学会了用@tool装饰器定义本地工具。本地工具虽然灵活,但有一个现实问题:
场景:你想让Agent具备"查火车票"的能力
方式一:自己写本地工具
→ 需要研究12306的API文档
→ 需要处理认证、签名、加密
→ 需要处理各种异常和边界情况
→ 需要持续维护(API一更新就得改)
方式二:如果有人已经把"查火车票"封装成了一个标准化的服务,你只需要"接上去"就能用呢?问题在于,不同的人封装工具服务的方式各不相同——有的用REST API,有的用WebSocket,有的用gRPC……如果每接入一个外部工具都要写一套不同的适配代码,那就太麻烦了。
MCP(Model Context Protocol,模型上下文协议)就是来解决这个问题的。 它定义了一套统一的标准,让所有工具服务都以相同的方式暴露能力,AI应用以相同的方式接入——无论底层工具是什么、在哪里运行。
3.2 MCP是什么
理解MCP最简单的方式是类比USB-C:

一句话总结:MCP是AI领域的"USB-C标准",它统一了LLM与外部工具之间的通信方式。
3.3 MCP架构
MCP采用客户端-服务器架构,涉及三个角色:

| 角色 | 职责 | 在我们场景中的对应 |
|---|---|---|
| MCP Host | 运行AI应用的宿主程序,内含MCP Client | 你的LangChain Agent程序 |
| MCP Client | 与MCP Server通信的客户端 | LangChain的MCP适配器 |
| MCP Server | 提供工具能力的服务端 | 别人封装好的工具服务 |
3.4 MCP工作流程
当Agent通过MCP调用一个外部工具时,完整的流程是这样的:
第①步 - 握手:Agent启动时连接MCP Server,获取工具列表和描述
Host:"你有哪些工具?"
Server:"我有 query_train(查火车票)、book_ticket(订票)……"
↓
第②步 - 注入:Host将工具描述和用户问题一起发给LLM
(和本地工具完全一样——LLM不知道也不关心工具是本地的还是远程的)
↓
第③步 - 决策:LLM决定要调用哪个工具、传什么参数
LLM:"我要调用 query_train,参数是 {from: '北京', to: '上海', date: '2026-05-01'}"
↓
第④步 - 路由执行:Host通过MCP协议将调用请求发给Server,Server执行并返回结果
Host → Server:"执行 query_train({from: '北京', to: '上海', date: '2026-05-01'})"
Server → Host:"找到3趟列车:G1 07:00, G3 08:00, G5 09:00"
↓
第⑤步 - 继续推理:Host将结果返回给LLM,LLM继续推理或输出最终回复关键理解: 对LLM来说,MCP工具和本地工具没有任何区别——它看到的都是"工具名 + 描述 + 参数"。MCP只是改变了工具在你的代码端的接入方式,对LLM完全透明。
3.5 传输协议
MCP Server和Client之间的通信支持多种传输方式,适用于不同的部署场景:
| 传输协议 | 原理 | 适用场景 | 示例 |
|---|---|---|---|
| Stdio | 通过标准输入/输出通信 | MCP Server和你的程序在同一台机器上 | 本地开发、调试 |
| Streamable HTTP | 通过HTTP流式传输通信 | MCP Server部署在远程服务器上 | 生产环境、云服务 |
| SSE | 服务器发送事件(Server-Sent Events) | 需要服务端主动推送的场景 | 实时通知(较少使用) |
实际开发中用得最多的是前两种: 本地开发调试用Stdio(简单、无需网络),生产部署用Streamable HTTP(支持远程调用)。
3.6 编写MCP Server(Stdio)
在接入别人的MCP Server之前,我们先自己写一个,理解Server端是怎么工作的。
python
# 文件名:mcp_server_stdio.py
# uv add mcp
from mcp.server.fastmcp import FastMCP
# ===== 创建MCP Server实例 =====
mcp = FastMCP("MyTools")
# ===== 用 @mcp.tool() 定义工具 =====
# 注意:写法和LangChain的 @tool 非常相似
@mcp.tool()
def add(a: int, b: int) -> int:
"""计算两个整数的和"""
return a + b
@mcp.tool()
def multiply(a: int, b: int) -> int:
"""计算两个整数的乘积"""
return a * b
# ===== 启动Server =====
if __name__ == "__main__":
mcp.run(transport="stdio") # 以Stdio方式运行MCP Server还支持暴露资源(Resource)和提示词模板(Prompt),不仅仅是工具:
python
# 资源:提供可读取的数据(类似GET接口)
@mcp.resource("greeting://default")
def get_greeting() -> str:
"""返回一条默认问候语"""
return "Hello from MCP Server!"
# 提示词模板:提供预定义的提示词
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
"""生成问候语的提示词"""
styles = {
"friendly": "写一句友善的问候",
"formal": "写一句正式的问候",
"casual": "写一句轻松的问候",
}
return f"为{name}{styles.get(style, styles['friendly'])}"资源:@mcp.resource —— "静态的文件柜"
工作机制: 定义了一个唯一的 URI(统一资源标识符)greeting://default。当 Agent 连接到这个 Server 时,它会知道:"哦,这里有一份叫做 greeting://default 的文档可以看"。
场景:比如读取一份系统日志、读取公司的员工手册、读取当前的配置参数
核心理解: 它就像是一个只读的 API 接口,或者一个虚拟的文件柜。大模型(LLM)不需要去"执行"什么动作,只是去"读取"里面的背景资料。
提示词:@mcp.prompt —— "标准化的模版库"
核心理解: 它是存在 Server 端的一套"话术模板"。它让 U盘(Server)自带说明书,告诉 Host(主机/大模型):"如果你想用我,你应该这样问问题"。
工作机制: 当 Agent 连接上 Server 时,Server 会告诉它:"我这有个模板叫 greet_user,你只要给我填入 name 和 style,我就会吐出一句完美的提示词给你。"
为什么要把提示词放在 Server 里?
- 如果你用 LangChain 写代码,你通常会在 Host(客户端)里硬编码系统提示词。但如果这个 Server 是别人写的第三方服务(比如 GitHub 官方提供的一个 MCP Server),GitHub 最知道怎么引导大模型写出高质量的 PR Review。所以 GitHub 直接在 Server 里把提示词写好,你只管调用,生成出来的提示词直接喂给 LLM。
测试Server
写好Server后,可以用MCP SDK自带的Client来测试:
python
import asyncio
import sys
import os
from mcp.client.stdio import stdio_client
from mcp import ClientSession, StdioServerParameters
async def test():
# 动态获取当前脚本所在的绝对路径所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 拼接出 server 脚本的绝对路径
server_script_path = os.path.join(current_dir, "mcp_server.py")
# 配置Server的启动命令
server_params = StdioServerParameters(
command=sys.executable, # 1:使用当前跑Client的同一个Python解释器(虚拟环境)
args=[server_script_path], # 2:使用绝对路径
)
# 连接Server
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize() # 第①步:握手
# 查看Server提供了哪些工具 它 只去拉取工具
tools = await session.list_tools()
print("可用工具:", tools)
# 调用工具
result = await session.call_tool("add", {"a": 10, "b": 20})
print("调用结果:", result) # 输出: 30
asyncio.run(test())核心代码解读
上面代码中最核心的一行是:
python
async with stdio_client(server_params) as (read, write):用之前的USB类比来说,这行代码就是"把准备好的U盘插到主机的USB接口上,并接通数据线"。拆成三个部分来理解:
① stdio_client(server_params) —— 启动Server进程
stdio是Standard Input/Output(标准输入/输出)的缩写。这个函数会根据server_params的配置(即python ./mcp_server_stdio.py),在后台启动Server脚本。启动后,Client和Server之间不走网络端口,而是直接通过进程的标准输入/输出来通信——Client往Server的stdin写数据,从Server的stdout读结果。这种方式极其轻量,非常适合本地运行的工具。
② as (read, write) —— 获取两根"数据线"
连接建立后,返回两个通信对象:
write(写通道):Host → Server 发送指令的管道
比如"调用add工具,参数是10和20"
read (读通道):Server → Host 返回结果的管道
比如Server算出结果30,Host通过read接收可以把它想象成一部对讲机:一根线负责说(write),一根线负责听(read)。后续代码中的session.initialize()(握手)、session.call_tool()(调用工具)等操作,底层都是通过这两根线收发数据的。
③ async with ... —— 自动管理生命周期
这是Python的异步上下文管理器。它的作用是:进入缩进块时建立连接,退出时自动关闭Server进程并断开通信管道——无论是正常执行结束还是中途报错。你不需要手写session.close()或server.kill(),不会出现后台残留僵尸进程的问题。
3.7 编写MCP Server(HTTP)
当Server需要部署在远程服务器上供多个Client调用时,使用Streamable HTTP方式:
python
# 文件名:mcp_server_http.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("MyTools")
@mcp.tool()
def add(a: int, b: int) -> int:
"""计算两个整数的和"""
return a + b
if __name__ == "__main__":
mcp.run(transport="streamable-http") # 默认启动在 127.0.0.1:8000对应的Client代码:
python
# 文件名:test_mcp_http_client.py
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
async def test():
url = "http://127.0.0.1:8000/mcp" # Server的HTTP地址
# 第三个对象 _(被忽略的对象): 这是跟底层网络传输(Transport)相关的元数据或回调函数。在流式 HTTP 的实现中,它通常是一个用来获取当前底层 HTTP 连接的 Session ID(会话标识) 的函数。除非你在写极其复杂的底层重连机制、或者需要追踪排查极端的网络断线日志,否则在日常调用工具开发 Agent 时根本用不到它
async with streamable_http_client(url=url) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print("可用工具:", tools)
result = await session.call_tool("add", {"a": 10, "b": 20})
print("调用结果:", result)
asyncio.run(test())对比两种方式: Server端的工具定义代码完全一样,只是启动时的transport参数不同。Client端的连接方式不同(一个启动本地进程,一个连HTTP地址),但调用工具的API完全一致。这就是MCP协议标准化带来的好处。
3.8 LangChain接入MCP
前面两节我们手写了Client来测试MCP Server。但在实际开发中,我们不需要手写Client——LangChain提供了langchain-mcp-adapters包,可以直接把MCP Server的工具转换成Agent可用的工具。
python
# uv add langchain-mcp-adapters
import os
import sys
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
load_dotenv()
# ===== 精准定位 Server 文件的绝对路径 =====
# 1. 获取当前脚本 (langchain_integration_mcp.py) 所在的目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 2. 向上退一级到 mcp 目录,然后进入 stdio 目录,找到 mcp_server.py
server_script_path = os.path.normpath(os.path.join(current_dir, "..", "stdio", "mcp_server.py"))
print(f"准备调用的 Server 路径: {server_script_path}")
# ===== 第1步:配置MCP Server连接 =====
client = MultiServerMCPClient({
# 连接一个本地Stdio Server
"my-local-tools": {
"transport": "stdio",
"command": sys.executable, # 使用当前虚拟环境的 Python
"args": [server_script_path], # 使用计算好的绝对路径
},
"12306-mcp": {
"transport": "streamable_http",
"url": "https://mcp.api-inference.modelscope.net/8e63d4dbeef046/mcp"
}
})
# ===== 第2步:获取所有Server的工具,创建Agent =====
async def main():
# 自动连接所有Server,获取全部工具列表
tools = await client.get_tools()
print(f"✅ 成功获取到 {len(tools)} 个工具")
# 此时tools里包含了Server的所有工具,和本地@tool定义的工具格式完全相同
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_agent(llm, tools)
# ===== 第3步:像平常一样使用Agent =====
print("\n 开始执行Agent...")
result = await agent.ainvoke({
"messages": [("user", "信阳有多少个火车站")]
})
# 打印最终结果
print("\n🤖 Agent回复:", result["messages"][-1].content)
asyncio.run(main())核心价值: 通过MultiServerMCPClient,你可以同时接入任意数量的MCP Server,它们的工具会被统一转换成LangChain工具格式。对Agent来说,MCP工具和本地@tool工具用起来没有任何区别。
3.9 本地工具 vs MCP工具
| 对比维度 | 本地工具(@tool) | MCP工具 |
|---|---|---|
| 定义位置 | 写在你的项目代码中 | 运行在独立的MCP Server上 |
| 适合场景 | 业务逻辑简单、不需要复用 | 通用能力、需要跨项目/跨团队复用 |
| 维护方式 | 和主项目一起维护 | 独立部署、独立维护 |
| 使用门槛 | 低——写个函数加个装饰器 | 中——需要启动Server |
| 生态复用 | 无——只有你自己能用 | 强——任何支持MCP的应用都能接入 |
实际建议: 项目早期或工具逻辑简单时,直接用
@tool定义本地工具最快。当工具需要被多个项目复用、或者你想接入社区已有的工具服务时,用MCP。两种方式可以混合使用——在同一个Agent中同时挂载本地工具和MCP工具。
3.10 本章小结
本章围绕"如何接入外部工具"展开:
- 为什么需要MCP: 不同的工具服务有不同的接入方式,MCP通过统一的协议标准解决了这个问题。
- MCP的工作方式: Host(你的应用)通过MCP Client连接MCP Server,获取工具列表,然后像使用本地工具一样使用远程工具。
- 两种传输协议: Stdio适合本地开发,Streamable HTTP适合远程部署。
- LangChain集成:
langchain-mcp-adapters的MultiServerMCPClient可以同时接入多个MCP Server,对Agent完全透明。
到目前为止,我们的Agent已经拥有了强大的工具调用能力(本地工具 + MCP远程工具)。但它还有一个明显的不足:每次调用都是"失忆"的——它不记得上一轮对话说了什么。下一章我们将学习如何给Agent添加记忆。