【导航台账】制造业数据与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 管不住是正常的。只有“源头约束 + 兜底处理”才能彻底解决。

落到具体操作上就是三条:

  1. Prompt 中要有“正确示例 vs 错误示例”:不要只写“禁止”,还要展示“正确的应该长什么样”。模型的模仿能力比理解指令更强,用示例约束比用规则约束更有效。

  2. 解析逻辑必须包含 Markdown 剥离json.loads() 不认识 Markdown,但你可以先剥离再解析。这一行代码可以解决 90% 的格式问题。

  3. 不要相信 Agent 会 100% 遵守格式约定:Agent 是概率模型,不是规则引擎。任何时候都要在工具层做好容错,而不是期望 Agent 永远正确。

适用范围

本文方案适用于所有使用 ReAct Agent 调用 JSON 格式参数的工具,尤其适用于本地小模型(Qwen2.5-7B 等),这类模型对格式的“惯性”比大模型更强,更需要双层兜底。

系列导航

💡本文问题源自《智联工坊实战:多工具协同Agent》实战过程,完整源码及深度教程见该文:链接

💡 建议收藏:开发多工具协同 Agent 时,Markdown 代码块是最容易被忽略的格式陷阱。本文的双层方案(Prompt 约束 + 解析兜底)可直接复用到所有工具定义中,遇到 JSON 解析失败时可直接对照排查。

互动与交流

        你在使用 LangChain ReAct Agent 时,有没有遇到过 Agent“自作主张”给参数加格式的情况?除了 Markdown 代码块,还见过哪些“画蛇添足”的格式?欢迎评论区吐槽,咱们互相交流一下——说实话,让 Agent 输出纯净 JSON 这件事,比教会它调用工具难多了。

关于作者

        制造业数据与 AI 践行者老蒋,23 年 IT 老兵。聚焦制造业数据架构与 AI 融合落地。全流程实战,全源码开源。

标签:#排坑笔记 #LangChain #Agent #ReAct Agent #多工具协同Agent #JSON解析失败 #Markdown代码块 #工具调用排坑

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐