企业级 Agent 上线的 27 条生产坑
一个从立项走到生产的真实 Agent 项目,27 条踩坑记录的全清单与分层复盘——事故级、设计级、选型级、工程级,每条都有根因和教训。
这 27 条坑来自一个完整走过「需求 → 开发 → 集成验证 → 上线」的 Agent 项目:LangGraph 编排 + 向量库检索 + 业务库审计 + 转人工工作流,跑在 2C4G 的云服务器上,对接真实 LLM API。每一条都是实测踩到、当天记录、修复验证过的,不是从别人的文章里转述的。
数字先标时点(截至 2026-09-04 项目上线首日):26 项集成测试全绿、30 题回归门禁连续三轮通过、11 个场景变体探针全过、50 并发 P95 = 4.7s。
坑按杀伤力分四级。建议读法:先看事故级(每条都是别人的事故报告),再看模式——27 条坑背后其实只有七个反复出现的模式。
事故级:不修就出安全事故#
这一级的共同点:LLM 自由生成的内容没有事实边界。
坑 1:LLM 捏造事实。 给模型一个「面试官」角色,它会自己编学员简历。教训:给 LLM 角色时必须同时给事实边界红线——角色设定管「怎么说话」,管不住「说什么」。
坑 2:引用了不存在的资料位置。 评分模型在总评里建议学员「回看 3.2 节」,那个小节不存在。自由文本里的引用必须代码级校验,不是提示词级「请不要编造」就能防的。
坑 3:编造请求未被拒答。 用户要求帮忙编造经历,模型照办了。教育场景里协助造假是双重事故。生成提示词里要有显式红线条款,且红线的行为要有测试用例盯着。
坑 4:等待态把用户永久卡死。 转人工后学员停在「暂停」态:旧工单不自动关、前端异步提交又刷新了页面,用户再也没有出路。「等待类状态」必须永远给用户一条出路——通知可以失败,工单不能丢,状态机必须有超时出口。
坑 5:UI 重写丢了请求字段。 前端重构时请求体漏了一个 mode 字段,所有面试请求被当成答疑处理——测试环境没抓到,因为测试用例没逐字段核对请求体。UI 重写那天应该做字段对照表。
设计级:不修体验就差#
坑 6:评分标准对学员不透明。 开放式题目配了全面性评分,学员答题前不知道标准,怎么答都像在赌。评测公平的前提是标准前置可见,rubric 要在题目规格里。
坑 7:答案技术腔重。 生成提示词没注入教学风格,产出全是工程师腔。教育产品必须显式注入教学方法:结论先行、比喻先行、学完自查。
坑 8:多轮指代追问失守。 用户先问 A 再说「它还有什么缺点」,检索只拿 top5 就接不住了——加上配置项被 dataclass 默认值遮蔽(见坑 22),实际检索数比配置的还少。这类问题正样本回归测不出来,要靠变体探针:一次通过是挖掘的起点,不是终点。
坑 9:评分员误判「编造」。 给评分模型的事实源只包含被引用的小节,漏了系统实际检索到的其他切片——模型不知道的信息被它判成「编造」。评分事实源必须等于被测系统实际可见的全部信息,否则测的是评分员自己的信息差。
选型级:选错了浪费时间和钱#
坑 10:2C4G 跑不动 Milvus。 内存账没算就选了重型向量库,实测起不来。先算资源账再选型——最后换 Qdrant(200~500MB 内存够用),并留了迁移触发线。约束驱动的选型才靠谱,名词热度的不可靠。
坑 11:checkpoint 组件要 Redis Stack。 langgraph-checkpoint-redis 依赖 Redis Stack,官方支持矩阵写得清楚,想当然没查。选型前先查官方支持矩阵,最后改用 SqliteSaver(零额外内存,文件可备份)。
坑 12:CentOS 7 停止维护。 老系统上生产等于裸奔安全更新。纯净 Ubuntu 22.04 LTS 重装,配初始化脚本做安全基线——基础设施即代码,可复现可审计。
坑 13:模拟面试评分太严格。 「严格」不等于「对」。置信口径细化 + 生成温度降 0.1 后,评分才和人工判断对齐。评测器本身也需要校准。
工程级:环境六条 + 后端十条 + 前端三条 + 模型两条#
环境(坑 14-19):SSH 禁密码登录导致 .env 密码进不去(密钥先行);SFTP 路径权限报错(用户目录权限核对);/opt 无写权限且用户不在 docker 组(权限清单化);MySQL 首次启动要等初始化完成才能冒烟(别拿慢启动当故障);思考型模型的 max_tokens 给小了直接返回空(思考过程也吃 token 预算);初始化脚本笔误(自审要覆盖脚本本身)。
后端(坑 20-29 里的十条):pymysql 默认单语句,一次建多表报 1064;SSH 隧道没起时客户端报连接拒绝(先验隧道再验代码);向量库的点 ID 不稳定,重跑入库数据翻倍(ID 必须内容寻址);思考型模型关不掉思考,正确姿势是 reasoning_effort 参数;图引擎的 resume 续跑和业务接单是两码事(职责边界要切开);图节点返回的键不在 State 声明里会被静默过滤(不报错、没日志,最阴险的一类);SqliteSaver 的连接上下文会被垃圾回收关掉;工单列表取第一条会拿到历史遗留单(按状态过滤,不是按位置取);配置默认值被 dataclass 字段默认值遮蔽(配置生效要有断言测试);测试数据不清理会累积污染(83 张遗留工单挤爆列表)。
前端(三条):异步发送函数配 onsubmit 返回值导致点发送=刷新页面;输入栏的 hidden 状态没解除;静态页改完要跑部署脚本才生效。
模型(两条):max_tokens 陷阱(同坑 18);reasoning_effort 是思考型模型的正确调参入口。
27 条坑背后的七个模式#
- 约束驱动选型:先算资源账(内存、成本、维护性),不按名词热度选。
- 官方支持矩阵先行:选型前查官方文档确认支持范围。
- 变体探针 > 正样本回归:门禁问「通没通」,探针问「稳不稳」。
- 通知是加速器不是事实源:任何通知链路都可能失败,业务状态机必须自洽。
- 评测公平的前提是标准前置可见:rubric 进题目规格。
- 评分事实源 = 被测系统实际可见的信息:不然测的是评分员的信息差。
- 基础设施即代码:纯净系统 + 初始化脚本,不用面板。
写在最后#
如果只带走一句话:LLM 应用的事故都不在「模型不行」里,在「边界没画清」里——事实边界、状态边界、职责边界、权限边界。模型负责生成,边界永远是工程的活。
(本文为项目实录的对外改写版,工程细节与数字截至 2026-09-04。)