当AI Agent接收到大模型返回的文本时,真正的挑战往往发生在解析阶段。模型承诺输出JSON,但可能在对象末尾多加了一个逗号;要求输出XML,却可能漏掉闭合标签。解析器一旦抛出异常,整个自动化流程就会中断。本文将分析如何为Agent输出构建可靠的XML与JSON解析层,从容错处理、安全校验到选型策略,给出可以直接落地的代码示例。

一、JSON解析器:处理模型输出的非标准JSON
标准库json.loads只能解析严格符合RFC 8259的JSON。但大模型生成的文本经常包含单引号、尾随逗号、注释甚至缺失引号。直接调用json.loads会触发JSONDecodeError,导致管道中断。可行的办法是先对原始文本做预处理,将常见非标准写法转换为合法JSON。
下面给出一个Python函数,它在解析前替换单引号为双引号、移除注释、补全尾随逗号,并尝试从文本中提取第一个完整的JSON对象。这种容错策略能覆盖大多数GPT系模型的输出问题。实际使用中,可以先用正则表达式提取模型输出中的代码块,再进行修复。
import json
import re
def safe_json_parse(raw: str):
# 去掉markdown代码块标记
text = re.sub(r'^```(?:json)?|```$', '', raw.strip(), flags=re.MULTILINE).strip()
# 去除单行注释 //...
text = re.sub(r'//.*?$', '', text, flags=re.MULTILINE)
# 去除块注释 /* ... */
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
# 将单引号字符串替换为双引号(简单场景)
text = re.sub(r"'([^']*)'", r'"\1"', text)
# 补全尾随逗号:在逗号后紧跟}或]时去掉逗号
text = re.sub(r',\s*([}\]])', r'\1', text)
try:
return json.loads(text)
except json.JSONDecodeError:
# 尝试提取第一个平衡的{}或[]对象
for start, end in [('{', '}'), ('[', ']')]:
s_idx = text.find(start)
if s_idx != -1:
depth = 0
for i in range(s_idx, len(text)):
if text[i] == start:
depth += 1
elif text[i] == end:
depth -= 1
if depth == 0:
candidate = text[s_idx:i+1]
return json.loads(candidate)
raise
上面的逻辑虽然简单,但能应对大量真实场景。对于更复杂的情况,例如模型在JSON字符串内部又混入了自然语言解释,可以先定位第一个{或[,再用深度计数器提取完整片段,忽略前后杂讯。这种方式叫做“宽松提取”,适合Agent流水线中要求快速获得结构化数据的场合。
需要注意的是,正则替换单引号可能误伤字符串内部原本合法的单引号,比如英文缩写。更稳妥的做法是使用专门的JSON修复库,例如json5或demjson3,它们原生支持注释、尾随逗号和单引号。但引入第三方依赖不一定总被允许,因此上面的纯标准库实现仍有价值。
二、XML解析器:严格模式与安全防护
XML格式在传统企业系统和配置文件中依然常见。大模型输出XML时,典型错误包括标签未闭合、属性值没有引号、非法控制字符等。Python标准库xml.etree.ElementTree默认在遇到这些错误时抛出ParseError。对于严格场景,这反而是好事,可以尽早发现问题;但在Agent自动处理中,过于严格会降低鲁棒性。
另一个不能忽视的问题是安全。XML解析器容易受到XXE(XML外部实体)攻击,如果直接解析不可信的模型输出,可能造成文件读取或服务端请求伪造。Python标准库在默认情况下会阻止部分外部实体,但为了绝对安全,建议使用defusedxml库,它对标准库和lxml进行了安全封装,能阻断DTD和实体扩展。
import defusedxml.ElementTree as ET
def parse_model_xml(raw: str):
# 移除markdown代码块
raw = raw.strip()
if raw.startswith('```xml'):
raw = raw[6:]
if raw.endswith('```'):
raw = raw[:-3]
raw = raw.strip()
# 使用defusedxml避免XXE
root = ET.fromstring(raw, forbid_dtd=True, forbid_entities=True, forbid_external=True)
result = {}
for child in root:
result[child.tag] = (child.text or '').strip()
return result
如果必须使用标准库,可以手动设置解析器,并避免解析包含DOCTYPE的文档。不过标准库对实体扩展的保护不完整,推荐在服务器端统一使用defusedxml。另外,若模型输出的XML只是轻微破损,可以借助lxml的recover=True模式尝试修复,但这样会掩盖真正的结构错误,需要权衡。
对于XML中常见的未转义特殊字符,例如文本内容含有&而模型没有写成&,解析器会直接报错。可以在预处理阶段用正则修复,但更可靠的策略是在Prompt中明确要求模型输出时对特殊字符进行转义。实践中,让模型自己保证格式正确,比事后修复更加高效。
三、JSON与XML选型对比及混合解析方案
选择哪种输出格式,取决于Agent下游消费者是谁。JSON的优势在于体积小、解析快、天然支持数组和对象,且大模型在训练语料中见过大量JSON,生成准确率较高。XML则适合需要属性与子元素并存的复杂结构,或者系统已经依赖XSD、XPath等XML生态工具。很多企业服务接口(如SOAP)仍要求XML,此时Agent输出也必须遵循XML。
下面从几个维度做对比:
| 维度 | JSON | XML |
|---|---|---|
| 可读性 | 简洁,但深层嵌套容易迷失 | 标签冗长,结构更直观 |
| 数据类型 | 原生支持null、布尔、数字、数组 | 全部是字符串,需要额外转换 |
| 容错难度 | 尾随逗号、单引号等容易修复 | 标签闭合、实体转义较难自动修复 |
| 安全性 | 一般无外部实体风险 | 需防XXE攻击 |
| 模型生成准确率 | 通常较高 | 较低,尤其长文档 |
如果Agent需要输出同时包含元数据和载荷的复杂消息,一种混合方案是XML外壳包裹JSON数据,但这种做法会同时引入两套解析负担,不推荐。更实际的做法是在系统设计中统一使用JSON,对于必须输出XML的场景,由专门的转换模块负责将JSON映射为XML,模型只负责生成JSON。这样既降低了模型出错概率,又保持了与旧系统的兼容。
最后,无论选择哪种格式,都应该在解析前进行Schema校验。JSON可以使用jsonschema库验证必需字段和类型,XML可以使用XSD或简单的元素检查。校验失败时,可以让Agent携带错误信息再次请求模型修复,而不是直接丢弃任务。重试机制配合解析容错,才能让Agent输出解析真正达到生产级稳定。