2026最新版 LangChain+LangGraph 实战教程:Agent 多智能体协同、RAG 检索增强与 MCP 协议全解析
1. 背景与核心概念
如果你最近开始接触大模型应用开发,大概率已经被 LangChain、LangGraph、RAG、Agent 这一串名词轰炸过。打开技术社区,到处都是“五分钟搭建知识库问答”“从零实现 Agent 工作流”,可真自己去动手时,却经常卡在概念理解上:LangChain 和 LangGraph 到底有什么关系?Agent 和 Chain 有什么区别?RAG 是不是就是“给大模型喂点资料”?MCP 又是什么?
这篇文章不打算绕弯子,直接从工程落地视角,把这几个核心概念一次讲透。我不只介绍它们是什么,更重要的是告诉你每一种能力在什么场景下用、怎么搭、踩过哪些坑。
先看一张最简化的技术分层图,方便后续理解。
| 技术层 | 解决的核心问题 | 典型工具 |
|---|---|---|
| 模型接入层 | 统一调用不同大模型 | LangChain ChatModels |
| 记忆与提示词层 | 让模型记住上下文、按指定格式输出 | LangChain Prompts、Memory |
| 编排层 | 控制调用流程、分支、并行、循环 | LangGraph、LangChain Chains |
| 智能体层 | 让模型自主决定调用哪些工具 | Agent(ReAct、Function Calling) |
| 检索层 | 从外部知识库检索相关资料 | RAG、Vector Store |
| 工具互操作层 | 统一模型与外部工具之间的协议 | MCP 协议 |
1.1 LangChain 到底是什么
LangChain 最初被大众熟知,是因为它解决了一个非常朴素的问题:让开发者用自己的提示词模板和外部数据,拼装成一套可以调用大模型的流程。它能统一不同厂商模型的调用方式,提供 Prompt 管理、输出解析、记忆集成、文档加载器、向量存储封装等能力。
不过 LangChain 的早期设计也有一些“坑”,最典型的问题是Chain 是线性的。你用LLMChain实现“先执行 A 再用结果执行 B”勉强可以,但一旦业务里出现“A 不满足就跳到 C”“B 和 D 并行执行”“循环直到满足条件”这样的复杂逻辑,表达起来就非常吃力。
这也是 LangGraph 出现的重要背景。
1.2 LangGraph 是什么
LangGraph 是建立在 LangChain 生态之上的一个有状态工作流编排框架。它的核心抽象是StateGraph,也就是说,你可以把整个应用看作一张图:
- 节点(Node):执行某个具体任务,可能是调用 LLM、检索数据库、调用工具。
- 边(Edge):定义节点之间的跳转关系。
- 状态(State):贯穿全局的数据容器,节点之间通过状态传递信息。
所以 LangGraph 不只是“LangChain 的升级版”,它两者做的是不同层级的事:LangChain 提供基础能力组件,LangGraph 负责把组件编排成复杂的非线性的 AI 应用,尤其是真正意义上的Agent。
1.3 Agent 与多智能体
Agent(智能体)和 Chain 最容易混淆。
| 对比项 | Chain(链) | Agent(智能体) |
|---|---|---|
| 控制方式 | 开发者预先写死流程 | 模型根据当前输入动态决策 |
| 工具调用 | 固定步骤中调用 | 模型自行决定调哪个工具、调几次 |
| 分支处理 | 靠代码 if-else | 模型推理选择路径 |
| 典型适用场景 | 固定问答、数据加工 | 需要多步推理、工具联动、自主规划 |
多智能体协同并不是“多个 Agent 摆在一起”,而是拆分成多个职责不同的 Agent,通过消息传递和任务交接完成整体目标。例如一个“研究型 Agent”负责检索资料,一个“编写型 Agent”负责生成内容,一个“审查型 Agent”负责质量检查。
1.4 RAG 核心思想
RAG 全称 Retrieval-Augmented Generation,检索增强生成。模板化的说法是:先从一个知识库中检索与用户问题相关的文本片段,把片段作为上下文拼接进 Prompt,再让大模型生成回答。
RAG 解决的核心问题是大模型不知道你私有的业务数据。你不需要重新训练模型,只需要把你的文档切块、向量化、存入向量数据库,回答问题时先“查”后“答”。
1.5 MCP 协议为什么重要
MCP(Model Context Protocol)是 Anthropic 提出的开放协议,目标是把“模型如何调用外部工具”标准化。你可以把它理解为 AI 世界的 USB-C 接口:以前不同工具都有各自的调用方式,现在通过 MCP 统一了工具暴露和调用的格式。
对开发者的意义在于:只要你的工具实现了 MCP Server,那么所有支持 MCP 的客户端都能直接使用它,不需要针对每家框架写各自的 adapter。
2. 环境准备与版本说明
动手之前,先把环境准备好。不同系统的路径和安装命令略有差异,但思路一致。下面以 Python 3.10+ 环境作为示例。由于 LangChain 生态更新非常快,建议你在安装时不要盲目追求最新版,而应该锁定与项目匹配的版本组合。
2.1 创建虚拟环境
推荐使用venv或者conda为项目单独建一套环境,避免把各种依赖混在系统 Python 里。
python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate2.2 安装依赖包
本文涉及的包包括langchain、langgraph、langchain-openai(或langchain-community配合其他模型)、langchain-chroma(向量库)、python-dotenv(读取环境变量)等。
以 OpenAI 模型为例,基础安装命令如下:
pip install langchain langgraph langchain-openai langchain-chroma python-dotenv如果你希望使用国内模型或其他厂商模型,把langchain-openai替换成对应的包即可,例如langchain-zhipu、langchain-qianfan等。LangChain 生态已经适配了大量模型厂商,注意查看对应版本的官方文档即可。
2.3 设置 API Key
在项目根目录下新建一个.env文件,内容如下:
OPENAI_API_KEY=你的API密钥然后在代码中加载:
from dotenv import load_dotenv load_dotenv()需要强调一个安全习惯:不要把 API Key 硬编码到代码里,更不要提交到 Git 仓库。如果项目要协作,记得把.env加入.gitignore。
2.4 关于版本兼容性的一句话建议
LangChain 系列的 API 变化比较频繁。2025 年之后,很多模块从langchain.xxx迁移到了langchain_core、langchain_community、langgraph独立包中。如果你在网上找到旧教程,代码里是from langchain.llms import OpenAI,大概率已经过时了。建议以你实际安装版本的官方 API Reference 为准。
3. LangGraph 核心原理解析
在写完整实战之前,先把 LangGraph 最关键的几个概念拆开讲清楚。只有理解了 State、Node、Edge 的关系,后面才不会被复杂的 Agent 流程带晕。
3.1 State(状态):所有节点共享的数据容器
State 是 LangGraph 的核心。每个节点执行完都会返回一个字典,LangGraph 会把返回结果合并到全局状态中。后续节点可以从状态里读取前面节点写入的数据。
from typing import TypedDict class AgentState(TypedDict): messages: list next_step: str这个状态定义里有两个字段:messages保存对话消息列表,next_step保存下一步要执行的动作名。
这里的关键点是:State 不只是一个“变量”,它决定了节点之间怎么传值。如果你需要并行分支,State 里也可以放多个独立字段,不同分支只修改自己的字段。
3.2 Node(节点):工作流里的执行单元
Node 就是一个普通的 Python 函数,输入是当前 State,输出是一个字典。LangGraph 会把输出字典中的字段合并回 State。
def node_a(state: AgentState): return {"messages": state["messages"] + ["A 节点执行完成"]}这种设计让每个节点非常容易测试:你不想通过整个图来跑,也可以直接传一个字典调用函数看结果。
3.3 Edge(边):定义状态跳转规则
Edge 分为两种:
- 普通边:节点执行完后无条件跳转到另一个节点。
- 条件边:节点执行完后,根据返回值动态决定跳转到哪个节点。这是 Agent 实现“自主决策”的关键。
from langgraph.graph import StateGraph, START, END graph = StateGraph(AgentState) graph.add_node("node_a", node_a) graph.add_node("node_b", node_b) graph.add_edge(START, "node_a") graph.add_edge("node_a", "node_b") graph.add_edge("node_b", END)3.4 LangGraph 与 LangChain 的区别总结
有很多人问:既然 LangGraph 能做 LangChain 的事,是不是可以直接只学 LangGraph?
准确地说,二者是互补关系:
- LangChain 提供模型抽象、Prompt 模板、输出解析器、文档加载器、向量存储封装等基础组件。
- LangGraph 负责把这些组件编排成图结构,提供状态管理、条件路由、跨线程控制等能力。
你在 LangGraph 的节点里完全可以调用 LangChain 的 ChatModel、Retriever、PromptTemplate。它们不是替代关系,而是编排层和组件层的关系。
4. 实战一:用 LangGraph 构建一个支持工具调用的 ReAct Agent
理解了基础概念后,我们直接进入第一个完整案例:构建一个能调用“搜索工具”和“计算工具”的 ReAct 风格 Agent。
这个 Agent 的流程是:
| 步骤 | 节点 | 要完成的事 |
|---|---|---|
| 1 | 接收用户问题 | 把用户输入写入状态 |
| 2 | LLM 推理 | 让大模型判断该调用哪个工具 |
| 3 | 执行工具 | 根据模型返回结果执行搜索或计算 |
| 4 | 生成回答 | 把工具结果带回模型,输出最终答案 |
| 5 | 如果工具结果不充分 | 回到第 2 步继续循环,直到模型认为可以回答为止 |
4.1 项目结构
langgraph-agent-demo/ ├── .env ├── agent.py └── requirements.txt4.2 定义状态和工具
# agent.py from typing import TypedDict, Literal from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langchain_core.messages import HumanMessage, AIMessage, ToolMessage class AgentState(TypedDict): messages: list这里把messages作为整个 Agent 的记忆容器。每走一步,消息列表都会增加新的内容。
接下来定义两个简单的工具函数:
def search_web(query: str) -> str: """模拟联网搜索。实际项目中请替换为真实搜索 API。""" return f"关于“{query}”的搜索结果:这是一条模拟数据,正式环境应接入搜索服务。" def calculate(expression: str) -> str: """简单的四则运算。""" try: result = eval(expression) return f"计算结果:{result}" except Exception as e: return f"计算失败:{str(e)}"注意:eval在实际项目中有安全风险,这里仅为演示。生产环境请使用ast.literal_eval或专门的表达式解析库。
4.3 创建模型并绑定工具
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) tools = [ {"type": "function", "function": {"name": "search_web", "description": "搜索互联网信息", "parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "搜索关键词"}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "calculate", "description": "执行数学计算", "parameters": {"type": "object", "properties": {"expression": {"type": "string", "description": "数学表达式"}}, "required": ["expression"]}}}, ] llm_with_tools = llm.bind_tools(tools)bind_tools是 LangChain 中把工具函数以 JSON Schema 方式传给模型的标准做法。模型本身不执行工具,它只输出“应该调用哪个工具、参数是什么”,真正执行工具的代码需要我们自己写。
4.4 定义 Agent 节点和工具执行节点
def agent_node(state: AgentState): response = llm_with_tools.invoke(state["messages"]) return {"messages": [response]} def tools_node(state: AgentState): last_message = state["messages"][-1] tool_calls = last_message.tool_calls outputs = [] for call in tool_calls: tool_name = call["name"] tool_args = call["args"] if tool_name == "search_web": result = search_web(tool_args["query"]) elif tool_name == "calculate": result = calculate(tool_args["expression"]) else: result = f"未知工具: {tool_name}" outputs.append(ToolMessage(content=result, tool_call_id=call["id"])) return {"messages": outputs}这个阶段非常关键:
agent_node主要负责让模型思考,模型可能返回一个普通回答,也可能返回多个工具调用请求。tools_node实际执行工具,并把结果包装成ToolMessage放回消息列表。tool_call_id必须与模型返回的请求 ID 对应,否则消息关联不起来。
4.5 定义条件路由
模型不一定每次都需要调用工具。如果它直接返回了最终答案,就应该跳到结束节点;如果返回了工具调用请求,就应该跳进工具执行节点。
def should_continue(state: AgentState) -> Literal["tools", "end"]: last_message = state["messages"][-1] if hasattr(last_message, "tool_calls") and last_message.tool_calls: return "tools" return "end"4.6 组装图并运行
graph = StateGraph(AgentState) graph.add_node("agent", agent_node) graph.add_node("tools", tools_node) graph.add_edge(START, "agent") graph.add_conditional_edges("agent", should_continue, {"tools": "tools", "end": END}) graph.add_edge("tools", "agent") app = graph.compile() def chat(question: str): result = app.invoke({"messages": [HumanMessage(content=question)]}) return result["messages"][-1].content if __name__ == "__main__": print(chat("帮我查询北京今天的天气")) print(chat("计算 (12 + 34) * 5 等于多少"))这里有一个容易被新手忽略的循环:agent -> tools -> agent。模型会先判断要不要工具,如果要,就执行工具,然后把工具结果交回给模型继续推理。正是因为多了这条环回边,才让 Agent 有能力处理多步工具调用,而不是一条直线走到底。
4.7 运行验证
执行命令:
python agent.py预期输出类似于:
关于“北京今天天气”的搜索结果:这是一条模拟数据,正式环境应接入搜索服务。 计算结果:230如果看到这个结果,说明你的 Agent 已经从“固定 Chain”升级成了“带自主决策能力的 Agent”。接下来的所有复杂应用,包括多智能体协同,都是在这个基础上扩展出来的。
5. 实战二:RAG 知识库问答系统
如果说 Agent 解决的是“模型怎么调用外部工具”,那 RAG 解决的是“模型怎么使用外部知识”。两者经常一起出现,但侧重点不同:RAG 强调的是信息获取,Agent 强调的是行动决策。
5.1 RAG 的标准流程
一个完整的 RAG 系统包含以下环节:
- 文本加载:读取 PDF、TXT、Markdown、Word 等文档。
- 文本切分:将长文档切成合适长度的 chunk。
- 向量化:用 Embedding 模型把每条 chunk 转成向量。
- 存储:把向量存入向量数据库。
- 检索:根据用户问题计算相似度,取 top-k 条。
- 生成:将检索结果拼入 Prompt,让模型作答。
文档 → 加载 → 切分 → 向量化 → 向量库 用户问题 → 向量化 → 相似度检索 → TopK 片段 TopK 片段 + 用户问题 → Prompt → 大模型 → 回答5.2 完整代码:RAG 基础版
# rag_basic.py from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain_chroma import Chroma from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough import os from dotenv import load_dotenv load_dotenv() # 第 1 步:加载文档 loader = TextLoader("data/knowledge.txt", encoding="utf-8") docs = loader.load() # 第 2 步:切分文档 splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) chunks = splitter.split_documents(docs) # 第 3 步:向量化并存入 Chroma embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents(chunks, embedding=embeddings) # 第 4 步:创建检索器 retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # 第 5 步:定义 Prompt prompt = ChatPromptTemplate.from_template(""" 你是一个知识库问答助手。请根据以下资料回答问题。 如果资料中没有相关内容,请如实回答“知识库中暂无相关信息”。 资料: {context} 问题:{question} """) # 第 6 步:构建 RAG 链 llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) def format_docs(docs): return "\n\n".join([d.page_content for d in docs]) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm ) # 第 7 步:测试 query = "公司的年度调薪规则是什么?" resp = rag_chain.invoke(query) print(resp.content)5.3 关键参数解读
chunk_size 和 chunk_overlap
chunk_size决定每个片段多长,chunk_overlap控制相邻片段之间重复多少内容。为什么要有重叠?因为很多关键信息可能刚好被切在边界上,如果完全不重叠,这部分上下文就丢了。常见设置是 400 到 800 之间,具体需要根据你的文档类型测试。
k 值
search_kwargs={"k": 4}表示检索 4 条相关片段。k 值太小,可能漏掉关键信息;k 值太大,可能引入大量无关信息,干扰模型生成。建议从 3 到 6 开始测试。
Embedding 模型
OpenAI 的text-embedding-3-small是一个兼顾效果和成本的入门选择。如果使用中文知识库,可以测试 BGE、M3E 等本地模型,也可以使用国内云厂商的 Embedding 接口。
5.4 进阶方向:Agentic RAG
基础 RAG 有一个明显短板:用户问一个需要多跳推理的问题时,单次检索往往不够。
例如用户问“XX 产品在 2025 年的销售额是多少?相比 2024 年增长了百分之几?”,基础 RAG 会先检索“XX 产品 2025 年销售额”,如果知识库里没有直接回答这个问题的完整段落,而是分散在两个不同地方,模型的回答质量就会明显下降。
Agentic RAG 的思路是:让 Agent 在检索过程中自动规划,多次检索、组合信息,甚至自主决定是否更换查询词重新查一遍。
实现方式很简单:把retriever封装成一个 Tool,然后用上一节实现的 Agent 框架去调用它。
def retrieve_tool(query: str) -> str: docs = retriever.invoke(query) return format_docs(docs)进一步,可以让 Agent 先对用户问题做拆解:如果是多跳问题,就拆成多个子查询,逐个检索,再汇总信息作答。这也是当前 RAG 领域比较热门的工程方向。
6. 实战三:多智能体协同工作流
多智能体协同没有想象中那么高不可攀。它本质上就是:创建多个不同职责的图,然后在一个总图里把每个子图当成一个节点来调用。
6.1 一个实用的多智能体案例
假设我们要开发一个“技术文章自动写作助手”,可以拆成三个智能体:
| 智能体 | 职责 | 输入 | 输出 |
|---|---|---|---|
| 研究型 Agent | 搜索资料、整理要点 | 用户主题 | 资料要点列表 |
| 写作型 Agent | 根据要点编写文章 | 资料要点 | 初稿 |
| 审查型 Agent | 检查逻辑、补充建议 | 初稿 | 修改意见或最终稿 |
6.2 子图定义
先用 LangGraph 定义子图。这里用简化版本展示主流程。
# multi_agent_demo.py from langgraph.graph import StateGraph, START, END from typing import TypedDict class ResearchState(TypedDict): topic: str research_notes: str def research_node(state: ResearchState): # 实际开发中这里接入搜索工具和 LLM,这里简化为模拟 return {"research_notes": f"已检索到关于 {state['topic']} 的资料:……(要点列表)"} research_graph = StateGraph(ResearchState) research_graph.add_node("research", research_node) research_graph.add_edge(START, "research") research_graph.add_edge("research", END) research_app = research_graph.compile()同理,可以定义写作子图和审查子图。这里为了缩短篇幅不再重复,只展示它们的入口函数。
def writing_node(state): topic = state["topic"] notes = state["research_notes"] draft = f"基于资料“{notes}”生成的文章初稿……" return {"draft": draft} def review_node(state): if "已通过" in state.get("review_result", ""): return {"final_output": state["draft"]} return {"final_output": "文章需要修改:" + state.get("review_comment", "")}6.3 总图编排
class MainState(TypedDict): topic: str research_notes: str draft: str review_result: str final_output: str main_graph = StateGraph(MainState) main_graph.add_node("research_agent", research_app) main_graph.add_node("writing_agent", writing_app) main_graph.add_node("review_agent", review_app) main_graph.add_edge(START, "research_agent") main_graph.add_edge("research_agent", "writing_agent") main_graph.add_edge("writing_agent", "review_agent") main_graph.add_edge("review_agent", END) main_app = main_graph.compile()这里的重点是:research_app是一个编译后的 Graph,但它完全可以作为一个普通节点加入更大的图。LangGraph 的这个特性让多智能体协同变得很清晰——每个智能体内部可以很复杂,但对外只暴露输入输出接口,很好地实现了关注点分离。
6.4 多智能体协同的工程建议
在实际项目中,多智能体之间传递数据时,结构一定要尽量简单。最常见的失败原因不是某个 Agent 能力不足,而是状态字段命名混乱导致数据传错位置。建议提前定义好一个数据契约,例如每个 Agent 的输入输出字段单独命名,不要相互覆盖。
7. MCP 协议实操入门
MCP 是最近非常热门的一个话题。很多不熟悉协议设计的开发者容易把它理解为“又一个工具框架”,其实它的定位是协议层。
7.1 MCP 的角色
在没有 MCP 之前,如果我们想让大模型调用一个内部 API,通常要做三件事:
- 写工具函数。
- 按大模型要求把工具描述成 JSON Schema。
- 自己编写工具执行逻辑,并处理与模型返回值的映射。
每一家模型提供商、每一个框架都有自己的一套工具描述格式。你的代码被某一种格式深度绑定后,换模型就意味着重写一套工具适配层。
MCP 的初衷是:工具的暴露方式统一为 MCP Server,模型的调用方式统一为 MCP Client。以后增加新模型,只要模型客户端支持 MCP,就能直接复用所有 MCP Server 提供的工具。
7.2 MCP 概念拆解
| 概念 | 含义 |
|---|---|
| MCP Server | 暴露工具、提示词、资源的服务端程序 |
| MCP Client | 连接 MCP Server 的客户端,通常嵌在 Agent 应用中 |
| Tool | Server 提供给模型调用的具体能力 |
| Transport | 客户端与服务器之间的通信方式,常见有 stdio 和 HTTP/SSE |
stdio模式适合本地工具集成,HTTP模式适合跨机器部署。开发时可以先从 stdio 模式开始,部署时再切换为 HTTP。
7.3 一个最简单的 MCP Server 示例
下面演示如何用 Python 实现一个返回天气信息的 MCP Server。这里只展示思路,具体实现需要参考你所用 MCP SDK 当前版本的 API。
# mcp_weather_server.py # 以下代码为示例思路,请按实际 MCP SDK 版本调整 from mcp.server.fastmcp import FastMCP mcp = FastMCP("weather_server") @mcp.tool() def get_weather(city: str) -> str: """查询某个城市的天气信息""" # 实际项目中这里应调用真实天气服务 return f"{city} 今日天气:晴,气温 18-26 摄氏度" if __name__ == "__main__": mcp.run()7.4 在 LangGraph Agent 中接入 MCP Server
接入逻辑可以概括为两步:先通过 MCP Client 获取工具列表,再把工具绑定给 LLM,最后由 LangGraph 的 Agent 节点执行调用。工具执行逻辑已经由 MCP Server 内部实现,Agent 这边只需要把它当成一个普通 tool 使用。
这样的架构带来一个很重要的工程收益:工具团队可以独立开发 MCP Server,用自己熟悉的语言实现,不需要关心上层是 LangGraph 还是其他 Agent 框架。只要协议符合规范即可。
8. 常见问题与排查思路
这一节把项目实战中最容易踩的坑集中整理一下,按问题现象、常见原因、解决思路分类。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Agent 循环调用工具不停止 | 没有设置最大迭代次数,或模型一直认为需要调工具 | 增加recursion_limit之类的限制,或在循环中加入“若无法解决则结束”的提示 |
| 工具返回结果没有生效 | ToolMessage的tool_call_id与模型请求不匹配 | 检查工具执行节点是否正确把call["id"]传给了tool_call_id |
| RAG 检索结果质量差 | chunk 切分不合理,或 Embedding 模型对领域术语不友好 | 尝试调整 chunk_size 和 overlap,换用领域微调的 Embedding 模型,同时检查文档解析质量 |
| 调用模型时总是遇到 context length 超限 | 历史消息积累过长 | 引入消息压缩、滑动窗口、摘要记忆 |
| 中文文档切分后语义断裂 | 使用英文空格分词方式切分 | 使用适合中文的分隔符,并按标点边界切分 |
| 多智能体之间数据串了 | 多个子图复用了同一个 state 字段名 | 给不同智能体的输入输出字段加前缀,例如research_notes、writing_input |
坑点一:模型无法正确选择工具
解决思路是检查工具描述是否足够清晰。工具描述里最好包含:这个工具是干什么的、什么场景下应该调用它、参数的具体含义。模型是靠描述来决策的,描述含糊就会导致乱选工具。
坑点二:循环没有终止条件
生产环境中,Agent 可能出现“调用工具 → 结果不满足 → 再调用工具”的死循环。LangGraph 提供了recursion_limit限制图执行的迭代次数,建议设置一个上限,并在达到上限时返回“需要人工介入”的提示。
坑点三:RAG 检索召回了不相关的内容
这不是简单地调大 k 值就能解决的。先检查文档切分是否合理,再检查检索器的相似度分数。如果分数普遍很低,说明 Embedding 模型与文档领域不匹配,可能需要换模型。
9. 最佳实践与工程建议
以下是做实际项目时反复验证过的建议,按优先级排列。
9.1 用 LangGraph 替代线性 Chain
除非你的场景非常简单、流程固定且不会变化,否则建议直接用 LangGraph。用图结构表达流程,后期扩展分支、加并行节点、接人工审批都非常自然。从线性 Chain 迁移到图结构的前期成本并不高,但可维护性提升非常明显。
9.2 工具函数要小、职责要单一
Agent 的决策质量很大程度上取决于工具函数的粒度。一个工具函数如果既做搜索又做解析又做计算,模型很难准确判断“到底该不该调用它”。把工具设计成尽可能小的原子能力,例如:
search_web(query)parse_pdf(file_path)calculate(expression)
每个工具只做一件事,参数简单明确。
9.3 日志与可观测性
Agent 应用最大的问题就是“不知道它内部到底做了什么”。生产环境中,每个节点的输入输出一定要打日志。LangGraph 支持按节点维度开启流式日志,也可以直接在节点函数里输出关键状态。
建议至少记录以下信息:
- 模型输入输出 token 数。
- 每个工具调用的参数和耗时。
- 条件路由的跳转决策结果。
- 最终回答生成完成时间。
9.4 RAG 评测不能只看一两个例子
RAG 上线前一定要做评测。评测维度至少包括:
- 检索命中率。
- 答案正确率。
- 答案可溯源率(能否定位到具体文档)。
- 无答案时是否会误导用户。
这类评测现在被称为 RAG 测评,主要做法是构造一组标准问答对,让系统批量回答后人工或自动打分。推荐每调整一次参数,都跑一遍同一套评测集,对比结果再决定是否上线。
9.5 安全与权限的底线
这部分必须强调:
- RAG 知识库如果包含敏感数据,必须做权限隔离,不能让所有用户都能检索到所有内容。
- 涉及删除、修改、发布类操作的工具,Agent 应该有独立的审批流程,不能直接自动执行。
- 所有 API Key 通过密钥管理服务保存,禁止写入代码仓库。
- MCP Server 暴露到外网时,必须具备认证和鉴权机制,不能裸奔。
9.6 从基础 Agent 开始,逐步叠加能力
很多团队一开始就规划“多智能体 + RAG + MCP”,结果项目周期严重超支。更稳妥的路径是:
- 先用 LangGraph 实现单 Agent + 基础工具。
- 稳定后再叠加 RAG。
- 然后把单个 Agent 拆分为多个子图。
- 最后引入 MCP 统一外部工具接口。
每步都验证效果和数据指标,再进入下一步。这样即使某个环节出问题,也能快速定位。
10. 学习路线与后续方向
如果你把前面的代码都跑通了,说明已经掌握 LangChain + LangGraph 的核心开发链路。下一步可以按以下方向深入:
关于 LangGraph 的进阶学习,可以从官方文档入手。不要只看 API 示例,建议重点理解状态图的执行机制,尤其是条件路由和循环调度。LangGraph 的源码量不算大,直接阅读源码对理解内部实现非常有帮助。
关于 RAG 的进阶学习,重点是 Agentic RAG 和多路召回。尝试让 Agent 根据用户问题决定是否检索、是否改写查询、是否多次检索。还可以尝试把检索结果按时间、来源、类型做差异化的 rerank。
关于 MCP 的进阶学习,可以从“使用 MCP Server”过渡到“编写自己的 MCP Server”。找一个内部系统,把它的一些查询接口封装成 MCP Server,再让 Agent 通过 MCP 调用,这是理解协议设计的最好方式。
最后提醒一点:这个领域技术演进非常快,今天“最新”的写法,半年后可能就变了。不要死记 API,而是理解架构思想——组件层做能力、编排层做流程、协议层做互联,这三个维度想清楚了,无论框架怎么变,你都能快速迁移。
如果本文对你有帮助,可以收藏备用。也欢迎在评论区分享你在 LangGraph、RAG 或 MCP 使用中遇到的坑,一起讨论一起进步。