EN中

为什么我的 LLM 结构化输出效果很差?

让大型语言模型(LLM)输出结构化结果对许多真实应用至关重要,但这依然是使用 LLM 时最具挑战的部分之一。

在我使用不同结构化输出方法的经验中,我发现不存在通用的解决方案。本文讨论我实践过的三种方式:工具调用 API、基于 XML 的方法,以及自定义领域特定语言(DSL)。它们各有优势与局限,适用于不同场景。

1. 工具调用 API ​

这是最常见的 LLM 结构化输出方法。但有些 LLM 对工具调用 API 的支持并不好,我们可能需要用 ReAct 来实现。我做了一个 Python 中间层,行为上类似工具调用 API,但底层使用提示词: https://github.com/BeautyyuYanli/tooluser。

工具调用 API 最大的问题在于,它与 LLM 的其他部分使用了不同的训练方式。

文本回复、推理、工具调用输出是分别训练的,所以我们经常遇到 LLM 思路一致、结果却完全不同的情况(尤其是在 O 系列早期版本和 DeepSeek R1 上)。工具使用 API 也存在类似问题,只是更隐蔽一些。

一个例子是角色扮演类应用。LLM 既需要有良好的文学表达,也要输出角色的想法、动作等结构化内容。如果在这些场景使用工具调用 API,LLM 的文学表达会完全消失。

2. 通用 XML ​

为了解决上述问题,我发现 XML 是一种有效的方法。XML 本身对文本表达很友好(没有引号、没有转义问题),这也是 HTML 被发明的原因。

对于 LLM,我只需要用提示词描述 XML 输出结构,再用一个校验器验证 LLM 的文本回复即可。使用 XML 可以让 LLM 在结构化输出的同时保持出色的文学表达。我写了一个简单的 Python 库供自己使用: https://github.com/BeautyyuYanli/qwq-tag

3. 自定义 DSL ​

与文学表达相对的另一种用例,是让 LLM 生成复杂的结构化输出。如果结构是 JSON 或像 PostgreSQL 这样的主流 DSL,LLM 往往能轻松产出结果。但在我的场景里,我要生成的是 ISO GQL(面向图数据库的类 SQL 图查询语言)。LLM 对它了解有限,很难正确理解我的指令。这会带来两个问题:如何验证输出,以及如何帮助 LLM 生成更好的结果?

核心思路是建立中间表达。我设计了两步流程:第一步把指令翻译成伪代码,第二步再从伪代码生成 DSL。它类似在回答前的 <think> 过程,但我让模型把伪代码当作 <think> 内容。这种方法能显著提升输出质量。

为了验证 LLM 的输出,我会把 DSL 映射成 JSON,然后用工具调用 API 生成 JSON。另一种方法是使用基于 AST 的校验器。

但如果 LLM 很难生成正确的语法,就需要不断试错修正,这可能会损害最终结果的质量。另外,写出一个带有清晰、人类/LLM 可读错误信息的高质量校验器并不容易。

LLM 的引导式生成(如 vLLM 支持的方案)也许是另一个很有希望的方向。详见: https://docs.vllm.ai/en/stable/features/structured_outputs.html。

结语 ​

每一种结构化输出方法都对应不同需求与优先级。最终的选择取决于你的具体场景。