Agent 输出带 Markdown 代码块?Prompt 约束 + 解析兜底解决 JSON 解析失败
【导航台账】制造业数据与AI践行者老蒋的技术博客全系列文章汇总(持续更新)
📌 文章摘要
LangChain ReAct Agent 调用工具时,Action Input 常自带 Markdown 代码块,导致 JSON 解析抛出 Expecting value 报错。本文拆解根因:模型训练数据形成的格式偏好,单靠 Prompt 无法根治,提供「Prompt 强约束 + 解析层兜底剥离」双层解决方案,可复用到所有工具定义中。
目录
问题现象
根因分析
第一层:ReAct Agent 的 output_parser 默认格式化成 Markdown
第二层:Markdown 代码块对工具解析是“灾难”
第三层:这个问题单靠 Prompt 很难根治
解决方案
第一步:Prompt 强约束(源头阻止)
第二步:解析层兜底(工具层容错)
验证结果
方案对比
通用工程规范扩展
经验总结
系列导航
互动与交流
关于作者
问题现象
兄弟们,《智联工坊实战:多工具协同Agent,让AI像人类一样规划与执行复杂任务》的前两个坑我们填平了:嵌套 JSON 用args_schema=None解决了,工具“死磕”用结构化返回+容错规则解决了。
我以为世界清净了。
结果跑起来之后,又看到了一个熟悉的身影:
Action: generate_work_order Action Input: ```json { "line_name": "交互屏组装A线", "fault_code": "E401", "solution": "重启工控机并检查USB连接线", "spare_parts_status": "充足", "assigned_shift": "白班" } 然后工具报错: ❌ 错误: Expecting value: line 1 column 1 (char 0)我当时的第一反应是:这不科学啊……😀
JSON 本身是合法的,但前面多了个```json,后面多了个```。Python 的json.loads()根本不认识 Markdown 语法,直接报错。
Agent 为什么要给 Action Input 套上 Markdown 代码块?这不是画蛇添足吗?
根因分析
说实话,这个问题我一开始以为是 Agent“抽风”了。多轮查询验证 LangChain 的源码,才发现——这是 ReAct Agent 的出厂设置,不是 Bug,是 Feature。
第一层:ReAct Agent 的 output_parser 默认格式化成 Markdown
LangChain 的create_react_agent使用的ReActSingleInputOutputParser,在解析 Agent 的输出时,默认期望的格式就是带 Markdown 代码块的。
虽然create_react_agent本身不会强制 Agent 输出 Markdown,但Agent 在训练数据中见过大量“工具调用用 Markdown 代码块包裹”的示例,于是它自然地“学会”了这种格式。尤其是在使用 Qwen2.5 这类本地模型时,这种倾向更加明显。
很多开发者遇到这个问题,第一反应是“Prompt 写得不够清楚”,于是反复加规则、加强调,甚至强行限制调用次数,但效果甚微。本质问题不在 Prompt 的措辞,而在返回值的信号形式——模糊的自然语言,天然不如结构化字段可靠。
第二层:Markdown 代码块对工具解析是“灾难”
工具端的_run方法收到的是:
```json {"line_name": "交互屏组装A线", ...}而json.loads()期望的是纯净的 JSON 字符串。多一个反引号、多一个空格,都会导致解析失败。
Agent 以为它在“美化”输出,实际上它在“破坏”输出。
第三层:这个问题单靠 Prompt 很难根治
我在 Prompt 里写了:
**严禁**使用 Markdown 代码块(例如 ```json ... ```)。但 Agent 依然会时不时地输出 Markdown。因为模型在生成文本时,格式习惯是“潜意识”层面的,就像一个人打字时习惯用两个空格而不是一个空格——你告诉他“不要用两个空格”,他下一句可能还是会按习惯敲两个空格。
本质上,这是一个“训练数据偏见”问题:模型在训练时见惯了 Markdown 格式的工具调用,它认为这就是“标准写法”。
解决方案
别慌,既然单靠 Prompt 管不住,那就“源头约束 + 兜底处理”双管齐下。
第一步:Prompt 强约束(源头阻止)
在builder.py的_get_prompt_template中,用“正确示例 vs 错误示例”的方式强化约束:
**调用工具的格式要求(必须严格遵守)**: - Action Input 必须是**纯净的 JSON 对象**,不包含任何 Markdown 标记。 - **严禁**使用 ```json ... ``` 代码块包裹 Action Input。 - **严禁**在 JSON 前后添加任何说明文字。 ✅ 正确格式: Action: generate_work_order Action Input: {"line_name": "交互屏组装A线", "fault_code": "E401"} ❌ 错误格式(严禁使用): Action: generate_work_order Action Input: ```json {"line_name": "交互屏组装A线"}第二步:解析层兜底(工具层容错)
在generate_work_order.py的_parse_agent_input方法中,增加“剥离 Markdown 代码块”的逻辑:
def _parse_agent_input(self, raw_input: Any) -> Dict[str, Any]: if isinstance(raw_input, dict): return raw_input if not isinstance(raw_input, str): return {} text = raw_input.strip() # ---- 核心:剥离 Markdown 代码块 ---- if text.startswith('```json'): text = re.sub(r'^```json\s*', '', text) text = re.sub(r'\s*```$', '', text) elif text.startswith('```'): text = re.sub(r'^```\s*', '', text) text = re.sub(r'\s*```$', '', text) # 然后继续尝试 JSON 解析 try: return json.loads(text) except json.JSONDecodeError: # 继续用正则兜底提取... pass验证结果
修改后重新运行,无论 Agent 是否输出 Markdown 代码块,工具都能正常解析:
情况1:Agent 遵守约束(纯净 JSON)
Action Input: {"line_name": "交互屏组装A线", ...} ✅ 直接解析成功情况2:Agent 仍带 Markdown
Action Input: ```json {"line_name": "交互屏组装A线", ...} ✅ 剥离后解析成功方案对比
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| 仅 Prompt 约束 | 实现简单 | 无法保证 100% 生效 | ⭐⭐ |
| 仅解析兜底 | 100% 容错 | 治标不治本,增加代码复杂度 | ⭐⭐⭐ |
| Prompt 约束 + 解析兜底 | 源头减少 + 兜底保障,双重保险 | 需要同时维护两处代码 | ⭐⭐⭐⭐⭐ |
通用工程规范扩展
基于本文经验,可进一步扩展统一的工具返回规范,定义通用状态码:
| 状态码 | 含义 | 使用场景 |
|---|---|---|
success | 操作成功 | 正常返回数据 |
not_found | 数据不存在 | 查询无结果 |
param_error | 参数错误 | 输入参数不合法 |
system_error | 系统异常 | 内部错误 |
所有工具遵循同一套结构,后续 Prompt 只需统一识别status字段,即可实现全链路容错,适配更多工具扩展。
经验总结
怕你忘了,我再啰嗦一遍😀😀😀Agent 给 Action Input 套 Markdown 代码块是“本能”,Prompt 管不住是正常的。只有“源头约束 + 兜底处理”才能彻底解决。
落到具体操作上就是三条:
Prompt 中要有“正确示例 vs 错误示例”:不要只写“禁止”,还要展示“正确的应该长什么样”。模型的模仿能力比理解指令更强,用示例约束比用规则约束更有效。
解析逻辑必须包含 Markdown 剥离:
json.loads()不认识 Markdown,但你可以先剥离再解析。这一行代码可以解决 90% 的格式问题。不要相信 Agent 会 100% 遵守格式约定:Agent 是概率模型,不是规则引擎。任何时候都要在工具层做好容错,而不是期望 Agent 永远正确。
适用范围:
本文方案适用于所有使用 ReAct Agent 调用 JSON 格式参数的工具,尤其适用于本地小模型(Qwen2.5-7B 等),这类模型对格式的“惯性”比大模型更强,更需要双层兜底。
系列导航
本文属于《数据与AI工程排坑笔记》系列(点击跳转查看)
- 上一篇:LangChain Agent 反复调用工具死循环?结构化返回 + Prompt 规则让它学会跳过
- 下一篇:《数据质量智能巡检Agent》(Case04,即将发布)点击查看往期实战分享
💡本文问题源自:《智联工坊实战:多工具协同Agent》实战过程,完整源码及深度教程见该文:链接
💡建议收藏:开发多工具协同 Agent 时,Markdown 代码块是最容易被忽略的格式陷阱。本文的双层方案(Prompt 约束 + 解析兜底)可直接复用到所有工具定义中,遇到 JSON 解析失败时可直接对照排查。
互动与交流
你在使用 LangChain ReAct Agent 时,有没有遇到过 Agent“自作主张”给参数加格式的情况?除了 Markdown 代码块,还见过哪些“画蛇添足”的格式?欢迎评论区吐槽,咱们互相交流一下——说实话,让 Agent 输出纯净 JSON 这件事,比教会它调用工具难多了。
关于作者
制造业数据与 AI 践行者老蒋,23 年 IT 老兵。聚焦制造业数据架构与 AI 融合落地。全流程实战,全源码开源。
标签:#排坑笔记 #LangChain #Agent #ReAct Agent #多工具协同Agent #JSON解析失败 #Markdown代码块 #工具调用排坑