DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

XML 根标签被删了

XMLOutputParser 把普通 encoding 属性误当声明,完整解析前删掉根标签。缩窄可配置规则,保留 defusedxml,分别验完整文档与流式片段。

XML 明明有完整根节点,XMLOutputParser.parse 却报 OutputParserException。异常中的输入已经少了开头这一行:

<root encoding="utf-8">

同一份内容经过 transform 可以返回结构化片段。先检查完整解析前是否误删了普通元素,而不是马上认定模型生成了坏 XML。

这条条件有 langchain-core 1.6.1 的反馈,已核对的正式 1.6.10 仍使用较宽的 encoding_matcher:它会匹配含 encoding 的标签及后面的换行,不要求标签真的是 <?xml ...?> 声明。

普通属性被当成声明

<root encoding="utf-8"> 是普通元素的开始标签。根标签一旦被预处理删除,剩下的 <item>x</item> 和 </root> 自然不能作为完整文档解析。

错误发生在底层 XML parser 之前,所以把 defusedxml 换成标准 xml 后端,并不能补回根标签。流式路径没有使用相同的删除规则;它能产出片段,也不代表完整解析入口已经正确。

根节点与子节点都可能有名为 encoding 的业务属性。不能为了绕过错误批量删除这些属性、去掉全部换行,或把所有 XML 标签叫作编码声明。

缩窄声明匹配范围

已核对版本允许配置 encoding_matcher。对于位于开头、以换行结束的标准 XML 声明,可以把规则限制到真正的 <?xml ...?>,保留普通元素:

import re
from langchain_core.output_parsers.xml import XMLOutputParser

parser = XMLOutputParser(
    parser="defusedxml",
    encoding_matcher=re.compile(
        r"\A(<\?xml\s+[^>]*\bencoding\s*=[^>]*\?>)[ \t]*\r?\n(.*)\Z",
        re.DOTALL,
    ),
)

这段只构造 parser,不解析数据。两个捕获组仍对应该版本的接口:第二组是声明之后的文档正文。规则限定开头、真实声明结束符与换行,不会把普通 root 标签当成匹配项。

没有匹配这条格式的输入仍交给原来的 XML parser 判断,不通过截取“看起来像 XML 的部分”伪造成功。已有 tags、parser 或其他配置应按原应用保留;这里继续使用 defusedxml,也需要实际环境具备相应依赖。

这只是修正声明预处理,不修复错误闭合标签、非法字符、截断内容或业务字段缺失。XML 解析器本身的安全限制也应保留,不能为了解决这一条正则问题关闭整套安全处理。

完整解析与流式分别验收

保留根节点含 encoding、子节点含 encoding、普通属性以及真实 XML 声明的对照,再覆盖 LF、CRLF、无尾随换行与代码围栏。先确认输入没有丢失合法开始标签,完整解析得到预期结构,再检查流式入口原来的行为。

流式 parser 可以输出中间节点,关闭时还会忽略未完整结束的 XML;不能把“已经看到一个 item”当作完整文档已验收。业务提交仍需要完整内容、结构与正常生成完成状态。

如果异常中的 XML 与进入 parser 前完全相同,就继续检查真实结构、字符和输入边界。不要把所有 OutputParserException 都归因于 encoding 属性。