Eval-core/evalcore

GitHub: Eval-core/evalcore

EvalCore 是一个免费的离线确定性 LLM 评估工具,通过在 CI 中记录和回放 AI 行为来自动检测每次代码变更导致的性能回归。

Stars: 13 | Forks: 3

EvalCore

在你的用户之前,发现 AI 性能的下降。

CI crates.io Documentation Apache-2.0

EvalCore grading a support bot against the policy each answer must cite: four cases pass, a compliance gate holds, and the run exits 0, all offline.

修改一个 prompt,更换一个模型,升级一个依赖。有什么东西崩坏了吗?你通常 是从用户那里得知的。 EvalCore 记录你的 AI 的行为方式,并根据该记录检查每一次变更。它是一个二进制文件,由一个 YAML 文件驱动,并且可以离线运行且成本为 $0, 因此 eval suite 可以成为每个 pull request 上的阻塞检查,而不是某个 人记得去运行的每周任务。 你永远不需要为了使用它而编写 Rust。Target 通过 HTTP 或 shell 通信,自定义 scorer 通过 stdin 和 stdout 使用 JSON 通信,而 judge 可以是任何兼容 OpenAI 的 endpoint。 **[文档](https://eval-core.github.io/evalcore/)** · [快速开始](https://eval-core.github.io/evalcore/getting-started/quickstart/) · [crates.io](https://crates.io/crates/evalcore) · [发布版本](https://github.com/eval-core/evalcore/releases) · [更新日志](CHANGELOG.md) ## 目录 - [安装](#install) - [快速开始](#quickstart) - [阅读输出](#reading-the-output) - [工作原理](#how-it-works) - [你可以用它做什么](#what-you-can-do-with-it) - [Target 和 scorer](#targets-and-scorers) - [在 CI 中运行](#running-in-ci) - [设计原则](#design-principles) - [维护者](#maintainers) - [贡献](#contributing) ## 安装 ``` cargo install evalcore ``` 或者从[发布页面](https://github.com/eval-core/evalcore/releases)下载适用于 Linux x64 或 macOS(x64 / arm64)的 预编译二进制文件。更多选项请查看 [安装指南](https://eval-core.github.io/evalcore/getting-started/installation/)。 ## 快速开始 本仓库提供了一个可运行的示例:一个小型银行支持机器人,其评分 标准是每个回答是否引用了它所依赖的策略。它不需要 API key,也不会发起 任何网络调用。 ``` git clone https://github.com/eval-core/evalcore cd evalcore cargo run -p evalcore -- run examples/quickstart/evals.yaml ``` ``` PASS late-refund (8ms) PASS fee-dispute (8ms) PASS card-lost (8ms) PASS wire-eta (8ms) 4 passed, 0 failed, 4 total GATE PASS pass_rate >= 0.95 (actual 1.00) PASSED ``` 在交互式终端中,状态词会带有颜色(绿色表示通过,红色表示失败), 在运行过程中,一个 spinner 会在 stderr 上统计用例数量,而最后的 `PASSED` / `FAILED` 会加粗显示——但这些文字始终存在,因此不依赖 颜色。在管道、重定向或 `NO_COLOR` 环境下,输出与上面的纯文本 完全相同:确定性的、可用 grep 搜索的,并且可以安全地粘贴到 pull request 中。 `--color auto|always|never`、`--progress auto|never` 和 `-q/--quiet`(仅显示失败) 可以对其进行调整;机器报告器和 `--output` 文件永远不会带有颜色。 一个 suite 由两个文件组成。YAML 说明要运行什么以及如何评分: ``` # evals.yaml targets: support-bot: type: shell # your real app goes here cmd: "sh examples/quickstart/bot.sh" datasets: - file: cases.jsonl scorers: - type: contains # grounding: cite a policy value: "policy" case_sensitive: false - type: regex # specificity: cite a numbered rule pattern: "policy [0-9.]+" run: gates: - type: pass_rate # a floor over the whole run min: 0.95 ``` JSONL 包含各个用例。`context` 是 RAG suite 进行评分时所依据的 检索到的证据。Scorer 可以看到它,但 target 永远看不到: ``` {"id": "late-refund", "input": "It has been three weeks and my refund still has not shown up.", "context": ["Policy 4.2: Approved refunds are processed within 30 business days."]} {"id": "wire-eta", "input": "How long will an international wire transfer take?", "context": ["Policy 5.3: International wire transfers settle within 3 to 5 business days."]} ``` ## 阅读输出 终端报告中的每一个字符都有特定的含义。这是一次带有 一个失败用例的运行,并附有注释。 **每个用例的行**,每个用例一行,始终按数据集顺序排列: ``` PASS late-refund (8ms) ──┬─ ─────┬───── ──┬─ │ │ └─ latency, measured by the target itself │ └────────── case id, straight from your cases.jsonl └────────────────── every scorer passed FAIL fee-dispute exact: expected "60 days", got "we will look into it" ──┬── ─────────────────────┬────────────────────── │ └─ that scorer's reason, verbatim └─ which scorer objected (one line per failing scorer) ``` 一个从未产生输出的用例会报告原因而不是分数,因为 target 错误是一个带有原因的失败用例,而不是崩溃: ``` FAIL wire-eta target error: connection refused ``` **摘要行**,接着是每个 gate 一行: ``` 2 passed, 1 failed, 3 total · 210 tokens · $0.0020 · 1 flaky ─────────┬────────────────── ────┬───── ───┬─── ───┬── │ │ │ └─ cases whose trials │ │ │ disagreed with each other │ │ └─ your declared rates × those tokens │ └─ reported by the provider (or read from a trace) └─ a case passes when every scorer passes GATE FAIL pass_rate >= 0.95 (actual 0.67) ──┬─ ──┬─ ────────┬─────── ────┬───── │ │ │ └─ what the run actually scored │ │ └─ the floor you declared under run.gates │ └─ this gate's verdict └─ gate lines appear only when you configure gates ``` 摘要行的最后三个片段仅在适用时出现:当运行报告了 token 使用量和成本时,当某个用例运行了多次 trial 时出现 `flaky`。 一个不包含这些信息的运行只会打印 `4 passed, 0 failed, 4 total`。 **判定结果**,始终在最后,一个词: ``` FAILED · 2 regressed, 1 new ──┬─── ────────┬───────── │ └─ a clause only when a baseline explains the failure └─ PASSED / FAILED, matching the exit code exactly ``` 它反映了*整个*契约 —— 用例、gate 以及任何 baseline —— 因此它 永远不会与退出代码不一致。当判定结果为 `PASSED` 时,`evalcore run` 退出代码为 **0**,否则为 **1**。这就是整个 CI 契约。 其他输出格式:用于完整结果树的 `--reporter json`, 用于 CI 测试面板的 `--reporter junit`,以及用于 你可以附加到 pull request 的独立页面的 `--html report.html`。请参阅 [CLI 参考](https://eval-core.github.io/evalcore/reference/cli/)。 ## 工作原理 ``` cases.jsonl evals.yaml │ │ │ id, input, │ targets, scorers, │ expected, context │ gates, trials └───────────┬────────────┘ ▼ ┌───────────────┐ ┌──────────────────────────┐ │ TARGET │◄──────►│ record / replay cache │ │ │ │ .evalcore/cache.db │ │ shell · http │ │ │ │ openai · trace│ │ keyed on a hash of the │ └───────┬───────┘ │ canonical request │ │ └──────────────────────────┘ │ output text, tokens, latency, trajectory ▼ ┌───────────────┐ │ SCORERS │ run in order, all of them, every case │ │ │ contains · exact · regex · json-schema · similarity │ judge · subprocess · trajectory └───────┬───────┘ │ a case passes when every scorer passes ▼ ┌───────────────┐ │ GATES │ floors over the whole run │ │ pass_rate · mean_score · accuracy · macro_f1 └───────┬───────┘ │ ▼ exit 0 or 1 ──► CI ``` 三个属性在任何地方都成立,它们是这个工具是有用 的、而不仅仅是方便的原因: **确定性。** 相同的输入产生相同的输出。结果保持 数据集顺序,报告器是纯函数,除了延迟之外,没有任何用户可见的东西会读取 时钟。这正是使缓存值得信赖的原因。 **记录一次,永远重放。** 每次对可缓存 target 的调用都会被记录到 本地的 SQLite 文件中,以规范化请求的哈希值作为键。提交这个文件, CI 就会重放它,因此该任务不会发起网络调用,不需要 API key,并且 成本为零。 ``` evalcore run evals.yaml # auto (default): replay hits, record misses evalcore run evals.yaml --cache replay # CI: cache only, a miss fails the case evalcore run evals.yaml --cache live # re-record everything evalcore run evals.yaml --cache off # bypass ``` 更改模型、URL 或用例的输入都会更改键,因此过时的 记录永远无法悄悄地为你未发出的请求提供答案。Shell target 永远不会被缓存,因为它们运行的是本地代码,这些代码可以在不更改 配置的情况下发生改变。 **失败即数据。** Target 错误变成带有原因的失败用例, scorer 错误变成带有原因的失败分数。运行永远不会 panic,并且一个 糟糕的用例永远不会中止整个 suite。 ## 你可以用它做什么 | 你想要 | 使用 | 指南 | |---|---|---| | 阻止回归而不是要求完美 | `--baseline` | [Gate 和 baseline](https://eval-core.github.io/evalcore/guides/gates-and-baselines/) | | 为整个运行设定质量底线 | `run.gates` | [Gate 和 baseline](https://eval-core.github.io/evalcore/guides/gates-and-baselines/) | | 停止信任单个幸运样本 | `run.trials` | [Trial 和统计数据](https://eval-core.github.io/evalcore/guides/trials-and-statistics/) | | 决定更便宜的模型是否足够好 | `--matrix a,b` | [比较模型](https://eval-core.github.io/evalcore/guides/comparing-models/) | | 评估一个 agent *做了*什么,而不仅仅是它说了什么 | `trace` + `trajectory` | [Agent 和 trace](https://eval-core.github.io/evalcore/guides/agents-and-traces/) | | 评估你已部署的 REST API | `type: http` | [评估 REST API](https://eval-core.github.io/evalcore/guides/evaluating-rest-apis/) | | 根据检索到的 context 进行评分 | 用例 `context` | [RAG 评估](https://eval-core.github.io/evalcore/guides/rag-evaluation/) | | 使用任何断言都无法表达的 rubric 进行评分 | `type: judge` | [LLM-as-judge](https://eval-core.github.io/evalcore/guides/llm-as-judge/) | | 跟踪支出并设置上限 | `cost` + `budget_usd` | [成本和预算](https://eval-core.github.io/evalcore/guides/cost-and-budgets/) | | 使用 Python 或任何语言进行评分 | `type: subprocess` | [自定义 scorer](https://eval-core.github.io/evalcore/guides/custom-scorers/) | | 浏览过去的运行并比较任意两个 | `evalcore serve` | [运行历史](https://eval-core.github.io/evalcore/guides/run-history-and-serve/) | 其中有三个值得看看它们的真实输出。 **Baseline 基于性能恶化进行拦截。** 保存一个可接受的状态,然后进行比较。已经 失败的用例保持容忍;只有真正的回归才会使运行失败: ``` baseline "main": 2/3 passed -> current: 0/3 passed REGRESSED refund-window exact: expected "30 days", got "I do not know" REGRESSED wire-eta exact: expected "3 to 5 days", got "I do not know" baseline gate: FAIL (2 regressed, 0 new failing) ``` 这里所有三个用例都失败了,但只有两个被报告。第三个在 保存 baseline 时就已经失败了。 **Trial 是测量而不是采样。** 随机模型的一次运行就是一个 样本。`run.trials` 将每个用例运行 N 次,并使用 `all`、`majority` 或 `any` 汇总结果。同一个不稳定的用例,在两种策略下: ``` run: trials: { count: 3, require: majority } PASS fee-dispute (10ms) [2/3 trials] 3 passed, 0 failed, 3 total · 1 flaky exit 0 run: trials: { count: 3, require: all } FAIL fee-dispute [2/3 trials] exact: trial 2: expected "60 days", got "unsure" 2 passed, 1 failed, 3 total · 1 flaky exit 1 ``` 无论哪种方式,你都会发现这个用例是不稳定的。`require` 只决定 这是否会使你的构建失败。 **矩阵在单次调用中并排比较 target**:两个模型、两个 prompt 或两个已部署的 endpoint。 ``` == comparison case baseline-bot improved-bot refund-window PASS PASS tie fee-dispute FAIL PASS improved-bot wire-eta PASS PASS tie wins: baseline-bot 0 · improved-bot 1 · ties 2 ``` ## Target 和 scorer **Target** 是被评估的事物。 | 类型 | 评估内容 | 是否缓存 | |---|---|---| | `shell` | 任何命令。用例输入到达 stdin,stdout 是输出。 | 否 | | `openai-compatible` | 任何 OpenAI 格式的 chat endpoint:OpenAI、vLLM、Ollama、gateway。 | 是 | | `http` | 任何 HTTP/JSON API,通常是你自己部署的应用。 | 是 | | `trace` | 记录的 agent 运行,采用 EvalCore 的原生格式或 OTel / OpenInference 导出。不调用任何东西。 | 否 | **Scorer** 决定输出是否可接受。每个 scorer 都会对每个 用例运行,并且只有当所有 scorer 都通过时,用例才算通过。 | 类型 | 通过条件 | 分数 | |---|---|---| | `contains` | 输出包含某个子字符串。 | 0 或 1 | | `exact` | 输出等于 `value`,或用例的 `expected`。 | 0 或 1 | | `regex` | 正则表达式匹配输出中的任意位置。 | 0 或 1 | | `json-schema` | 输出解析为 JSON 并通过 draft 2020-12 schema 验证。 | 0 或 1 | | `similarity` | 输出与 `expected` 之间的余弦相似度超过阈值。 | 余弦值 | | `judge` | 根据你的 rubric 进行评分的 LLM 分数达到或超过阈值。 | judge 的分数 | | `subprocess` | 你自己的命令打印出通过判定。任何语言。 | 你的分数 | | `trajectory` | Agent 的工具调用满足每条规则(`must_call`、`must_not_call`、`max_steps`)。 | 0 或 1 | Judge 和 similarity 调用通过与 target 相同的记录/重放缓存,因此 LLM 评分的 suite 可以确定性地且免费地重放。每种类型的每个字段都记录在 [配置参考](https://eval-core.github.io/evalcore/reference/configuration/)中。 ## 在 CI 中运行 一个步骤运行一个 suite 并拦截该任务。终端报告显示在步骤 摘要中,而独立的 HTML 报告作为 artifact 上传,审查者 可以直接从 pull request 中打开它: ``` - uses: eval-core/evalcore@v0.7.5 with: config: evals/evals.yaml args: --cache replay --baseline main html-artifact: evalcore-report # default; set to "" to disable ``` `--cache replay` 意味着该任务不需要 API key 且零花费。 `--baseline main` 意味着它因回归而失败,而不是因为不完美。即使 suite 失败,HTML 报告也会上传,而这正是最有用的时候。 该 action 是为了方便,而不是必需的。二进制文件的退出代码就是 整个契约,因此任何 CI 系统都可以工作。请参阅 [在 CI 中运行](https://eval-core.github.io/evalcore/guides/running-in-ci/)以了解 GitLab、Jenkins 和 bare-shell 设置。 ## 设计原则 **协议优先于 SDK。** 每个扩展点都是语言无关的。Target 通过 HTTP 或 shell 通信,自定义 scorer 通过 stdin 和 stdout 使用 JSON 通信,judge 是 任何兼容 OpenAI 的 endpoint,agent trace 以 OTel 或 OpenInference JSON 到达。Rust 是引擎,永远不是接口。 **配置优先。** 功能从 YAML 开始。如果某件事值得做,就值得 作为数据来描述,让审查者可以在 diff 中阅读。 **构造上确定。** 相同的输入,相同的字节输出。缓存、 baseline 和 CI 拦截都是基于此构建的。 **本地优先。** 你的 suite、你的记录和你的运行历史位于你 仓库旁边的 SQLite 文件中。没有服务器要运行,也没有账户要 创建,并且该工具不会向任何地方发送任何内容。 ## 维护者 EvalCore 由 [Abhishek Manyam](https://github.com/abhishekmanyam) 和 [Kuladeep Mantri](https://github.com/kuladeepmantri) 构建。 ## 贡献 欢迎提交 Bug 报告、功能请求和 pull request。从 [CONTRIBUTING.md](CONTRIBUTING.md) 开始,了解工作区布局、四条 架构规则以及 CI 运行的检查。安全问题请通过 [SECURITY.md](SECURITY.md) 处理,而不是公共 tracker。 ``` cargo build cargo nextest run --workspace # or: cargo test --workspace cargo clippy --workspace --all-targets -- -D warnings cargo fmt --all ``` ## 许可证 Apache-2.0。请参阅 [LICENSE](LICENSE)。
标签:AI测试, DLL 劫持, LLM评估, Ollama, Petitpotam, SOC Prime, 可视化界面, 大语言模型, 开发工具, 用户代理, 通知系统