当提示词、模型或生成参数发生变化时,最容易被忽略的问题不是“能不能调用模型”,而是“这次改动到底改变了什么”。本篇在前面基础主题已经覆盖的前提下,做一个小型综合项目:调用模型生成结构化结果,保存输入和输出,再用 Python 对两份结果做确定性的差异审查。模型只负责提出结果,审查器负责发现变化,不把“输出不同”直接判定为“输出错误”。

先定义审查对象

示例程序处理一个产品描述,要求模型返回固定 JSON:summary 是摘要,keywords 是关键词列表,risk 只能是 low、medium 或 high。我们会用同一输入运行两次,得到 baseline.json 和 candidate.json,然后比较结构、字段值和关键词集合。

这里有三个边界。第一,差异比较是确定性 Python 代码,不让模型自己解释差异。第二,模型输出必须先通过 JSON 和字段校验,未通过的结果不能进入比较。第三,审查器只生成报告,不自动发布内容;是否接受候选结果仍由人决定。

准备环境和输入文件

在独立虚拟环境安装官方 Python SDK:

1
2
3
python -m venv .venv
source .venv/bin/activate
python -m pip install openai

密钥和模型名只从环境变量读取:

1
2
export OPENAI_API_KEY="替换为你的真实密钥"
export MODEL_NAME="替换为你可用的模型名称"

新建 case.json,不要把真实密钥写进文件:

1
2
3
{
"text": "一款面向初学者的 Python 命令行记账工具,支持 CSV 导入、按月份统计和本地备份。"
}

编写一次调用程序

将下面代码保存为 run_case.py。它使用 Responses API,将模型输出文本交给严格校验函数;如果接口或模型返回非法 JSON,程序直接失败,而不是悄悄保存一个无法比较的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import json
import os
import sys
from pathlib import Path

from openai import OpenAI


SCHEMA_KEYS = {"summary", "keywords", "risk"}


def validate(raw: str) -> dict:
try:
value = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError("模型输出不是合法 JSON") from exc
if set(value) != SCHEMA_KEYS:
raise ValueError("字段必须恰好是 summary、keywords、risk")
if not isinstance(value["summary"], str) or not value["summary"].strip():
raise ValueError("summary 必须是非空字符串")
if (not isinstance(value["keywords"], list)
or not all(isinstance(item, str) for item in value["keywords"])):
raise ValueError("keywords 必须是字符串列表")
if value["risk"] not in {"low", "medium", "high"}:
raise ValueError("risk 不是允许的值")
return value


def main() -> None:
if len(sys.argv) != 3:
raise SystemExit("用法:python run_case.py case.json output.json")
case_path, output_path = map(Path, sys.argv[1:])
case = json.loads(case_path.read_text(encoding="utf-8"))
text = case.get("text")
if not isinstance(text, str) or not text.strip():
raise SystemExit("case.json 的 text 必须是非空字符串")

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
response = client.responses.create(
model=os.environ["MODEL_NAME"],
instructions=(
"你是内容整理器。只返回 JSON,不要 Markdown。"
"字段必须为 summary(字符串)、keywords(字符串数组)、"
"risk(low、medium 或 high)。"
),
input=text,
temperature=0,
)
result = validate(response.output_text)
output_path.write_text(
json.dumps({"input": case, "result": result}, ensure_ascii=False, indent=2)
+ "\n",
encoding="utf-8",
)
print(f"已保存:{output_path}")


if __name__ == "__main__":
main()

运行两次即可得到两份快照:

1
2
3
python run_case.py case.json baseline.json
# 修改提示词、模型或参数后再运行
python run_case.py case.json candidate.json

代码中的 output_path 只会写入通过校验的结果。temperature=0 不能保证服务端永远返回完全相同的文字,因此它适合降低干扰,不是替代审查的办法。真实运行时,终端能确认文件是否生成;文件里的具体摘要必须以你的模型实际返回为准,不能预先假设。

编写确定性的差异审查器

新建 review_diff.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import json
import sys
from pathlib import Path


def load(path: str) -> dict:
data = json.loads(Path(path).read_text(encoding="utf-8"))
if set(data) != {"input", "result"}:
raise ValueError(f"{path} 不是预期快照")
return data


def review(old: dict, new: dict) -> list[str]:
notes = []
if old["input"] != new["input"]:
notes.append("输入发生变化,不能把本次差异归因于模型配置")
for field in ("summary", "risk"):
if old["result"][field] != new["result"][field]:
notes.append(f"字段 {field} 发生变化")
old_keywords = set(old["result"]["keywords"])
new_keywords = set(new["result"]["keywords"])
if old_keywords != new_keywords:
added = sorted(new_keywords - old_keywords)
removed = sorted(old_keywords - new_keywords)
notes.append(f"关键词新增={added},移除={removed}")
return notes or ["两份结果没有检测到差异"]


if __name__ == "__main__":
if len(sys.argv) != 3:
raise SystemExit("用法:python review_diff.py baseline.json candidate.json")
for line in review(load(sys.argv[1]), load(sys.argv[2])):
print(f"- {line}")

运行 python review_diff.py baseline.json candidate.json,程序只根据文件内容输出审查意见。集合比较用于忽略关键词顺序变化;摘要和风险等级仍按完整值比较。若业务关心关键词顺序,就应改为列表比较,而不是让工具替业务做决定。

如何把差异变成可执行检查

差异报告只是第一步。可以为每条规则增加明确结果:输入必须相同;risk 从 low 升到 high 时标记为高优先级;关键词数量超过上限时标记为人工复核;摘要变化则保留新旧文本供人阅读。规则应写在 Python 中并配固定 JSON 测试样例,不要只依赖一次人工观察。

如果结果包含个人资料或内部文档,快照本身就是敏感数据。保存前应脱敏,限制文件权限,并设置保留期限。日志不要打印环境变量,也不要把完整请求发送到公共错误追踪服务。模型输出不能因为“通过 JSON 校验”就自动获得业务上的可信度。

常见问题

为什么不让模型直接判断哪个版本更好? 模型可以作为辅助评审者,但它的判断也会变化,且可能忽略硬性规则。先用确定性代码检查字段、枚举和输入一致性,再把确实需要语义判断的少量样本交给人工或另一个评审流程。

两份结果完全一样,能说明改动有效吗? 不能。它只说明这组输入下没有观察到差异。评测应准备覆盖不同长度、边界风险和空字段的多条样例,并记录模型、提示词版本和参数。

调用失败时为什么不自动重试? 最小示例把网络调用和差异审查分开,避免把暂时性失败误当成模型结果。生产代码可对明确的超时或限流做有限指数退避,但认证错误和参数错误通常应立即失败;每次尝试也要记录错误类型而不是保存伪造结果。

小结

这个项目把一次模型调用扩展成了“固定输入—结构化输出—严格校验—快照保存—确定性比较—人工决策”的闭环。它的重点不是制造一个漂亮的差异页面,而是保留证据,让提示词、模型或参数变化可以被复核。AI 负责生成候选结果,Python 负责协议、规则和副作用边界;当综合项目继续变大时,这种分工比堆叠更多提示词更可靠。