主题
生产化与选型
一句话:Agent 上线后的问题,八成不是“模型不够聪明”,而是循环失控、上下文爆炸、外部依赖不稳定、权限过大和成本不可见。
适用版本:LangChain v1.x、LangGraph 1.x。部署与观测能力迭代很快,具体产品名与配额以官方文档为准。
为什么需要
Demo 和生产之间隔着五件事:
- 可观测:一次请求经过几个节点、调了几次模型、每次多少 token、哪个工具慢。
- 可评测:改 prompt、换模型、加工具之后,效果是变好还是变坏。
- 可恢复:进程重启、模型超时、工具失败之后,任务能继续而不是重来。
- 可控:轮数、并发、成本、权限都有上限。
- 可运维:多副本、灰度、回滚、数据隔离。
LangChain/LangGraph 提供了钩子和运行时,但这些能力要自己接上。
核心机制
追踪:先看见,再优化
bash
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY="..."
export LANGSMITH_PROJECT="prod-agent"一次 trace 至少要能看到每个节点的耗时、每次模型调用的 in/out token 与费用、每次工具调用的结果;没有这层,线上只能看到“慢”和“贵”,定位不到具体节点。不用 LangSmith 也可以接 OpenTelemetry,但必须让模型调用和工具调用成为独立的 span。
评测:固定数据集 + 回归
| 层 | 评什么 | 方法 |
|---|---|---|
| 检索 | 该召回的文档有没有进候选 | Recall@k、MRR、nDCG |
| 单步 | 工具选得对不对、参数对不对 | 标注集 + 规则校验 |
| 轨迹 | 步骤是否合理、有没有绕路 | 与参考轨迹对比,或 LLM 评审 |
| 最终 | 答案是否正确、是否有引用 | 人工标注 + LLM-as-judge |
| 安全 | 是否越权、是否泄露 | 红队用例 |
每次改动只跑一遍全量,成本高;日常用小子集做冒烟,发版前跑全量。
部署形态
| 方式 | 说明 | 适合 |
|---|---|---|
| 自建服务 | FastAPI/Flask + Postgres checkpointer + 自己的队列 | 已有基础设施、需要深度定制 |
| LangSmith Agent Server | 托管运行时,自动处理持久化、伸缩、流式 | 想快速上线、接受平台绑定 |
自建时最容易漏的是:checkpointer 必须外部化。多副本共享同一个 Postgres,才能让任意实例恢复任意 thread。
可靠性模式
- 超时:模型调用、工具调用、整次 run 三层都要有。
- 重试:只重试 429/超时/5xx,指数退避 + 抖动;业务错误不重试。
- 降级与熔断:主模型失败切备用,备用也失败返回可解释错误;某个工具连续失败就暂时摘除,避免拖垮整个 Agent。
- 幂等:写操作带幂等键,恢复重跑不会重复扣款/发消息。
- 死信:多次失败的 run 落库人工处理,不要无限重试。
安全边界
| 风险 | 措施 |
|---|---|
| 提示注入(文档/网页/工具输出) | 工具输出当不可信输入;system prompt 只作约束,不作防线 |
| 工具越权 | 最小权限、按用户身份鉴权、危险操作二次确认 |
| 代码执行 | 放沙箱,限制网络、文件系统、执行时间 |
| 数据泄露 | 多租户 namespace/thread 隔离;日志脱敏 |
| 密钥 | 放密钥管理服务,不放进 state、prompt、trace |
| 成本攻击 | 按用户限流、设 token/轮数上限、异常用量告警 |
一次生产请求的完整链路(框架件怎么各就各位)
一条请求自上而下经过哪些框架件、失败时沿哪条虚线返回,是这张图要回答的。
先认三条纵线:入口层只做鉴权、限流与归属校验,中间是装配链,右侧是观测与死信;四条外部依赖的差别只在故障策略,不在调用方式。虚线是全图唯一的失败回程,任何一环超时都要带终止原因回到入口,不能挂住请求。
实战场景
演练一:上线前把四种故障各打一遍(比任何评测集都早暴露问题)
| 演练 | 操作 | 预期表现 | 典型暴露的缺陷 |
|---|---|---|---|
| 杀进程 | run 进行中 kill -9 副本 | 另一副本按 thread_id 续跑,不重复扣款 | 副作用没幂等;InMemorySaver 混进了预发 |
| 断模型 | 网关对主模型注入 5xx | fallback 接管;备用也挂则返回可解释错误 | 无备用直接 500;重试风暴打垮备用 |
| 断工具 | 让检索工具持续超时 | 熔断摘除,Agent 降级为"资料暂不可用" | 无限重试烧光预算;错误堆栈进 prompt |
| 成本攻击 | 单用户脚本刷长对话 | 按用户限流 + token 上限自动降级 | 只限 QPS 不限 token;无租户归因 |
四项对应开头五件事里的四件(可恢复、可控、可运维、成本可见);演练要定期重跑——每次改 middleware、换模型、动 checkpointer 配置后至少跑前三项。
演练二:单请求成本估算的实际做法
上线评审里"成本可控"要有数,不是形容词。方法:取最近 200 条真实会话,按 每 run 模型调用数 × 平均输入 token × 单价 ×(1+缓存命中折扣) + 工具外呼费 算分布,报 P50 / P95 / 最坏值三个数。经验上 P95 和最坏值的差距来自循环失控(重试叠加、并行扇出聚合),所以最坏值必须用 run_limit 封顶后的分布算,不用自然分布——这也回答了"上限设多少":让封顶后的最坏成本仍是业务可接受值。
场景:什么时候从 LangGraph 迁到 Temporal(选型的边界真的会发生)
信号:业务要"审批三小时无响应自动升级给上级,并给申请人发催办"——框架的挂起没有超时和定时器,业务侧要自己搭调度器。这时两条路:
- LangGraph 保留编排、外部加调度:挂起 thread 表由业务维护
due_at,定时任务扫表发Command(resume=...)或关单——审批量小、逻辑简单时这是正解,别引入第二个引擎; - Temporal 做外层、图做内层:跨系统长事务(扣款、发货、退款多步补偿)交给 Temporal 的事件历史与补偿,模型决策段仍跑在一张 LangGraph 图里作为 activity。分工原则:事务语义归 Temporal,认知流程归 LangGraph,谁承担什么就在谁的持久化模型里。
最小上手
一个可恢复的最小服务:
python
from fastapi import FastAPI
from langchain.agents import create_agent
from langgraph.checkpoint.postgres import PostgresSaver
app = FastAPI()
with PostgresSaver.from_conn_string("postgresql://postgres:postgres@db:5432/agents") as cp:
cp.setup()
agent = create_agent(model="openai:<model-id>", tools=[search_kb],
system_prompt="你是客服助手。", checkpointer=cp)
@app.post("/chat")
def chat(body: dict):
cfg = {"configurable": {"thread_id": body["thread_id"]}}
out = agent.invoke({"messages": [{"role": "user", "content": body["text"]}]}, cfg)
return {"reply": out["messages"][-1].content}生产还要补:鉴权、限流、超时、追踪、错误映射、健康检查。
生产动作
- 按 thread 限流与计量:记录每个用户的模型调用次数、token、工具次数,异常时自动降级。
- 成本分层与预算前置:分类、摘要、改写用便宜小模型,最终生成用强模型,成本通常能降一个量级;上下文用摘要 + 裁剪 + 检索条数上限,别等模型报超长才处理。
- 发版带评测:prompt、模型、工具、检索参数任一变更都要跑回归;用 feature flag 灰度。
- 保留完整 trace 一段时间:至少覆盖一个排障周期;trace 里不要存敏感原文。
- 定期清理 checkpoint 与 store:长会话和长期记忆都会无限增长。
- 演练恢复:主动杀进程、断模型、断工具,验证
thread_id能续跑、审批能恢复。
选型
下表是 2026-08 快照,只覆盖 Java/Python 生态的主流选择(部分框架另有 TS/Go 实现),不是全部框架清单。
| 方案 | 定位与短板 | 语言栈 | 持久化 / 中断 | 可观测 / 多租户 | 核对 |
|---|---|---|---|---|---|
| 直接调厂商 SDK | 无框架,自己写循环;短板是每家胶水重写 | 各家 SDK | 自己实现 | 自己接 | 2026-08 |
| LangChain | ReAct-first 装配层,底层编译成 LangGraph;短板是抽象泄漏、版本变化快 | Python / JS | 通过 checkpointer;HumanInTheLoop | LangSmith / OTel;按 thread 隔离 | 2026-08 |
| LangGraph | 图运行时,持久化是一等能力;短板是要自己设计 state | Python / JS | checkpointer 内置;interrupt() | LangSmith / OTel;按 thread/namespace 隔离 | 2026-08 |
| LlamaIndex | 检索与索引优先;短板是 Agent 编排相对弱 | Python / TS | 索引/文档存储,无执行 checkpoint | 集成多;namespace | 2026-08 |
| Claude Agent SDK | Claude 生态的 Agent 运行时;短板是绑定 Claude | Python / TS | 会话管理 | OTel 等;租户自建 | 2026-08 |
| OpenAI Agents SDK | OpenAI 生态,handoff/guardrail 清晰;短板是主要面向 OpenAI | Python / JS | session(SQLite/Redis 等);工具审批 | 内置 tracing;租户自建 | 2026-08 |
| Temporal | 持久化执行引擎;短板是不提供模型与工具抽象 | Go / Java / Python / TS | 事件历史一等能力;人工信号/超时 | 自带 UI/metrics;namespace | 2026-08 |
不用“生产就绪度:中/高”这种没有判据的标签。如果必须给结论:LangGraph 为高——v1 把持久化与中断做成一等能力;LangChain 是 ReAct-first 装配层,不单独承担运行时职责。判据就是表里的持久化、中断、可观测、多租户四列,而不是印象分。
把上面的选型表压成一条判定路径:先问要不要框架,再依次收窄到具体方案。
先问“要不要框架”,单轮单厂商能直接调 SDK 就不要引入抽象;标准 ReAct 用 LangChain 省胶水,但它的底层仍是 LangGraph,拓扑变复杂时是迁移而不是重写;需要暂停恢复再上 LangGraph;检索为核心看 LlamaIndex;确定性长流程和跨系统补偿交给工作流引擎,别让 LLM 框架承担事务语义。
边界
- 框架不保证 Agent 可靠:可靠性来自上限、幂等、评测和监控,不是来自抽象层。
- 托管平台有绑定:部署、追踪、评测一体化很省事,但迁移时要补等价能力。
- 版本升级是持续成本:v1 已经稳定,但小版本仍会改行为;锁版本并保留回归测试。
- 多 Agent 不是默认答案:角色拆分增加通信开销和不确定性,单 Agent + 好工具通常更稳。
- 成本可能失控:长上下文、多轮工具、并行扇出会成倍放大 token;上线前必须做单请求成本估算。
- 降本数字要带条件:常见的“图/工作流降本 60–70%”只是确定性节点占比高时的粗略估算,不是实测;确定性节点越多、模型调用越少,节省越接近上限,全模型决策的流程可能没有节省。
讲清楚
Agent 生产化可以按五件事检查:能不能看见每次模型和工具调用,有没有固定评测集,挂了能不能恢复,轮数和成本有没有上限,权限和数据有没有隔离。
技术上最关键的是持久化外部化:checkpointer 放 Postgres,多副本共享,任意实例都能按 thread_id 接着跑。再配超时、重试、降级、幂等和死信,才算能扛故障。选型不要从框架出发,从约束出发:单厂商单轮直接调 SDK;标准工具循环用 LangChain;需要暂停恢复和复杂拓扑用 LangGraph;以检索为核心看 LlamaIndex;纯确定性长流程用工作流引擎。框架只降低胶水成本,可靠性和成本控制仍然要自己做。
适用:LangChain v1.x / LangGraph 1.x | 初稿 2026-10