先承认一个事实:大多数「调优」没有基线

改一句提示词、换一个模型版本,功能看起来更好了——但「更好」是拿什么比的?如果只是随手试了两条输入,那既说明不了变好,也发现不了变差。模型行为是分布,不是开关;没有一组固定用例,「感觉变差了」就永远只能是感觉,团队也只能靠回滚和争论来收场。

promptfoo 解决什么

promptfoo 把评测写成声明式配置:一份 YAML 里列出用例(输入与期望)、断言(怎么判通过)和待测对象(不同提示词、不同模型)。一条命令跑完,输出一张横向对照表,可以直接看逐条结果,也能接进 CI;红队与漏洞扫描作为另一类用例一起跑,用它检查提示注入、越权之类的边界。

# 快速起手
npx promptfoo@latest init
npx promptfoo@latest eval
npx promptfoo@latest view   # 打开本地结果面板,逐条对照

# 在 CI 里跑,通过率不达标就失败
npx promptfoo@latest eval --ci --output results.json

第一步:把散落的用例收成一份配置

prompts:
  - file://prompts/support.txt
providers:
  - openai:gpt-6
  - anthropic:claude-opus-4.5
tests:
  - vars:
      question: "订单 12345 什么时候到?"
    assert:
      - type: contains
        value: "物流"
      - type: not-contains
        value: "我无法"
      - type: is-json

用例不用多,先覆盖真实业务里最容易出错的三四类:格式被破坏、长尾事实答错、该拒绝时没拒绝、以及多语言或术语。把这些写成 vars 与 assert,跑得起来比写得全重要;后续再按线上真实失败样本往里加。

第二步:跑一次基线,把「现在的水平」固定下来

第一次跑的目的不是变好,是留下一个可对照的起点:记录通过率、逐条失败项、耗时与 token 成本。这份基线和配置一起进版本管理,之后每次改动都与它比,而不是与记忆比。凡是「我觉得更好了」,都要能落到这张表上。

该断言什么,不该断言什么

  • 优先用规则断言:包含 / 不包含、正则、JSON Schema、耗时上限,确定性高、可复现
  • 主观质量别只看总分:如果要用模型评分,就固定评分模型与评分提示词的版本,并保留逐条理由
  • 别只测顺风用例:空输入、超长输入、恶意输入、多语言都要进集合
  • 用例要能公开复现:脱敏后放进仓库,否则换个人就跑不了
  • 红队扫描当成一类用例:不是一次性的安全审计,而是每次发布前都跑一遍

边界与几个坑

  • 它评的是你写的用例,不等于「功能好不好」:用例设计偏了,结论就偏
  • 模型评分本身有噪声:同一份输出跑两次可能不同,多次取平均并记录波动
  • 别拿评测集当训练集:反复针对同一批用例调优会过拟合,要留一批没参与调优的样本做最终验收
  • 密钥不要写进配置:用环境变量或 provider 的密钥管理
一句话:把验收从「点几下试试」换成「跑一条命令」,团队才有资格说这个功能是变好了,还是变差了。