给 LLM 立规矩:调试 Agent 的三个战场 —— Prompt、中文与兜底
摘要把 Agent 调试到能用的过程回头看我反复修改的其实只有三样东西各 Agent 的 Prompt、模型对中文的支持换其他模型也一样、兜底方案。8 月 5 日那天我在系统里做了 79 次真实 LLM 调用日志里躺着 22 个可数的失败事件——感知输出解析失败 7 次、决策输出解析失败 9 次、响应为空 6 次另有一类更诡异的问题不在 22 个里模型把好好的中文输入看成乱码。这篇就是把这三场仗的实况写清楚每个问题长什么样、我改了什么、改完有没有用。一句话结论先放这Prompt 磨的是输出可解析中文靠硬约束 不依赖模型兜底管的是识别失败时系统怎么活。三个战场都收尾后AI 助手才从能聊变成能干活。一、三个战场调试的真正内容先看 8 月 5 日这个调试日的账观测项数量LLM 调用总数79 次step-3.7-flash × 69step-3.5-flash × 10LLM 感知结果解析失败7 次LLM 决策结果解析失败9 次LLM 响应为空6 次Router 关键词预路由命中17 次ChatAgent 硬编码快速回复命中17 次当天我提交了三次代码10:16 新增 RouterAgent 和 ChatAgentPrompt 硬编码在 Java 里、14:02 根据验证结果修改三个 Agent 的行为BusinessQueryAgent 96 行、ChatAgent 116 行、RouterAgent 76 行、14:48 把 Prompt 全部外置到配置文件并加上全局中文保障。这三次提交正好对应三个战场。二、战场一Agent 的 Prompt —— 反复改的是什么2.1 改的出发点输出不能解析先看两个真实的反面教材。13:20:29决策层响应是这样的原始响应: {没了就一个{。9 次决策解析失败里有这种截断的还有格式漂移的——13:52:50LLM 直接把toolName放在 JSON 顶层返回原始响应: {toolName:orderTool,action:list,params:{...}}而系统要的是{description:..., steps:[{toolName, action, params}]}。模型不是不会干活是不会按我的格式交活。于是 Prompt 的修改几乎全部围绕一个目标让输出可解析。2.2 三类修改逐个说① 输出结构约束。决策 Prompt 里开始出现这种话【规则】 - 只输出 JSON绝对不能输出任何其他文字、解释或对话 - 不允许输出 json 代码块标记 - 不允许输出 markdown、注释或任何非 JSON 内容调用时再补一句兜底prompt.append(\n【再次强调】只输出 JSON不要输出任何其他内容。)。这是对付截断和格式漂移的第一道闸。② 规范白名单。路由 Prompt 直接给模型立规矩禁止清单一条条列死【绝对禁止】以下所有情况必须路由到 chat绝不能路由到 businessQuery 1. 用户问你是谁、你叫什么、你能做什么、你好 → chat 2. 用户表达问候嗨、在吗、hello→ chat 3. 用户表达感谢或告别谢谢、再见→ chat 4. 用户问助手相关的问题你会什么、你能帮我做什么→ chat为什么写得这么啰嗦因为日志见过你是谁“被路由进业务查询、还进一步被识别成查订单”13:20:15 → 13:20:27 的双幻觉链。白名单写的是出过事的地方。③ 示例注入。光说按格式输出不够还得给个样子。决策 Prompt 里塞了完整示例意图order, keyword张三 - {description:查询张三的订单,steps:[{toolName:orderTool,action:list,params:{pageNum:1,pageSize:10,keyword:张三}}]}2.3 修改的节奏日志驱动的三个版本版本时间内容驱动事件V110:16Prompt 硬编码在 Java 类里初版可用即可V214:02三个 Agent 行为修正上午验证暴露的误判/解析问题V314:48全部外置到 agent-prompts.properties硬编码改一次重编译一次Prompt 和代码彻底分离V3 的提交信息就叫将提示词 prompt 抽出去。外置之后改 Prompt 不再动代码——这也让我敢频繁改。Prompt 的迭代频率从改一次重启一次变成改一次加载一次。三、战场二模型对中文的支持 —— 换模型也治不好3.1 现象输入是好的模型看到的却是乱码13:11 到 13:49 这段时间日志里反复出现同一幕——输入在日志里明明是完整的中文[RouterAgent] LLM 感知: 输入我钟意你, 目标Agentchat, 置信度0.95, 原因?????????????????????????????????模型返回的 reason 字段全是问号。更直白的是模型自己的抱怨[StepFunClient] 响应内容: 你好呀目前你之前发的5个问题都显示为乱码全是问号大概率是输入/传输的时候出现了编码错误哦 [StepFunClient] 响应内容: 您当前提供的内容出现了严重的乱码大概率是文本编码错误导致所有可识别字符都显示为问号…后端收到的明明是好的中文模型看到的却是一整片问号。这是我调试以来最诡异的一段。3.2 排查过程模型换了一圈中文问题依旧第一步想的当然是换个模型试试。我的模型演进事实上就是这样阶段模型结果初版step-3.5-flash指令遵循弱频繁输出Identified intent type: order这种非结构化文本——结构病升级step-3.7-flash配合response_format: json_objecttemperature: 0.3结构化输出明显变稳继续其他模型试过中文支持还是不友好换其他模型也一样结论很干脆升级模型治好了结构病但中文问题是模型层的通病——不是配置问题不是我的代码问题就是不友好。这条路走到头了对策必须换个方向不指望模型善待中文而是约束它 兜底它。3.3 对策不换模型改约束对策一全局中文前缀所有调用统一注入。14:48 的提交里加上了这段/** 全局中文指令前缀 —— 所有 LLM 调用统一注入确保输出为中文 */privatestaticfinalStringCHINESE_ENFORCEMENT_PREFIX【重要】你必须使用中文回复不能输出英文或其他语言。禁止输出乱码、问号或其他非中文字符。你的所有回复必须是完整的中文句子。\n\n;所有 Agent 调 LLM 都经过StepFunClient.chat()所以这一条前缀全局生效。对策二每条 Prompt 里再写一遍回复使用中文。外置的 agent-prompts.properties 里chat.system 第 2 条规则就是回复使用中文。全局一道、局部一道双保险。对策三尽量别让模型说中文的场合让它闭嘴。高频问候你好、你是谁、谢谢以及被误判过的高频输入全部落进 ChatAgent 的 QUICK_REPLIES 表用硬编码直接回一次 LLM 都不调。当天该表命中 17 次——17 次对话零次模型调用。注意这里跟 Router 的业务词预路由是两回事预路由拦的是明显要查业务的句子让它进 businessQueryQUICK_REPLIES 拦的是明显要聊天的句子不让 LLM 说话一张表管一边别混。这三招之后模型输出的中文问题基本被摁住了。但有个地方我至今保留着待复测标记输入侧为什么变问号没有找到单一根因——中文前缀管的是输出输入链路那一环未来要专门回归。这个标记本身就是调试的收获问题可以被记录、被追踪而不是改着改着就忘了。3.4 顺带的成本账中文问题还催生了一个副产品模型分工。闲聊走便宜的 step-3.5-flash当天 10 次路由 业务走贵的 step-3.7-flash当天 69 次。便宜模型只出现在不需要它认真干活的场合成本降在哪、正确性有没有受影响日志全看得见。四、战场三兜底方案 —— 识别不出意图系统怎么活4.1 兜底的定义兜底解决的是同一个问题模型识别不出客户意图或选不到正确的工具时系统必须给出可用结果而不是报错或胡说。这个战场的总原则是答得不准不如坦率不答不答的时候规则要能接住。4.2 三个真实的兜底触发点全部来自日志触发点一感知输出非法/解析失败7 次。LLM 返回描述性文字而非枚举值时白名单校验拦截降级到关键词匹配if(!validIntents.contains(intent)){log.warn([BusinessQueryAgent] LLM 返回非法意图值: {}降级为关键词匹配,intent);returnperceiveWithKeywords(originalInput);}触发点二决策失败9 次。决策 JSON 解析失败立刻走规则表——intent → toolName action 参数的硬编码映射 12 个意图全覆盖。9 次失败里业务查询该出数据的都照常出数据了用户根本感知不到 LLM 决策崩过。触发点三感知 unknown。unknown 的哲学上篇讲过这里补一个实测视角LLM 返回unknown, confidence0.5和一个言之凿凿的order, 0.95后者往往更危险。因为前者会老实走兜底后者会带着幻觉真去查一遍数据再编一个结果给你。日志里你是谁→ order(0.95)就是活例子。所以决策层有一道硬规则if(unknown.equals(perception.getIntentType())){log.warn([BusinessQueryAgent] 意图为 unknown直接走 fallback 规则);returndecideWithRules(perception);}unknown 不进 LLM 决策——置信度再低走规则比让模型发挥安全。4.3 入口层的预防式兜底还有一类兜底发生在 LLM 之前Router 的关键词预路由当天命中 17 次。帮我查询所有历史订单这类肉眼可见的业务查询在 13:49:30 被预路由直接抓回 businessQuery——这不是降级是预防让模型根本没机会误判。预路由的词表就是从上午的误判里长出来的。4.4 一条完整失败链的复盘13:49 的这条链路是兜底到底怎么兜的标本13:49:30 Router 关键词预路由命中 → businessQuery ✅ 预防层救回 13:49:45 BusinessQueryAgent LLM 感知 → unknown(0.7) ❌ LLM 没认出订单意图 13:49:51 未知意图兜底回复您当前提供的内容出现了严重的乱码… ❌ 兜底也被带偏系统全程没崩、没超时、没报错——但用户拿到的是错误的乱码回复。这件事教会我兜底链能保证系统活着保证不了答得对。活着但答错一样过不了验收。所以才有最后一个战场改完怎么验证。五、改完怎么验证日志即评测5.1 测试输入从日志里长出来两个调试日的日志里实际问出的输入去重后约 30 条天然分三类类别真实输入正常业务查询查询最近的订单列表 / 查看所有活动 / 查一下张三的订单 / 有哪些内容待审核 / 查看营收数据打招呼闲聊你好 / 你是谁 / 你能帮我做什么 / 我钟意你 / who are you找茬边界“无法识别的意图”把引导语当问题问/ “帮我查询历史全部订单” / “你是谁可以帮我解决什么问题”每一条标上期望的意图、实体、工具。这几十分钟标注工作就是把脑子里的验收标准明文写下来。5.2 四个指标指标用途意图准确率看哪些意图互相混淆降级触发率最关键兜底率越高说明 LLM 越没在干活实体准确率关键词/时间/数量抽得对不对端到端成功率前面全对、最后一步解析炸了照样失败——必须看整体5.3 回归评测集是给你的不是给模型的它的真实用法是——改一次 Prompt、换一次模型就把这 30 条重跑一遍看哪几条答案变了。变了要么是新 bug要么是意外收获总之一定会被发现。没有这套东西看不出变化是调试时最可怕的状态。我现在还是手动跑自动化跑批留到下一篇但每次改动都有日志、有提交、能重跑已经让每一轮修改不再是盲改。六、上线判据三个数字三道门三个战场收尾后用三道门衡量能不能上线判据我的现状坏样本能被枚举错误是有限的、能分类的✅ 误差分析收敛复合句/短输入/闲聊混业务三类每条坏样本都有兜底结果答错但不崩、用户能绕过✅ 可数预路由拦截 17 次、硬编码回复 17 次、22 个失败事件全部落入降级链路没有一次裸奔改动可回归改一次能重跑答案变化能被发现⚠️ 评测集有了自动化没做按这个尺子我的结论是方向对、标准没完全达标。但三个战场打完至少每一轮调试都有了记录、有了对照、有了兜底——LLM 的波动终于不再以玄学的形式影响系统。结尾三场仗的收条Prompt磨到输出可解析为止——约束、白名单、示例三件套目标永远是让模型稳定交活。中文模型对中文不友好换其他模型也一样——不指望它用全局中文前缀 双保险 Prompt 硬编码兜底把它摁住。兜底识别不出意图、选不到工具规则必须接得住——活着不崩只是及格答得对才过验收。写这篇最大的收获是调试 LLM 不是玄学。只要每一步都有日志、有记录、有兜底波动总会从感觉不对变成能说清差在哪——这就是立规矩的意义。文中所有观测数据均来自 2026-08-04 / 2026-08-05 两个调试日的真实日志与提交记录可复核。本文为《把 AI Agent 接进真实业务意图识别与业务系统的解耦》的续篇。