从零开发带工具调用能力的智能问答系统,核心是把 LLM(大脑)+ Tools(外部能力)+ Memory(记忆)+ Agent 编排 四块拼起来。LangChain 现在的推荐做法是:业务内简单工具用 @tool 直接封装,跨系统、要复用的能力走 MCP(Model Context Protocol) 标准化接入。下面按可落地的顺序一步步来。
一、整体架构与选型
一个生产可用的问答智能体通常包含:
用户问题 → Agent(LLM 推理) → 选择工具 → 执行工具(API/MCP/RAG) → 观察结果 → 组织答案
↑ |
└─────────────── 多轮循环 ───────────────┘
关键选型:
- 模型 :支持 tool calling 的大模型(GPT-4o、Claude、Qwen、DeepSeek 等均可,通过
langchain-openai的base_url接兼容接口) - Agent 框架 :
langchain+langgraph(LangChain 官方现在主推 LangGraph 做 Agent 编排) - 工具 :
- 简单/业务紧耦合 →
@tool原生封装 - 跨系统/需复用/独立部署 → MCP Server ,通过
langchain-mcp-adapters接入
- 简单/业务紧耦合 →
- MCP 与 Function Calling 的关系 :互补而非替代。MCP 解决"工具接口标准化、多 Agent 共享",Function Calling 解决"单次模型如何调 API"。完整链路是:MCP Client 把 Server 的工具注册进来 → 转成模型的 tools 参数 → 模型用 Function Calling 选定工具 → Client 经 MCP Server 执行 → 结果回传模型。
二、环境准备
bash
# 核心依赖
pip install langchain langchain-openai langchain-community langgraph
pip install langchain-mcp-adapters # MCP 适配器
pip install python-dotenv # 环境变量管理
# 如需本地 MCP Server(Node 版)
npm install -g @modelcontextprotocol/server-filesystem
.env 文件:
OPENAI_API_KEY=你的key
OPENAI_BASE_URL=模型代理地址(可选,用于对接兼容接口)
MODEL_NAME=gpt-4o-mini
三、Step 1:搭建最小可跑通的 Agent(原生 @tool)
先用最简单的计算器工具跑通"思考→选工具→执行→回答"闭环:
python
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
from langchain_community.chat_message_histories import FileChatMessageHistory
load_dotenv()
# 1. 自定义工具:用 @tool 装饰器封装
@tool
def calculator(num1: float, num2: float, op: str) -> str:
"""数字计算工具,用于两个数字运算
:param num1: 第一个数字
:param num2: 第二个数字
:param op: 运算符号,支持 + - * /
"""
if op == "+": res = num1 + num2
elif op == "-": res = num1 - num2
elif op == "*": res = num1 * num2
elif op == "/": res = num1 / num2
else: return "不支持该运算符"
return f"计算结果:{res}"
tools = [calculator]
# 2. 初始化 LLM
llm = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o-mini"), temperature=0)
# 3. Prompt 模板(必须包含 agent_scratchpad)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个擅长使用工具完成任务的助手,优先调用工具获取结果,不要凭空编造数据。"),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
# 4. 创建 Agent 与 Executor
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 5. 持久化记忆
history = FileChatMessageHistory("./agent_memory.json")
while True:
user_input = input("\n请输入你的问题(输入 exit 退出):")
if user_input == "exit": break
resp = agent_executor.invoke({"input": user_input, "chat_history": history.messages})
print(f"AI:{resp['output']}")
history.add_user_message(user_input)
history.add_ai_message(resp["output"])
💡 工具描述的写法直接影响工具选择准确率。docstring 要写清:这个工具做什么、何时用、参数含义。模糊的描述会让模型乱调工具。
四、Step 2:接入外部 API 作为工具
真实问答系统几乎一定要调外部 REST API(订单查询、天气、搜索等)。用 @tool 封装 HTTP 请求即可:
python
import requests
from langchain_core.tools import tool
@tool
def query_order(order_id: str) -> str:
"""查询订单状态,输入为订单ID(如 ORD12345)"""
try:
resp = requests.get(
f"https://api.your-domain.com/orders/{order_id}",
timeout=5
)
resp.raise_for_status()
data = resp.json()
return f"订单 {order_id} 状态: {data.get('status')}, 物流: {data.get('tracking_no')}"
except requests.RequestException as e:
return f"查询失败: {e}"
@tool
def get_weather(city: str) -> str:
"""获取指定城市的当前天气"""
api_key = os.getenv("WEATHER_API_KEY")
resp = requests.get(
"https://api.weatherapi.com/v1/current.json",
params={"key": api_key, "q": city},
timeout=5
)
data = resp.json()
return f"{city}: {data['current']['temp_c']}°C, {data['current']['condition']['text']}"
工具开发的最佳实践:
- 异常处理:网络请求必须 try/except,返回友好错误信息而非抛异常
- 超时控制 :所有 HTTP 调用设
timeout - 参数校验 :用 Pydantic
StructuredTool做强类型校验 - 敏感信息:API Key 走环境变量,禁止硬编码
- 幂等性:工具最好设计为可重复调用
把 API 工具加入 tools 列表即可让 Agent 自主调用:
python
tools = [calculator, query_order, get_weather]
五、Step 3:接入 MCP 服务(重点)
当工具需要跨项目复用、独立部署、或被多个 Agent 共享时,应该把它做成 MCP Server。
5.1 编写一个 MCP Server
用 Python 的 FastMCP 写一个提供数学工具的 Server(math_server.py):
python
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Math")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
@mcp.tool()
def multiply(a: int, b: int) -> int:
"""Multiply two numbers"""
return a * b
if __name__ == "__main__":
mcp.run(transport="stdio")
也可以用 Node.js 写,两端完全解耦,互不干扰。
5.2 在 LangChain 中接入 MCP 工具
langchain-mcp-adapters 支持 stdio(本地进程) 和 Streamable HTTP(远程服务) 两种传输方式:
python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
async def main():
# 连接多个 MCP Server(stdio + http 混合)
client = MultiServerMCPClient({
"math": {
"command": "python",
"args": ["./math_server.py"],
"transport": "stdio",
},
"weather": {
"url": "http://localhost:8000/mcp",
"transport": "http",
}
})
tools = await client.get_tools()
print(f"加载了 {len(tools)} 个 MCP 工具")
# 创建 Agent
agent = create_agent("openai:gpt-4.1", tools)
result = await agent.ainvoke({"messages": "what's (3 + 5) x 12?"})
print(result["output"])
asyncio.run(main())
如果用 JS/TS,@langchain/mcp-adapters 支持更丰富的配置(认证头、OAuth、自动重连等):
javascript
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
import { createAgent } from "langchain";
import { ChatOpenAI } from "@langchain/openai";
const client = new MultiServerMCPClient({
mcpServers: {
math: {
transport: "stdio",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-math"],
},
weather: {
url: "https://example.com/weather/mcp",
headers: { Authorization: "Bearer token123" }
}
}
});
const tools = await client.getTools();
const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
const agent = createAgent({ llm: model, tools });
⚠️ 生产环境务必 Pin MCP Server 版本,避免远程 Server 升级导致工具签名变化。
5.3 原生 @tool vs MCP 怎么选
| 维度 | 原生 @tool |
MCP Server |
|---|---|---|
| 复用性 | 仅当前应用 | 任意 MCP 客户端可用 |
| 跨语言 | 不支持 | Python/Node/Rust 全兼容 |
| 部署 | 随应用一起 | 可独立部署、升级 |
| 适用场景 | 快速原型、1-2 个工具 | 多系统共享、需独立运维 |
经验法则 :工具会被多个项目用到 → MCP;只是当前业务的简单封装 → @tool。
六、Step 4:RAG + Agent 组合(知识库问答)
纯工具调用适合"操作类"问题,但企业问答往往需要基于私有文档回答。把 RAG 检索也封装成一个工具即可:
python
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_core.tools import tool
# 假设已构建好向量库
vectorstore = Chroma(collection_name="docs", embedding_function=OpenAIEmbeddings())
@tool
def search_knowledge_base(query: str) -> str:
"""在企业知识库中检索相关文档,用于回答产品、政策、规范类问题"""
docs = vectorstore.similarity_search(query, k=3)
return "\n\n".join([doc.page_content for doc in docs])
# 工具组合:RAG + API + 计算
tools = [search_knowledge_base, query_order, calculator]
这样 Agent 会根据问题自主决定:是查知识库、还是调 API、还是直接计算。
七、Step 5:生产级增强
7.1 多轮记忆与上下文
除了上面演示的 FileChatMessageHistory,生产环境建议用 Redis 或数据库:
python
from langchain_redis import RedisChatMessageHistory
history = RedisChatMessageHistory(session_id="user_123", url="redis://localhost:6379")
7.2 工具调用治理
- 工具数量控制 :单个 Agent 工具数建议 10-15 个以内,过多会干扰模型选择
- 超时与熔断:给每个工具设超时,失败重试 1-2 次
- 日志与追踪:用 LangSmith 或 OpenTelemetry 记录每次 tool call 的输入输出
- 权限控制:敏感工具(如退款、删除)加人工确认环节
7.3 错误处理模式
python
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=10, # 防止无限循环
handle_parsing_errors=True, # 解析错误时优雅降级
return_intermediate_steps=True, # 返回中间步骤便于调试
)
八、完整项目结构建议
my-agent/
├── .env # API Keys
├── math_server.py # MCP Server(独立进程)
├── agent.py # 主 Agent 入口
├── tools/ # 原生 @tool 工具
│ ├── api_tools.py # 外部 API 封装
│ └── rag_tool.py # 知识库检索工具
├── config/
│ └── mcp_servers.json # MCP Server 配置
└── memory/ # 持久化记忆(如果用文件)
启动 MCP 增强的 Agent:
python
import json
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from tools.api_tools import query_order, get_weather
from tools.rag_tool import search_knowledge_base
# 1. 加载 MCP 配置
with open("config/mcp_servers.json") as f:
mcp_config = json.load(f)["mcpServers"]
# 2. 原生工具 + MCP 工具合并
mcp_client = MultiServerMCPClient(mcp_config)
mcp_tools = await mcp_client.get_tools()
native_tools = [query_order, get_weather, search_knowledge_base]
all_tools = native_tools + mcp_tools
# 3. 构建 Agent
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个企业智能助手,可调用工具查询订单、天气、知识库等。优先用工具获取真实数据。"),
MessagesPlaceholder("chat_history"),
("user", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
agent = create_tool_calling_agent(llm, all_tools, prompt)
executor = AgentExecutor(agent=agent, tools=all_tools, verbose=True)
# 4. 运行
result = executor.invoke({"input": "查一下订单 ORD12345 的状态,并告诉我北京今天天气"})
print(result["output"])
九、调试与上线路径
- 本地跑通 :用
verbose=True观察 Agent 的思考链 - 单元测试 :对每个
@tool单独测试,确保输入输出符合预期 - 集成测试:用 LangSmith 追踪完整调用链路
- 生产部署 :
- Agent 服务化(FastAPI 封装)
- MCP Server 独立部署,通过 HTTP 传输
- 加限流、鉴权、审计日志
- 监控工具调用成功率与耗时
📌 最容易踩的坑:
- 忘记在 Prompt 里加
agent_scratchpad→ Agent 无法写入思考过程- 工具 docstring 太模糊 → 模型选错工具
- MCP Server 用 stdio 传输但路径不对 → 子进程拉起失败
- 工具执行时间长阻塞主线程 → 改用异步工具
按这个路径,从零搭建的智能问答系统既能查知识库(RAG),又能调业务 API,还能通过 MCP 复用外部工具生态------这才是 2026 年生产级 Agent 的标准形态。