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

作者
  • avatar
    姓名
    Nino
    职业
    Senior Tech Editor

在大型语言模型 (LLM) 之上构建生产级应用程序,需要超越简单的文本生成。当您的系统依赖模型的输出驱动 UI 组件、触发 CI/CD 流水线或填充数据库时,您需要一个严格的契约。您需要结构化的 JSON。然而,当前的 LLM 供应商领域是一个碎片化的生态系统,每个供应商对于如何强制执行“结构”都有不同的想法。

n1n.ai,我们每天都看到开发人员在应对这种碎片化。无论您使用的是 Claude 3.5 SonnetOpenAI 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 语法,但对于键名或数据类型则不提供任何保证。对于这些模型,以及通过通用端点访问的其他模型(如 DeepSeekMistral),必须通过系统提示词 (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 实例,其工作方式都是完全相同的。

容错机制:重试与降级

当模型失败时会发生什么?一个稳健的流水线永远不应该崩溃。我们实施了“重试一次,然后降级”的策略。

  1. 第一次尝试:请求结构化输出。
  2. 验证:运行 ParseFindings。如果通过,则缓存并返回。
  3. 重试:如果验证失败,触发最后一次尝试。第二次尝试通常可以修复幻觉或格式错误。
  4. 降级:如果第二次尝试也失败,不要返回错误。相反,将原始文本视为 Markdown 处理。UI 将渲染文本作为回退,系统会记录一条警告。

这种方法确保了用户始终能得到代码审查结果,即使结构化的“卡片”不可用。当使用 n1n.ai 时,这种多模型容错能力变得更加强大,因为如果某个供应商持续无法满足 Schema 要求,您可以动态切换供应商。

专业提示:Schema 并不等于正确性

请永远记住:Schema 使输出 可解析,但不代表 正确。模型可以完美地遵循您的 JSON 结构,同时编造不存在的行号,或者对代码中的 Bug 产生幻觉。结构化输出是机器可读性的基础,但它不能替代人工判断或严密的自动化测试。

为了衡量发现的实际质量,您应该实施一个评估框架 (Evaluation Harness),将模型输出与标准答案语料库进行对比。只有这样,您才能确定某个模型是否真正为您特定的用例做好了生产准备。

n1n.ai 获取免费 API 密钥。