第一阶段到第五阶段的基础主题已经覆盖:从一次 API 请求、多轮消息和生成参数,到错误处理、日志、评估与性能边界。本篇不再引入新的框架,而是做一个综合小项目:把同一组对话输入和配置保存下来,重复调用模型,并用文本差异帮助我们观察提示词或参数修改带来的变化。重点是“可回放”,不是假装模型每次都返回完全相同的文字。
先定义实验边界 实验器的输入是一份 JSON 文件,里面包含模型名、系统指令、对话消息和生成参数;输出是一份新的 JSON 记录,保存请求快照、模型返回文本、耗时和错误。程序只负责读取、请求、记录和展示差异,不发送邮件、不修改业务数据,也不把模型输出当成已经批准的内容。
“回放”有两种含义,需要区分。重新调用 API 是网络回放:它能验证当前模型和提示词的行为,但结果可能变化,也会消耗额度。读取已经保存的结果是离线回放:它不调用模型,适合调试展示和编写测试。下面的程序先实现前者,同时保留足够的记录,方便后者扩展。
准备环境和实验文件 在独立虚拟环境中安装官方 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="替换为你可用的模型名称"
不要把密钥写进 JSON、源码或 Git。新建 experiment.json:
1 2 3 4 5 6 7 { "instructions" : "你是一个简洁的 Python 学习助手。用中文回答,先给结论,再给一个小例子。" , "input" : [ { "role" : "user" , "content" : "解释 Python 中生成器和列表的一个区别。" } ] , "temperature" : 0.2 }
这里的 input 是 Responses API 接受的输入消息列表,instructions 单独放置系统级要求。若使用其他服务或兼容接口,应先查该服务的官方文档确认字段和参数,不要仅凭名称猜测兼容程度。
编写最小实验器 创建 replay_chat.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 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 60 import jsonimport osimport sysimport timefrom datetime import datetime, timezonefrom pathlib import Pathfrom openai import OpenAIdef load_case (path: Path ) -> dict : case = json.loads(path.read_text(encoding="utf-8" )) if not isinstance (case .get("instructions" ), str ): raise ValueError("instructions 必须是字符串" ) if not isinstance (case .get("input" ), list ) or not case ["input" ]: raise ValueError("input 必须是非空消息列表" ) if not all (item.get("role" ) in {"user" , "assistant" } and isinstance (item.get("content" ), str ) for item in case ["input" ]): raise ValueError("消息必须包含合法 role 和字符串 content" ) return case def run (case : dict ) -> dict : model = os.environ["MODEL_NAME" ] client = OpenAI(api_key=os.environ["OPENAI_API_KEY" ]) started = time.perf_counter() response = client.responses.create( model=model, instructions=case ["instructions" ], input =case ["input" ], temperature=case .get("temperature" , 0.2 ), ) return { "model" : model, "output" : response.output_text, "elapsed_ms" : round ((time.perf_counter() - started) * 1000 , 1 ), } def main () -> None : if len (sys.argv) != 3 : raise SystemExit("用法:python replay_chat.py experiment.json result.json" ) case_path, result_path = map (Path, sys.argv[1 :]) case = load_case(case_path) result = run(case ) record = { "created_at" : datetime.now(timezone.utc).isoformat(), "case" : case , "result" : result, } result_path.write_text( json.dumps(record, ensure_ascii=False , indent=2 ) + "\n" , encoding="utf-8" , ) print (f"已保存实验记录:{result_path} " ) if __name__ == "__main__" : main()
运行:
1 python replay_chat.py experiment.json result-1.json
responses.create 发起请求,output_text 取出 SDK 汇总的文本结果。程序没有打印一个预先写好的“成功答案”,因为真实输出取决于模型、服务状态和配置;可验证的结果是文件是否生成、记录是否包含输入快照、输出和耗时。若希望使用兼容服务,只有在其文档明确支持相同 API 时,才额外配置对应的客户端地址。
做一次有意义的对比 复制一份配置为 experiment-creative.json,只把 temperature 改为 0.8,再运行一次:
1 python replay_chat.py experiment-creative.json result-2.json
可以用标准库做一个简单差异查看器:
1 2 3 4 5 6 7 8 9 import jsonfrom pathlib import Pathold = json.loads(Path("result-1.json" ).read_text(encoding="utf-8" )) new = json.loads(Path("result-2.json" ).read_text(encoding="utf-8" )) print ("旧配置:" , old["case" ]["temperature" ])print ("新配置:" , new["case" ]["temperature" ])print ("\n--- 旧输出 ---\n" , old["result" ]["output" ])print ("\n--- 新输出 ---\n" , new["result" ]["output" ])
一次对比不能证明哪个参数更好。更可靠的做法是准备多条固定问题,逐个改变一个变量,并记录结构、正确性、长度、延迟和费用。若同时修改提示词、模型和参数,结果变化就无法归因。实验记录中的 case 快照正是为了避免“凭印象比较”。
常见问题 为什么同样的输入仍可能得到不同答案? 模型生成具有随机性,服务端模型版本也可能变化。低温度只能降低变化倾向,不能把自然语言输出变成严格的函数。需要稳定协议时,应增加结构化输出、程序校验和明确的失败处理。
能不能把完整 API 响应都保存下来? 调试阶段可以按数据合规要求保存必要字段,但不要默认保存敏感内容。对话可能包含个人信息或内部资料;日志应脱敏、限权并设置保留期限。示例只保存文本结果,是为了突出实验流程。
请求失败时如何保留记录? 当前代码在请求成功后才写结果。实际项目应捕获超时、认证失败和限流错误,在记录中写入错误类型、尝试次数和时间,同时避免把密钥或完整敏感请求写入日志。重试也应设置上限,不能无限调用。
离线测试是否必须调用模型? 不必须。把 run 改成接收一个客户端参数,测试时注入返回固定 output_text 的假客户端,就能测试配置校验、记录格式和差异逻辑。真实 API 只作为少量集成测试运行。
小结 这个综合项目把多轮输入、生成参数、环境变量、耗时记录和结果比较串成了一个可回放的实验闭环。回放并不等于复现完全相同的文字,而是保留足够的输入和配置,让变化可以被观察、讨论和再次验证。模型负责生成候选内容,Python 负责配置边界、记录证据和隔离副作用;这正是把一次演示逐步变成可维护 AI 功能的基础。