📌 前置知识:已完成第一课至第十课
🎯 本课目标:构建评测套件 + 黄金数据集,在每次修改后自动验证 Agent 是否"坏了"
💡 核心概念:评测套件 / 黄金数据集 / 硬断言 vs 软断言 / 回归检测


前言

前十课,我们搭出了一个功能越来越丰富的 Agent:

agent = Agent(model="qwen2.5:7b")

# 第三课:结构化输出
result = agent.generate_structured("解释量子计算", schema)
# → {"topic": "量子计算", "difficulty": "beginner"}

# 第五课:工具调用
agent.use_tool(calculator)
response = agent.chat("42 * 7 是多少?")
# → 调用 calculator(operation="multiply", a=42, b=7) → 294

# 第七课:记忆
agent.memory.store("用户的名字是 Alice")
agent.chat("我叫什么名字?")
# → "你叫 Alice"

看起来完美。但有一个致命问题——

你怎么知道修改了 Prompt 之后,这些能力还能正常工作?

场景 1:你觉得 System Prompt 不够简洁,改了几句话
  → 结果:LLM 开始在 JSON 前面加解释性文字
  → 结果:extract_json_from_text() 解析失败
  → 结果:整个结构化输出崩了

场景 2:你给工具描述加了个"also"
  → 结果:LLM 把两个工具搞混了
  → 结果:42 * 7 变成了调用 web_search
  → 结果:答案从 294 变成了一堆搜索结果

场景 3:你优化了记忆存储的 Prompt
  → 结果:LLM 不再提取关键事实
  → 结果:存进去的是废话
  → 结果:查询时什么都检索不到

这些都不是"功能性问题",而是"静默回归"——看起来在运行,实际上已经坏了。

没有测试,你根本不知道。就像你改了一行 CSS,发现整个页面的按钮都不见了——但你不知道是哪行改动导致的。

本课要解决这个问题。给 Agent 装上评测套件,每次修改后跑一遍,确保"之前能做的事现在还能做"。


一、为什么 Agent 需要评测?

1.1 Agent 的"脆弱性"

和传统软件不同,Agent 有一个特殊的脆弱性:

传统软件:改了代码 → 编译报错 → 立刻知道坏了
Agent:    改了 Prompt → LLM 输出变了 → 不一定报错 → 悄悄坏了

Prompt 的修改是自然语言的,不是代码的。一个措辞的变化,可能导致 LLM 输出格式的变化,进而导致解析失败、工具调用错误、记忆提取失败……

这些都是静默失败——没有异常,没有报错,只是结果不对。

1.2 评测套件是什么?

评测套件 = 一组已知的测试用例 + 预期结果 + 自动运行脚本。

和传统软件的单元测试一模一样。只不过测试对象从"函数"变成了"Agent 的行为"。

评测用例 1:
  输入:"Explain quantum computing in one sentence"
  断言:输出是有效 JSON,且包含 "topic" 和 "difficulty" 字段
  预期通过

评测用例 2:
  输入:"42 * 7 是多少?"
  断言:调用了 "calculator" 工具,且 operation 是 "multiply"
  预期通过

评测用例 3:
  先存:"我的名字是 Alice"
  再查:"我叫什么?"
  断言:回答中包含 "Alice"
  预期通过

如果所有用例都通过,说明修改没有破坏已有能力。如果有用例失败,说明有回归。

1.3 本课 vs 第三课

第三课做了单次验证——生成一次 JSON,检查是否合法,失败就重试。

本课做的是系统化评测——跨多个用例、多个能力维度,系统性地检查 Agent 是否正常。

第三课(结构化输出) 第十一课(评测)
验证时机 每次生成时 每次修改后
验证范围 单个请求 多个用例 × 多个能力
有历史记录吗 没有 有(通过率趋势)
能捕获回归吗 不能
能发现边缘情况吗 不能

核心洞察:评测不是"更高级的验证",而是"系统化的验证"。一次验证看的是当下,评测套件看的是趋势。


二、核心概念

2.1 评测套件

评测套件是验证 Agent 行为的测试用例集合。

每个用例有:

  • 输入:给 Agent 的提示
  • 预期:期望的结果(硬断言或软断言)
  • 实际:Agent 的实际输出
  • 通过/失败:实际是否匹配预期
{
    "input": "What is 42 * 7?",
    "expected_tool": "calculator",
    "expected_args": {"operation": "multiply"}
}

本课的实现不依赖任何测试框架——纯 Python,用 dataclass 定义结果,用函数组织用例。简单、透明、零依赖。

2.2 黄金数据集

黄金数据集是"绝对不能失败"的测试用例集合。

它是你的真值来源——已知的、经过验证的、必须始终通过的示例。

黄金用例 1:结构化输出必须返回有效 JSON + 必需字段
黄金用例 2:计算问题必须调用 calculator 工具
黄金用例 3:存入的事实必须能被检索到
...

关键原则:

原则 说明
100% 通过 黄金数据集必须 100% 通过。任何失败 = Agent 坏了,不是测试坏了
版本控制 黄金数据集和 Prompt 一起提交到 Git
只增不减 只能新增用例,不能删除通过的用例
覆盖核心能力 每个核心能力至少 3-5 个用例

黄金数据集是你的"契约"。有人问"Agent 工作吗?",你指向黄金数据集——100% 通过就工作,否则具体说哪里坏了。

2.3 硬断言 vs 软断言

硬断言必须始终通过——不通过就是 Bug:

# 硬断言示例
assert json_output is not None                    # 必须是有效 JSON
assert "topic" in result                           # 必需字段必须存在
assert tool_call["tool"] == "calculator"           # 工具名称必须匹配

软断言通常应该通过,但不通过不一定意味着 Bug:

# 软断言示例(语义级别的检查)
assert "量子" in response                          # 回答大概提到了量子(可能措辞不同)
assert tool_args.get("precision") == "high"        # 参数最优(但 medium 也行)

本课的策略:先硬断言,后软断言。 从最基础的检查开始(JSON 有效?字段存在?工具正确?),确认基础设施没问题,再加语义层的检查。


三、代码实现

3.1 评测结果数据结构:agent/evals.py

from dataclasses import dataclass, field
from typing import Any


@dataclass
class EvalResult:
    """单个评测用例的结果。"""
    passed: bool
    input: str
    expected: Any = None
    actual: Any = None
    error: str | None = None


@dataclass
class EvalSuiteResult:
    """运行评测套件的结果。"""
    name: str
    passed: int = 0
    failed: int = 0
    results: list[EvalResult] = field(default_factory=list)

    @property
    def pass_rate(self) -> float:
        return self.passed / (self.passed + self.failed) if (self.passed + self.failed) > 0 else 0.0

    def add_result(self, result: EvalResult):
        """添加单个测试结果。"""
        self.results.append(result)
        if result.passed:
            self.passed += 1
        else:
            self.failed += 1

    def summary(self) -> str:
        """生成一行摘要。"""
        status = "✓ PASSED" if self.failed == 0 else "✗ FAILED"
        return f"{self.name}: {status} ({self.passed}/{self.passed + self.failed})"

逐段拆解:

EvalResult——单个用例的结果

@dataclass
class EvalResult:
    passed: bool          # 通过了吗?
    input: str            # 输入是什么?
    expected: Any         # 预期是什么?(可选)
    actual: Any           # 实际输出是什么?(可选)
    error: str | None     # 如果失败,错误信息是什么?

每个评测用例的结果都被完整记录——不仅知道"过没过",还知道"输入了什么、期望什么、实际输出什么、哪里不对"。这是调试回归问题的第一手资料

EvalSuiteResult——套件级别的汇总

@property
def pass_rate(self) -> float:
    return self.passed / (self.passed + self.failed) if (self.passed + self.failed) > 0 else 0.0

通过率是最关键的指标。100% = 一切正常,< 100% = 有回归。

注意 add_result() 方法——它自动统计 passed/failed 计数,调用方只需要传入 EvalResult,不用手动维护计数。

3.2 评测器:AgentEval

class AgentEval:
    """智能体能力的回归测试。"""

    def __init__(self, agent):
        self.agent = agent

    def test_structured_output(self, cases: list[dict]) -> EvalSuiteResult:
        """测试结构化输出是否正确解析并匹配 schema。"""
        suite = EvalSuiteResult(name="Structured Output")

        for case in cases:
            result = self.agent.generate_structured(case["input"], case["schema"])

            # 检查 1:得到有效 JSON 了吗?
            if result is None:
                suite.add_result(EvalResult(
                    passed=False,
                    input=case["input"],
                    error="Failed to parse JSON"
                ))
                continue

            # 检查 2:必需字段存在吗?
            missing = [f for f in case.get("must_have_fields", []) if f not in result]
            if missing:
                suite.add_result(EvalResult(
                    passed=False,
                    input=case["input"],
                    expected={"fields": case.get("must_have_fields", [])},
                    actual={"missing": missing},
                    error=f"Missing fields: {missing}"
                ))
                continue

            suite.add_result(EvalResult(
                passed=True,
                input=case["input"],
                actual=result
            ))

        return suite

    def run_all(self, structured_cases=None, tool_cases=None, memory_cases=None) -> list:
        """运行所有评测套件。"""
        results = []

        if structured_cases:
            results.append(self.test_structured_output(structured_cases))

        if tool_cases:
            results.append(self.test_tool_calls(tool_cases))

        if memory_cases:
            results.append(self.test_memory_cycle(memory_cases))

        return results

逐段拆解:

test_structured_output()——结构化输出测试

# 两层检查:
# 第 1 层:JSON 解析成功了吗?(硬断言)
if result is None:
    suite.add_result(EvalResult(passed=False, error="Failed to parse JSON"))

# 第 2 层:必需字段存在吗?(硬断言)
missing = [f for f in case.get("must_have_fields", []) if f not in result]
if missing:
    suite.add_result(EvalResult(passed=False, error=f"Missing fields: {missing}"))

先检查 JSON 本身是否合法(result is None),再检查字段是否完整(missing)。两层都通过才算通过。

② 纯 Python,零依赖

没有 pytest,没有 unittest,没有任何测试框架。就是 if/else + dataclass

为什么?因为评测不需要框架。你只是在"跑 Agent → 检查结果 → 报告通过/失败"。这不需要 200 个依赖包和复杂的 fixture 系统。

run_all()——一键运行全部

results = evaluator.run_all(
    structured_cases=STRUCTURED_OUTPUT_GOLDEN,
    tool_cases=TOOL_CALL_GOLDEN,
    memory_cases=MEMORY_GOLDEN
)

传入黄金数据集,运行所有评测,返回结果列表。简单、直接。

3.3 黄金数据集:evals/golden_datasets.py

STRUCTURED_OUTPUT_GOLDEN = [
    {
        "input": "Explain quantum computing in one sentence",
        "schema": """{
  "topic": "the topic name as a string",
  "difficulty": "beginner" or "intermediate" or "advanced"
}

Example: {"topic": "machine learning", "difficulty": "intermediate"}""",
        "must_have_fields": ["topic", "difficulty"]
    },
    {
        "input": "What does 'hello world' mean in programming?",
        "schema": """{
  "explanation": "brief explanation as a string",
  "language": "the programming language it's most associated with"
}""",
        "must_have_fields": ["explanation", "language"]
    },
    {
        "input": "描述一下 Python 的用途",
        "schema": """{
  "language": "language name",
  "use_cases": "list of main use cases",
  "popularity": "beginner" or "intermediate" or "advanced"
}""",
        "must_have_fields": ["language", "use_cases", "popularity"]
    },
    {
        "input": "What is artificial intelligence?",
        "schema": """{
  "topic": "the topic",
  "summary": "one sentence summary",
  "difficulty": "beginner" or "intermediate" or "advanced"
}""",
        "must_have_fields": ["topic", "summary", "difficulty"]
    },
]

TOOL_CALL_GOLDEN = [
    {
        "input": "What is 42 * 7?",
        "expected_tool": "calculator",
        "expected_args": {"operation": "multiply"}
    },
    {
        "input": "Calculate 100 divided by 4",
        "expected_tool": "calculator",
        "expected_args": {"operation": "divide"}
    },
    {
        "input": "What's the square root of 144?",
        "expected_tool": "calculator",
        "expected_args": {"operation": "sqrt"}
    },
    {
        "input": "Add 25 and 75",
        "expected_tool": "calculator",
        "expected_args": {"operation": "add"}
    },
    {
        "input": "Search for latest AI news",
        "expected_tool": "web_search",
        "expected_args": {"query": "AI news"}
    },
]

MEMORY_GOLDEN = [
    {
        "store_input": "My name is Alice",
        "query_input": "What's my name?",
        "expected_in_response": "Alice"
    },
    {
        "store_input": "I live in Shenzhen, China",
        "query_input": "Where do I live?",
        "expected_in_response": "Shenzhen"
    },
    {
        "store_input": "My favorite programming language is Python",
        "query_input": "What's my favorite language?",
        "expected_in_response": "Python"
    },
]

逐段拆解:

① Schema 设计很关键

"schema": """{
  "topic": "the topic name as a string",
  "difficulty": "beginner" or "intermediate" or "advanced"
}

Example: {"topic": "machine learning", "difficulty": "intermediate"}"""

注意两件事:

  • 多行 schema 比单行好。单行 schema 容易让模型困惑,多行带缩进和示例的 schema 更稳定。
  • 带了 Example。一个具体示例比 100 字的描述有用。LLM 看到示例就知道期望的输出格式。

② 覆盖多种输入

# 英文
"Explain quantum computing in one sentence"
# 带引号和特殊字符
"What does 'hello world' mean in programming?"
# 中文
"描述一下 Python 的用途"

黄金数据集应该覆盖:不同语言、特殊字符、长输入、短输入。在生产中遇到的边缘情况,都应该加到黄金数据集里。

③ 记忆测试是"先存后查"

{
    "store_input": "My name is Alice",     # 先存
    "query_input": "What's my name?",      # 再查
    "expected_in_response": "Alice"        # 断言回答包含 "Alice"
}

记忆测试不是简单的输入→输出,而是两步操作。先存一个事实,再查询这个事实,检查回答是否包含关键信息。

3.4 报告打印

def print_eval_report(results: list[EvalSuiteResult]):
    """打印评测报告。"""
    print("=" * 50)
    print("EVAL REPORT")
    print("=" * 50)
    print()

    total_passed = 0
    total_failed = 0

    for suite in results:
        print(suite.summary())
        total_passed += suite.passed
        total_failed += suite.failed

        # 打印失败的用例详情
        for r in suite.results:
            if not r.passed:
                print(f"  ✗ Input: {r.input[:50]}...")
                if r.expected:
                    print(f"    Expected: {r.expected}")
                if r.actual:
                    print(f"    Actual: {r.actual}")
                if r.error:
                    print(f"    Error: {r.error}")
        print()

    print("-" * 50)
    status = "✓ ALL PASSED" if total_failed == 0 else f"✗ {total_failed} FAILED"
    total = total_passed + total_failed
    print(f"Overall: {status} ({total_passed}/{total})")
    print("=" * 50)

报告分两层:

  • 摘要层:每个套件一行——“Structured Output: ✓ PASSED (4/4)”
  • 详情层:只打印失败的用例——输入是什么、期望什么、实际输出什么、错误信息

正常通过的用例不打印详情。 报告应该突出"出了什么问题",而不是"一切正常"的噪音。


四、运行示例

4.1 全量评测

from agent.agent import Agent
from agent.evals import AgentEval, print_eval_report
from evals.golden_datasets import (
    STRUCTURED_OUTPUT_GOLDEN,
    TOOL_CALL_GOLDEN,
    MEMORY_GOLDEN
)

agent = Agent(model="qwen2.5:7b")
evaluator = AgentEval(agent)

# 运行所有评测
results = evaluator.run_all(
    structured_cases=STRUCTURED_OUTPUT_GOLDEN,
    tool_cases=TOOL_CALL_GOLDEN,
    memory_cases=MEMORY_GOLDEN
)

# 打印报告
print_eval_report(results)

全部通过时:

==================================================
EVAL REPORT
==================================================

Structured Output: ✓ PASSED (4/4)
Tool Calls: ✓ PASSED (5/5)
Memory Cycle: ✓ PASSED (3/3)

--------------------------------------------------
Overall: ✓ ALL PASSED (12/12)
==================================================

有失败时:

==================================================
EVAL REPORT
==================================================

Structured Output: ✗ FAILED (3/4)
  ✗ Input: What does 'hello world' mean in progra...
    Expected: Fields: ['explanation']
    Actual: Missing: ['explanation']
    Error: Missing fields: ['explanation']

Tool Calls: ✓ PASSED (5/5)
Memory Cycle: ✓ PASSED (3/3)

--------------------------------------------------
Overall: ✗ 1 FAILED (11/12)
==================================================

注意失败报告的格式——精确定位到哪个用例、缺了什么字段。不需要猜测,直接告诉你哪里出了问题。

4.2 捕获回归:故意改坏 Prompt

# 假设你修改了 System Prompt,加了一段"先简单解释再输出 JSON"的指令
agent.system_prompt += "\n\nAlways explain your reasoning before outputting JSON."

# 重新运行评测
results = evaluator.run_all(
    structured_cases=STRUCTURED_OUTPUT_GOLDEN
)
print_eval_report(results)

可能的结果:

Structured Output: ✗ FAILED (2/4)
  ✗ Input: Explain quantum computing in one sent...
    Error: Failed to parse JSON
  ✗ Input: What does 'hello world' mean in progra...
    Error: Failed to parse JSON

LLM 开始在 JSON 前面加解释性文字了 → extract_json_from_text() 解析失败 → 评测捕获了回归。

这就是评测的价值——你不改 Prompt 时一切正常,改了之后评测立刻告诉你:你搞坏了结构化输出。

4.3 交互模式

cd lesson11
python complete_example.py

会进入交互模式,你可以输入任意问题,观察 Agent 的响应,然后手动检查是否符合预期。


五、评测什么?

组件 评测什么 示例断言
结构化输出 JSON 有效性 + Schema 契约 result is not None and "topic" in result
决策 正确的动作路由 decision in valid_choices
工具调用 正确工具 + 正确参数 tool_call["tool"] == "calculator"
记忆 存储和检索周期 "Alice" in response
规划 输出有效的步骤列表 plan["steps"] is not None and len(plan["steps"]) > 0
原子动作 步骤能被正确拆解 atomic["action"] is not None

从硬断言开始(JSON 有效?字段存在?工具名称匹配?),后加软断言(语义正确?参数最优?措辞恰当?)。

硬断言帮你抓住基础设施级别的问题(解析失败、格式错误),软断言帮你抓住质量级别的问题(答案不够好、参数不是最优的)。


六、与前课的关系

6.1 第三课 vs 第十一课

第三课(结构化输出):
  生成 JSON → 检查有效性 → 失败就重试
  关注点:单次生成的正确性

第十一课(评测):
  准备测试用例 → 批量运行 Agent → 系统检查结果 → 报告通过率
  关注点:跨多个用例、多个能力的整体稳定性

第三课是"实时验证"——每次生成时检查。第十一课是"批量验证"——每次修改后系统检查。

第三课 第十一课
触发时机 每次生成 每次修改后
验证范围 单个输出 多个用例 × 多能力
有趋势数据吗 没有 有(通过率变化)
能捕获静默回归吗 不能

6.2 评测保护的是什么?

回顾前十课,每个能力都需要评测保护:

第一、二课:对话能力 → "Agent 能正常回复吗?"
第三课:结构化输出 → "JSON 解析能成功吗?"
第四课:决策能力 → "能正确路由到不同动作吗?"
第五课:工具调用 → "能选对工具、传对参数吗?"
第七课:记忆 → "存进去的能查出来吗?"
第八课:规划 → "生成的步骤列表格式正确吗?"
第九课:原子动作 → "步骤能被拆解为有效动作吗?"
第十课:AoT → "依赖图结构有效、能正确执行吗?"

每个能力都是 LLM 输出 + 你的代码解析/处理。任何一环出问题,能力就坏了。评测就是确保"一环都没坏"的安全网。


七、关键洞察

7.1 评测就是断言

这里没有魔法。你运行 Agent,检查输出,报告通过/失败。

result = agent.generate_structured(input, schema)
if result is None:
    failed("JSON 解析失败")
elif "topic" not in result:
    failed("缺少必需字段")
else:
    passed()

力量不在于"怎么做评测",而在于"系统化地做"。 一两个断言没有意义——30 个断言覆盖所有核心能力,每次修改后都跑一遍,这才叫评测套件。

7.2 黄金数据集是你的"契约"

当有人问"Agent 工作吗?",你没有模糊的回答——你指向黄金数据集:

黄金数据集通过率:100%
  结构化输出:4/4 ✓
  工具调用:5/5 ✓
  记忆:3/3 ✓

100% = 工作。99% = 有一个具体要修的问题。50% = 有严重问题。

数据集就是契约。通过了就部署,通不过就修。不需要争论"好不好",只看"过不过"。

7.3 工作流:每次修改前/后都跑

1. 运行评测 → 100% 通过(修改前基线)
2. 修改 Prompt
3. 运行评测 → 97% 通过
4. 检查失败的用例 → 定位问题
5. 修复或回滚
6. 运行评测 → 100% 通过
7. 提交

这就是质量保障的工作流。很简单,但大多数人不做。不做的原因不是"太难",而是"觉得没必要"——直到某天发现 Agent 在生产环境中悄悄坏了,才追悔莫及。

7.4 从简单开始

你不需要 1000 个测试用例。每个能力 3-5 个黄金用例就够起步了:

结构化输出:4 个用例(英文、中文、带特殊字符、空输入)
工具调用:5 个用例(加、减、乘、除、搜索)
记忆:3 个用例(存名字、存地点、存偏好)

共 12 个用例,覆盖 3 个核心能力。当你在生产中发现新的边缘情况时,再往里加。


八、常见问题

Q:每次跑评测太慢了怎么办?

A:三个策略。第一,分层——快速检查时只跑黄金数据集(12 个用例),提交前跑完整套件。第二,缓存模型加载——Ollama 模型第一次加载慢,后续请求快。第三,并行运行——不同套件之间没有依赖,可以同时跑。

Q:软断言不稳定,时过时不过怎么办?

A:先不写软断言。硬断言覆盖基础设施(JSON 有效、字段存在、工具名称正确),等硬断言 100% 稳定后,再逐步添加软断言。软断言可以设置"允许 N 次失败"的阈值,而不是一刀切。

Q:不知道该测什么怎么办?

A:从"快乐路径"开始——正常输入、正常输出。然后在生产中遇到 bug 时,把触发 bug 的输入加到黄金数据集里。生产 Bug = 免费的测试用例。 你的黄金数据集会随着时间自然增长,覆盖越来越多的边缘情况。

Q:改了 Prompt 之后评测失败了,是改 Prompt 还是改评测?

A:改 Prompt。黄金数据集是"真值"——这些用例通过了才能说明 Agent 正常工作。如果评测失败了,说明你的修改导致了回归,应该修复 Prompt 或回滚。唯一例外是:你有意改变了行为(比如不再支持某个工具),这时才更新黄金数据集。

Q:可以用 LLM 来评判评测结果吗(LLM-as-Judge)?

A:可以,但本课不涉及。LLM-as-Judge 属于"软断言的高级版本"——让另一个 LLM 来评判输出质量。问题是引入了不确定性(评判 LLM 本身也不可靠)。先掌握硬断言,后续再考虑。


九、十一课演进线

把前十一课放在一起看,Agent 从"裸模型"变成了一个有测试保障的工程系统:

第一课:能对话了(LLM + Ollama)
第二课:有角色了 + 能多轮对话(System Prompt + History)
第三课:能输出 JSON 了(结构化输出 + 验证 + 重试)
第四课:能做选择了(意图理解 → 动作路由)
第五课:能调用工具了(工具选择 + 参数提取 + 安全执行)
第六课:能循环了(Agent Loop + 状态追踪 + 终止条件)
第七课:能记住了(跨对话记忆存储 + 检索 + 显式管理)
第八课:能规划了(计划生成 → 验证 → 逐步执行)
第九课:能拆解了(模糊步骤 → 原子动作 → Schema 验证)
第十课:能编排了(依赖图 + 拓扑排序 + 并行潜力)
第十一课:能自检了(评测套件 + 黄金数据集 + 回归检测)

前十课在建设能力,第十一课在保护能力

没有评测,前十课的能力就像一栋没有保险的楼——看着挺好,但你不知道哪次小改动会让它塌了。


十、系列总结(更新)

到这一课,我们的 Agent 系统已经完整了:

能力 实现课程 核心机制
对话 第一、二课 LLM + System Prompt + 对话历史
结构化输出 第三课 JSON + extract_json + 重试
决策 第四课 意图分类 + 动作路由
工具调用 第五课 工具选择 + 参数提取 + 安全执行
循环执行 第六课 Agent Loop + 状态追踪 + 终止条件
记忆 第七课 存储 + 检索 + 显式管理
规划 第八课 目标 → 步骤列表 → 验证 → 执行
原子动作 第九课 模糊步骤 → 带参数的动作 → Schema 验证
任务编排 第十课 依赖图 + 拓扑排序 + 并行执行
质量保障 第十一课 评测套件 + 黄金数据集 + 回归检测

你可能注意到了:每个能力的底层都是同一套模式——让 LLM 输出结构化数据,然后你的代码来验证和处理。 而第十一课的评测,就是验证"这套模式有没有坏"。

LLM 负责理解和生成,你的代码负责验证和执行,评测负责确保一切没坏。

这就是"手搓 AI Agent"的全貌——没有黑盒,没有魔法,每一行代码你都理解,每一项能力都有测试保护。


十一、下期预告

第十二课:遥测——给 Agent 装上"运行时监控"

本课的评测解决了"修改后还能不能正常工作"的问题。但还有一个问题——Agent 在生产环境中实际在做什么?

评测告诉你"能力没坏",但不知道"每次请求花了多久、用了什么工具、为什么走了这条路径、有没有异常"。

下一课,我们将添加遥测(Telemetry)——在运行时追踪 Agent 的每一次决策、每一次工具调用、每一轮循环,让你不仅知道"对不对",还知道"为什么"。

敬请期待!


完整代码获取

本课涉及的完整代码包括:

  • agent/evals.py——评测框架(EvalResult + EvalSuiteResult + AgentEval + 报告打印)
  • evals/golden_datasets.py——黄金数据集(结构化输出 + 工具调用 + 记忆)
  • agent/agent.py——Agent 类(第十一课完整版)
  • complete_example.py——演示模式(全量评测 + 回归检测 + 交互模式)

标签

#Python #AI Agent #LLM #评测 #回归测试 #黄金数据集 #Ollama #Qwen #大模型 #手搓Agent


本文为《手搓 AI Agent 从 0 到 1》系列教程第 11 课

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐