14-让大模型稳定输出JSON-Prompt约束与结构化输出

让大模型稳定输出 JSON:Prompt 约束与结构化输出实战

系列:Python + FastAPI 大模型应用基础(第 14 篇)

1. "要求输出 JSON"为什么还不够

模型生成的是 Token 序列,不是数据库记录。它可能输出 Markdown 代码围栏、漏字段、错误枚举或错误类型。可靠链路应当是:

text 复制代码
定义 Schema → 请求结构化输出 → 解析 → 业务校验 → 有限重试或失败

若模型 API 支持 JSON Schema(JSON 模式),优先使用其官方结构化输出能力;具体参数随厂商和版本变化,应核对当前文档。即便如此,业务规则仍要在服务端校验。

2. 使用 Pydantic 定义输出契约

python 复制代码
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, ValidationError


class TicketResult(BaseModel):
    """模型输出必须满足的结构;禁止悄悄接收未知字段。"""

    model_config = ConfigDict(extra="forbid")

    category: Literal["物流", "退款", "产品", "其他"]
    urgency: Literal["low", "medium", "high"]
    summary: str = Field(min_length=1, max_length=200)
    needs_human: bool


def parse_ticket_result(raw_text: str) -> TicketResult:
    """Pydantic 同时完成 JSON 解析和字段校验。"""
    try:
        return TicketResult.model_validate_json(raw_text)
    except ValidationError as exc:
        # 对外返回统一错误;完整原文是否记录需服从数据安全规范
        raise ValueError("模型输出不符合 TicketResult 契约") from exc

3. Prompt 只描述任务,Schema 才是机器契约

python 复制代码
import json


def build_extraction_messages(ticket_text: str) -> list[dict[str, str]]:
    if not ticket_text.strip():
        raise ValueError("工单内容不能为空")

    # 由程序生成 Schema,避免 Prompt 描述与代码模型发生漂移
    schema = json.dumps(
        TicketResult.model_json_schema(),
        ensure_ascii=False,
    )
    return [
        {
            "role": "system",
            "content": (
                "你是工单信息抽取器。只提取原文事实;"
                "无法确认时选择"其他",高风险或不确定时 needs_human=true。"
            ),
        },
        {
            "role": "user",
            "content": (
                f"严格返回符合下列 JSON Schema 的 JSON,不要使用 Markdown:\n"
                f"{schema}\n\n"
                f"<ticket>\n{ticket_text.strip()}\n</ticket>"
            ),
        },
    ]

这里没有把 schema 当成绝对保证。它只是告诉模型目标格式;最终仍以 TicketResult 的校验结果为准。

4. 失败后如何处理

无限重试会放大成本和故障。可以限定一次"修复重试",仍失败就进入人工或降级流程:

python 复制代码
from collections.abc import Callable


def extract_with_one_retry(
    call_model: Callable[[list[dict[str, str]]], str],
    ticket_text: str,
) -> TicketResult:
    messages = build_extraction_messages(ticket_text)
    first_output = call_model(messages)

    try:
        return parse_ticket_result(first_output)
    except ValueError:
        repair_messages = messages + [
            {"role": "assistant", "content": first_output},
            {
                "role": "user",
                "content": "上次输出未通过 Schema 校验。请只返回修正后的 JSON。",
            },
        ]
        second_output = call_model(repair_messages)
        return parse_ticket_result(second_output)

重试只解决偶发格式问题,不能修复错误的业务定义。如果失败率持续上升,应查看模型版本、输入分布和 Prompt 版本,而不是继续加重试。

5. 可复验测试

python 复制代码
def test_valid_json_can_be_parsed() -> None:
    result = parse_ticket_result(
        '{"category":"物流","urgency":"high",'
        '"summary":"包裹超过承诺时间仍未送达","needs_human":true}'
    )
    assert result.category == "物流"
    assert result.needs_human is True


def test_unknown_field_is_rejected() -> None:
    invalid = (
        '{"category":"物流","urgency":"high","summary":"延迟",'
        '"needs_human":true,"refund_amount":9999}'
    )
    try:
        parse_ticket_result(invalid)
    except ValueError:
        pass
    else:
        raise AssertionError("未知字段不应被静默接收")

6. 对抗性审查

  • JSON 合法不代表内容真实,金额、ID、时间仍需与事实来源核对;
  • 不使用正则表达式从大段回答里"抠 JSON",它难以正确处理嵌套和转义;
  • 不自动执行模型返回的 action,先映射到服务端白名单;
  • 对未知字段采用拒绝策略,避免攻击者夹带参数;
  • 记录结构校验失败率,但敏感原文应脱敏或不落日志;
  • Schema 变更要兼容消费者,并与 Prompt 版本一起发布。

7. 总结

稳定 JSON 不是一句 Prompt 的功劳,而是 Schema、厂商结构化能力、服务端校验和有限失败策略共同形成的闭环。

相关推荐
隔窗听雨眠30 分钟前
PostgreSQL单表从Windows迁移到Linux:差异剖析、方案对比与全流程实战
linux·windows·postgresql
Helix2503 小时前
Ansible混合环境自动化:Windows与Linux统一配置管理实战
windows·自动化·ansible·playbook·配置管理·winrm·混合环境
forest_095 小时前
用Python复刻ClaudeDuet:主动式Agent四件套,定时晨报、后台巡检、危险动作审批(附完整可运行代码)
人工智能·python·架构·prompt·agi
Java开发追求者5 小时前
windows快速安装docker
windows·docker·安装·1024程序员节
Helix2508 小时前
Windows ETW与KQL实战:终端性能诊断与遥测数据分析指南
windows·性能诊断·kql·etw·azure monitor·遥测数据·tracert
陈葛杰9 小时前
Adobe Premiere Pro2025安装教程
windows·adobe·开源软件·1024程序员节
承渊政道11 小时前
iOS远程控制电脑用什么好?iPhoneiPad远程控制Windows电脑最佳方案2026横评
windows·ios·远程控制·1024程序员节·iphone ipad
易点云徐源11 小时前
PDF 默认打开方式刚修好,过一分钟又变成“未知应用”?使用 ProcMon 成功抓出后台程序
windows·pdf
“AI国潮设计-小江”11 小时前
AIGC实战:Python+SDXL批量生成英歌舞IP立牌,解锁猪猪蛋糕“反差萌”流量密码(附完整API源码)
开发语言·人工智能·python·prompt·aigc
我是大猴子11 小时前
py how claude work项目分析
运维·服务器·windows·1024程序员节