mcpField
L
llm-eval-harness

llm-eval-harness

github.com/daymade测试与质量1.5k377 次安装今天更新
代码仓库

关于 llm-eval-harness

用于测量模型端点可用性、请求保真度、速度、并发稳定性、协议兼容性和用例质量的评估技能,适合接入新模型或排查网关表现。通过环境变量名传递 API 密钥,避免把密钥写入命令参数或报告。

主要功能

  1. 01测量模型 ID 可用性
  2. 02检查提示词、工具与多轮请求传递
  3. 03评估首 token 延迟和持续速度
  4. 04测试并发稳定性与协议兼容性
  5. 05运行用例回归并读取结构化结果

适用场景

  1. 01接入新模型或网关前验证端点
  2. 02排查系统提示词或工具调用未生效
  3. 03比较模型速度并测试并发上限
  4. 04检查 Anthropic thinking 与历史重放兼容性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/daymade/claude-code-skills --skill llm-eval-harness

安装后在 Claude Code 中输入/llm-eval-harness确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
example_usecases.json
references/
evaluation_disciplines.md
machine-results.md
production_testing_patterns.md
quality_blind_judge.md
vendor_evidence_protocol.md
scripts/
availability_probe.py
concurrency_probe.py
eval_result.py
fidelity_probe.py
protocol_probe.py
speed_probe.py
usecase_runner.py
tests/
test_results.py

详细介绍

LLM 评估框架

概述

为此技能提供一个端点(base_url + model + 通过环境变量提供的 API 密钥),它就会测量该端点是否确实可用,以及模型是否足够快速、稳定、符合协议且满足要求——而不是盲目相信厂商醒目的宣传数字。六个维度通常散落在各种临时脚本中,而这些脚本每次都会被重写(也会重复出现相同的 bug):

维度脚本可回答的问题
可用性scripts/availability_probe.py哪些模型 ID 在这里可用,并对错误进行三态分类
请求保真度scripts/fidelity_probe.pysystem 提示词 / 工具 / 历史记录是否确实传达给了模型?
速度scripts/speed_probe.pyTTFT + 持续解码 tok/s,感知思考过程
并发 / 稳定性scripts/concurrency_probe.py成功率、p50/p90 延迟,以及在哪些情况下会出问题
协议合规性scripts/protocol_probe.py请求时 Anthropic thinking 块是否确实触发,以及端点是否接受后续轮次历史记录中已有的 thinking 块(N≥10,两项检查彼此独立——见维度 3)?
质量 / 用例回归scripts/usecase_runner.py + 盲测评审是否通过了你积累的用例?

应运行哪些维度

根据用户实际要做的事选择:

  • “接入新模型/新网关先测一下” / 接入服务商 → 可用性 → 保真度, 如果看起来可能采用,再测速度/并发
  • “这个 ID 能不能用 / 写个支持列表” → 可用性(并在判定“不可用”之前阅读 纪律说明 §9)
  • “模型不听 system prompt / agent 行为怪但没报错” → 保真度(system canary)
  • “快不快 / tok/s 是真的吗” → 速度;“扛不扛得住” → 并发
  • “这个兼容端点是真兼容吗” → 协议 + 保真度中的工具/身份验证检查
  • “这个模型/渠道用着用着就 400 了 / session 断了 / continue 不下去了”(扩展思考客户端、 多轮 session)→ 协议的 --check history-replay,具体来说——这种症状意味着生成检查通过了,但历史记录重放却悄无声息地失败;不要只检查生成
  • “换模型质量会不会掉” → 质量回归
  • “要给厂商报 bug” → 使用 --output 运行相关探测,然后参阅 references/vendor_evidence_protocol.md
  • “部署闸门 / 常驻监控 / 告警老误报” → references/production_testing_patterns.md

不要例行执行全部六项——只选择能回答用户问题的项目。

密钥处理(不可妥协): 每个脚本都通过环境变量名称接收 API 密钥 (--key-env MY_KEY),绝不在命令行中传入密钥值——这样密钥就不会出现在 ps、 shell 历史记录或任何已保存的报告中。绝不要将密钥硬编码到用例文件或包装脚本中。阅读 references/evaluation_disciplines.md ,了解这一要求及其他纪律背后的完整理由。

你的私有数据应保存在此资源包之外。 用例库、模型列表和密钥应放在 ~/.llm-eval/ (或你存放机密信息的位置),而不是此技能目录中——此技能通用且公开;你的测试套件属于你自己。参阅下文的“用例库”。

机器启动器与中断的运行: 使用配套的 --result-json / --run-id 接口,以及捆绑的 scripts/eval_result.py 读取器,以区分已完成、不合规、部分完成、未测量、失败和已取消的运行。集成启动器前请阅读 机器结果;该文档说明了回执时效性、类型化证据、退出代码、身份验证转发以及离线回归命令。保留旧版 --output 和质量答案产物。

快速开始

检测你拥有的内容,然后运行适用的维度。对于兼容 OpenAI 的模型:

export MY_KEY=sk-...                       # the key never appears in a command below

# Speed: real-task throughput + sustained decode ceiling
uv run --with openai python scripts/speed_probe.py \
  --base-url https://api.example.com/v1 --model some-model --key-env MY_KEY --mode both

# Concurrency: ramp until it breaks
uv run --with aiohttp python scripts/concurrency_probe.py \
  --url https://api.example.com/v1/chat/completions --model some-model --key-env MY_KEY \
  --format openai --concurrency 10 20 40 60

如果端点采用 Anthropic-Messages 格式(/v1/messages),还要运行协议探测(见下文)。 根据用户实际提出的问题选择维度——如果用户只问“速度快吗?”,就不要运行全部六项。

当 Anthropic 协议/并发探测所用的路由要求使用 Authorization 而非 x-api-key 时,传入 --auth bearer。默认仍为 x-api-key,并同时适用于协议生成和历史记录重放。OpenAI 并发探测仍使用 Bearer 身份验证。

维度 0 — 可用性(哪些模型 ID 实际可用)

uv run --with aiohttp python scripts/availability_probe.py \
  --base-url <base> --key-env <ENV> --format both \
  --models model-a vendor/model-b @models.txt --output /tmp/avail.json
  • 对每种故障,结论分为 3 种状态,而非通过/失败:no-channel(没有此 ID 对应的路由)、upstream-error(路由存在,但上游故障——稍后重试)、empty-content(通常是推理模型的 max_tokens 问题,并不代表模型已损坏)。探测默认将 --max-tokens 设为 8192,正是为了避免思考模型被误判为不可用——不要将其调低来“优化”。
  • 在编写任何“不可用”列表之前:确认每个故障 ID 都存在于官方文档中。当前一代模型名称可能晚于你的训练数据——请通过 WebSearch 查找供应商/模型开发者的模型页面,而不是凭猜测列举;后缀(-preview、日期变体、层级名称)决定路由是否可用。完整陷阱见 disciplines §9。
  • 切勿将客户端标记当作真实模型 ID 来探测。 带方括号后缀的字符串(例如 some-model[1m])通常是 CLI 客户端自己的本地约定(Claude Code 会在构建请求前解析并移除 [1m]——完整机制见 claude-switch-models-setup 的“配置上下文窗口大小”一节),它们永远不会出现在网络请求中。探测发现此类字符串时会发出警告,但应由你自行遵守这一原则:探测供应商文档中记录的原始模型 ID,而不是从 Claude Code 的 ANTHROPIC_MODEL 环境变量中复制的字符串。
  • 网关的 /v1/models 列表只是一个输入,绝非最终结论——真实网关可能会路由列表中未列出的模型。

维度 0.5 — 请求保真度(你的负载是否到达模型?)

uv run --with aiohttp python scripts/fidelity_probe.py \
  --base-url <base> --model <model> --key-env <ENV> \
  --format anthropic --check all --repeat 10 --output /tmp/fidelity.json
  • 四项检查:system(在系统提示词中植入金丝雀代码——模型能否复述?)、tools(完成完整往返,包括返回工具结果并确认最终回答使用了该结果)、multiturn(在多个轮次中植入事实,并确认模型能否全部回忆)、auth(在两个协议端点上分别测试 x-api-key 与 Bearer——网关通常会在一个路径上同时接受两者,而在另一个路径上只接受其中一种;错误的请求头导致的 401 与密钥失效的表现完全相同)。
  • 这项检查能发现最棘手的网关故障:所有请求都返回 200,聊天也流畅,但系统提示词从未到达模型——因此,任何规则写在系统提示词中的工具都会悄无声息地出错,且不会出现任何错误。在一个真实网关上,某个模型别名在 100 多个样本中,无论通过哪种合法方式发送,都从未传递系统提示词;而它的其他同类别名则间歇性传递,且传递率每小时都在变化。
  • 经由路由网关传递具有概率性且随时间变化的特点——因此需要使用 --repeat(默认值为 10)和三态结论(已传递/间歇性传递 k-of-N/未传递)。在一个时间窗口内得到的干净结果也只代表该时间窗口:发布数值前,应在另一个时间段重新采样(见 disciplines §12)。
  • 探测会报告用于佐证的 token 计量数据,但绝不会单独信任这些数据——以行为证据为准;请参见 disciplines §10(校准计量器)和 §11(为什么金丝雀是中性的外部事实,而非身份或服从性测试)。

维度 1 — 速度(考虑思考过程)

uv run --with openai python scripts/speed_probe.py \
  --base-url <…/v1> --model <model> --key-env <ENV> --mode both --output /tmp/speed.json
  • mixed 运行有代表性的任务(反映真实使用体验);decode 强制生成一段长输出,以找出持续速度上限(用于对比供应商宣传数据的数值);both 则两者都运行。
  • **此脚本旨在避免的陷阱:**推理模型会在单独的 reasoning_content 字段中流式输出思考过程,但 completion_tokens 会将其计入。若只收集 content,却用 completion_tokens 作除数,得出的数字会被大幅夸大——某个实际速度约为 750 tok/s 的模型,曾以这种方式测出 4700 tok/s。脚本会同时捕获两者,将 TTFT 设为任一类型的首个 token,并报告 completion_tokens / (total − TTFT)。
  • **正确解读输出:**真实任务的吞吐量会低于解码速度上限,因为短输出无法达到稳定状态——这是预期现象,不是故障。请报告两个数值,并注明模型是否会输出思考过程(其端到端延迟包含推理时间,而不只是打字时间)。

维度 2 — 并发 / 稳定性

uv run --with aiohttp python scripts/concurrency_probe.py \
  --url <full endpoint URL> --model <model> --key-env <ENV> \
  --format openai|anthropic --concurrency 10 20 40 60 --output /tmp/conc.json
  • 传入多个 --concurrency 值逐步提升并发量,找出上限——即成功率下降或延迟激增的并发等级。单线程速度很快的模型,在中等并发量下仍可能崩溃(真实案例:一家供应商在 50 个并发请求下保持 0.4s,另一家却在仅 5 个并发请求时就开始丢弃请求)。
  • 脚本会与任何环境代理隔离(trust_env=False),并禁用 keep-alive 连接池(force_close),否则测到的会是代理的限制,或某个固定的上游副本,而不是模型。脚本会输出“并发证明”(重叠的请求对),供你确认请求确实并行运行。
  • 根据输出区分故障类型:HTTP 429(正常限流,可重试)、导致请求一直挂起直到超时的 TCP 断连(对用户体验的影响严重得多),以及 5xx。它们所需的修复方式截然不同。

维度 3 — 协议合规性(Anthropic thinking 块:生成和历史重放)

uv run python scripts/protocol_probe.py \
  --url <…/v1/messages> --model <model> --key-env <ENV> --check all --repeat 10 --output /tmp/proto.json
  • 仅适用于声称兼容 Anthropic /v1/messages 的端点。--check all (默认值)会运行两个子检查,它们测试不同的代码路径;服务提供商可能通过其中一项,却在另一项上彻底失败:
    • generation:请求时设置 thinking: {type: enabled},是否真的会生成 thinking_delta / signature_delta SSE 事件?(原始检查)
    • history-replay:在后续轮次重放历史记录时,端点是否接受已存在于先前助手轮次中的 type: "thinking" 块——这正是 Claude Code 和所有其他 Agent 客户端在每次继续对话时都会执行的操作? (运行 --check history-replay 可单独执行此项检查)
  • 促使我们加入历史重放检查的真实事件(2026-07-21):通过中国转售商接入的 Kimi/Moonshot 模型顺利通过了生成检查(按要求正确输出 thinking),却彻底拒绝历史重放——一旦先前的 thinking 块作为输入再次传回,就报错 400「invalid part type: thinking」,导致会话在后续每一轮都中断。 生成检查通过对此毫无说明;这是彼此正交的故障模式(响应生成与请求验证)。
  • 不要仅凭一次跨维度比较就断定“服务提供商/转售商 X 有问题”——也不要因为某次单维度比较证实了服务提供商文档中记录的参数,就此打住。 我们花了三轮才弄清楚这一点,并将其作为典型警示记录在纪律条款第 17–19 节:第一轮同时比较了不同模型和不同转售商,因而(错误地)归咎于转售商。第二轮修正了这一点——转售商相同,仅改变模型——结果与一个文档中明确命名的服务提供商参数(Moonshot 自己的 preserve_thinking)吻合,看起来像是得到了证实。但第二轮仍然是错的:探测始终只经过那一家转售商,因此看不到服务提供商自己的直连/原生端点其实可以正常处理那个“被拒绝”的模型——真实生产流量证实了这一点。将任何经单一转售商验证的结果视为定论之前,请先阅读第 19 节;在将“X 不支持 thinking”之类的结论写入任何内容之前,请检查服务提供商自己的端点或相关渠道的真实流量。
  • 这两项检查的合规性往往都是概率性的,而非二元的。 某个真实服务提供商仅在约 13% 的生成请求中保留了 thinking 块(而两家竞争对手的比例为 100%)。因此 --repeat 默认值为 10;生成检查的结论有三种状态(fully-implemented、intermittent (k/N)、not-implemented),历史重放检查则有三种以上状态(accepts-thinking-in-history、rejects-thinking-in-history、inconsistent,或者当错误看起来与 thinking 完全无关时为 inconclusive)。切勿根据单个样本下结论。
  • 每个请求都会强制设置 Connection: close,以防负载均衡器将所有样本固定分配给同一个副本,从而掩盖真实分布(一次真实探测在同一端点上使用 keep-alive 时为 0/10,使用 close 时为 17/90)。

维度 4 — 质量/用例回归(盲评)

这里特意分为两个阶段:收集,然后独立评判。

第 1 步 — 收集模型对你的用例库的回答:

uv run --with openai python scripts/usecase_runner.py \
  --base-url <…/v1> --model <model> --key-env <ENV> \
  --usecases ~/.llm-eval/usecases.json --output-dir ~/.llm-eval/runs/<model>

第 2 步 — 由独立盲评评审进行评判(直接编排——不要让模型给自己打分)。 对运行目录中的每个回答,启动 3 个独立的 Task Agent(快速检查时可以更少)。每位评审只能获得:提示词、回答和该案例的 rubric,并须明确告知其独立评判,不得了解其他评审的评分或任何先前评估(这可以避免锚定效应)。然后汇总:

  • 只有在评审多数意见一致时,该案例才算通过。
  • 按类别计算精确率(使用每个案例的 tags):如果评审对某个类别与评分标准系统性地意见不一,这就是一个真实弱点——在一次实际评估中,某个类别的精确率仅为 12.5%,暴露了单个评分者可能漏掉的系统性误分类。
  • 只统计明确的判断。 未给出结论的评审不算通过——沉默 ≠ 同意。这可以防止自动化偏差。

关于评分标准的评分机制(LLM-as-judge 阈值、llm-rubric),你也可以结合使用 promptfoo-evaluation 技能——将其 providers 指向同一端点。此工具包的盲评方法与 promptfoo 的评分标准断言相辅相成:使用 promptfoo 快速进行逐案例的通过/失败判定,使用盲评评审检查你怀疑较弱的类别的精确率。完整方法:references/quality_blind_judge.md。

用例库

将它保存在此工具包之外(例如 ~/.llm-eval/usecases.json),这样它就能在技能更新后保留,也不会进入公共仓库。它是一个普通的 JSON 列表——将其置于私有仓库中进行版本管理,逐步积累回归测试集:

[
  {"id": "refund-window", "prompt": "A customer asks for a refund 20 days after purchase. Reply as support.",
   "rubric": "1.0 if it correctly cites the 30-day refund window; 0.0 if it refuses or invents a different window.",
   "tags": ["support", "policy"]},
  {"id": "lru-cache", "prompt": "Implement an LRU cache in Python with O(1) get/put.",
   "rubric": "1.0 if get and put are both O(1) via dict + doubly linked list and the self-test passes.",
   "tags": ["code"]}
]

assets/example_usecases.json 是一个可供复制的入门示例。只需 id 和 prompt;添加 rubric、expected 和 tags 可以让评判更加准确。

运行完整评估

当用户说“评估 / 基准测试这个模型”时,典型流程如下:

  1. 确定接口类型——是 OpenAI 兼容的(/v1/chat/completions),还是 Anthropic-Messages (/v1/messages)?调用 GET /v1/models 或查看供应商文档;不要想当然。这决定了 适用哪些探测(协议探测仅适用于 Anthropic)。请记住,无论哪种情况,模型列表都不是完整的 证据(规范 §9)。
  2. 对于新端点,先检查可用性和保真度——如果模型始终收不到系统提示词,那么它的速度 数据回答的就是错误的问题。
  3. 运行用户关心的维度——如果用户想知道“是否快速 / 稳定”,就测试速度和并发;如果是 Anthropic 供应商,则增加协议测试;如果用户有用例套件,则增加质量测试。将每个探测的 --output JSON 写入运行目录。
  4. 如实报告,并区分实测结果与推断。 开头先说明用户询问的重点(例如,“持续解码上限 超过供应商声称的 tok/s,但实际任务的吞吐量较低”)。 如果某个数字看起来不可能(例如,吞吐量远高于供应商声称的水平,或仅凭单个样本就给出 协议结论),应将其视为有待调查的测量伪差,而不是结果——这正是这套测试工具的核心 用途。路由网关上的速率还会随时间变化:在将任何数字写入文档之前,请换个时间段重新 采样(规范 §12)。
  5. 要比较两个模型? 使用相同的标志,对每个模型运行完全相同的探测,并将两个 JSON 输出并列比较。保持测试条件一致(并发级别、用例都相同),否则比较就毫无意义。
  6. 发现值得报告的供应商缺陷? 不要将原始探测输出粘贴到供应商的支持渠道——请按照 references/vendor_evidence_protocol.md 准备证据材料包(先进行自审、使用描述观察结果的措辞、附上请求 ID 表、预先登记阈值、 进行对抗性复核)。

如需针对在线系统反复运行的测试——部署门禁、常驻金丝雀、故障注入模拟,以及那些会撒谎的 监控统计数据,请参阅 references/production_testing_patterns.md。

下一步

运行后,提供顺理成章的后续选项:

Evaluation complete for <model>.

Options:
A) Render an HTML dashboard of the results — compose with a visualization skill (Recommended if sharing)
B) Compare against another model — same probes, side-by-side
C) Add the failing cases to ~/.llm-eval/usecases.json as a permanent regression guard
D) Done — the numbers answer the question

评论

支持 Markdown