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、厂商结构化能力、服务端校验和有限失败策略共同形成的闭环。

相关推荐
枫叶v.4 小时前
Prompt Injection 防不住怎么办?从 Source-Sink 模型设计 Agent 安全边界
数据库·安全·prompt
趣味科技7 小时前
记录修复EAGET忆捷移动硬盘损坏
windows·数据分析·硬件工程
我命由我123458 小时前
Java 开发 - List subList 方法
java·windows·操作系统·list·intellij-idea·idea·intellij idea
吃辣我第一9 小时前
监听SHP目录自动注册SuperMap iServer地图服务(Windows版)
windows·iserver·shapefile
用户7783366132119 小时前
SERP API JSON Schema 演进版本管理实战
json·api
程序员老陆9 小时前
FFmpeg6 在 Windows 打开麦克风并录成 PCM:不走 Qt Multimedia,也不碰 WASAPI
windows·ffmpeg·音视频·pcm
chushiyunen10 小时前
神雕武功排名
windows
酩酊仙人10 小时前
Windows Server部署django
windows·django·sqlite
杰瑞学AI11 小时前
一个回答需要10分钟:飞书问答机器人踩坑实录——纯Agent自由检索,差点让我们的机器人“难产”
人工智能·机器人·prompt·transformer·飞书·ai-native
IT小盘11 小时前
16-Prompt版本管理-从手工修改到可追踪配置系统
开发语言·人工智能·python·prompt