> ## Documentation Index
> Fetch the complete documentation index at: https://agent-skills.zhcndoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 评估技能输出质量

> 如何使用评估驱动的迭代来测试你的技能是否产生良好的输出。

你编写了一个技能，在一个提示词上进行了尝试，它似乎有效。但它是否可靠地工作——在各种提示词下、在边界情况中、比根本没有技能更好？运行结构化评估（evals）可以回答这些问题，并为你提供一个系统改进技能的反馈循环。

## 设计测试用例

一个测试用例包含三个部分：

* **提示词**：一个真实的用户消息——某人实际会输入的内容。
* **预期输出**：关于成功样子的可读描述。
* **输入文件**（可选）：技能需要处理的文件。

将测试用例存储在你的技能目录内的 `evals/evals.json` 中：

```json evals/evals.json theme={null}
{
  "skill_name": "csv-analyzer",
  "evals": [
    {
      "id": 1,
      "prompt": "I have a CSV of monthly sales data in data/sales_2025.csv. Can you find the top 3 months by revenue and make a bar chart?",
      "expected_output": "A bar chart image showing the top 3 months by revenue, with labeled axes and values.",
      "files": ["evals/files/sales_2025.csv"]
    },
    {
      "id": 2,
      "prompt": "there's a csv in my downloads called customers.csv, some rows have missing emails — can you clean it up and tell me how many were missing?",
      "expected_output": "A cleaned CSV with missing emails handled, plus a count of how many were missing.",
      "files": ["evals/files/customers.csv"]
    }
  ]
}
```

**编写良好测试提示词的技巧：**

* **从 2-3 个测试用例开始。** 在看到第一轮结果之前不要过度投入。你可以稍后扩展集合。
* **变化提示词。** 使用不同的措辞、详细程度和正式程度。有些提示词应该随意（“嘿，你能清理一下这个 csv 吗”），有些应该精确（“解析 data/input.csv 处的 CSV，丢弃列 B 为 null 的行，并将结果写入 data/output.csv"）。
* **覆盖边界情况。** 包含至少一个测试边界条件的提示词——格式错误的输入、不寻常的请求，或技能指令可能模糊的情况。
* **使用真实的上下文。** 真实用户会提到文件路径、列名和个人上下文。像“处理这个数据”这样的提示词太模糊，无法测试任何有用的东西。

暂时不用担心定义具体的通过/失败检查——只需提示词和预期输出。在你看到第一次运行产生什么之后，你将添加详细的检查（称为断言）。

## 运行评估

核心模式是将每个测试用例运行两次：一次**使用技能**，一次**不使用它**（或使用旧版本）。这为你提供了一个比较的基线。

### 工作区结构

在你的技能目录旁边的工作区目录中组织评估结果。每次通过完整的评估循环都会有自己的 `iteration-N/` 目录。在其中，每个测试用例都有一个评估目录，包含 `with_skill/` 和 `without_skill/` 子目录：

```
csv-analyzer/
├── SKILL.md
└── evals/
    └── evals.json
csv-analyzer-workspace/
└── iteration-1/
    ├── eval-top-months-chart/
    │   ├── with_skill/
    │   │   ├── outputs/       # 运行产生的文件
    │   │   ├── timing.json    # Token 数和持续时间
    │   │   └── grading.json   # 断言结果
    │   └── without_skill/
    │       ├── outputs/
    │       ├── timing.json
    │       └── grading.json
    ├── eval-clean-missing-emails/
    │   ├── with_skill/
    │   │   ├── outputs/
    │   │   ├── timing.json
    │   │   └── grading.json
    │   └── without_skill/
    │       ├── outputs/
    │       ├── timing.json
    │       └── grading.json
    └── benchmark.json         # 聚合统计
```

你手动编写的主要文件是 `evals/evals.json`。其他 JSON 文件（`grading.json`、`timing.json`、`benchmark.json`）是在评估过程中产生的——由智能体、脚本或你生成。

### 启动运行

每次评估运行都应该从干净的上下文开始——没有来自先前运行或技能开发过程的遗留状态。这确保智能体只遵循 `SKILL.md` 告诉它的内容。在支持子智能体的环境中（例如 Claude Code），这种隔离是自然的：每个子任务都是从头开始。如果没有子智能体，为每次运行使用单独的会话。

对于每次运行，提供：

* 技能路径（基线则无技能）
* 测试提示词
* 任何输入文件
* 输出目录

以下是你为单次使用技能运行提供给智能体的指令示例：

```
Execute this task:
- Skill path: /path/to/csv-analyzer
- Task: I have a CSV of monthly sales data in data/sales_2025.csv.
  Can you find the top 3 months by revenue and make a bar chart?
- Input files: evals/files/sales_2025.csv
- Save outputs to: csv-analyzer-workspace/iteration-1/eval-top-months-chart/with_skill/outputs/
```

对于基线，使用相同的提示词但不带技能路径，保存到 `without_skill/outputs/`。

当改进现有技能时，使用旧版本作为基线。在编辑之前对其进行快照（`cp -r <skill-path> <workspace>/skill-snapshot/`），将基线运行指向快照，并保存到 `old_skill/outputs/` 而不是 `without_skill/`。

### 捕获计时数据

计时数据让你可以比较技能相对于基线消耗了多少时间和 token——一个显著改善输出质量但使 token 使用量翻三倍的技能，与一个既更好又更便宜的技能是不同的权衡。当每次运行完成时，记录 token 数量和持续时间：

```json timing.json theme={null}
{
  "total_tokens": 84852,
  "duration_ms": 23332
}
```

<Tip>
  在 Claude Code 中，当子智能体任务完成时，[任务完成通知](https://platform.claude.com/docs/en/agent-sdk/typescript#sdk-task-notification-message) 包含 `total_tokens` 和 `duration_ms`。立即保存这些值——它们不会持久化到其他地方。
</Tip>

## 编写断言

断言是关于输出应该包含或实现内容的可验证陈述。在你看到第一轮输出后添加它们——你通常直到技能运行后才知道“好”是什么样子的。

良好的断言：

* `"输出文件是有效的 JSON"` — 可通过程序验证。
* `"条形图有标记的轴"` — 具体且可观察。
* `"报告包含至少 3 个建议"` — 可计数。

薄弱的断言：

* `"输出很好"` — 太模糊，无法评分。
* `"输出完全使用短语 'Total Revenue: $X'"` — 太脆弱；措辞不同的正确输出也会失败。

并非所有内容都需要断言。有些品质——写作风格、视觉设计、输出是否“感觉正确”——很难分解为通过/失败检查。这些最好在 [人工审查](#reviewing-results-with-a-human) 期间捕获。将断言保留给可以客观检查的内容。

将断言添加到 `evals/evals.json` 中的每个测试用例：

```json evals/evals.json highlight={9-14} theme={null}
{
  "skill_name": "csv-analyzer",
  "evals": [
    {
      "id": 1,
      "prompt": "I have a CSV of monthly sales data in data/sales_2025.csv. Can you find the top 3 months by revenue and make a bar chart?",
      "expected_output": "A bar chart image showing the top 3 months by revenue, with labeled axes and values.",
      "files": ["evals/files/sales_2025.csv"],
      "assertions": [
        "The output includes a bar chart image file",
        "The chart shows exactly 3 months",
        "Both axes are labeled",
        "The chart title or caption mentions revenue"
      ]
    }
  ]
}
```

## 评分输出

评分意味着针对实际输出评估每个断言，并记录 **通过** 或 **失败** 以及具体证据。证据应该引用或参考输出，而不仅仅是陈述意见。

最简单的方法是将输出和断言提供给 LLM 并要求它评估每一个。对于可以通过代码检查的断言（有效的 JSON、正确的行数、文件存在且具有预期尺寸），使用验证脚本——对于机械检查，脚本比 LLM 判断更可靠，并且可在迭代中重用。

```json grading.json theme={null}
{
  "assertion_results": [
    {
      "text": "The output includes a bar chart image file",
      "passed": true,
      "evidence": "Found chart.png (45KB) in outputs directory"
    },
    {
      "text": "The chart shows exactly 3 months",
      "passed": true,
      "evidence": "Chart displays bars for March, July, and November"
    },
    {
      "text": "Both axes are labeled",
      "passed": false,
      "evidence": "Y-axis is labeled 'Revenue ($)' but X-axis has no label"
    },
    {
      "text": "The chart title or caption mentions revenue",
      "passed": true,
      "evidence": "Chart title reads 'Top 3 Months by Revenue'"
    }
  ],
  "summary": {
    "passed": 3,
    "failed": 1,
    "total": 4,
    "pass_rate": 0.75
  }
}
```

### 评分原则

* **要求通过的具体证据。** 不要给予怀疑的利益。如果断言说“包含摘要”而输出有一个标题为"Summary"的部分，只有一句模糊的句子，那是失败——标签在那里但实质不在。
* **审查断言本身，而不仅仅是结果。** 在评分时，注意断言是否太容易（无论技能质量如何总是通过）、太难（即使输出良好也总是失败）或无法验证（仅从输出无法检查）。为下一次迭代修复这些问题。

<Tip>
  对于比较两个技能版本，尝试 **盲测**：将两个输出都呈现给 LLM 评判者，而不透露哪个来自哪个版本。评判者根据其自己的评分标准对整体品质——组织、格式、可用性、润色——进行评分，不受关于哪个版本“应该”更好的偏见影响。这补充了断言评分：两个输出可能都通过所有断言，但在整体质量上显著不同。
</Tip>

## 汇总结果

一旦迭代中的每次运行都完成评分，计算每个配置的汇总统计信息，并将它们保存到 `benchmark.json` 中，与评估目录放在一起（例如，`csv-analyzer-workspace/iteration-1/benchmark.json`）：

```json benchmark.json theme={null}
{
  "run_summary": {
    "with_skill": {
      "pass_rate": { "mean": 0.83, "stddev": 0.06 },
      "time_seconds": { "mean": 45.0, "stddev": 12.0 },
      "tokens": { "mean": 3800, "stddev": 400 }
    },
    "without_skill": {
      "pass_rate": { "mean": 0.33, "stddev": 0.10 },
      "time_seconds": { "mean": 32.0, "stddev": 8.0 },
      "tokens": { "mean": 2100, "stddev": 300 }
    },
    "delta": {
      "pass_rate": 0.50,
      "time_seconds": 13.0,
      "tokens": 1700
    }
  }
}
```

`delta` 告诉你技能的代价（更多时间、更多 token）以及它带来的收益（更高的通过率）。一个增加 13 秒但将通过率提高 50 个百分点的技能可能是值得的。一个使 token 用量加倍但仅提高 2 个百分点的技能可能不值得。

<Note>
  标准差 (`stddev`) 仅在每次评估有多次运行时才有意义。在只有 2-3 个测试用例和单次运行的早期迭代中，关注原始通过计数和 delta —— 随着你扩展测试集并多次运行每个评估，统计措施会变得有用。
</Note>

## 分析模式

汇总统计可能会掩盖重要的模式。计算基准测试后：

* **移除或替换在两种配置中始终通过的断言。** 这些不能告诉你任何有用的信息 —— 模型在没有技能的情况下也能很好地处理它们。它们虚高了带技能的通过率，而没有反映实际技能价值。
* **调查在两种配置中始终失败的断言。** 要么是断言坏了（要求模型做不到的事情），要么是测试用例太难，要么是断言检查了错误的内容。在下次迭代之前修复这些问题。
* **研究带技能通过但不带技能失败的断言。** 这是技能明显增加价值的地方。理解*为什么* —— 哪些指令或脚本起了作用？
* **当运行结果不一致时收紧指令。** 如果同一个评估有时通过有时失败（在基准测试中反映为高 `stddev`），评估可能不稳定（对模型随机性敏感），或者技能的指令可能足够模糊，导致模型每次解释不同。添加示例或更具体的指导以减少模糊性。
* **检查时间和 token 异常值。** 如果一个评估花费的时间是其他的 3 倍，阅读其执行记录（模型在运行期间所做的事情的完整日志）以找到瓶颈。

## 与人工一起审查结果

断言评分和模式分析能捕捉到很多问题，但它们只检查你想到要编写断言的内容。人工审查员带来全新的视角 —— 捕捉你没预料到的问题，注意到输出技术上正确但未切中要点时，或发现难以用通过/失败检查来表达的问题。对于每个测试用例，结合评分审查实际输出。

记录每个测试用例的具体反馈，并将其保存在工作区中（例如，作为 `feedback.json` 与评估目录放在一起）：

```json feedback.json theme={null}
{
  "eval-top-months-chart": "图表缺少轴标签，且月份按字母顺序排列而不是时间顺序。",
  "eval-clean-missing-emails": ""
}
```

"图表缺少轴标签" 是可操作的；"看起来不好" 则不是。空反馈意味着输出看起来没问题 —— 该测试用例通过了你的审查。在 [迭代步骤](#iterating-on-the-skill) 期间，将改进重点放在你有具体投诉的测试用例上。

## 迭代技能

评分和审查后，你有三个信号来源：

* **失败的断言** 指向具体的差距 ——  missing 步骤、不清楚的指令，或技能未处理的情况。
* **人工反馈** 指向更广泛的质量问题 —— 方法错误、输出结构糟糕，或技能产生了技术上正确但无帮助的结果。
* **执行记录** 揭示事情出错的*原因*。如果代理忽略了指令，指令可能模糊。如果代理花费时间在没有成效的步骤上，这些指令可能需要简化或移除。

将这些信号转化为技能改进的最有效方法是将所有这三个——连同当前的 `SKILL.md`——交给 LLM 并要求它提出更改。LLM 可以综合失败断言、审查员投诉和记录行为中的模式，这些模式手动连接会很繁琐。提示 LLM 时，包含以下指南：

* **从反馈中概括。** 技能将用于许多不同的提示，而不仅仅是测试用例。修复应广泛解决根本问题，而不是为特定示例添加狭窄的补丁。
* **保持技能精简。** 更少、更好的指令通常优于详尽的规则。如果记录显示浪费的工作（不必要的验证、不需要的中间输出），移除这些指令。如果尽管添加了更多规则通过率仍停滞不前，技能可能约束过多 —— 尝试移除指令，看看结果是否保持或改善。
* **解释原因。** 基于推理的指令（“做 X，因为 Y 往往会导致 Z"）比僵硬的指令（“总是做 X，绝不做 Y"）效果更好。当模型理解目的时，它们更可靠地遵循指令。
* **打包重复工作。** 如果每次测试运行都独立编写类似的辅助脚本（图表构建器、数据解析器），这是一个将脚本打包到技能的 `scripts/` 目录的信号。参见 [使用脚本](/skill-creation/using-scripts) 了解如何执行此操作。

### 循环

1. 将评估信号和当前的 `SKILL.md` 交给 LLM 并要求它提出改进。
2. 审查并应用更改。
3. 在新的 `iteration-<N+1>/` 目录中重新运行所有测试用例。
4. 评分并汇总新结果。
5. 与人工一起审查。重复。

当你对结果满意时停止，反馈持续为空，或者你在迭代之间不再看到有意义的改进。

<Tip>
  [`skill-creator`](https://github.com/anthropics/skills/tree/main/skills/skill-creator) 技能自动化了这个工作流的许多部分 —— 运行评估、评分断言、汇总基准测试，并呈现结果供人工审查。
</Tip>
