【手搓 AI Agent 从 0 到 1】第十一课:评测——给 Agent 装上“回归测试“
📌 前置知识:已完成第一课至第十课
🎯 本课目标:构建评测套件 + 黄金数据集,在每次修改后自动验证 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 课
更多推荐

所有评论(0)