第 0 章:先看全景,再开始写 Agent
这一章先不急着堆代码。你会先跟完一次真实请求,知道模型、上下文、工具和 Harness 各自站在哪里。以后遇到 Memory、Skill、MCP、Hook 或 Goal,就把它放回这张地图,不会再把术语学成一地碎片。
学习成果
完成本章后,你应该能:
- 用自己的话解释
Agent = LLM + Context + Tools + Harness。 - 区分模型“建议调用工具”和系统“真的执行工具”。
- 从 Web 请求一路追踪到模型、RAG/MCP、事件流和最终产物。
- 看一条运行日志时,指出当前是模型阶段、工具阶段、等待阶段还是终态。
- 解释 Skill 为什么是工作方法,而不是新权限。
- 说出一个聊天机器人变成可用 Agent 至少还缺哪些工程环节。
先自测一个:一个完整可用的 Agent,四个要素分别是什么?
- LLM
- LLM + Context
- LLM + Context + Tools
- LLM + Context + Tools + Harness
本章的验收不是“记住四个名词”,而是能够画出一次请求的控制流,并标明每个边界由谁负责。
这个能力有什么用
Agent 相关资料常从一个局部切入:
- 有的只讲 Prompt;
- 有的只讲 Tool Calling;
- 有的把 Memory 当数据库;
- 有的把 MCP 当 Agent;
- 有的把多个角色提示当成多 Agent。
局部知识本身没有错,问题是缺少坐标。全景图能帮你做三件事。
第一,定位问题。模型答错,可能是模型能力不足,也可能是上下文缺证据、工具返回错误或 Harness 提前停止。四层原因要分别检查。
第二,控制权限。模型可以提议 create_support_ticket,但 CapabilityPolicy 才决定这个 Skill、这个用户、这个环境是否获准执行。
第三,设计可观察系统。用户不应该只看到“正在思考”。他应该看到检索开始、工具完成、等待批准、引用核验和产物生成等状态。
没有它会发生什么
想象一个客服 Agent 回答:“退款工单已创建。”
如果没有全景视角,你可能只检查最终文字;实际上可能发生了四种不同情况:
- 模型凭空声称创建成功,根本没调用工具。
- 模型调用了工具,但参数中的订单号错误。
- 工具返回失败,Harness 却没有把失败结果送回模型。
- 工具已成功,浏览器 SSE 断线后又重复创建一次。
这四种问题的修法完全不同。只调 Prompt 会让问题更隐蔽。
另一个常见失败是权限混淆。Skill 中写了“必要时创建工单”,不代表它获得 ticket:write。MCP Server 暴露了这个 Tool,也不代表所有调用者都有资格使用。
先定位层,再修问题。不要看到 Agent 出错就把所有责任推给模型。
核心机制
先记住这条课程公式:
Agent 系统 = LLM + Context + Tools + HarnessLLM:提出下一步
LLM 根据本次请求中可见的信息,生成回答或结构化 Tool Call。它不会天然记住上次进程,也不会天然拥有文件、网络或数据库。
Context:这一次能看见什么
Context 不是“聊天记录”的同义词。它可以包含:
- 系统规则;
- 当前用户请求;
- 经过筛选的历史;
- Skill 摘要或完整正文;
- Tool Schema;
- RAG 证据;
- Tool Result;
- 待办项、Goal 和预算状态。
State 是系统保存的全部事实,Context 只是从 State 中为下一次模型调用选出的部分。
Tools:允许做哪些动作
Tool 有名称、描述、输入 Schema、输出约定和实现。模型只生成调用建议,代码负责:
- 校验名称与参数;
- 检查权限;
- 执行或拒绝;
- 记录结果;
- 把 Observation 放回下一轮 Context。
Harness:让概率模型进入可靠流程
Harness 是模型外围的控制系统,包括:
- Agent Loop;
- Context Builder;
- Tool Registry 与 Capability Policy;
- 状态和事件;
- Hook、预算、超时、重试与取消;
- Skill、记忆与 RAG;
- 评估、人工批准、发布和回滚;
- Web/API/MCP 产品界面。
《深入理解 AI Agent》第 1 章把 Harness 工程视为产品差异的重要来源。来源定位:ai-agent-book@1c18370279f8f0457bf2c44dfa585d08d4d5f281:book/chapter1.md。
Skill、MCP 和 Agent 放在哪里
| 名称 | 它回答的问题 | 是否直接执行 |
|---|---|---|
| Skill | “这类任务应该怎样做?” | 加载本身不产生副作用 |
| Tool | “这一步具体怎样执行?” | 是 |
| MCP | “外部能力怎样被统一发现和调用?” | 协议不执行,Server 实现执行 |
| Agent | “根据当前状态,下一步选什么?” | 由 Harness 驱动 |
| Harness | “哪些选择允许落地,怎样继续或停止?” | 编排和治理执行 |
完整流程
下面先跟踪课程的目标架构。它说明最终作品应该怎样把 Web Skill 接到统一 Agent Loop,而不是声称当前 v0 已经全部接通。
逐段看目标链路:
POST创建一次 Run。刷新或重连事件流不应再创建第二次 Run。- Skill Registry 解析固定版本。未批准、隔离或下架版本不能在线运行。
- Runner 只向模型暴露 Policy 允许的 Tool Schema。
- 模型提出
search_kb,Policy 在副作用前检查。 - 检索结果作为 Observation 进入下一次模型调用。
- Event Store 保存事实,SSE 负责传输这些事实。
- 用户看到的状态来自事件,不来自模型随口编写的“进度说明”。
如果需要创建工单,Policy 还要检查 ticket:write。如果证据不足,成功路径应是拒答或转人工,而不是继续猜。
当前 v0 实现与目标架构的差距
当前 api.py:create_run() 已经能解析已发布 Skill、执行客服 RAG 或 Diff Review、保存 Run,并通过 SSE 按 Last-Event-ID 重放事件。但它还没有把 Web Run 接入 AgentRunner:
| 环节 | 当前 v0 | 本章目标 |
|---|---|---|
| 运行入口 | create_run() 直接调用 CustomerSupportService 或 ReviewAgent | 统一调用 AgentRunner.run_turn() |
| 工具权限 | 业务 Service 自己限制动作 | 所有 Tool 在执行前经过 CapabilityPolicy |
| 事件 | 请求完成后保存 run.started/retrieval.completed/run.completed | 运行中持续产生 model/tool/turn 事件 |
| SSE | 对已完成事件做断点重放 | 既能实时订阅,也能断线后重放 |
| 返回时机 | 整个 Run 完成后返回 202 与 completed | 创建 Run 后立即返回,后台继续执行 |
当前实现的精确入口是 mini-emperor/backend/src/mini_emperor/api.py:create_run 和 stream_run。学习时先跑通 v0,再把这张差距表逐项消掉。这样你不会在教材中看到 AgentRunner,却在 Web 入口里找不到调用链。
如何观察当前状态
一次运行至少需要这些字段:
| 字段 | 回答的问题 | 示例 |
|---|---|---|
run_id | 是哪一次运行? | UUID |
event.id / sequence | 事件顺序是什么? | 42 |
event.type | 当前发生什么? | tool.completed |
iteration | 第几次模型决策? | 2 |
tool_call_id | 哪次动作? | call_7 |
skill_version | 用的哪版方法? | 1.2.0 |
policy_decision | 为什么允许或拒绝? | allow: kb:read |
status | 当前生命周期状态? | running |
artifact_ids | 产物存在哪里? | answer.json |
error | 在哪一层失败? | tool_timeout |
把事件序列看成状态变化:
run.created
→ turn.started
→ model.completed(tool_calls=[search_kb])
→ tool.started
→ tool.completed
→ model.completed(tool_calls=[])
→ turn.completed如果停在 tool.started,查超时、外部服务和取消;如果出现多个相同 turn.started,查幂等;如果模型最终回答没有引用,查 Context Builder 和引用核验,不要先怪 SSE。
视频内容提炼
视频 1 在 [00:00:15–00:00:47] 用“只能说不能做”引出 Agent,在 [00:02:03–00:02:22] 给出“思考、选择工具、执行、观察”的核心循环。
最值得带走的三点:
- LLM 像大脑,Tool 提供行动通道。
- 规划、记忆、工具和感知组成自主任务闭环。
- 工具不是越多越好,无关 Schema 会增加 Context 和选择负担。
需要修正的地方也很重要:
- “长期记忆写入向量数据库”只是一种实现,不是定义。
- “给基础工具,让模型自己创造”必须受沙箱和 Policy 约束。
- “任务成功后停止”要求有外部可验证的停止条件。
视频证据:video-01,完整笔记见 视频 01 来源笔记。
教学仓库代码演进
固定仓库用 12 个累计文件把全景展开:
step01 单次调用
→ step02 循环
→ step03 history
→ step04 system prompt
→ step05 tool loop
→ step06 Skill
→ step07 memory
→ step08 plan
→ step09 Subagent
→ step10 Agent Team
→ step11 MCP
→ step12 Hooks不要直接从 1762 行的 step12_hooks.py 第一行读起。先精读 Step 05 的 Loop,再对每一步做能力差分。
来源定位:
claude-agent-examples@54a18980334541773f13940aa0c0475728d30ee0:build-agent-example/code/step01_single_call.pyclaude-agent-examples@54a18980334541773f13940aa0c0475728d30ee0:build-agent-example/code/step05_tool_use.pyclaude-agent-examples@54a18980334541773f13940aa0c0475728d30ee0:build-agent-example/code/step12_hooks.py
每次阅读只问四个问题:
- 上一步用户能看到什么失败?
- 本步新增了什么状态?
- 新状态在哪个边界被读写?
- 用什么测试证明它有效?
《深入理解 AI Agent》工程补充
书第 1 章给出三层核心:
- LLM 是决策大脑;
- Context 是当前可见信息;
- Tool 是动作空间。
课程在此基础上显式加入 Harness,因为工程系统必须回答权限、状态、错误、评估和产品体验。
必做实验 chapter1/context 会对 History、Reasoning、Tool Definitions 和 Tool Results 做消融。重点不是追求最高分,而是观察缺少某类 Context 时出现哪种退化。
cd "$(git rev-parse --show-toplevel)/references/repos/ai-agent-book"
find chapter1/context -maxdepth 2 -type f | sort先读实验 README,再记录每个变体的输入差异和失败现象。不要把实验中的外部模型配置复制进教材或提交仓库。
进一步来源定位:ai-agent-book@1c18370279f8f0457bf2c44dfa585d08d4d5f281:chapter1/context/README.md。
Mini Emperor 对照
Mini Emperor 的最小运行时在:
mini-emperor/backend/src/mini_emperor/agent.pymini-emperor/backend/src/mini_emperor/model.pymini-emperor/backend/src/mini_emperor/skills.pymini-emperor/backend/src/mini_emperor/api.py
核心接口与全景的映射:
| 代码 | 全景角色 |
|---|---|
ModelClient.complete() | LLM |
messages / Tool Result | Context |
ToolRegistry | Tools |
AgentRunner | Loop |
CapabilityPolicy | 权限边界 |
AgentEvent | 可观测性 |
SkillRegistry | 方法与版本 |
| FastAPI + SSE | 产品接口 |
运行最小验证:
cd "$(git rev-parse --show-toplevel)"
uv run --python 3.12 --extra dev pytest \
mini-emperor/backend/tests/test_agent.py -v测试使用脚本化模型,不需要真实 API Key。它能稳定复现 Tool Call、迭代上限和越权阻断。
逐步实现
ModelClient 是 Protocol,不能直接实例化。下面用和测试相同的脚本化模型搭出一个可运行骨架:
from mini_emperor.agent import (
AgentRunner,
CapabilityPolicy,
ModelReply,
ToolCall,
ToolRegistry,
)
class ScriptedModel:
def __init__(self, replies: list[ModelReply]) -> None:
self.replies = iter(replies)
async def complete(self, messages: list[dict], tools: list[dict]) -> ModelReply:
return next(self.replies)
def search_kb(query: str) -> str:
return f"已检索受控知识库:{query}"
model = ScriptedModel([
ModelReply(tool_calls=[
ToolCall(id="call-1", name="search_kb", arguments={"query": "退货条件"})
]),
ModelReply(content="请以知识库证据为准。"),
])
tools = ToolRegistry()
tools.register("search_kb", "Search approved support articles", search_kb)
policy = CapabilityPolicy(allowed_tools=frozenset({"search_kb"}))
runner = AgentRunner(model=model, tools=tools, policy=policy)然后按这个顺序增加能力。
第一步:只输出事件
async for event in runner.run_turn("退货需要什么条件?"):
print(event.type, event.data)第一眼不要看回答质量,先确认事件顺序稳定。
第二步:替换为真实只读 Tool
# search_kb 的实现改为调用 HybridRetriever,但函数名和返回契约不变。把 Tool 描述写成“做什么、何时用、返回什么”,不要写成隐藏权限指令。
第三步:限制动作空间
policy = CapabilityPolicy(
allowed_tools=frozenset({"search_kb"}),
network_policy="deny",
)即使模型请求 create_support_ticket,Handler 也不会执行。
第四步:让 Web 只订阅事件
创建 Run 和订阅 Event 必须分开。SSE 断线重连只重放事件,不能重新调用 run_turn()。
第五步:添加完成证据
对客服回答,证据是文档 ID、标题和引用片段;对代码任务,证据是测试结果;对 Skill 发布,证据是评估与人工批准。
失败实验与调试
失败实验:Tool 成功,任务仍失败
把用户问题设为“查看桌面文件”,但 Tool Handler 固定返回当前工作目录。
预期现象:
tool.completed正常出现;- 模型可以写出流畅总结;
- 用户目标仍没有完成。
这证明“调用成功”只说明执行层无异常,不说明语义目标满足。
调试顺序:
- 查 Tool Arguments 是否表达“桌面”。
- 查 Handler 是否使用正确路径。
- 查 Tool Result 是否包含可验证路径。
- 查结束门禁是否真的验证了目标。
再做一次越权实验:
cd "$(git rev-parse --show-toplevel)"
uv run --python 3.12 --extra dev pytest \
mini-emperor/backend/tests/test_agent.py \
-k capability_policy -v预期是危险 Handler 从未被调用,事件记录 tool.failed。如果模型随后声称成功,那是回答验证问题,不是 Policy 问题。
本章练习
- 画出“在线运行 customer-support”和“下载 Skill 到本地 Agent”两条不同链路。
- 给每条链路标出最少三个信任边界。
- 将
AgentEvent事件表扩展一个policy.denied事件,说明与tool.failed的差别。 - 解释为什么 Tool Result 是数据,不应覆盖 System Prompt。
- 找出一次真实运行中最适合用确定性检查而不是 LLM Judge 的环节。
- 用 80 字解释 MCP Server 为什么不是 Agent。
完成标准:你的答案必须出现“谁做决定、谁执行、证据在哪里”,不能只列术语。
无资料复述
合上资料,用三分钟回答:
- Agent 与单次 LLM 调用的分界是什么?
- Context 和 State 有什么不同?
- 模型能看到 Tool,为什么仍可能没有执行权限?
- Skill、Tool、MCP、Harness 各自负责什么?
- 一条 SSE 事件能证明什么,不能证明什么?
- 如果客服 Agent 声称工单已创建,你如何逐层核验?
能画出 用户 → Web → API → Runner → Model → Policy → Tool → Event → 用户,并讲清每条箭头,才算通过。
深入学习导航
视频P012 分钟 · 视频 01 [00:00:15–00:02:22]
- 预计
- 12 分钟
- 位置
视频 01 [00:00:15–00:02:22]- 阅读任务
- 只看痛点、四要素和核心循环;画出哪些是模型、哪些是外部动作。
- 完成证据
- 提交一张带 LLM、Context、Tool、Harness 标签的图。
教学仓库P035 分钟 · claude-agent-examples Step 01、05、12
- 预计
- 35 分钟
- 位置
claude-agent-examples Step 01、05、12- 阅读任务
- 比较单次调用、工具循环和 Hook 后的新增边界,不线性阅读大文件。
- 完成证据
- 写出三步各新增的状态与失败模式。
书与实验P050 分钟 · ai-agent-book 第 1 章 + chapter1/context
- 预计
- 50 分钟
- 位置
ai-agent-book 第 1 章 + chapter1/context- 阅读任务
- 读公式、ReAct、Harness,并对 Context 元素做一次消融。
- 完成证据
- 保存一张四列消融表:移除项、现象、原因、修复层。
论文P050 分钟 · ReAct Figure 1 与 Section 3
- 预计
- 50 分钟
- 位置
ReAct Figure 1 与 Section 3- 阅读任务
- 把 Thought/Action/Observation 映射到 Step 05 和 AgentEvent。
- 完成证据
- 指出哪些推理内容不应直接暴露给最终用户。
官方文档P145 分钟 · DeepSeek Chat Completion + FastAPI Streaming + WHATWG SSE
- 预计
- 45 分钟
- 位置
DeepSeek Chat Completion + FastAPI Streaming + WHATWG SSE- 阅读任务
- 分清模型流式输出、服务器流和浏览器 EventSource 三个层。
- 完成证据
- 写出一次断线重连为何不会重复执行 Tool。