最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
让模型输出可落地:结构化结果的校验、重试与降级实践
时间:2026-08-10 15:58:50 编辑:袖梨 来源:一聚教程网
在模型应用的早期阶段,把自然语言回答显示在页面上通常已经足够。但一旦模型参与邮件分类、工单分派、内容审核、数据抽取或自动化编排,输出就不再只是给人阅读的文本,而是下游程序要直接消费的数据。

这时,最常见的问题并不是模型完全没有理解任务,而是结果不满足程序约定:字段缺失、枚举值拼写不一致、数字被包装成字符串、JSON 外混入解释文字,或者模型返回了语法正确但业务上不允许的内容。仅靠提示词要求“请严格输出 JSON”并不能形成可靠的接口契约。
更稳妥的做法是把模型调用看作一个不完全可靠的外部服务,在边界建立四层保护:请求约束、语法解析、结构校验和业务判定。校验失败时只进行有限重试;仍然失败,则进入明确的人工复核或规则降级路径。
核心原理
结构化输出包含三个不同层次,不能混为一谈。
序列化正确:响应可以被 JSON 解析器读取。 结构正确:字段类型、必填项、数组元素和枚举值符合 Schema。 业务正确:例如分类结果与邮件内容相符,置信度不能替代证据,敏感操作还要经过人工审批。JSON Schema 主要解决第二层问题。它能描述对象属性、字段类型、必填字段以及取值范围,但不能证明模型的结论真实,也不能替代权限检查和业务规则。因此,应用应当先解析,再用 Schema 校验,最后执行业务规则。
重试也需要有边界。语法错误或字段缺失通常适合让模型按照错误信息重新生成;认证失败、请求被拒绝、配额耗尽或服务不可用,则应根据错误类型采用退避、切换供应商或直接降级。无条件重试会放大延迟、费用和重复副作用。
在 API 接入层,可以使用 HaerAPI 作为一种待评估的模型接口来源,但实际请求格式、兼容范围、可用模型和错误语义必须以其当前文档为准。
设计一个可验证契约
下面以“工单分类”为例。分类服务只允许返回三个类别,并要求给出简短理由和 0 到 1 之间的置信度。示例中的模型地址和密钥均通过环境变量提供,未假定某个供应商一定支持特定的结构化输出参数。
建议先定义一个与业务无关的稳定数据模型:
from pydantic import BaseModel, Fieldfrom typing import Literalclass TicketResult(BaseModel):category: Literal["billing", "technical", "other"]confidence: float = Field(ge=0.0, le=1.0)reason: str = Field(min_length=1, max_length=300)schema = TicketResult.model_json_schema()这里的 Literal 限制枚举值,Field 限制数值和文本边界。Schema 应该由代码模型生成,而不是在多个文件里手工维护,否则字段变更容易只改了一处。
如果使用的模型 API 支持 JSON Schema 或工具调用,应在请求中传入该契约;如果只支持普通文本生成,则应把 Schema 的关键约束写入提示词,并把本地校验视为必经步骤。两种方式都不能省略服务端校验。
可执行实现
以下代码使用 Python 标准库完成 HTTP 请求,用 Pydantic 做结果校验。接口路径、请求字段和响应字段采用常见的 OpenAI 兼容形态仅作示例;部署前必须按照实际 API 文档调整。
import jsonimport osimport timefrom urllib.request import Request, urlopenfrom urllib.error import HTTPError, URLErrorfrom pydantic import ValidationErrorAPI_BASE = os.environ["MODEL_API_BASE"].rstrip("/")API_KEY = os.environ["MODEL_API_KEY"]MODEL = os.environ["MODEL_NAME"]SYSTEM = """你是工单分类器。只返回 JSON 对象,不要输出 Markdown 或额外解释。category 只能是 billing、technical、other;confidence 是 0 到 1 的数字;reason 是不超过 300 字的分类依据。"""def call_model(ticket: str, repair: str = "") -> dict:payload = {"model": MODEL,"temperature": 0,"messages": [{"role": "system", "content": SYSTEM},{"role": "user", "content": f"工单内容:{ticket}{repair}"}]}request = Request(f"{API_BASE}/chat/completions",data=json.dumps(payload).encode("utf-8"),headers={"Authorization": f"Bearer {API_KEY}","Content-Type": "application/json"},method="POST")with urlopen(request, timeout=30) as response:body = json.loads(response.read().decode("utf-8"))content = body["choices"][0]["message"]["content"]return json.loads(content)def classify(ticket: str) -> TicketResult | None:repair = ""for attempt in range(2):try:raw = call_model(ticket, repair)result = TicketResult.model_validate(raw)if result.category == "other" and result.confidence > 0.9:# 示例业务规则:高置信度的兜底分类需要人工确认return Nonereturn resultexcept (json.JSONDecodeError, ValidationError) as exc:repair = ("上一次结果未通过校验。只修复格式和字段约束,"f"不要增加字段。校验错误摘要:{str(exc)[:500]}")time.sleep(0.5 * (attempt 1))except (HTTPError, URLError, TimeoutError):breakreturn Noneresult = classify("本月账单重复扣款,请核对并退款")if result is None:print("进入人工复核或规则队列")else:print(result.model_dump_json())安装依赖并设置环境变量:
python -m pip install pydanticexport MODEL_API_BASE="https://example.invalid/v1"export MODEL_API_KEY="replace-with-an-environment-secret"export MODEL_NAME="replace-with-a-documented-model"python app.py示例中的地址、模型名和密钥只是占位符。不要把密钥写入源代码、镜像层、前端代码或日志;生产环境还应使用密钥管理系统,并限制出站请求的目标范围。
让重试真正可控
第一,按错误类型分类。解析失败和 Schema 失败可以重试一次;超时是否重试要结合请求是否已经在服务端执行;认证错误不应重试;限流错误可根据响应提供的等待时间退避。实际错误码和响应头要以服务文档为准。
第二,重试提示词只携带必要的错误摘要。不要把完整响应、用户隐私或内部堆栈原样发回模型。错误信息也应截断长度,避免修复请求反而消耗大量上下文。
第三,处理重复副作用。分类本身通常是幂等的,但“模型判断后自动退款”“自动发送邮件”等动作不是。应给每个业务请求建立唯一 request_id,把模型结果和动作状态持久化,在执行动作前检查幂等键,并让高风险动作经过人工审批。
第四,记录可审计字段:请求 ID、模型标识、Schema 版本、校验结果、重试次数、耗时、降级原因和脱敏后的输入摘要。不要默认记录完整的用户内容,日志留存期限也应符合组织的数据政策。
常见问题
只使用正则表达式提取 JSON 可以吗? 不建议作为主方案。模型可能输出嵌套对象、转义字符或多个代码片段,正则很容易误截断。应优先使用 JSON 解析器,再进行 Schema 校验。
temperature=0 是否保证结果完全一致? 不能据此承诺完全一致。采样参数只是影响因素之一,模型服务、请求并发和后端实现也可能影响结果。因此仍需校验、幂等和降级。
Schema 校验通过就能自动执行吗? 不能。通过只说明形状和部分范围符合约定,仍要检查权限、资源状态、业务规则、敏感信息和人工审批条件。
为什么不无限重试? 因为失败可能来自服务不可用、契约不匹配或输入本身无法判定。无限重试会造成延迟和成本不可控,还可能重复触发外部动作。生产系统应设置最大次数、总超时和熔断策略。
模型不支持原生结构化输出怎么办? 可以使用严格提示词加本地解析校验,但可靠性取决于模型和任务复杂度。对高风险流程,应增加规则抽取、人工复核或改用明确支持结构化约束的接口,并在上线前用真实脱敏样本验证。
总结
模型输出要进入软件系统,关键不是把提示词写得更长,而是建立可验证的边界:用数据模型定义契约,用解析器处理语法,用 Schema 检查结构,用业务规则判断是否允许执行,再用有限重试、幂等和降级保证链路可恢复。
这套方法也让模型供应商更容易替换。应用只依赖内部统一的数据模型和错误语义,具体 API 的请求格式、模型能力和限流策略集中在适配层处理。最终,模型负责生成候选结果,系统负责验证、授权、记录和决定是否执行。
相关文章
- 适合初学者的AI部署工具是什么 08-10
- Rust在Debian上的跨平台支持如何 08-10
- 哔咔漫画网页版入口在哪-哔咔漫画网页版入口地址 08-10
- 在Debian上如何调试Rust程序 08-10
- Debian系统下Rust项目如何构建 08-10
- Debian系统中Rust的性能测试如何做 08-10