首页 独立开发实战内容详情

Agent 上线的教训清单:6 个生产事故与修复

2026-11-03 3 zhuadmin

凌晨一点半,报警群先弹“发布失败”,紧接着是“全站 500”。这是我让 Agent 自动写稿发站的第 9 天,也是我第一次真切体会到“自动化”三个字有多贵。

9 天,踩了 6 个坑,每一个都不是玄学,全是工程上的低级硬伤。下面逐条拆开,说人话,附上能直接抄的修复方案。

核心要点

  • MySQL 1366 的锅九成不在数据库,在你按字节裁剪 UTF-8 字符串
  • Z-Blog 编译缓存:改模板不重建,线上跑的还是旧产物,攒到一起重建就全站 500
  • 思考模型 reasoning 吃 token:`max_tokens=60` 等于让模型想完就死,输出空串
  • json.dumps 默认转义:漏了 `ensure_ascii=False`,中文标题全变 `\uXXXX`
  • worker 轮询间隔:3 秒 claim 一轮,你插的调试数据没有“等下手动删”的机会
  • LIKE 未转义:`\`、`%`、`_` 裸拼进 SQL,误报成“数据污染”

一、MySQL 1366:按字节裁中文标点,把 UTF-8 裁成了残废

Agent 生成的标题要做长度控制,链路里有人写了按字节截断到 80。一个中文逗号占 3 字节,裁剪点正好落在它中间,产出了非法字节序列。

MySQL 1366:数据库收到不完整的 UTF-8 字节序列时抛出的 `Incorrect string value`,本质是它在替你挡脏数据。

写入直接报错,当天任务全军覆没。为什么本地测试没炸?因为测试数据全是英文,单字节怎么裁都裁不残。

修复:截断一律按字符(Python3 的 str 本身就是 unicode,别碰 bytes),入库前加一道编码校验。

预防机制:中文标点必须进测试用例;封装统一的截断函数,全项目禁止裸切片。

二、Z-Blog 改模板不重建编译缓存,全站 500

白天改了模板文件,前台毫无变化。我以为没生效,又连改三处,晚上手动清缓存重建——全站 500。

编译缓存:Z-Blog 会把模板编译成 PHP 文件缓存起来,改源文件不等于改了线上实际执行的代码。

坑在哪?你看到的“没生效”会诱导你继续叠改动,等缓存一重建,所有改动一次性引爆。中间任何一步引用了不存在的变量,整站直接崩。这不叫缓存,这叫延时炸弹。

修复:回滚模板,逐个改动单独验证后重新上线。

预防机制:改模板的标准动作 = 改完立即重建缓存 + 刷新验证,一步一确认,永远别攒改动。

三、GLM 思考模型 max_tokens=60,reasoning 吃光额度输出空串

接入 GLM 思考模型跑批量生成,沿用旧配置 `max_tokens=60`。API 返回 200,`content` 是空字符串,`reasoning_content` 里躺着 60 个 token 的思考过程。

reasoning 吃 token:思考模型的推理过程和最终输出共享 max_tokens 上限,思考完了,额度也没了。

最气人的是这不算报错。空串一路穿透发布流程,生成了几十篇“空壳文章”。为什么日志里查不到异常?因为没有异常,200 + 空 content,一切“正常”。

修复:思考模型 `max_tokens` 提到 4096 起步;代码层校验 content 为空直接抛异常,进重试队列。

预防机制:按模型类型维护独立参数配置;空输出视为硬失败,禁止落库更禁止发布。

四、json.dumps 漏 ensure_ascii=False,标题变 \uXXXX

发布链路中间有一环用 `json.dumps` 存缓存,取出来直接当标题用了。于是中文标题变成了 `\u6807\u9898` 这种鬼画符。

ensure_ascii:Python json 序列化的默认开关,开着就把所有非 ASCII 字符转成 `\uXXXX` 转义串,“安全”但不成人话。

单测为什么没拦住?断言比较的是 json 字符串本身,两边都被转义得一样面目全非,当然全绿。

修复:全项目 grep `json.dumps`,统一补上 `ensure_ascii=False`。

预防机制:封装团队级 `to_json()` 函数,禁止裸调;断言必须比较反序列化后的对象,别比字符串。

五、worker 每 3 秒 claim 任务,调试行被立刻真发布

排查问题时往队列表插了一条测试数据,想着“等下手动删”。worker 3 秒一轮,我切回数据库的那两秒里,它已经走完了整个发布流程,测试文章真的上线了。

轮询间隔:worker 每轮扫表 claim 待处理任务的周期,3 秒意味着你没有任何反悔窗口。

怪 worker 吗?它只是做了它该做的事。被坑惨了才想明白:调试数据和生产数据共用一张表,这本身就是设计硬伤。

修复:紧急下线测试文章,给队列表加 `source` 来源字段。

预防机制:worker 只 claim 白名单来源的任务,调试数据打上独立标记天然被过滤;实在要插生产表,先停 worker,别赌运气。

六、LIKE 反斜杠没转义,误报“数据污染”

发布前有一步标题查重,用 `LIKE` 匹配。内容里带 `\` 和 `%` 的路径类标题,全被误判成“已存在”,正常文章被静默跳过,我一度以为库进了脏数据。

LIKE 通配符:`%` 匹配任意串、`_` 匹配单字符、`\` 是默认转义符,不转义就拼进 SQL 等于把匹配逻辑交给运气。

真被污染了吗?没有,是你的查询语句被污染了。误报比漏报更恶心——它不报错,只是安静地让正确的数据消失。

修复:封装转义函数,`\`、`%`、`_` 统一处理,配合 `ESCAPE` 子句显式声明转义符。

预防机制:涉及用户内容的一律不裸拼 LIKE,精确匹配用 `=`,模糊查重上全文索引。

七、复盘提速:先把时间线对齐

六个事故复盘下来,最耗时间的不是找原因,是对时间线:worker 什么时候 claim 的、报警什么时候触发的、缓存什么时候重建的。

日志里全是 Unix 时间戳,肉眼对齐能把人逼疯。我直接把几个关键节点的秒级时间戳贴进 时间戳转换,换成可读时间后先后顺序一眼看清。

这种笨工具没什么技术含量,但比什么都靠谱。

FAQ / 常见问题

Agent 出事故是不是模型不行? 六个里五个是纯工程问题,模型只背半个锅——token 那半个也是你配置的锅。

测试全跑过了为什么还翻车? 中文边界、缓存时序、轮询竞态全是环境耦合问题,单测覆盖不到,只有真实流量会教你做人。

出事后再补监控可以吗? 监控只告诉你“炸了”,上面这些预防机制的作用是“炸不了”,顺序反了就是永远在救火。

  • 评论列表 (0条)

 暂无评论,快来抢沙发吧~

发布评论