用JSON管理测试用例、自动断言(关键词存在、JSON合法性、长度约束),可免费用本地模型跑实验,输出diff化的pass/fail报告。
大多数我见过的开发者在迭代 prompt 时都是这样做的:把 prompt 粘贴到 playground,改一个词,肉眼看一下输出,再改。一小时后,你已经花了不少钱在 API 调用上,却仍然无法回答那个唯一重要的问题:这次改动到底有没有效果?
问题不在模型本身,而在于没有度量。你不会不发测试就提交代码变更,但 prompt 却经常被 YOLO 式地直接推到生产环境。
这篇文章展示一个小而可复用的 Python Prompt 测试工具(harness),外加一个利用免费模型访问来运行的 workflow,这样实验阶段完全不花钱。你最终会得到一份通过/失败报告,可以在不同 prompt 版本之间做 diff 对比。
核心思想:prompt 是代码,要像测代码一样测它
一个 prompt 测试由三部分组成:
一组固定输入——真实用例,包括那些烦人的边界情况。
一个断言——低成本、自动化的检查:关键词是否存在、JSON 是否合法、长度限制、正则匹配。
一个运行器——执行每一个(prompt 版本 × 输入 × 断言)的组合,并打印报告。
保持简单。复杂的 LLM-as-judge 打分可以以后再加;大多数 prompt 回归问题通过平淡的断言就能捕获。
把测试用例存为 JSON,这样非工程师也能编辑:
// cases.json
[
{
"name": "extracts email",
"input": "Reach me at sara@example.com before Friday.",
"expect": { "contains": ["sara@example.com"], "max_chars": 80 }
},
{
"name": "no email present",
"input": "Call the office tomorrow morning.",
"expect": { "contains": ["none"], "max_chars": 80 }
},
{
"name": "multiple emails",
"input": "CC a@x.com and b@y.com on the reply.",
"expect": { "contains": ["a@x.com", "b@y.com"], "max_chars": 120 }
}
]
运行器。通过环境变量指向任意 OpenAI 兼容端点,这样同一套工具既可以对接付费 API,也可以对接本地模型或免费托管模型:
# harness.py
import json, os, sys, time
from openai import OpenAI
client = OpenAI(
base_url=os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1"),
api_key=os.environ.get("LLM_API_KEY", "unused"),
)
MODEL = os.environ.get("LLM_MODEL", "gpt-4o-mini")
PROMPT_TEMPLATE = open(sys.argv[1]).read() # prompt file passed as arg
cases = json.load(open("cases.json"))
def check(text, expect):
fails = []
for kw in expect.get("contains", []):
if kw.lower() not in text.lower():
fails.append(f"missing '{kw}'")
if len(text) > expect.get("max_chars", 10**9):
fails.append(f"too long ({len(text)} chars)")
return fails
results = []
for c in cases:
start = time.time()
resp = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": PROMPT_TEMPLATE},
{"role": "user", "content": c["input"]},
],
temperature=0,
)
out = resp.choices[0].message.content
fails = check(out, c["expect"])
results.append((c["name"], not fails, fails, round(time.time() - start, 2)))
passed = sum(1 for r in results if r[1])
print(f"\n{passed}/{len(results)} passed against {MODEL}\n")
for name, ok, fails, secs in results:
status = "PASS" if ok else "FAIL"
print(f"[{status}] {name} ({secs}s)" + (f" -> {', '.join(fails)}" if fails else ""))
pip install openai
LLM_BASE_URL="https://your-endpoint/v1" \
LLM_API_KEY="your-key" \
LLM_MODEL="some-model" \
python harness.py prompt_v3.txt
现在迭代 prompt 的流程变成:编辑 prompt_v3.txt,重新运行,读取报告。当 v4 在"no email present"这个用例上发生回归时,你清楚地知道是哪个用例出了问题,而不是凭模糊的感觉判断输出变差了。
免费算力在哪里派上用场
Prompt 工作中开销大的地方在于量:一打测试用例 × 几十次 prompt 修改 × 几个候选模型,在付费 API 上累加得很快。便宜的解决方案是:整个迭代循环都对接免费模型访问,最后再用生产模型跑一次 harness 作为最终关卡。
声明:本文是 MonkeyCode 产品推广的一部分。
免费层的一个选择是 MonkeyCode,它提供免费模型访问,外加一个可作为运行器环境的免费服务器选项——如果你不想让 harness 在笔记本上跑或消耗 CI 时间,这很方便。由于上面的 harness 对端点是无关的,你只需把 LLM_BASE_URL 和 LLM_MODEL 设置成那里可用的值,workflow 无需任何改变。如果你正在搭建类似的循环,这里是跑免费迭代阶段的好地方。
何时信任免费层结果决策表
原则:用免费模型回答差异问题("v4 打败 v3 了吗?"),用付费/生产模型回答绝对问题("这够格发布了吗?")。
局限性,坦诚说
层级间的模型漂移。在免费模型上调好的 prompt,在你的生产模型上表现可能不同。发布前一定要在真实模型上重新跑完整套测试——这是关卡,不是走过场。
断言很浅层。contains 检查捕获不了微妙的品质回归。任何面向用户的内容,对前几个失败用例都要加上人工复核环节。
免费层会变。任何免费服务的可用量、速率限制和模型选择都可能不经通知地变化。不要让你的 CI 管道依赖某一个;把它当作便利层就好。
并发有影响。如果把这个 harness 扩缩,免费端点通常会先被限流。保持测试套件小而顺序执行。
如果你一周只跑几次 prompt,playground 确实就够了——harness 在这里是多余的。但当你有多个人在编辑 prompt、prompt 内嵌在产品中、或者需要在用户发现之前捕获回归时,它就值得了。
更广泛地说,这不是关于某个特定提供商的问题。Prompt 迭代没有度量就是在花冤枉钱做猜测。一旦写好这个平淡的测试工具,利用任何你能获取的免费算力跑起来,把付费调用留到真正需要做决策的时候。