开源 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。
开源 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 模式挂载。
graph TB
subgraph CLI["CLI (cmd/eval)"]
RUN[eval run]
BENCH[eval benchmark]
CMP[eval compare]
OPT[eval optimize]
LB[eval leaderboard]
end
subgraph API["Web API (api/)"]
REST[REST Endpoints]
WEB[Web UI]
end
subgraph Core["Core Engine"]
RUNNER[Runner]
LOADER[Prompt Loader]
TC[Test Case Loader]
end
subgraph LLM["LLM Providers (internal/llm)"]
CLAUDE[Claude / Anthropic]
OPENAI[OpenAI / Compatible]
end
subgraph Evaluators["Evaluators (internal/evaluator)"]
direction LR
BASIC["Basic\ncontains | exact | regex"]
SEMANTIC["Semantic\nllm_judge | factuality | similarity"]
SAFETY["Safety\ntoxicity | bias | hallucination"]
AGENT["Agent\ntool_selection | efficiency"]
RAG["RAG\nfaithfulness | relevancy | precision"]
end
subgraph Benchmark["Benchmarks (internal/benchmark)"]
MMLU[MMLU]
GSM8K[GSM8K]
HUMANEVAL[HumanEval]
end
subgraph Storage["Storage"]
SQLITE[(SQLite)]
end
CLI --> Core
API --> Core
Core --> LLM
Core --> Evaluators
BENCH --> Benchmark
Benchmark --> LLM
LB --> SQLITE
BENCH --> SQLITE
RUNNER --> LOADER
RUNNER --> TC
style CLI fill:#e1f5fe,stroke:#01579b
style API fill:#e8f5e9,stroke:#1b5e20
style Core fill:#fff3e0,stroke:#e65100
style LLM fill:#f3e5f5,stroke:#4a148c
style Evaluators fill:#fce4ec,stroke:#880e4f
style Benchmark fill:#e0f2f1,stroke:#004d40
style Storage fill:#f5f5f5,stroke:#616161
跑一次评估的流程长这样:
sequenceDiagram
participant User
participant CLI
participant Runner
participant LLM as LLM Provider
participant Eval as Evaluators
participant DB as SQLite
User->>CLI: eval run --prompt example
CLI->>Runner: Load prompt + test cases
loop For each test case
loop For each trial (1..N)
Runner->>LLM: Send prompt + input
LLM-->>Runner: Response
Runner->>Eval: Evaluate response
Eval-->>Runner: Score + Pass/Fail
end
end
Runner->>DB: Store results
Runner-->>CLI: Suite results
CLI-->>User: Pass/Fail report
每个 test case 跑 N 次 trial,每次都过评估器打分,最后算 pass@k。结果存 SQLite,下次可以对比。
怎么用
思路很直接:一个 YAML 定义 Prompt,一个 YAML 定义测试用例。
Prompt 定义:
name: code-review
version: "1.0"
is_system_prompt: true
template: |
你是一个代码审查助手。
请审查以下代码:{{.code}}
tools:
- name: suggest_fix
description: 建议修复方案
input_schema: {...}
测试用例:
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 需要执行生成的代码来验证正确性,所以有个安全开关:
# 默认 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:
sequenceDiagram
participant User
participant CLI
participant BM as Benchmark Runner
participant DS as Dataset
participant LLM as LLM Provider
participant LB as Leaderboard
User->>CLI: eval benchmark --dataset mmlu
CLI->>DS: Load questions
DS-->>BM: Questions[]
loop For each question
BM->>LLM: Question prompt
LLM-->>BM: Answer
BM->>DS: Evaluate answer
DS-->>BM: Score
end
BM->>LB: Save result
BM-->>User: Accuracy report
Agent 测试
Agent 场景有专门支持。测试用例可以配 Tool Mock,不用真的去调外部 API:
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。
# 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,商用需要单独授权
更多推荐



所有评论(0)