从五个不兼容的 LLM API 中提取结构化 JSON
- 作者

- 姓名
- Nino
- 职业
- Senior Tech Editor
在大型语言模型 (LLM) 之上构建生产级应用程序,需要超越简单的文本生成。当您的系统依赖模型的输出驱动 UI 组件、触发 CI/CD 流水线或填充数据库时,您需要一个严格的契约。您需要结构化的 JSON。然而,当前的 LLM 供应商领域是一个碎片化的生态系统,每个供应商对于如何强制执行“结构”都有不同的想法。
在 n1n.ai,我们每天都看到开发人员在应对这种碎片化。无论您使用的是 Claude 3.5 Sonnet、OpenAI o3 还是 DeepSeek-V3,保证特定 JSON 形状的基础机制都大不相同。本指南探讨了如何构建一个统一的流水线,在五个(或更多)不兼容的 API 中定位单一 Schema,以及当这些模型忽略您的指令时该如何处理。
单一事实来源:Schema
在深入研究特定于 API 的实现之前,您必须定义一个严格的内部 Schema。在我们的代码审查系统中,每个发现(Finding)都必须遵循特定的 Go 结构体。这确保了下游消费者(如卡片渲染器或 JSON 报告器)能够接收到可预测的数据。
type Finding struct {
Severity Severity `json:"severity"` // critical, high, medium, low, info
File string `json:"file"`
Line int `json:"line"`
LineEnd int `json:"line_end,omitempty"`
Title string `json:"title"`
Description string `json:"description"`
Suggestion string `json:"suggestion"`
Language string `json:"language,omitempty"`
Snippet string `json:"snippet,omitempty"`
}
此数据的外壳始终是 {"findings": [ ... ]}。这个固定的词汇表就是线上契约。虽然用户可能会通过配置文件自定义审查逻辑,但输出的 形状 是不可协商的。这就是复杂性的开始:迫使不同的 LLM 遵守这个形状。
结构化输出的四种“方言”
在多个供应商之间实现此 Schema 需要将其映射到四种不同的“方言”。
1. Anthropic:强制工具调用 (Forced Tool Use)
Anthropic 的 Claude 模型没有像 OpenAI 那样提供原生的“JSON 模式”。相反,获取结构化数据最可靠的方法是将 Schema 定义为一个工具 (Tool),然后使用 tool_choice 强制模型调用它。
在这种方法中,模型不是在“回答”问题;它是在“执行一个函数”,而函数的参数就是您的审查结果。通过将工具设置为必选,您可以消除那些经常破坏解析器的前导文本(例如,“以下是您的发现:”)。
2. OpenAI:严格 JSON Schema (Strict JSON Schema)
OpenAI 通过其 Strict 模式提供了行业标准。当设置 strict: true 时,API 会在生成过程中根据您的 Schema 在服务器端验证模型的输出。如果模型尝试输出 Schema 中不存在的字段,生成过程将受到约束。
func buildResponseFormat() sdk.ChatCompletionNewParamsResponseFormatUnion {
return sdk.ChatCompletionNewParamsResponseFormatUnion{
OfJSONSchema: &shared.ResponseFormatJSONSchemaParam{
JSONSchema: shared.ResponseFormatJSONSchemaJSONSchemaParam{
Name: "code_review_report",
Strict: sdk.Bool(true),
Schema: responseSchema,
},
},
}
}
3. Gemini:响应 Schema 与 MIME 类型
Google 的 Gemini API 采取了中间路线。您提供一个 ResponseSchema(一个 *genai.Schema)并将 ResponseMIMEType 设置为 application/json。这通常是稳健的,尽管它缺乏 OpenAI 实现中的某些严格强制力。
4. Ollama 及其他:仅限语法的约束
像 Ollama 这样的本地供应商通常支持 format: "json" 标志。这保证了输出将是有效的 JSON 语法,但对于键名或数据类型则不提供任何保证。对于这些模型,以及通过通用端点访问的其他模型(如 DeepSeek 或 Mistral),必须通过系统提示词 (System Prompt) 来强制执行 Schema。
可靠性光谱
将这些供应商可视化为一个可靠性光谱会很有帮助:
| 机制 | 约束级别 | 主要供应商 |
|---|---|---|
| 强制工具 / 严格 Schema | 强制执行精确形状 | Anthropic, OpenAI, Gemini |
| format: "json" | 仅限语法 | Ollama |
| 提示词指令 | API 级别无强制 | DeepSeek, Mistral, Cohere |
在使用 n1n.ai 时,您可以在这些供应商之间切换,以在成本和结构可靠性之间找到最佳平衡。
构建解析器后盾 (Parser Backstop)
由于并非所有供应商都能保证 Schema 的一致性,您的应用程序需要一个中央验证函数。我们称之为 ParseFindings。它不仅检查输出是否为 JSON,还验证数据的业务逻辑。
如果模型返回的严重程度为 "urgent"(这不在我们的 Severity 枚举中)或者忘记了 file 字段,解析器必须拒绝它。这种统一的验证确保了下游逻辑——例如根据 high 严重程度发现而失败的 CI 门控——无论模型是 Claude 3.5 Sonnet 还是本地的 Qwen 实例,其工作方式都是完全相同的。
容错机制:重试与降级
当模型失败时会发生什么?一个稳健的流水线永远不应该崩溃。我们实施了“重试一次,然后降级”的策略。
- 第一次尝试:请求结构化输出。
- 验证:运行
ParseFindings。如果通过,则缓存并返回。 - 重试:如果验证失败,触发最后一次尝试。第二次尝试通常可以修复幻觉或格式错误。
- 降级:如果第二次尝试也失败,不要返回错误。相反,将原始文本视为 Markdown 处理。UI 将渲染文本作为回退,系统会记录一条警告。
这种方法确保了用户始终能得到代码审查结果,即使结构化的“卡片”不可用。当使用 n1n.ai 时,这种多模型容错能力变得更加强大,因为如果某个供应商持续无法满足 Schema 要求,您可以动态切换供应商。
专业提示:Schema 并不等于正确性
请永远记住:Schema 使输出 可解析,但不代表 正确。模型可以完美地遵循您的 JSON 结构,同时编造不存在的行号,或者对代码中的 Bug 产生幻觉。结构化输出是机器可读性的基础,但它不能替代人工判断或严密的自动化测试。
为了衡量发现的实际质量,您应该实施一个评估框架 (Evaluation Harness),将模型输出与标准答案语料库进行对比。只有这样,您才能确定某个模型是否真正为您特定的用例做好了生产准备。
在 n1n.ai 获取免费 API 密钥。