开源 AI-Eval:Prompt 评估系统,用单元测试跑
TLDR: ai-eval 是一套 Go 写的 Prompt 评估系统。把 Prompt 测试当单元测试跑------写 YAML 定义用例,配评估器(模式匹配、LLM Judge、RAG、Agent、安全检测),跑 pass@k 处理 LLM 的不确定性。带 Web API、Leaderboard、CI 集成,支持 MMLU/GSM8K/HumanEval 标准 Benchmark。
| 维度 | 能力 |
|---|---|
| 评估器 | 模式匹配、LLM Judge、语义相似度、RAG、Agent、安全性 |
| Benchmark | MMLU、GSM8K、HumanEval(Docker 沙箱) |
| Provider | Claude (Anthropic)、OpenAI |
| 接入方式 | CLI、Web API、CI/CD |
| 存储 | SQLite + Leaderboard |
问题
改 Prompt 是个玄学活。
加了一句"请用中文回答",准确率从 85% 掉到 60%。把"你是专家"换成"你是资深工程师",效果又回来了。这种事我遇到不止一次。
传统代码有单元测试兜底,改了跑一遍就知道有没有 break。但 Prompt 的输出是非确定性的------同一个输入跑三次,三次结果可能都不一样。靠人眼盯?Prompt 一多根本盯不过来。
ai-eval 就是来解决这个问题的:给 Prompt 写测试,像跑 go test 一样跑评估。
架构
系统分六层,CLI 和 Web API 都走同一个 Core Engine,评估器通过 Registry 模式挂载。
跑一次评估的流程长这样:
每个 test case 跑 N 次 trial,每次都过评估器打分,最后算 pass@k。结果存 SQLite,下次可以对比。
怎么用
思路很直接:一个 YAML 定义 Prompt,一个 YAML 定义测试用例。
Prompt 定义:
yaml
name: code-review
version: "1.0"
is_system_prompt: true
template: |
你是一个代码审查助手。
请审查以下代码:{{.code}}
tools:
- name: suggest_fix
description: 建议修复方案
input_schema: {...}
测试用例:
yaml
prompt: code-review
suite: basic
cases:
- id: null-check
input:
code: "if (user) { user.name }"
evaluators:
- type: contains
expected: ["null", "undefined"]
- type: llm_judge
criteria: "应该指出潜在的空指针问题"
一个 case 可以挂多个评估器。contains 检查输出里有没有关键词,llm_judge 让另一个 LLM 当裁判打分。两个都过了才算通过。
评估器
评估器是这套系统的核心,用 Registry 模式实现,加新的评估器只要实现一个接口。目前有 5 类 16 个:
模式匹配 ------ 最快,不调 LLM
- •
exact: 精确匹配 - •
contains: 包含检查 - •
regex: 正则 - •
json_schema: JSON Schema 校验
LLM 评估 ------ 用 LLM 当裁判
- •
llm_judge: 根据 criteria 打 1-10 分(Likert 量表),这个用得最多 - •
similarity: 语义相似度 - •
factuality: 事实准确性
RAG 专用
- •
faithfulness: 回答是否忠于检索内容 - •
relevancy: 检索相关性 - •
precision: 检索精度
Agent 专用
- •
task_completion: 任务完成度 - •
tool_selection: 工具选择对不对 - •
efficiency: 执行效率(调用次数、Token 消耗)
安全性
- •
hallucination: 幻觉检测 - •
toxicity: 毒性检测 - •
bias: 偏见检测
每个评估器返回统一的四元组:Passed(布尔)、Score(0-1)、Message、Details。写自定义评估器也是返回这个结构。
pass@k
LLM 输出不确定,跑一次不能说明问题。pass@k 处理这个:
pass@k = 1 - (1 - pass_rate)^k
就是跑 k 次至少有一次通过的概率。pass_rate 0.7 的情况下:
| k | pass@k |
|---|---|
| 1 | 0.700 |
| 3 | 0.973 |
| 5 | 0.998 |
配置里指定 trials(跑几次)和 threshold(通过阈值)就行。我一般设 trials=3,threshold=0.8。
Benchmark
除了自定义测试,内置三个标准 Benchmark:
| Benchmark | 干什么 | 细节 |
|---|---|---|
| MMLU | 通用知识 | 57 个学科的多选题 |
| GSM8K | 数学推理 | 小学数学应用题 |
| HumanEval | 代码生成 | 默认跑在 Docker 沙箱里 |
HumanEval 需要执行生成的代码来验证正确性,所以有个安全开关:
bash
# 默认 Docker 沙箱(推荐)
AI_EVAL_ENABLE_CODE_EXEC=1 eval benchmark --dataset humaneval --sample-size 50
# 直接在宿主机跑(别在生产环境用)
AI_EVAL_ENABLE_CODE_EXEC=1 AI_EVAL_SANDBOX_MODE=host eval benchmark --dataset humaneval
跑完记录准确率、Token 用量、延迟,按类别细分。结果存 SQLite,eval leaderboard --dataset mmlu 看历史对比。
Benchmark 的流程和评估不太一样,走的是专门的 Benchmark Runner:
Agent 测试
Agent 场景有专门支持。测试用例可以配 Tool Mock,不用真的去调外部 API:
yaml
cases:
- id: search-task
input:
task: "搜索最新的 Go 1.23 特性"
tool_mocks:
search:
- query: "Go 1.23 features"
response: "Go 1.23 支持 range over func..."
evaluators:
- type: tool_selection
expected_tools: ["search"]
- type: task_completion
criteria: "应该准确总结 Go 1.23 的新特性"
执行时自动处理 Tool-calling loop,用 mock 响应替代真实调用。这样测试是确定性的,不依赖外部服务。
Web API
ai-eval 不只是个 CLI 工具。cmd/server 起一个 Web API 服务,带 REST 接口和静态页面:
api/
├── server.go # HTTP server
├── routes.go # 路由注册
├── handlers.go # 评估相关 handler
├── leaderboard.go # Leaderboard 查询接口
├── middleware.go # 日志、CORS、认证
└── security_config_test.go
Leaderboard 接口可以查历史 Benchmark 结果,按模型、数据集筛选。web/static/ 下有个简单的前端页面。
说实话这个 Web UI 目前比较简陋,但 API 本身够用了,可以自己接 Grafana 或者写个 Dashboard。
Red Team 测试
internal/redteam/ 里有个对抗性测试生成器。给它一个 Prompt,它会自动生成试图绕过安全限制的输入,配合 safety 评估器(toxicity、bias、hallucination)跑一遍,看 Prompt 的防御能力。
这个功能我觉得挺有意思,但目前生成的攻击模式还比较基础。
CI 集成
eval ci --threshold 0.8 跑完评估,分数低于阈值直接返回非零退出码。
internal/ci/github.go 里有 GitHub 集成------可以在 PR 评论里贴评估结果。典型用法:Prompt 改动走 PR,CI 自动跑评估,不达标就 block merge。
bash
# CI 模式,阈值 0.8
eval ci --threshold 0.8
# 跑指定 Prompt 的测试
eval run --prompt code-review --suite basic --trials 3
# 对比两个版本
eval compare --prompt code-review --versions v1,v2
# 让 LLM 分析失败用例,建议怎么改 Prompt
eval optimize --prompt code-review --suite basic
optimize 命令会分析失败用例,用 LLM 生成改进建议,直接输出修改后的 Prompt。省了不少手动分析的功夫。
项目结构
ai-eval/
├── cmd/
│ ├── eval/ # CLI 入口
│ └── server/ # Web API server
├── api/ # HTTP handlers、路由、中间件
├── configs/
│ └── config.yaml.example
├── internal/
│ ├── app/ # 编排层(run orchestrator)
│ ├── benchmark/ # MMLU、GSM8K、HumanEval
│ ├── ci/ # GitHub CI 集成
│ ├── claude/ # Claude API client
│ ├── config/ # 配置加载
│ ├── evaluator/ # 评估器实现
│ │ ├── agent/ # Agent 评估器
│ │ ├── rag/ # RAG 评估器
│ │ └── safety/ # 安全评估器
│ ├── generator/ # 测试用例生成
│ ├── leaderboard/ # Leaderboard 存储
│ ├── llm/ # LLM Provider(Claude、OpenAI)
│ ├── optimizer/ # Prompt 优化(失败分析 + 改写)
│ ├── prompt/ # Prompt 加载 + 模板渲染
│ ├── redteam/ # Red Team 对抗测试
│ ├── runner/ # 测试执行引擎
│ ├── store/ # SQLite 存储层
│ └── testcase/ # 测试用例加载
├── prompts/ # Prompt 定义(YAML)
├── tests/ # 测试用例(YAML)
└── web/static/ # Web UI 静态文件
适合什么场景
我觉得这几个场景用起来收益最大:
Prompt 迭代 ------ 改之前先把现有行为写成测试。改完跑一遍,有没有 regression 一目了然。比手动对比强太多。
模型迁移 ------ 从 Claude 切到 GPT,或者升级模型版本,同一套测试跑一遍就知道新模型在你的场景下行不行。我们从 Sonnet 4.5 切到 Opus 4.5 的时候就是这么验的。
CI 质量门禁 ------ Prompt 改动走 PR,CI 自动跑评估,不达标 block merge。这个对团队协作帮助很大,不用每次 review Prompt 改动都靠人肉判断。
RAG 调优 ------ faithfulness、relevancy、precision 三个评估器量化 RAG 效果。调了检索策略之后跑一遍,比人眼看靠谱。
最后
ai-eval 解决的问题很具体:怎么系统化地测试 Prompt,不靠感觉靠数据。
如果你在维护多个 Prompt,或者团队里有人改 Prompt 改出过事故,可以试试。Go 写的,go install 一行搞定。
项目地址:github.com/stellarlinkco/ai-eval
欢迎提 Issue 和 PR。特别缺的是:更多 Provider 支持(Gemini、Mistral、国产模型)、多模态评估、更好的 Web Dashboard。
License: AGPL-3.0,商用需要单独授权