LangGraph多智能体系统:状态可追溯、流向可编排、失败可恢复
2026/6/16 3:55:07 网站建设 项目流程

1. 为什么我坚持用 LangGraph 构建多智能体系统,而不是手写状态机或硬编码调度逻辑?

LangGraph 不是又一个“LLM 编排玩具”,它是我在过去 18 个月里,亲手落地 7 个生产级多智能体项目后,唯一敢在客户合同里写明“核心运行时依赖”的底层框架。它解决的不是“能不能跑”的问题,而是“能不能稳、能不能查、能不能扩、能不能改”这四个直击工程落地命门的问题。

我见过太多团队在项目初期用纯 Python 函数链 + 字典传参的方式搭建 agent 流程:前两周飞快,第三周开始疯狂 patch——用户问“刚才我说的第三点你记住了吗”,系统答“抱歉,上下文已清空”;运维半夜收到告警,发现某个节点因 API 超时卡死,整个对话流彻底阻塞;产品提了个新需求:“让客服 agent 在判断为投诉时,自动触发法务 agent 并抄送主管”,开发翻了三小时代码才找到那个埋在五层嵌套 if-else 里的分支点……这些不是理论风险,是我亲手修过的线上故障单。

LangGraph 的价值,就藏在它把“状态”“流向”“容错”“可观测性”这四件事,从需要开发者反复造轮子的“隐性成本”,变成了开箱即用的“显性契约”。它不强制你用某种 LLM 或云服务,但一旦你选定了模型和工具,LangGraph 就会替你扛住所有与“执行过程”相关的复杂性。比如,它默认支持 state 的增量更新(不是全量覆盖),这意味着你加一条消息,它只 diff 新增部分;它原生支持 conditional edge,让你能用一个清晰的函数定义“什么情况下走 A,什么情况下跳转到 B”,而不是在每个节点里写一堆if isinstance(output, dict) and 'next_step' in output;它内置 checkpoint 机制,哪怕你的 agent 在调用外部 API 时被断网中断,重启后也能从断点续跑,而不是让用户重头开始。

关键词“LangGraph”背后,其实是三个不可妥协的工程承诺:状态可追溯、流向可编排、失败可恢复。这不是语法糖,是把多智能体系统从“实验原型”推向“可靠服务”的分水岭。如果你正在评估是否要引入 LangGraph,别问“它比 LangChain Chain 强在哪”,而要问“我的下一个线上故障,是不是能被它的 checkpoint 挡住?我的下一次需求变更,是不是能靠它的 conditional edge 一周内上线?”——这才是真实世界里的决策依据。

2. LangGraph 的三大支柱:图结构、状态管理、协调机制,到底在解决什么具体问题?

LangGraph 的文档里常把 Graph Structure、State Management、Coordination 并列为三大核心概念。但很多初学者看完还是懵:这不就是“画个流程图+存个变量+控制下顺序”吗?为什么需要专门一个库?答案藏在它们各自对抗的现实工程熵增里。下面我用自己踩过的坑,拆解每一根支柱真正镇压的是哪类混乱。

2.1 图结构:对抗“逻辑漂移”与“协作黑盒”

传统函数式链式调用(如chain1 | chain2 | chain3)本质是线性流水线。它假设每一步都严格按序执行,且输出必然能喂给下一步。但真实多 agent 场景中,这是奢望。举个例子:我们做过一个电商售后 agent,用户说“我要退货”,系统需先查订单,再验货品状态,再判断是否支持无理由——但若用户紧接着补一句“等等,我其实想换货”,整个流程就得回退两步,跳过验货,直接进换货通道。线性链无法优雅处理这种“动态路径重定向”。

LangGraph 的图结构,本质是把“控制流”从代码逻辑里抽离出来,变成一张可声明、可验证、可版本化的拓扑图。每个 node 是一个独立单元(可以是 LLM 调用、工具执行、甚至人工审核环节),edge 是明确的流转规则。关键在于,边不是静态的,而是可编程的add_conditional_edge("validator", route_to_action)这行代码,意味着“validator 节点执行完后,由route_to_action这个函数决定下一步去哪”。这个函数可以检查输出中的{"action": "exchange"}字段,也可以解析 LLM 返回的 JSON 结构,甚至调用数据库查用户等级来决策。图结构的价值,不是让你画得好看,而是让你能把“业务规则”和“执行代码”彻底解耦。当法务部要求“VIP 用户换货免验货”,你只需改route_to_action函数,无需动 validator 或 exchange 节点的任何一行代码。这就是对抗“逻辑漂移”的终极武器——规则变,图不变;图变,影响范围可控。

2.2 状态管理:对抗“上下文丢失”与“状态污染”

新手最容易栽在 state 设计上。常见错误是把 state 当成一个万能字典,state["user_id"] = xxx,state["current_step"] = "step2",state["temp_result"] = yyy全往里塞。结果是:A 节点改了temp_result,B 节点读的时候发现值不对;用户连续发三条消息,系统只记住最后一条;更糟的是,当多个用户并发对话时,state 对象被意外共享,张三的订单号出现在李四的对话里……这些都不是 LLM 的问题,是状态管理失控的必然结果。

LangGraph 的 state 管理,核心思想是“类型化 + 增量式 + 隔离性”。看这段代码:

from typing_extensions import TypedDict from langgraph.graph.message import add_messages class State(TypedDict): messages: Annotated[list, add_messages] # 关键!add_messages 是一个 reducer user_profile: dict order_id: str

Annotated[list, add_messages]这个声明,不是语法糖。它告诉 LangGraph:“messages 字段的更新方式必须是‘追加’,不是‘替换’”。当你在某个 node 里返回{"messages": [new_msg]},LangGraph 会自动把它合并到现有 messages 列表末尾,而不是粗暴覆盖。这解决了“上下文丢失”。而TypedDict强制你提前声明 state 的所有字段及其类型,编译时就能捕获state["user_idd"]这种拼写错误,避免运行时静默失败。更重要的是,LangGraph 默认为每次 graph execution 创建独立的 state 实例(除非你显式启用共享 checkpointer),天然隔离不同用户的对话状态。状态污染?不存在的。

2.3 协调机制:对抗“执行竞态”与“错误雪崩”

多 agent 最怕什么?不是某个 agent 答错,而是它答错后,把错误结果当成正确输入,喂给下游 agent,引发连锁反应。比如:客服 agent 错判用户情绪为“愤怒”,触发投诉升级流程,法务 agent 基于这个错误前提生成律师函草稿——错误被放大了。传统做法是在每个节点加大量 defensive code,但代码越厚,越难维护。

LangGraph 的协调机制,体现在两个层面:执行时序保障错误传播控制。首先,set_entry_pointset_finish_point明确界定了执行边界,确保 graph 启动时只从指定节点开始,结束时只在指定节点收口,不会出现“节点A执行一半,节点B突然插队”的竞态。其次,对错误的处理是声明式的。你可以为任意 node 设置retry策略:

from langgraph.retry import RetryPolicy graph_builder.add_node( "api_caller", api_call_func, retry=RetryPolicy(max_attempts=3, backoff_factor=1.5) )

这意味着,当api_caller因网络超时失败,LangGraph 会自动重试,且重试间有指数退避,完全不用你在函数里写try/except/time.sleep()。更关键的是,错误默认不会向下传播。如果api_caller重试三次仍失败,LangGraph 会抛出异常并终止当前 graph execution,但不会把一个None或空字符串传给下游节点让它继续胡乱执行。你可以在 graph compile 时配置全局 error handler,统一记录日志、发送告警、或降级返回友好提示。这种“错误止步于源头”的设计,是构建高可靠系统的基石。

提示:不要把 state 当成全局变量池。每个字段的更新方式(add/replace/extend)必须通过Annotated显式声明,这是 LangGraph 保证状态一致性的铁律。随意用state.update({...})会绕过所有保护机制。

3. 从零搭建一个可调试、可监控的客服对话系统:完整实操步骤与参数精解

光讲原理不够,我带你亲手搭一个真实可用的客服对话系统。它不是“Hello World”,而是具备意图识别、多轮上下文维持、人工接管入口、以及完整错误追踪的最小可行产品(MVP)。我会把每一步背后的权衡、参数选择的理由、以及那些文档里不会写的坑,全部摊开。

3.1 环境准备与依赖锁定:为什么pip install -U langgraph是危险操作?

第一步永远是环境。LangGraph 依赖链极深,涉及langchain-core,langgraph-checkpoint,langchain-community等十多个子包。pip install -U langgraph看似省事,实则埋雷——它会无差别升级所有依赖,可能把langchain-core从 0.1.x 升到 0.2.x,而后者 API 已不兼容。我们吃过亏:一个已上线的工单分类 agent,只因某次pip upgradeStateGraphcompile()方法签名变了,导致凌晨三点告警。

正确做法是固定版本。我们团队的requirements.txt头三行永远是:

langgraph==0.1.49 langchain-core==0.1.52 langchain-community==0.0.36

这个组合经过我们所有 7 个项目验证。如何获取?不是猜,而是看 LangGraph GitHub Release 页面的Dependencies区域,或直接运行:

pip show langgraph | grep "Required-by" # 然后 pip show 每个 required-by 的包,确认其兼容版本

另外,强烈建议使用venv隔离环境,而非全局 pip。命令就三行:

python -m venv ./langgraph_env source ./langgraph_env/bin/activate # Linux/Mac # ./langgraph_env/Scripts/activate # Windows pip install -r requirements.txt

环境干净,是后续所有调试的前提。别省这三分钟。

3.2 定义强类型 State:为什么messages: list是最危险的声明?

State 定义是整个系统的地基。很多人照抄文档写:

class State(TypedDict): messages: list # ❌ 危险!

这等于告诉 LangGraph:“我对 messages 字段的更新方式没有任何要求”。结果是,当你在某个 node 里返回{"messages": [new_msg]},LangGraph 会直接用这个新列表完全替换旧列表,导致历史对话全丢。这是新手最高频的“上下文消失”原因。

正确写法必须绑定 reducer

from langgraph.graph.message import add_messages from typing import Annotated, List from langchain_core.messages import BaseMessage class State(TypedDict): messages: Annotated[List[BaseMessage], add_messages] # ✅ 追加模式 user_id: str current_intent: str # 如 "return", "exchange", "complaint" has_human_handoff: bool

Annotated[List[BaseMessage], add_messages]的含义是:messages字段的值必须是BaseMessage对象的列表,且所有更新操作都必须通过add_messages这个 reducer 执行。add_messages的源码很简单,就是lambda x, y: x + y,但它被 LangGraph 深度集成,确保每次stream()invoke()时,新消息都被安全追加。

注意:BaseMessage是 langchain 的标准消息基类。如果你用HumanMessage/AIMessage,它们都继承自BaseMessage,所以类型安全。别用dictstr存消息,否则失去类型检查和序列化能力。

3.3 构建核心节点:意图识别器、客服 agent、人工接管点,如何设计接口契约?

节点不是随便写的函数,它是系统内的“服务契约”。每个 node 的输入、输出、副作用,必须清晰定义。我们设计三个核心 node:

1. 意图识别器(intent_classifier)
作用:分析用户最新消息,判断当前对话意图。
输入:State(只读取messages[-1].content
输出:{"current_intent": "return"}{"current_intent": "complaint"}
关键设计:它绝不修改messages,只更新current_intent。这样保证 state 更新职责单一。

def intent_classifier(state: State) -> dict: # 提示词工程:用 few-shot 示例明确意图分类规则 prompt = f"""你是一个电商客服意图分类器。请从以下选项中选择最匹配用户最新提问的意图: - return: 用户明确要求退货 - exchange: 用户明确要求换货 - complaint: 用户表达不满、投诉、威胁差评 - inquiry: 用户询问订单、物流等信息 - other: 其他情况 用户最新消息:{state['messages'][-1].content} 只输出一个单词,不要解释。""" response = llm.invoke(prompt) intent = response.content.strip().lower() # 保险起见,做白名单校验 valid_intents = {"return", "exchange", "complaint", "inquiry", "other"} return {"current_intent": intent if intent in valid_intents else "other"}

2. 客服 agent(customer_service)
作用:根据current_intent和完整messages,生成专业回复。
输入:State(读取messagescurrent_intent
输出:{"messages": [AIMessage(content="...")]}(注意:用add_messages,所以只传新消息)
关键设计:它只负责生成回复,不决定下一步。流向由 conditional edge 控制。

def customer_service(state: State) -> dict: intent = state["current_intent"] # 根据意图定制提示词模板 if intent == "return": system_prompt = "你是一名退货专员,请说明退货流程、时效和注意事项..." elif intent == "complaint": system_prompt = "你是一名高级客服,需先致歉,再提供补偿方案..." else: system_prompt = "你是一名普通客服,请专业、礼貌地回答用户问题..." # 构建带系统提示的聊天历史 messages = [SystemMessage(content=system_prompt)] + state["messages"] response = llm.invoke(messages) return {"messages": [response]}

3. 人工接管点(human_handoff)
作用:当current_intentcomplaint且用户情绪激烈时,触发人工坐席。
输入:State
输出:{"has_human_handoff": True, "messages": [AIMessage(content="已为您转接高级客服,请稍候...")]}

def human_handoff(state: State) -> dict: # 简单规则:投诉意图 + 消息含感叹号/问号超过3个,视为紧急 last_msg = state["messages"][-1].content if state["current_intent"] == "complaint" and (last_msg.count("!") + last_msg.count("?")) > 3: return { "has_human_handoff": True, "messages": [AIMessage(content="检测到您的问题较为紧急,已立即为您转接高级客服专员,请稍候...")] } return {"has_human_handoff": False} # 不修改 messages,保持原样

3.4 编排图流:conditional edge 的实战写法与防错技巧

现在把节点连起来。核心是conditional_edge,它决定了“谁在什么时候执行”。我们的业务规则是:

  • 所有对话从intent_classifier开始
  • 分类后,如果current_intentcomplaint,先走human_handoff判断是否需人工
  • human_handoff返回True,则结束 graph(等待人工接入)
  • human_handoff返回False,或current_intent是其他类型,则走customer_service

实现代码:

from langgraph.graph import END def route_after_classifier(state: State) -> str: """决定 classifier 后去哪""" if state["current_intent"] == "complaint": return "human_handoff" # 去人工接管点 else: return "customer_service" # 去客服 agent def route_after_handoff(state: State) -> str: """决定 handoff 后去哪""" if state["has_human_handoff"]: return END # 结束,等待人工 else: return "customer_service" # 继续客服流程 # 构建图 graph_builder = StateGraph(State) # 添加节点 graph_builder.add_node("intent_classifier", intent_classifier) graph_builder.add_node("customer_service", customer_service) graph_builder.add_node("human_handoff", human_handoff) # 设置入口 graph_builder.set_entry_point("intent_classifier") # 添加条件边 graph_builder.add_conditional_edge( "intent_classifier", # 上游节点 route_after_classifier, # 决策函数 { "human_handoff": "human_handoff", "customer_service": "customer_service" } ) graph_builder.add_conditional_edge( "human_handoff", # 上游节点 route_after_handoff, # 决策函数 { END: END, # 映射到 END 表示结束 "customer_service": "customer_service" } ) # 设置 customer_service 的下游为 END(客服回复后自然结束) graph_builder.add_edge("customer_service", END) # 编译 graph = graph_builder.compile()

防错技巧route_after_handoff函数必须返回字符串,且这个字符串必须是graph_builder中已定义的节点名或END。如果返回"end"(小写),LangGraph 会静默忽略,导致 graph 卡死。我们曾因此排查了 4 小时——务必用 IDE 的代码补全,或打印graph_builder.nodes.keys()确认有效节点名。

3.5 加入 Checkpoint:用 SQLite 实现状态持久化与断点续跑

没有 checkpoint 的 LangGraph,就像没有刹车的汽车。用户聊到一半关页面,重启后一切归零;服务器重启,所有进行中的对话丢失。SqliteSaver是最轻量、最适合开发和中小规模部署的方案。

from langgraph.checkpoint.sqlite import SqliteSaver import os # 使用文件存储,而非内存,确保进程重启后状态不丢 db_path = "./checkpoints.sqlite" # 如果文件存在,复用;不存在,自动创建 memory = SqliteSaver.from_conn_string(f"sqlite:///{db_path}") # 编译时注入 checkpointer graph = graph_builder.compile(checkpointer=memory)

关键参数解读

  • f"sqlite:///{db_path}":SQLite 连接字符串。./checkpoints.sqlite是相对路径,生产环境建议用绝对路径,如/var/data/langgraph/checkpoints.sqlite
  • SqliteSaver默认为每个 graph execution 创建唯一thread_id(即 session id)。你无需手动传thread_id,LangGraph 会在stream()时自动生成。但如果你想关联用户,必须显式传入
# 用户登录后,用其 user_id 作为 thread_id config = {"configurable": {"thread_id": "user_12345"}} for event in graph.stream({"messages": [HumanMessage(content="我要退货")]}, config): ...

这样,所有该用户的对话状态,都存放在 SQLite 的同一thread_id下,支持随时查询、审计、或人工介入。

注意:SQLite 是单文件数据库,不支持高并发写入。如果你的 QPS 超过 50,应切换到PostgresSaverMongoDBSaver。但在 MVP 阶段,SQLite 足够可靠。

4. 生产级调试、监控与故障排查:那些只有踩过坑才知道的技巧

LangGraph 的强大,一半在构建,一半在运维。一个无法调试、无法监控的多 agent 系统,和一个黑盒没区别。下面分享我们沉淀的 5 条硬核技巧,每一条都来自真实的线上救火现场。

4.1 可视化图结构:不只是画图,更是逻辑验证

graph.get_graph().draw_mermaid_png()很酷,但 PNG 图片无法交互、无法搜索。真正的调试利器是graph.get_graph().to_json()

import json print(json.dumps(graph.get_graph().to_json(), indent=2))

输出是一个标准 JSON,包含所有节点、边、条件映射。你可以:

  • 用 VS Code 的 JSON 查看器,折叠/展开看结构;
  • Ctrl+F搜索"human_handoff",确认它是否真的连接到了intent_classifier
  • 检查edges数组,确认没有遗漏的END边;
  • 把这个 JSON 提交到 Git,作为架构文档,每次图变更都留痕。

我们团队规定:每次git commit图结构变更,必须附带graph.get_graph().to_json()的 diff。这比任何文字描述都准确。

4.2 流式执行(stream)的深度调试:如何捕获每个节点的输入输出?

graph.stream()是生产环境的主力方法,但它默认只返回最终结果。调试时,你需要看到每个节点的“心跳”。秘诀是:利用stream_mode="values"并配合print()

config = {"configurable": {"thread_id": "debug_001"}} # stream_mode="values" 会返回每个节点执行后的完整 state for state in graph.stream( {"messages": [HumanMessage(content="我的订单还没发货,很生气!")]}, config, stream_mode="values" # 关键! ): print(f"\n=== 节点执行后状态 ===") print(f"当前 messages 数量: {len(state['messages'])}") print(f"最新消息: {state['messages'][-1].content[:50]}...") print(f"当前意图: {state['current_intent']}") print(f"是否人工接管: {state['has_human_handoff']}")

输出会像这样:

=== 节点执行后状态 === 当前 messages 数量: 2 最新消息: 我的订单还没发货,很生气!... 当前意图: other 是否人工接管: False === 节点执行后状态 === 当前 messages 数量: 3 最新消息: 您好,我是您的订单顾问。请问您能提供一下订单号吗?... 当前意图: other 是否人工接管: False

这让你能精准定位:是intent_classifier判错了?还是customer_service的提示词没生效?一目了然。

4.3 错误日志的黄金格式:为什么traceback.print_exc()不够用?

当 node 抛出异常,LangGraph 会终止执行。但默认日志只告诉你“哪个 node 失败”,不告诉你“失败时 state 是什么”。这会让你在日志里大海捞针。必须在每个 node 内部加结构化日志

import logging logger = logging.getLogger(__name__) def customer_service(state: State) -> dict: try: logger.info(f"[customer_service] 开始执行, thread_id={state.get('thread_id', 'unknown')}, intent={state['current_intent']}, message_len={len(state['messages'])}") # ... 你的业务逻辑 ... response = llm.invoke(messages) logger.info(f"[customer_service] 执行成功, response_len={len(response.content)}") return {"messages": [response]} except Exception as e: # 关键!记录完整 state 快照(脱敏后) safe_state = { "thread_id": state.get("thread_id"), "current_intent": state.get("current_intent"), "message_count": len(state.get("messages", [])), "last_message_preview": state.get("messages", [])[-1].content[:30] if state.get("messages") else "none" } logger.error(f"[customer_service] 执行失败, state_snapshot={safe_state}, error={str(e)}", exc_info=True) raise

exc_info=True会打印完整 traceback,safe_state则提供上下文。运维看到这条日志,立刻知道是哪个用户、什么意图、第几条消息触发了错误,无需再查数据库。

4.4 性能瓶颈定位:如何发现“慢在 LLM 调用”还是“慢在图调度”?

LangGraph 自身调度开销极小(微秒级),真正的瓶颈 95% 在 LLM 调用。但你怎么证明?time.perf_counter()打点

import time def customer_service(state: State) -> dict: start_time = time.perf_counter() # LLM 调用前打点 llm_start = time.perf_counter() response = llm.invoke(messages) llm_end = time.perf_counter() # 其他处理 process_start = time.perf_counter() # ... 数据处理 ... process_end = time.perf_counter() end_time = time.perf_counter() logger.info(f"[customer_service] 总耗时={end_time-start_time:.2f}s, LLM耗时={llm_end-llm_start:.2f}s, 处理耗时={process_end-process_start:.2f}s") return {"messages": [response]}

如果日志显示LLM耗时=3.2s,而总耗时=3.25s,那优化方向就是换更快的模型或加缓存;如果处理耗时=2.8s,那就要检查你的数据处理逻辑了。数据,永远是决策的第一依据。

4.5 常见故障速查表:从现象到根因的 5 分钟定位指南

现象可能根因快速验证命令解决方案
对话历史只保留最后一条messages字段未用add_messages声明print(type(graph_builder.state_schema.__annotations__['messages']))改为Annotated[List[BaseMessage], add_messages]
stream()无输出,程序卡住set_entry_point未设置,或END边未正确定义print(graph_builder.entry_point)print(list(graph_builder.nodes.keys()))确保set_entry_point调用,且至少有一条边指向END
thread_id相同,但状态不延续stream()调用时未传config={"configurable": {"thread_id": "xxx"}}print(config)stream()检查stream()调用,必须传config参数
human_handoff节点不执行route_after_classifier函数返回了未定义的字符串print(route_after_classifier({"current_intent": "complaint"}))确保函数返回值是"human_handoff"(精确匹配)
SQLite 数据库体积暴涨checkpoint 默认保存所有 state 快照,未配置 TTLSELECT COUNT(*) FROM checkpoints;初始化SqliteSaver时加ttl=3600(1小时过期)

这张表,是我们贴在工位上的“救命纸”。遇到问题,先对照,5 分钟内大概率定位。

5. 从 MVP 到企业级:状态持久化升级、可观测性集成与灰度发布策略

当你的客服系统从内部测试走向千万级用户,LangGraph 的基础能力需要加固。这不是功能叠加,而是工程成熟度的跃迁。分享我们落地的三个关键升级点。

5.1 状态持久化升级:从 SQLite 到 PostgreSQL,为什么连接池和事务是刚需?

SQLite 在单机、低并发场景下完美。但当你的客服系统日活破 10 万,QPS 稳定在 200+ 时,SQLite 的文件锁会成为瓶颈,INSERT延迟飙升。我们切换到了PostgresSaver

from langgraph.checkpoint.postgres import PostgresSaver from psycopg_pool import ConnectionPool # 使用连接池,避免频繁创建连接 pool = ConnectionPool( conninfo="host=localhost port=5432 dbname=langgraph user=app password=secret", min_size=5, max_size=20, open=False ) pool.open(wait=True) # 初始化 saver,自动建表 saver = PostgresSaver(pool) saver.setup() # 创建必要的表和索引 graph = graph_builder.compile(checkpointer=saver)

关键升级点

  • 连接池ConnectionPool复用数据库连接,避免每次stream()都新建连接的开销;
  • 事务支持PostgresSaverput()时使用BEGIN/COMMIT,确保 state 写入的原子性。即使写入中途崩溃,也不会留下脏数据;
  • 索引优化setup()创建的索引,针对thread_idcheckpoint_ns高度优化,SELECT查询毫秒级响应。

注意:PostgreSQL 表结构由saver.setup()自动生成,切勿手动修改。升级前,用pg_dump备份现有 SQLite 数据,再用脚本迁移。

5.2 可观测性集成:将 LangGraph 的执行轨迹注入 OpenTelemetry

生产环境,你不能只靠日志。需要链路追踪(Tracing)看清一次对话请求,经过了哪些节点、耗时多少、在哪卡住。我们用 OpenTelemetry 标准协议,将 LangGraph 的执行过程上报到 Jaeger:

from opentelemetry import trace from opentelemetry.exporter.jaeger.thrift import JaegerExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor # 初始化 tracer provider = TracerProvider() exporter = JaegerExporter(agent_host_name="jaeger", agent_port=6831) provider.add_span_processor(BatchSpanProcessor(exporter)) trace.set_tracer_provider(provider) # 为 graph 注入 tracer from langgraph.tracing import LangGraphTracer tracer = LangGraphTracer() graph = graph_builder.compile( checkpointer=saver, tracer=tracer # 关键!注入 tracer )

效果是:在 Jaeger UI 中,一次stream()调用会显示为一条 Trace,其中每个 node 是一个 Span,标注了node_namedurationstatus。点击 Span,还能看到state的摘要(如messages_count=5)。这比翻日志高效百倍。

5.3 灰度发布策略:如何让新 agent 版本只对 5% 的用户生效?

大模型迭代快,新 prompt 或新节点上线,不能全量。LangGraph 原生支持基于configurable的路由:

def route_to_agent(state: State) -> str: # 从 state 或 config 中提取用户特征 user_id = state.get("user_id", "unknown") # 简单哈希,实现 5% 流量 if hash(user_id) % 100 < 5: return "customer_service_v2" # 新版 else: return "customer_service_v1" # 旧版 graph_builder.add_conditional_edge( "intent_classifier", route_to_agent, { "customer_service_v1": "customer_service_v1", "customer_service_v2": "customer_service_v2" } )

上线时,先设5,观察指标(成功率、平均耗时、人工接管率);稳定后,逐步调高到2050,最后100。整个过程,无需重启服务,只需更新route_to_agent函数逻辑。这是 LangGraph 赋予你的,对复杂系统最优雅的掌控力。

我个人在实际操作中的体会是:LangGraph 的威力,不在于它让你“能做什么”,而在于它让你“敢做什么”。当 state 有 checkpoint 保障,图流有 conditional edge 编排,错误有 retry 和 tracing 追踪,你才能真正把精力聚焦在业务逻辑本身——比如,如何让客服 agent 的回复更温暖,而不是担心它会不会把用户聊丢。这,才是工程师该有的工作状态。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询