
peg-native: `Until(…)` GBNF generation grammar and PEG parser disagree on the value/terminator boundary — malformed tool-call XML (duplicate “) is generated but unparseable; aborts the stream. (Not fixed by #24839)
快速结论:这个报错发生在 llama.cpp 工具调用场景下,当 GBNF 生成语法中的 Until(分隔符) 与 PEG 解析器对值/终止符边界的处理不一致时触发。优先排查是否使用了 peg-native 格式且分隔符自身存在重叠前缀(例如 \n</parameter>\n)。
问题场景
用户使用 llama-server 在 Mac/Linux 上以 CUDA 后端运行 unsloth/Qwen3.6-35B-A3B 模型,采用了 --jinja 和外部 --chat-template-file 自定义聊天模板(XML 工具调用方言:<tool_call><function=NAME><parameter=ARG>value</parameter></function></tool_call>),以及 --chat-template-kwargs '{"preserve_thinking": false}'。报告显示使用的聊天格式为 peg-native。
报错原文
common_chat_peg_parse: unparsed peg-native output:
/Users/.../pallium-finish-done.story
<-- stray duplicate close tag
<parameter=startLine>
1
50
原因分析
核心原因是 GBNF 生成语法与 PEG 解析器对 Until(分隔符) 的值边界处理不一致。当分隔符自身前缀(\n</parameter>)出现在值末尾时,两个组件会得出不同结论:
- GBNF 生成语法(约束采样)—— 过于宽松:内部 AC 自动机允许值以分隔符的前缀结尾,然后紧跟着完整分隔符。例如,模型可以合法地输出
value\n</parameter>\n</parameter>\n,因为 AC 自动机在状态until-25-13(已消耗\n</parameter>)时只有[^\n] 继续和空(结束)两种选择,没有[\n]转移,导致下一个\n被解释为值终止位置。 - PEG 解析器(解析已完成文本)—— 贪婪/最小匹配:
Until('\n</parameter>\n')匹配第一个完整分隔符就停止,导致剩余</parameter>\n...无法被解析。
这种不一致允许模型生成 PEG 解析器无法处理的输出,导致流被终止。
环境排查
- 确认
llama.cpp版本:9744 (063d9c156)或更早(注意 #24839 未修复此问题)。 - 确认聊天格式是否为
peg-native。 - 确认分隔符是否包含自身重叠前缀(例如
\n</parameter>\n中\n</parameter>\n本身与\n</parameter>\n有重叠)。 - 确认是否使用自定义聊天模板中的工具调用 XML 方言。
解决步骤
- 可优先尝试:修改工具调用模板,避免使用可能重叠的分隔符。例如,将分隔符改为更独特或更长的字符串(如
\n</param>\n改为\n</parameter-end>\n),确保分隔符自身不会成为值的有效后缀。 - 如果无法修改模板,可考虑在应用层增加后处理逻辑,检测并丢弃重复的关闭标签。
- 也可以暂停使用
peg-native格式,切换为其他稳定的工具调用格式(如 JSON 模式)作为临时方案。 - 注意:此问题的根本修复需要在
llama.cpp源码级别调整Until()在 GBNF 生成语法和 PEG 解析器中的实现,使之对分隔符边界的处理一致。社区已认识到此问题,但暂无合并的修复。
验证方法
使用相同的模型和参数进行多次工具调用测试,确认不再出现 common_chat_peg_parse: unparsed peg-native output 报错,并且所有工具调用都能被正确解析并返回 tool_calls。



