第一阶段到第五阶段的基础主题已经覆盖:从一次 API 请求、多轮消息和生成参数,到错误处理、日志、评估与性能边界。本篇不再引入新的框架,而是做一个综合小项目:把同一组对话输入和配置保存下来,重复调用模型,并用文本差异帮助我们观察提示词或参数修改带来的变化。重点是“可回放”,不是假装模型每次都返回完全相同的文字。

先定义实验边界

实验器的输入是一份 JSON 文件,里面包含模型名、系统指令、对话消息和生成参数;输出是一份新的 JSON 记录,保存请求快照、模型返回文本、耗时和错误。程序只负责读取、请求、记录和展示差异,不发送邮件、不修改业务数据,也不把模型输出当成已经批准的内容。

“回放”有两种含义,需要区分。重新调用 API 是网络回放:它能验证当前模型和提示词的行为,但结果可能变化,也会消耗额度。读取已经保存的结果是离线回放:它不调用模型,适合调试展示和编写测试。下面的程序先实现前者,同时保留足够的记录,方便后者扩展。

准备环境和实验文件

在独立虚拟环境中安装官方 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="替换为你可用的模型名称"

不要把密钥写进 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 json
import os
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

from openai import OpenAI


def 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 json
from pathlib import Path

old = 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 功能的基础。