当提示词、模型或生成参数发生变化时,最容易被忽略的问题不是“能不能调用模型”,而是“这次改动到底改变了什么”。本篇在前面基础主题已经覆盖的前提下,做一个小型综合项目:调用模型生成结构化结果,保存输入和输出,再用 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/activatepython -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 jsonimport osimport sysfrom pathlib import Pathfrom openai import OpenAISCHEMA_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 jsonimport sysfrom pathlib import Pathdef 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 负责协议、规则和副作用边界;当综合项目继续变大时,这种分工比堆叠更多提示词更可靠。