企业级 AI Agent 开发绕不开两个名字:LangChain 和 LangGraph。到了 2026 年前后,学习资料越来越多,但很多人的状态是概念零散:今天学一下 Prompt 模板,明天看一段工具调用,后天又听人讲多智能体,等真正要写一个能跑的 Agent 项目时,才发现代码不知道往哪放、状态从哪里来、工具调用失败该看哪一层日志。这篇文章以 LangChain 1.x 与 LangGraph 1.x 的稳定接口为主线,先讲清楚两个框架的分工,再从核心组件拆解到单 Agent 实战,再到多智能体架构中最常用的 Supervisor 模式,最后落到生产环境必须补的配置、安全、监控和排查方法。读完以后,可以把一条完整的学习路线和一个可运行的最小项目结构同时带走。
1. 先分清 LangChain 与 LangGraph:一个管零件,一个管流程
1.1 LangChain 的定位:把模型、提示词、工具串成组件
LangChain 最初解决的核心问题是:LLM 应用不能只靠一次模型调用完成。一个真实功能,往往需要把用户输入转换成 Prompt,把 Prompt 发给模型,把模型输出解析成结构化数据,再决定是否调用外部工具或检索文档。LangChain 把这条链路中的每个环节都抽象成独立组件:ChatModel 负责模型调用,PromptTemplate 负责文本拼接,OutputParser 负责输出解析,Retriever 负责检索,Tool 负责把外部能力封装给模型。这些组件可以自由组合,构成一条固定流程的 Chain。
典型例子是 RAG。用户提出一个问题,系统先去向量库检索相关片段,再把片段与问题拼成一个带上下文的 Prompt,最后让模型生成回答。这个流程基本是线性的,用 LangChain 的组件就能串起来。但问题随之出现:一旦流程需要根据中间结果决定走哪条分支,或者需要循环调用同一个步骤多次,或者需要在某个环节停下来等人工确认,早期的 Chain 模型就显得很笨重。真正适合企业级 Agent 的流程控制,需要由 LangGraph 来承担。
1.2 LangGraph 的定位:用有状态的图表达 Agent 执行流程
LangGraph 把 Agent 运行过程建模成一张有向图。图中每个 Node 是一个处理函数,比如“调用大模型”“调用工具”“判断是否要继续”;Edge 连接节点,表达执行顺序;Conditional Edge 则根据状态自动决定下一步走向。执行期间,所有数据保存在一个全局 State 对象里,每个节点从 State 取输入,向 State 写输出。
理解 LangGraph 要抓住三个核心设计。第一是 State 统一管理:无论是模型消息、工具结果还是自定义字段,都放在同一个状态对象里,用 reducer 函数控制合并方式。第二是 Checkpointer:把每一步状态持久化,程序中断后可以从某个快照恢复,也可以基于历史状态实现多轮记忆。第三是条件分支:让“模型自己决定调用哪个工具、是否需要结束”成为可控的图逻辑,而不是写死的 if else。这些能力正是多智能体协作需要的底座。
1.3 两者的边界与 1.x 版本后的变化
从 LangChain 1.x 开始,生态里的分工越来越明确:LangChain 负责组件和集成,LangGraph 负责 Agent 的执行控制。过去 LangChain 里承载 Agent 逻辑的 AgentExecutor 类正在被 LangGraph 的图执行方式取代。新项目如果直接使用 LangGraph 的 StateGraph、ToolNode 和条件边来编写 Agent 流程,后面维护状态、加记忆、做人工确认都会更顺。
| 维度 | LangChain | LangGraph |
|---|---|---|
| 定位 | 组件库与编排工具 | 有状态、可控制的 Agent 执行框架 |
| 核心抽象 | ChatModel、Prompt、OutputParser、Retriever、Tool | State、Node、Edge、Conditional Edge、Checkpointer |
| 适合场景 | 固定流程、RAG、一次性生成 | 循环、分支、多步、多智能体、人机协作 |
| 状态管理 | 弱,主要靠外部传参 | 强,State 加 reducer 统一管理 |
| 持久化 | 需要自己实现 | Checkpointer 提供快照与恢复 |
| 作为多智能体底座 | 可以辅助 | 推荐 |
注意:标题里的 LangChain V1.3 属于目标版本号。实际开发前先运行
pip show查看安装版本,接口如果与示例不一致,优先看目标版本的官方迁移文档,不要照抄旧教程。
2. 环境准备与最小项目骨架
2.1 Python 虚拟环境与依赖安装
学习阶段建议使用 Python 3.10 或 3.11 的独立虚拟环境,避免和系统 Python 或其它项目互相影响。
python -m venv .venv source .venv/bin/activate python -m pip install -U pip然后安装核心依赖:
pip install -U langchain langchain-openai langgraph python-dotenv其中:
langchain:核心组件库,提供消息、工具、提示词和输出解析等抽象。langchain-openai:OpenAI 模型适配包,负责把 LangChain 的消息对象转换成模型接口请求。langgraph:图执行框架,负责状态、节点、条件和持久化。python-dotenv:本地加载.env文件,方便管理密钥。
安装完成后,用下面命令确认版本。生产项目一定要锁定版本号,不能每次安装都更新到最新。
pip show langchain langgraph langchain-openai2.2 模型密钥与本地配置
以 OpenAI 兼容接口为例,先在命令行导出密钥:
export OPENAI_API_KEY="your-api-key"本地开发更推荐使用.env文件,并在入口处调用load_dotenv():
from dotenv import load_dotenv load_dotenv()如果企业自建了兼容接口网关,可以在创建模型时传入base_url:
from langchain_openai import ChatOpenAI model = ChatOpenAI( model="gpt-4o-mini", temperature=0, base_url="https://your-endpoint.example.com/v1", api_key="your-api-key", )两个注意点:第一,.env和密钥文件绝对不能提交到代码仓库;第二,base_url指向的是企业自己的模型网关或云厂商兼容端点,实际地址以公司内部平台为准。
2.3 项目目录设计
学习环境可以先用单个文件跑通,生产项目建议按模块拆分。
agent_project/ ├── .env ├── requirements.txt ├── app/ │ ├── __init__.py │ ├── main.py │ ├── state.py │ ├── agents/ │ │ ├── supervisor.py │ │ ├── researcher.py │ │ └── writer.py │ └── tools/ │ └── device_tools.py └── tests/ └── test_agent.py这样拆的好处是:Agent 只负责流程和提示词,工具层单独维护,State 独立定义,后续加监控、加测试、换模型都不会牵一发动全身。
2.4 最小模型调用验证
环境配置完成后,先做一个最小编译验证:
from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage model = ChatOpenAI(model="gpt-4o-mini", temperature=0) resp = model.invoke([ SystemMessage(content="你是设备运维助手。"), HumanMessage(content="设备 M-001 现在是什么状态?"), ]) print(resp.content)能输出一句合理的自然语言回复,说明模型链路、密钥和依赖都通了。这一步失败时不要急着往下写,先确认网络、密钥、模型名和依赖版本。
3. 核心组件拆解:消息、工具、结构与状态
3.1 ChatModel 与消息对象
LangChain 与模型交互时统一使用消息对象,而不是直接传字符串。最常用的三类消息:
SystemMessage:系统设定,定义助手身份和行为边界。HumanMessage:用户输入。AIMessage:模型输出;当模型决定调用工具时,它还会携带tool_calls字段。
这个设计的意义在于:多轮对话必须区分“谁说的”,工具调用必须记录“模型想调哪个工具、参数是什么”。把这些信息统一成消息列表,LangGraph 才能用同一个messages字段管理全部上下文。
3.2 bind_tools 与工具定义
让模型具备调用工具的能力,核心是bind_tools:
from langchain_core.tools import tool @tool def query_device_status(device_id: str) -> str: """查询指定设备的最新运行状态。 Args: device_id: 设备编号,例如 M-001。 """ status_map = {"M-001": "运行中", "M-002": "待机", "M-003": "故障"} return status_map.get(device_id, "未找到该设备") tools = [query_device_status] model_with_tools = model.bind_tools(tools)工具定义的三个关键点:
| 要点 | 说明 | 错误示范 |
|---|---|---|
| docstring | 描述工具功能和适用场景,模型据此判断何时调用 | 不写 docstring |
| 参数类型标注 | 生成 JSON Schema 时依赖类型信息 | 不写类型或者用*args |
| 返回值 | 尽量返回结构化文本,方便下游解析和排查 | 返回None或只打日志 |
工具定义完成后,可以把生成的 schema 打印出来检查:
print(model_with_tools.bind_tools(tools).model_dump())3.3 结构化输出
很多下游系统需要 JSON,而不是一段自由文本。可以用with_structured_output把输出约束成 Pydantic 模型:
from pydantic import BaseModel, Field class DeviceQueryResult(BaseModel): device_id: str = Field(description="设备编号,例如 M-001") status: str = Field(description="设备最新运行状态") suggestion: str = Field(description="针对该状态的处理建议") structured_model = model.with_structured_output(DeviceQueryResult) result = structured_model.invoke("请查询设备 M-003 的状态并给出建议") print(result.device_id, result.status, result.suggestion)注意,不同 LangChain 版本对 Pydantic 版本的兼容有差异。落地前先确认当前环境支持的模型基类,不要假设所有版本的导入路径都一致。
3.4 State 与 reducer
LangGraph 中的 State 是节点之间传递数据的唯一通道。最常见的定义方式:
from typing import Annotated, TypedDict from langgraph.graph.message import add_messages class AgentState(TypedDict): messages: Annotated[list, add_messages]Annotated[list, add_messages]表示:当多个节点都向messages写数据时,用add_messages这个 reducer 决定如何合并。add_messages会把新消息追加到列表尾部,而不是直接覆盖。这个细节是让多轮对话和多次工具调用能够累积上下文的关键。
4. 用 LangGraph 实现一个会调用工具的单 Agent
4.1 目标与运行流程
现在实现一个最小但完整的单 Agent:用户提出设备状态查询,Agent 调用工具查询,把结果告诉用户。图中只有两个节点:
agent:调用绑定了工具的模型,模型可能返回普通回复,也可能返回tool_calls。tools:执行模型指定的工具,把结果写回 messages。
条件边判断:最后一条消息有tool_calls就进入tools,没有就直接结束。
from typing import Annotated, TypedDict from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage from langchain_core.tools import tool from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode, tools_condition class AgentState(TypedDict): messages: Annotated[list, add_messages] @tool def query_device_status(device_id: str) -> str: """查询指定设备的最新运行状态。""" status_map = {"M-001": "运行中", "M-002": "待机", "M-003": "故障"} return status_map.get(device_id, "未找到该设备") tools = [query_device_status] model = ChatOpenAI(model="gpt-4o-mini", temperature=0).bind_tools(tools) def agent_node(state: AgentState): messages = [SystemMessage(content="你是设备运维助手,可以调用工具查询设备状态。")] + state["messages"] return {"messages": [model.invoke(messages)]} tool_node = ToolNode(tools) graph = StateGraph(AgentState) graph.add_node("agent", agent_node) graph.add_node("tools", tool_node) graph.add_edge(START, "agent") graph.add_conditional_edges( "agent", tools_condition, { "tools": "tools", END: END, }, ) graph.add_edge("tools", "agent") app = graph.compile() result = app.invoke({"messages": [HumanMessage(content="请查询设备 M-001 的状态")]}) print(result["messages"][-1].content)4.2 这段代码的四个关键点
第一,agent_node返回的是{"messages": [...]},LangGraph 会根据add_messages自动把新消息追加到状态里,不需要手动维护列表。第二,ToolNode(tools)会执行tools中所有工具,并把执行结果包装成ToolMessage写回状态。第三,tools_condition会检查最后一条消息是否包含tool_calls,有则返回"tools",没有则返回END。第四,graph.add_edge("tools", "agent")让工具执行完再回到模型节点,形成“思考-调用-再看结果-再思考”的循环。
4.3 运行结果与预期输出
正常运行时会看到类似输出:“设备 M-001 当前状态为:运行中。” 由于模型生成内容有随机性,措辞可能不同,但核心信息应包含设备编号和状态。如果只输出了“抱歉,我无法查询”之类的回答,而日志里没有任何工具调用,说明模型没有正确触发工具,优先检查bind_tools是否生效,以及模型本身是否支持 function calling。
4.4 用 Checkpointer 实现多轮记忆
单 Agent 默认是无状态的。要让多轮对话记住上下文,需要给图编译时挂上 Checkpointer,并在调用时传入固定的thread_id:
from langgraph.checkpoint.memory import InMemorySaver app = graph.compile(checkpointer=InMemorySaver()) session_id = "thread-001" app.invoke( {"messages": [HumanMessage(content="帮我记录一下,设备 M-002 需要加润滑油。")]}, config={"configurable": {"thread_id": session_id}}, ) result = app.invoke( {"messages": [HumanMessage(content="我之前提醒过哪台设备需要维护?")]}, config={"configurable": {"thread_id": session_id}}, ) print(result["messages"][-1].content)InMemorySaver只适合本地演示和测试,进程重启后数据就没了。生产环境要使用支持持久化的检查点实现,例如 PostgreSQL 或 Redis 版本,具体接入方式以实际环境为准。
5. 多智能体架构:先用 Supervisor 模式跑通协作
5.1 为什么需要多智能体
单 Agent 在场景变大后会遇到三个问题。第一,提示词越来越长:既要会检索,又要会写作,还要会审核,System Prompt 膨胀后模型更容易忽略关键指令。第二,工具集冲突:检索工具和写数据库工具放在同一个 Agent 里,模型可能因为上下位语义混淆而选错工具。第三,职责无法审计:所有业务逻辑混在一个节点里,出了问题很难定位是哪一步答错。
多智能体的思路是拆分:每个 Agent 只负责一个专业领域,由总控 Agent 负责调度。这样每个 Prompt 更短、工具集更小、可解释性更强。
5.2 常见多智能体协作模式
| 模式 | 特点 | 适用场景 |
|---|---|---|
| Supervisor 模式 | 一个总控 Agent 决定下一步交给谁 | 任务边界清晰、专家 Agent 工具集差异大 |
| Handoff 模式 | 当前 Agent 直接把会话移交给另一个 Agent | 客服场景,需要按用户意图转接 |
| 分层模式 | 上级拆解任务,下级执行并汇总 | 复杂企业流程,有明确组织层级 |
这篇文章重点实现 Supervisor 模式,因为它最容易理解,也是大多数企业项目的第一步。
5.3 实现一个最小 Supervisor 多智能体
下面示例包含三个节点:supervisor负责路由,researcher负责检索,writer负责写作。worker 执行完必须回到 supervisor,由 supervisor 判断是继续还是结束。
from typing import Annotated, TypedDict from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage, AIMessage from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.checkpoint.memory import InMemorySaver class MultiAgentState(TypedDict): messages: Annotated[list, add_messages] next: str llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) SUPERVISOR_PROMPT = ( "你是多 Agent 协作的总控。可选专家:\n" "researcher:负责检索资料、查证事实。\n" "writer:负责整理内容并输出最终文本。\n" "请根据用户的请求选择下一个专家,只输出一个词:researcher、writer 或 FINISH。" ) def supervisor_node(state: MultiAgentState): messages = [SystemMessage(content=SUPERVISOR_PROMPT)] + state["messages"] resp = llm.invoke(messages) text = resp.content.strip().upper() if "WRITER" in text: next_agent = "writer" elif "FINISH" in text: next_agent = "FINISH" else: next_agent = "researcher" return {"next": next_agent} def researcher_node(state: MultiAgentState): # 真实项目在这里调用检索工具、RAG 服务或知识库 API return {"messages": [AIMessage(content="研究员已完成资料检索,收集到 3 条关键信息。")]} def writer_node(state: MultiAgentState): # 真实项目在这里读取上下文,重新整理成最终交付文本 return {"messages": [AIMessage(content="写作专家已完成最终内容输出。")]} def route_after_supervisor(state: MultiAgentState): return state.get("next", "researcher") graph = StateGraph(MultiAgentState) graph.add_node("supervisor", supervisor_node) graph.add_node("researcher", researcher_node) graph.add_node("writer", writer_node) graph.add_edge(START, "supervisor") graph.add_conditional_edges( "supervisor", route_after_supervisor, { "researcher": "researcher", "writer": "writer", "FINISH": END, }, ) graph.add_edge("researcher", "supervisor") graph.add_edge("writer", "supervisor") app = graph.compile(checkpointer=InMemorySaver()) result = app.invoke( { "messages": [HumanMessage(content="请先查一下 M-001 最近一周的告警,再帮我写一段运维周报。")], "next": "researcher", }, config={"configurable": {"thread_id": "multi-agent-demo"}}, ) print(result["messages"][-1].content)5.4 消息流转顺序
一次典型执行顺序如下:
HumanMessage -> supervisor -> researcher -> supervisor -> writer -> supervisor -> FINISH每轮 worker 执行完都把结果追加到messages,supervisor 下一次决策时能读到全部上下文。需要注意的是,next字段没有 reducer 时默认是覆盖更新,所以初始状态里必须显式传入"next": "researcher",否则路由函数会在第一次读取时拿不到字段。这是多智能体代码最常见的初始化错误。
5.5 防止多智能体死循环
supervisor 如果反复把任务分给同一个 worker,图会一直跑下去。LangGraph 提供了递归上限:
result = app.invoke( {...}, config={ "configurable": {"thread_id": "multi-agent-demo"}, "recursion_limit": 25, }, )当执行步数超过recursion_limit时,LangGraph 会抛出异常。生产环境除了提高上限,还要在 supervisor 提示词里明确“任务完成就输出 FINISH”,并在代码层加入最大轮次判断,双保险防止失控。
6. 运行验证、调试与链路追踪
6.1 查看图结构
写完图之后,可以用命令直接查看图的拓扑结构,确认边和节点关系是否符合预期:
app.get_graph().print_ascii()输出里会显示节点连接顺序。如果发现某个节点没有按预期连接到 supervisor,或者条件边缺失,返回结果会与设计不符。建议在编写复杂多智能体时先打印图结构,再写业务逻辑。
6.2 节点内部日志
在节点函数里打印关键信息是最直接的调试方式:
def supervisor_node(state: MultiAgentState): resp = llm.invoke(messages) print(f"[supervisor] route text: {resp.content}") return {"next": next_agent}生产环境不要用print,应改为结构化日志,带上thread_id和节点名。但本地调试阶段,print比任何工具都直观。
6.3 用 LangSmith 做链路追踪
如果项目开启了 LangSmith,可以在环境变量里配置:
export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_API_KEY="lsv2-..." export LANGCHAIN_PROJECT="agent-demo"之后每次调用都会生成一条 trace,可以看到模型输入输出、工具调用参数、各节点耗时和 token 消耗。排查“模型为什么没有调用工具”“某个节点为什么超时”“工具返回值是什么”这类问题,直接看 trace 比翻代码快得多。企业没有使用 LangSmith 时,也可以用 OpenTelemetry 或公司自建的链路追踪体系,核心思想相同:把每次 Agent 执行的完整路径记录下来。
6.4 检查点状态查看
使用 Checkpointer 后,可以在运行被中断后查看当前状态快照:
state = app.get_state(config) print(state.values)通过app.get_state能确认消息累积到了哪一步、next字段当前是什么值。这是排查多智能体路由问题的重要工具。
7. 企业级落地:生产环境要补的六件事
7.1 配置外置与密钥管理
学习环境可以把密钥写在.env里,生产环境必须做到配置外置:环境变量、配置中心或密钥管理服务统一管理。模型名称、温度、超时时间、API 地址都不要硬编码在代码里。发布时通过 CI/CD 注入,而不是由开发手工拷贝服务器配置文件。
7.2 可观测性
生产 Agent 至少要记录三类数据:
- 日志:每次用户请求的入参、模型选择、工具调用、错误信息和耗时。
- 指标:请求量、成功率、平均延迟、token 消耗、工具调用失败率。
- 链路:一次 Agent 执行经过了哪些节点,每一步输入输出是什么。
缺少链路追踪时,多智能体一旦路由错误,排查成本是指数级上升的。
7.3 安全与权限
Agent 会调用真实系统时,需要提前建立工具白名单:模型只能调用事先登记的工具,不能动态执行任意代码。工具参数要做类型和取值范围校验,防止模型生成异常参数打坏下游系统。涉及用户敏感数据时,输入和输出都要做脱敏处理,并记录完整审计日志。工具层的权限应遵循最小化原则:Agent 只拿到完成任务所需的最小权限。
7.4 工具层稳定性
工具是 Agent 最容易出错的环节。生产环境里,每个工具应该具备超时控制、失败重试和限流能力。工具内部要捕获异常,把错误转换成结构化的错误消息返回给模型,而不是让异常直接抛出导致整个图中断。比如查询接口超时,工具应该返回{"error": "timeout", "message": "设备服务不可用"},让模型基于这个信息决定下一步。
7.5 成本与模型治理
不要所有任务都使用同一个大模型。路由、摘要、工具调用选择不同规格的模型,可以显著降低成本。同时为每个请求设置 token 上限,对高频问题使用缓存,把相似请求在模型调用前直接命中。模型名称和参数变更要走版本管理,每次升级后都要用评测样本回归验证。
7.6 发布、灰度与回滚
Agent 项目发布前需要做三件事:锁定依赖版本、准备一套评测集、明确回滚方案。评测集至少覆盖正常问题、边界问题和必须拒绝的问题。发布采用灰度策略:先让少量真实流量进入新版本,对比成功率、延迟和用户反馈后再全量。一旦发现问题,能够快速切换回上一个模型版本或上一版图配置。
7.7 发布前检查清单
- [ ] 密钥和配置是否已外置,仓库里是否有硬编码。
- [ ] 依赖版本是否在 requirements.txt 或 lock 文件中固定。
- [ ] 每个工具是否都有超时、重试和异常处理。
- [ ] 工具白名单是否确认,模型可调用的工具是否是最小集合。
- [ ] 是否接入链路追踪,能否查看单次执行的完整节点路径。
- [ ] 是否设置
recursion_limit或轮次上限,防止死循环。 - [ ] 是否有多轮对话的
thread_id会话管理设计。 - [ ] 是否准备评测集,并在发布前完成回归。
- [ ] 是否有回滚方案,包括模型版本和代码版本。
8. 常见问题与排查路径
8.1 模型没有调用工具
现象:用户问题明显需要查询设备,但模型直接给出“无法查询”的回答。原因可能是模型不支持 function calling、工具没有正确 bind、或者提示词没有引导模型使用工具。检查顺序:
- 确认
model.bind_tools(tools)是否生效,打印 schema。 - 确认模型型号是否支持工具调用能力。
- 确认 System Prompt 是否明确说明“可以调用工具,不要凭空编造”。
8.2 条件边返回的 key 与节点名不一致
现象:运行时报错,提示路由映射里找不到某个 key,或图执行走向完全不对。原因通常是自定义路由函数返回了映射表中不存在的值。检查方式:在路由函数里打印返回值,并逐一核对add_conditional_edges的映射表。不要让路由函数返回None,要给状态字段设置默认值或使用state.get("next", "默认节点")。
8.3 多轮对话没有记忆
现象:第二次提问时,模型完全不记得第一次对话内容。原因是没有配置 Checkpointer,或者调用了不同的thread_id。检查方式:确认graph.compile(checkpointer=...)是否传参,确认每次invoke的 config 里thread_id是否固定。InMemorySaver重启后数据会丢失,这在生产环境不是 bug,而是架构限制。
8.4 工具报错导致整个流程失败
现象:一个下游接口超时,整个 Agent 请求失败。原因是工具函数内部没有捕获异常,异常直接抛出 Graph。推荐做法是在工具函数内部捕获异常并返回结构化错误信息:
from langchain_core.tools import tool @tool def call_device_api(device_id: str) -> str: """调用设备接口查询状态。""" try: raw = request_device_status(device_id) return raw except TimeoutError: return "{\"error\": \"timeout\", \"message\": \"设备服务响应超时\"}" except Exception as exc: return f"{{\"error\": \"unknown\", \"message\": \"{exc}\"}}"这样模型可以在下一轮根据错误信息决定重试策略,而不是让整个链路直接失败。
8.5 常见问题速查表
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 模型没有调用工具 | 工具未 bind、模型不支持 tool calling | 打印 bind_tools 结果 | 换支持 function calling 的模型并确认绑定 |
| 工具参数解析失败 | 工具缺少类型标注或 docstring | 查看工具生成的 schema | 补全函数签名和描述 |
| 条件边找不到节点 | 路由 key 与映射表不一致 | 打印路由返回值 | 给 next 设置默认值,核对映射表 |
| 多轮对话无记忆 | 未配置 checkpointer 或 thread_id 变化 | 检查 compile 和 config | 使用持久化 Checkpointer 并固定会话 ID |
| 工具异常导致全链路失败 | 工具内未捕获异常 | 查看 trace 和日志 | 工具内 try/except 返回结构化错误 |
| 多智能体死循环 | supervisor 一直分发给同一 worker | 查看 trace 节点数 | 设置 recursion_limit 并在提示词中要求 FINISH |
| 成本过高 | 全部任务使用大模型 | 统计 token 消耗 | 分层模型、设置预算、加缓存 |
9. 最佳实践与后续学习路径
9.1 写 Agent 代码时坚持的几条原则
第一,节点函数保持简单和可重放。每个节点只做一件事:调用模型、调用工具、或做路由判断。第二,工具永远返回结构化数据,不要返回“成功”这样没有信息量的文本。第三,自定义状态字段都要考虑默认值,避免路由函数在最开始就报错。第四,提示词与代码分离,System Prompt 不要散落在业务代码深处。第五,不要让单个 Agent 承担所有职责,先把职责边界划清楚,再决定是否需要多智能体。
9.2 学习顺序建议
如果从零开始,可以按照下面顺序推进:
- 掌握 LangChain 的核心组件:ChatModel、消息对象、工具定义、结构化输出。
- 跟着 LangGraph 官方教程跑通 StateGraph、条件边、Checkpointer 三个基础概念。
- 做一个 RAG Agent:加载文档、切分、向量化、检索、生成,把组件串成完整流程。
- 做一个会调用外部工具的单 Agent,把工具调用循环跑顺。
- 用 Supervisor 模式改造项目,从单 Agent 演进到多智能体。
- 最后补生产能力:评测集、链路追踪、安全白名单、成本控制。
学习资源方面,LangGraph 官方文档是最值得优先阅读的,因为它的 API 更新较快,第三方教程很可能滞后。遇到接口不一致时,以官方文档和当前安装版本的源码为准。
9.3 一个更务实的起点
很多人一开始就想做完整的企业级多智能体平台,这是弯路。更务实的起点是:用一两天时间把这个最小例子跑通,然后替换成自己领域的问题和工具。把“状态如何累积,条件边如何路由,Checkpointer 如何保存会话”这三个问题真正理解透,后面加多少 Agent 都只是重复同样一套模式。所谓少走弯路,核心不是囤积教程,而是把状态、工具、控制流三件事一次想清楚,再用代码验证一次。