《AI Agent 高级编程》1.1 框架分层:引擎/编排/SDK/协议四层定位图

本节给出 Agent 框架的四层定位图:引擎层(loop 内核)、编排层(LangGraph 图执行器)、SDK 层(PydanticAI、Claude Agent SDK、OpenAI Agents SDK、smolagents)、协议层(MCP、A2A),用本机真实 import 打印模块归属与版本号作证,并把卷一 minagent 与卷二 ResearchCopilot 钉到同一张图上。

1.1 框架分层:引擎/编排/SDK/协议四层定位图

卷一《AI Agent 开发入门》从零手写了 minagent,卷二《AI Agent 工程实战》把它做成了 ResearchCopilot 服务。走到本卷,问题换了一个:市面上那些「Agent 框架」到底各自负责哪一层,能不能拿来重写我们已经跑通的引擎?

在回答之前必须先有一张地图——否则你会在 langgraph、pydantic_ai、agents、mcp 这些名字之间反复迷路,把「编排」当成「引擎」,把「协议」当成「SDK」。这类混淆不是小事:它会让你的技术选型在错误的粒度上发生,比如「用 LangGraph 替换 PydanticAI」——而这两个根本不在同一层,谈不上替换。

本节把「框架全景」推进到一张可落地的四层定位图:引擎层 / 编排层 / SDK 层 / 协议层,每层给出代表库、职责边界与本机实测的模块归属,并把卷一的 minagent 与卷二的 ResearchCopilot 钉到图上。后续每一章都会回到这张图,说明「这一章在动哪一层」。

1.1.1 为什么必须先分层

一个常见的错误提问是「PydanticAI 和 LangGraph 哪个更好」。这个问法本身就不成立——它们根本不在同一层。

PydanticAI 是「让一个 Agent 跑起来」的 SDK;LangGraph 是「让多个步骤按图编排、可检查点恢复」的执行器。你可以用 LangGraph 编排三个 PydanticAI Agent,也可以完全不用 LangGraph 而直接用 PydanticAI 的循环。把它们并列比较,就像比较「发动机」和「变速箱」——它们协同工作,但不互相替代。

分层的价值在于:它把「选型」拆成了「分层选型」。你不再问「用哪个框架」,而是分四次问:

层你要决定的问题典型答案
引擎层我用谁的 loop 内核跑单次推理?自研 minagent / 某 SDK 内置循环
编排层多步骤、多 Agent 怎么串、怎么恢复?LangGraph / 自研状态机
SDK 层单个 Agent 的提示、工具、输出模型怎么写?PydanticAI / Claude Agent SDK / OpenAI Agents SDK / smolagents
协议层Agent 与外部工具、与别的 Agent 怎么通信?MCP / A2A

四层是可以自由组合的。这正是本卷的主线:用 SDK 层重写卷二的问答路径,用编排层补上持久化,用协议层接上外部能力。

还有一层现实收益:分层让「替换」变得可控。当你想把 PydanticAI 换成 Claude Agent SDK,只要两者都在 SDK 层、都被 1.2 的统一 Protocol 包住,业务代码就不用动。如果当初把编排逻辑也写进了 SDK 调用里,替换就会牵一发动全身。

1.1.2 四层定位图

下面这张图是本节的核心产出。左列是层的名字,中列是职责,右列是本机已装、本卷要用的代表库。

┌──────────────────────────────────────────────────────────────────────┐
│  协议层  Protocol        「怎么对话」——跨进程/跨厂商的线上约定          │
│    MCP (mcp 2.3.0 / fastmcp 4.1.0)   A2A                              │
│    · Agent ↔ 工具/资源    · Agent ↔ Agent                             │
├──────────────────────────────────────────────────────────────────────┤
│  SDK 层   Agent SDK      「单个 Agent 怎么写」——提示/工具/输出模型      │
│    PydanticAI 2.55.0 · Claude Agent SDK 0.2.165                       │
│    OpenAI Agents SDK 0.23.1 · smolagents 1.26.0                       │
│    · 自带 run 循环(引擎层的一种实现)  · 工具注册与权限               │
├──────────────────────────────────────────────────────────────────────┤
│  编排层   Orchestration  「多步骤怎么跑」——图/状态机/检查点/恢复        │
│    LangGraph 1.2.14 (langchain-core 1.6.9)                            │
│    · StateGraph 节点与边  · checkpoint 持久化  · 中断与恢复           │
├──────────────────────────────────────────────────────────────────────┤
│  引擎层   Engine         「一次推理循环怎么转」——messages↔ModelResponse │
│    卷一的 minagent  ·  裸 provider SDK (anthropic 1.13.0 / openai 3.28.0)│
│    · 组装 messages  · 调模型  · 解析 tool_calls  · 回灌结果  · 收敛判断 │
└──────────────────────────────────────────────────────────────────────┘
        ↑ 下层不知道上层;上层通过接口调用下层(依赖倒置)

把两卷的产物钉上去,图就完整了:

产物落在哪一层说明
卷一 minagent引擎层手写 loop:run(model, registry, user_input, ...) -> RunResult
卷二 ResearchCopilot编排层 + 服务壳REST/队列/多租户在服务层;多步研究流程是编排
PydanticAISDK 层(内含引擎)Agent.run() 内部就是一个 loop
LangGraph编排层它调 SDK 或裸模型,自己不做提示工程
MCP / A2A协议层只定义线上格式,不定义 loop

关键洞察:SDK 层是「引擎层 + 提示/工具/输出」的打包。卷一你手写的循环,在 PydanticAI 里被封装成 Agent.run();在 Claude Agent SDK 里被封装成一次 query() 会话。理解这一点,本卷后面每一章「读源码」就有了统一的坐标——你要读的,无非是「这个 SDK 把引擎层的哪几件事替我做了」。

本卷的章节安排,本身就是沿这张图从上到下、再从下到上走一遍:

章节主要动哪一层一句话
第 2–4 章SDK 层用四个 SDK 分别重写问答路径并同题对照
第 5 章引擎层 + 编排层读源码,把 SDK 藏起来的循环挖出来
第 6 章编排层补持久化与确定性重放
第 7 章SDK 层(扩展)计算机使用与浏览器 Agent
第 8 章协议层A2A 跨 Agent 协作
第 10–11 章横切可观测、成本、安全、平台化

1.1.3 真机佐证:import 各框架看归属

分层不能只靠嘴说。下面这段脚本在本机 /tmp/agentbook/venv3 里真实执行,逐个 import 并用 importlib.metadata 读版本、用 __file__ 读模块归属,用实测输出给分层背书:

import importlib
import importlib.metadata as md

LAYERS = {
    "协议层": [("mcp", "mcp"), ("fastmcp", "fastmcp")],
    "SDK 层": [("pydantic_ai", "pydantic-ai"), ("agents", "openai-agents"),
               ("smolagents", "smolagents"), ("claude_agent_sdk", "claude-agent-sdk")],
    "编排层": [("langgraph", "langgraph"), ("langchain_core", "langchain-core")],
    "引擎层": [("anthropic", "anthropic"), ("openai", "openai")],
}

for layer, pkgs in LAYERS.items():
    print(f"== {layer} ==")
    for modname, dist in pkgs:
        mod = importlib.import_module(modname)
        ver = md.version(dist)
        path = getattr(mod, "__file__", None) or "<namespace package>"
        leaf = path.split("site-packages/")[-1] if "site-packages/" in path else path
        print(f"  {dist:18s} v{ver:8s} {modname:16s} -> {leaf}")

真跑输出(Python 3.14.6):

== 协议层 ==
  mcp                v2.3.0    mcp              -> mcp/__init__.py
  fastmcp            v4.1.0    fastmcp          -> fastmcp/__init__.py
== SDK 层 ==
  pydantic-ai        v2.55.0   pydantic_ai      -> pydantic_ai/__init__.py
  openai-agents      v0.23.1   agents           -> agents/__init__.py
  smolagents         v1.26.0   smolagents       -> smolagents/__init__.py
  claude-agent-sdk   v0.2.165  claude_agent_sdk -> claude_agent_sdk/__init__.py
== 编排层 ==
  langgraph          v1.2.14   langgraph        -> <namespace package>
  langchain-core     v1.6.9    langchain_core   -> langchain_core/__init__.py
== 引擎层 ==
  anthropic          v1.13.0   anthropic        -> anthropic/__init__.py
  openai             v3.28.0   openai           -> openai/__init__.py

两处细节值得记下来,它们是实测才能发现的:

  • openai-agents 的 import 名是 agents,不是 openai_agents;claude-agent-sdk 的 import 名是 claude_agent_sdk(连字符转下划线)。凭记忆写 import 是本节最容易翻车的点,写错会直接 ModuleNotFoundError。
  • langgraph 是 namespace package(__file__ 为 None),它的实体在子模块里:langgraph.graph、langgraph.checkpoint。所以第 5.1 节读源码时要直接定位到 langgraph/graph/,不能靠 langgraph.__file__。

红线提醒:claude_agent_sdk 与 agents 的运行会真实发起网络请求、消耗用户额度。本节只做 import 与 inspect,未执行任何 query() / Runner.run() 调用。第 3、4 章也遵守同一约束。

1.1.4 每层的典型失败模式

分层还有一个实用价值:它告诉你「出了问题该去哪一层找」。每一层都有自己的经典故障,认得出层,就找得到根因。

层典型失败模式症状排查入口
引擎层收敛判断错 / 工具结果回灌格式错循环不结束,或提前结束打印 Step 轨迹
编排层检查点丢失 / 边条件写反重启后从头跑,或走错分支看 checkpoint 存储
SDK 层输出模型校验失败 / 工具 schema 不匹配重试耗尽报错看 SDK 的 retry 日志
协议层握手失败 / 版本不兼容连不上 Server / 字段缺失看传输层报文

举个真实场景:服务重启后任务从头跑了一遍。这是编排层的检查点没落盘,不是引擎层的循环 bug。如果一开始没分层,你可能花一天去读 Agent.run 的源码,而问题其实在 checkpoint 的存储配置上。分层不是学术分类,是排障的第一把手术刀。

再比如「模型明明返回了工具调用,却没被执行」——这是引擎层的 tool_calls 解析或回灌出了问题,和 SDK、编排都无关。先定层,再定因,能省掉大量无效搜索。

1.1.5 怎么判断一个库属于哪一层

面对一个没见过的新库,用下面三个问题快速定位:

  1. 它有没有自己的 run 循环? 有 → 至少是引擎层或 SDK 层。没有、只是定义数据格式 → 协议层。
  2. 它管不管「多步骤的状态与恢复」? 管 → 编排层。不管、只关心单次交互 → SDK 层。
  3. 它能不能脱离任何具体模型厂商? 能、只定义线上约定 → 协议层。绑死某家 → 多半是 SDK 层。

用这三问过一遍:mcp 只定义工具调用格式、没有循环、与厂商无关 → 协议层。langgraph 有节点/边/检查点 → 编排层。pydantic_ai 有 Agent.run 但不管跨步骤恢复 → SDK 层。anthropic 只有 messages.create 一次调用、没有循环 → 引擎层的零件。

再补一条经验法则:一个库如果「换掉它你就要重写循环」,它是引擎/SDK 层;如果「换掉它你只是改流程编排」,它是编排层;如果「换掉它你只是改通信地址」,它是协议层。替换成本,就是层的指纹。

一个常见的误判:把 fastmcp 当成「又一个 Agent 框架」。它没有循环、不组装 messages、只把 Python 函数暴露成 MCP 工具——它是协议层的一个实现库,不是 SDK 层。用上面的三问过一遍就清楚了。

1.1.6 同一个任务在四层分别长什么样

抽象讲多了会飘,用同一个任务「回答一个问题」过一遍四层,分层立刻具体起来。下面是四段伪代码骨架(只为对照结构,不是本节要跑的代码),注意每一层关心的东西完全不同:

# 引擎层(卷一 minagent):只关心「循环怎么转」
result = run(model, registry, user_input="研究 X", max_steps=8)
#   ↑ 组装 messages、调模型、解析 tool_calls、回灌、判收敛,全在这一个函数里

# SDK 层(PydanticAI):只关心「一个 Agent 怎么写」
agent = Agent(model, output_type=Report, tools=[search])
result = await agent.run("研究 X")      # 循环被 SDK 藏起来了

# 编排层(LangGraph):只关心「多步骤怎么连、怎么恢复」
graph = StateGraph(State)
graph.add_node("plan", plan_node); graph.add_node("search", search_node)
graph.add_edge("plan", "search")
app = graph.compile(checkpointer=checkpointer)   # 崩溃可从检查点续跑

# 协议层(MCP):只关心「工具怎么被外部 Agent 调用」
@server.tool()
def search(q: str) -> str: ...          # 只定义线上接口,不含任何循环

对着这四段,前面几节的话就落地了:

  • 引擎层那段把「循环」写死在调用里,换模型要动 run 内部。
  • SDK 层那段用 Agent.run 把循环封掉,你拿到的是「一个能跑的 Agent」。
  • 编排层那段根本不碰循环,它把 SDK 或引擎当节点用,价值在 checkpointer。
  • 协议层那段连「谁在调、调几次」都不关心,它只把 search 暴露成一个标准接口。

同一个「研究 X」,在四层里被切成四种不同的关注点。 这也是为什么「换框架」要先问「换哪一层」——引擎层和 SDK 层可以互相替换(都能跑单次循环),但它们都不能替掉编排层的检查点,也不能替掉协议层的线上约定。

1.1.7 与站内选型矩阵的分工

站内 /posts/llm/ 下已有 llm-agent-frameworks 一篇,那是一张选型矩阵:把各框架的特性列成表,帮你「挑一个」。本卷和它不是重复关系,而是接力关系:

维度站内 llm-agent-frameworks本卷《AI Agent 高级编程》
视角横向对比(选哪个)纵向拆解(怎么跑起来)
深度特性清单、优劣势源码调用链、可运行重写
产出一张对照矩阵每节一段能跑的真实代码
落点决策辅助把卷二 ResearchCopilot 真重写一遍

换句话说:那篇帮你「决定用 PydanticAI」,本卷帮你「用 PydanticAI 把服务重写出来,并说清它内部发生了什么」。如果你还没做过选型,先读那篇;如果你已经定了框架要落地,本卷才是你要的。MCP 的协议规范、Server 实现、传输与 OAuth 一律不在本卷范围,遇到就链 /posts/mcp/。

1.1.8 协议层内部:MCP 管工具、A2A 管协作

协议层里有两个名字经常被混为一谈,值得先分清,第 8 章会展开:

协议连接的是解决什么本卷落点
MCPAgent ↔ 工具/资源让工具一次实现、到处复用第 8.2 节与 /posts/mcp/ 分工
A2AAgent ↔ Agent让多个 Agent 互相委派任务第 8.1 / 8.3 节

一句话区分:MCP 是「Agent 怎么用工具」,A2A 是「Agent 怎么找别的 Agent 帮忙」。二者都只定义线上格式,都不含循环。本卷不重讲 MCP 的协议规范(站内 /posts/mcp/ 已覆盖),只在第 8 章讲 A2A 与二者的分工。把它们放在定位图最上层,是因为它们不关心你下层用什么框架——这正是协议的价值:跨厂商、跨语言、跨进程。

1.1.9 这套框架替你做了什么、你又让渡了什么

分层图最终要落到一个务实的判断上。用 SDK 层替掉手写引擎,收益和代价是对称的:

框架替你做了:run 循环的收敛判断、工具 schema 生成与调用解析、输出模型校验与失败重试、消息历史管理、流式与取消。这些卷一你写了一整章的东西,在 SDK 里是一行 Agent(...).run(...)。

你让渡了:对循环内部细节的直接控制权。想改「什么时候算收敛」,在 minagent 里改三行;在 SDK 里可能要读源码找 hook 点,甚至根本改不动。这就是锁定度——本卷第 1.3 节会把它量化成打分维度。

所以本卷的姿势是:先用框架把东西跑起来,再读源码把让渡掉的控制权拿回来。第 5 章整章都在干这件事。分层图不是终点,是你决定「在哪一层用框架、在哪一层保留自研」的起点。

阅读导航:上一节:目录 · 下一节:1.2 统一抽象:一个 Protocol 适配多家并写适配层 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「agent」更多文章

  1. 《AI Agent 高级编程》目录
  2. 《AI Agent 高级编程》11.3 全景回顾:从零到框架的完整路径与展望
  3. 《AI Agent 高级编程》11.2 真实 API 冒烟:假模型 vs 真模型量化差距