Docling 仓库中的 Pydantic AI 架构决策指南:从决策树到架构总览
Docling 仓库在 .agents/skills/building-pydantic-ai-agents/ 目录下内置了一套面向 AI 编程助手的开发技能(development skill),其中的 ARCHITECTURE.md 是 Pydantic AI 框架的“架构与决策指南”。本文以该文档为核心,完整梳理其六棵决策树(工具注册、输出模式、多智能体模式、行为扩展、能力选择、测试方案)与五张对比表(输出模式、模型供应商前缀、工具装饰器、内置能力、Agent 运行方法),并结合技能目录中的入口文件 SKILL.md 与姊妹篇 AGENTS-CORE.md 的示例代码,把“选哪个抽象、为什么选它”讲透,读完你可以根据任务特征直接定位到正确的 Pydantic AI 写法。
1. 文档定位:为什么 Docling 仓库里放着一份 Pydantic AI 指南
按 AGENTS.md 与 agent_skills.md 的说明,Docling 仓库中的技能分为两类:一类是随 Python 包一起发布、教 Agent “使用 Docling”的 usage skill;另一类存放在仓库根目录 .agents/skills/ 下、供贡献者“开发 Docling 时使用”的 development skill,building-pydantic-ai-agents 即属后者(与 dignified-python 并列)。
building-pydantic-ai-agents 技能的入口是 SKILL.md,它声明了技能的适用场景(构建 Agent、加工具/能力、结构化输出、流式、YAML 规格定义、测试)并给出一张“任务路由表”。而 ARCHITECTURE.md 在该技能中承担的角色是:当用户要在多个抽象之间做选择、或需要对比表与决策树时才加载,即它是整个技能包的“比较与选型参考”。SKILL.md 的路由表明确写道:Compare abstractions, output modes, decorators, or model-string patterns → references/ARCHITECTURE.md。
这一点很关键:ARCHITECTURE.md 自身也强调自己是“comparison and abstraction choices”文件,并列出姊妹文档——若读者已明确知道要做什么,应改读更窄的任务指南:
- AGENTS-CORE.md:创建/配置 Agent、选择输出类型、deps、规格定义、运行方法
- CAPABILITIES-AND-HOOKS.md:可复用行为捆绑、生命周期事件拦截
- TOOLS-CORE.md:函数工具、toolset、MCP、显式搜索工具
- BUILTIN-TOOLS.md:供应商原生 web search / web fetch / 代码执行
- TOOLS-ADVANCED.md:审批、重试、
ToolReturn、校验器、超时 - INPUT-AND-HISTORY.md:多模态输入、消息历史、上下文裁剪
- TESTING-AND-DEBUGGING.md:测试与调试
- ORCHESTRATION-AND-INTEGRATIONS.md:多 Agent 协同、图工作流、A2A、持久执行
2. 决策树(一):如何注册工具
工具注册方式由“是否需要运行上下文”驱动,文档给出的决策树为:
Need RunContext (deps, usage, messages)?
├── Yes → Use @agent.tool
└── No → Pure function, no context needed?
├── Yes → Use @agent.tool_plain
└── Tools defined outside agent file?
├── Yes → Use tools=[Tool(...)] in constructor
└── Dynamic tools based on context?
├── Yes → Use ToolPrepareFunc
└── Multiple related tools as a group?
└── Yes → Use FunctionToolset
对应“何时用哪个装饰器”的对比表:
| 场景 | 选择 |
|---|---|
| 工具需要访问 deps、用量统计、消息、重试信息 | @agent.tool —— 首参必须是 RunContext |
| 纯函数,不需要 Agent 上下文 | @agent.tool_plain |
| 工具定义在独立模块中,或在多个 Agent 间共享 | Tool(fn) —— 通过 tools=[...] 传给 Agent 构造器 |
结合 SKILL.md 中的骰子游戏示例可以看到两种装饰器的实际分工:roll_dice 是无上下文纯函数,用 @agent.tool_plain;get_player_name 需要读取注入的用户名,用 @agent.tool 并以 ctx: RunContext[str] 作为首参读取 ctx.deps。SKILL.md 的 “Common Gotchas” 同时提醒:@agent.tool 要求首参是 RunContext,而 @agent.tool_plain 绝不能带这个参数,混用会引发运行时错误。
从技能包结构看,工具进阶特性(审批、重试、校验器、超时、ToolReturn、动态 ToolPrepareFunc、FunctionToolset 等)的完整写法被拆分在 TOOLS-CORE.md 与 TOOLS-ADVANCED.md 中,ARCHITECTURE.md 只负责“选型”,不承载实现细节。
3. 决策树(二):如何选择输出模式
Pydantic AI 的四种结构化/文本输出模式选择逻辑:
Need structured data with Pydantic validation?
├── Yes → Does provider support native JSON mode?
│ ├── Yes, and you want it → Use NativeOutput(MyModel)
│ └── No, or prefer consistency → Use ToolOutput(MyModel) [default]
└── No → Need custom parsing logic?
├── Yes → Use TextOutput(parser_fn)
└── No → Just plain text?
└── Yes → Use output_type=str [default]
Dynamic schema at runtime?
└── Yes → Use StructuredDict(json_schema)
配套的场景对比表:
| 场景 | 模式 |
|---|---|
| 需要结构化数据,且希望最大供应商兼容性 | ToolOutput(默认)—— 兼容所有供应商,支持流式 |
| 希望供应商原生强制 JSON schema 合规 | NativeOutput —— 仅限 OpenAI、Anthropic、Google;流式支持有限 |
| 供应商既不支持工具也不支持 JSON mode | PromptedOutput —— 作为兜底到处可用 |
| LLM 返回非 JSON 的结构化文本(markdown、YAML、领域格式) | TextOutput —— 自定义解析函数 |
AGENTS-CORE.md 给出了默认用法:output_type=MyModel(Pydantic 模型)即触发结构化输出,output_type=str 为纯文本。SKILL.md 的 “Common Gotchas” 还指出一个实践要点:当 output_type 是包含 str 的联合类型(或未设置 output_type)时,模型可以用纯文本提前结束运行;若必须走工具式输出,应从联合类型中剔除 str。
4. 决策树(三):多智能体模式的选型
Child agent returns result to parent?
├── Yes → Use agent delegation via tools
└── No → Permanent hand-off to specialist?
├── Yes → Use output functions
└── Application code between agents?
├── Yes → Use programmatic hand-off
└── Complex state machine?
└── Yes → Use Graph-based control
四种模式可以概括为:子 Agent 以工具形式被父 Agent 调用(结果回流父级)、用输出函数实现向专家 Agent 的永久性交接、在 Agent 之间插入应用代码的程序化交接、以及面向复杂状态机的图(Graph)式控制。多 Agent 委托的具体代码模式(如通过工具把整个子 Agent 作为工具暴露)在 ORCHESTRATION-AND-INTEGRATIONS.md 的 “Coordinate Multiple Agents” 一节中展开。
5. 决策树(四):如何扩展 Agent 行为
行为扩展是 Capability 体系的主战场,决策树如下:
Need reusable behavior across agents (tools + hooks + instructions)?
├── Yes → Build a custom capability (subclass AbstractCapability)
└── No → Just intercepting lifecycle events?
├── Yes → Complex interception needing tools/instructions too?
│ ├── Yes → Subclass AbstractCapability
│ └── No → Use Hooks capability with decorators
└── No → Defining agents from config files?
├── Yes → Use Agent.from_file() with YAML/JSON specs
└── No → Just adding tools?
├── Yes → Use @agent.tool or Toolset
└── Pass args directly to Agent constructor
要点归纳:
- 可跨 Agent 复用的行为束(工具 + hooks + 指令):继承
AbstractCapability构建自定义能力; - 仅拦截生命周期事件:用
Hooks能力配合装饰器,无需子类化;若拦截逻辑复杂到还需要注入工具或指令,则升级为AbstractCapability子类; - 从配置文件定义 Agent:
Agent.from_file()加载 YAML/JSON 规格; - 只是加工具:
@agent.tool或 Toolset,否则直接把参数传给 Agent 构造器。
SKILL.md 展示了 Hooks 的最小用法——@hooks.on.before_model_request 装饰器可以在模型请求发出前打印消息数量并原样返回 ModelRequestContext,然后把 Hooks() 实例放入 capabilities=[...]。其 Gotchas 还特别提醒:hook 装饰器名在 .on 上不重复 on_ 前缀,应写 hooks.on.run_error 而不是 hooks.on.on_run_error。
同文档的 YAML 规格示例也印证了“声明式定义”路径:
model: anthropic:claude-opus-4-6
instructions: "You are helping {{user_name}} with research."
capabilities:
- WebSearch
- Thinking:
effort: high
再用 Agent.from_file('agent.yaml', deps_type=UserContext) 加载,并通过 deps=UserContext(user_name='Alice') 注入依赖(见 AGENTS-CORE.md 的 “Define Agents Declaratively with Specs” 一节)。注意 instructions 中的 {{user_name}} 模板变量,说明规格文件中支持模板字符串。
6. 决策树(五):内置能力(Capability)怎么选
Need model thinking/reasoning?
├── Yes → Use Thinking(effort='high')
└── Need web search?
├── Yes → Use WebSearch() (auto-fallback to local)
└── Need URL fetching?
├── Yes → Use WebFetch()
└── Need MCP servers?
├── Yes → Use MCP()
└── Need lifecycle hooks only?
├── Yes → Use Hooks()
└── Need to filter/modify tool defs per step?
└── Yes → Use PrepareTools()
内置能力清单(含“是否可用于 YAML 规格”一列):
| 能力 | 提供什么 | 可用于 YAML 规格 |
|---|---|---|
Thinking |
可配置努力度的模型思考/推理 | 是 |
Hooks |
基于装饰器的生命周期钩子注册 | 否 |
WebSearch |
网络搜索——供应商支持时用原生实现,否则本地兜底 | 是 |
WebFetch |
URL 抓取——供应商支持时用原生实现,否则自定义兜底 | 是 |
ImageGeneration |
图像生成——供应商支持时用原生实现,否则自定义兜底 | 是 |
MCP |
MCP 服务器——供应商支持时用原生实现,否则直连 | 是 |
PrepareTools |
按步骤过滤或修改工具定义 | 否 |
PrefixTools |
包装一个能力并给其工具名加前缀 | 是 |
BuiltinTool |
向 Agent 注册一个内置工具 | 是 |
Toolset |
包装一个 AbstractToolset |
否 |
HistoryProcessor |
包装一个历史处理函数 | 否 |
SKILL.md 的快速上手示例给出了能力的典型装配方式:
from pydantic_ai import Agent
from pydantic_ai.capabilities import Thinking, WebSearch
agent = Agent(
'anthropic:claude-opus-4-6',
instructions='You are a research assistant. Be thorough and cite sources.',
capabilities=[
Thinking(effort='high'),
WebSearch(),
],
)
值得注意的是“可 YAML 化”这一列:凡依赖 Python 可调用对象(装饰器、函数、Toolset 实例)的能力(Hooks、PrepareTools、Toolset、HistoryProcessor)无法写入声明式规格,只能以代码方式装配——这解释了为什么 YAML 规格路径天然适合“标准能力组合”,而深度定制必须走代码。
7. 决策树(六):测试方案怎么选
Need deterministic, fast tests?
├── Yes → Use TestModel with agent.override()
└── Need specific tool call behavior?
├── Yes → Use FunctionModel
└── Testing against real API (integration)?
└── Yes → Use pytest-recording with VCR cassettes
三档测试策略:确定性快测(TestModel + agent.override())、指定工具调用行为(FunctionModel)、以及对真实 API 的集成回放(pytest-recording + VCR 磁带)。SKILL.md 提供了 TestModel 的标准写法:
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
my_agent = Agent('openai:gpt-5.2', instructions='...')
async def test_my_agent():
"""Unit test for my_agent, to be run by pytest."""
m = TestModel()
with my_agent.override(model=m):
result = await my_agent.run('Testing my agent...')
assert result.output == 'success (no tool calls)'
assert m.last_model_request_parameters.function_tools == []
其中 Gotchas 强调 TestModel 必须经 agent.override() 上下文管理器注入,不能直接改 agent.model;m.last_model_request_parameters.function_tools 则允许测试断言“本次请求实际携带了哪些工具”,实现了对请求内容的白盒校验。
8. 对比表:模型供应商前缀
模型字符串统一采用 "provider:model-name" 格式(例如 "openai:gpt-5.2")。ARCHITECTURE.md 给出的前缀对照表:
| 供应商 | 前缀 | 示例 |
|---|---|---|
| OpenAI | openai: |
openai:gpt-5.2 |
| Anthropic | anthropic: |
anthropic:claude-sonnet-4-6 |
| Google (AI Studio) | google-gla: |
google-gla:gemini-3-pro-preview |
| Google (Vertex) | google-vertex: |
google-vertex:gemini-3-pro-preview |
| Groq | groq: |
groq:llama-3.3-70b-versatile |
| Mistral | mistral: |
mistral:mistral-large-latest |
| Cohere | cohere: |
cohere:command-r-plus-08-2024 |
| AWS Bedrock | bedrock: |
bedrock:anthropic.claude-sonnet-4-6 |
| Azure | azure: |
azure:gpt-5.2 |
| OpenRouter | openrouter: |
openrouter:anthropic/claude-sonnet-4-6 |
| xAI | xai: |
xai:grok-3 |
| DeepSeek | deepseek: |
deepseek:deepseek-chat |
| Fireworks | fireworks: |
fireworks:accounts/fireworks/models/llama-v3p3-70b-instruct |
| Together | together: |
together:meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo |
| Ollama(本地) | ollama: |
ollama:llama3.2 |
| GitHub Models | github: |
github:openai/gpt-5.2 |
| Hugging Face | huggingface: |
huggingface:meta-llama/Llama-3.3-70B-Instruct |
| Cerebras | cerebras: |
cerebras:llama-4-scout-17b-16e-instruct |
| Heroku | heroku: |
heroku:claude-sonnet-4-6 |
文档还列出附加前缀:litellm:、nebius:、ovhcloud:、alibaba:、sambanova:、vercel:、outlines:、moonshotai:。对于真正自定义的供应商,则继承 Model 基类,或用 OpenAIChatModel 配合自定义 base_url。
使用注意:模型字符串必须带供应商前缀——写 'gpt-5.2' 而非 'openai:gpt-5.2' 会导致 Pydantic AI 无法解析供应商(SKILL.md 明确列为常见错误)。当需要供应商特有的构造参数时,应传入模型实例而非字符串,例如 AGENTS-CORE.md 的故障切换示例:
from pydantic_ai import Agent
from pydantic_ai.models.anthropic import AnthropicModel
from pydantic_ai.models.fallback import FallbackModel
from pydantic_ai.models.openai import OpenAIChatModel
fallback = FallbackModel(
OpenAIChatModel('gpt-5.2'),
AnthropicModel('claude-sonnet-4-6'),
)
agent = Agent(fallback)
该示例体现了 FallbackModel 的用途:主模型失败时自动切换到备用供应商,并保持同一提示/输出契约。
9. 对比表:Agent 运行方法与流式
| 场景 | 方法 |
|---|---|
| 构建聊天机器人/助手,需实时展示工具调用、进度与输出 | agent.run(event_stream_handler=...) —— 运行到完成的同时流式消费所有事件 |
| 运行自主 Agent、批处理作业或后台任务 | agent.run() |
| CLI 工具、脚本、Jupyter notebook(无 async) | agent.run_sync() |
| 向 UI 逐词流式输出最终文本 | agent.run_stream() |
| CLI/脚本的同步流式(无 async) | agent.run_stream_sync() |
| 接收类型化事件的异步迭代器(工具调用、结果、最终输出) | agent.run_stream_events() |
| 在 Agent 步骤之间检查/修改状态、人工介入审批 | agent.iter() |
event_stream_handler 的写法(详见 AGENTS-CORE.md 的 “Run Methods and Streaming”):
from collections.abc import AsyncIterable
from pydantic_ai import Agent, AgentStreamEvent, FunctionToolCallEvent, RunContext
agent = Agent('openai:gpt-5.2')
async def stream_handler(ctx: RunContext[None], events: AsyncIterable[AgentStreamEvent]):
async for event in events:
if isinstance(event, FunctionToolCallEvent):
print(f'Calling {event.part.tool_name}...')
async def main():
await agent.run('Do the task', event_stream_handler=stream_handler)
这个模式适合“不手动消费事件流,但仍要在运行时收到进度”的交互界面:把 handler 作为参数传入后,run() 照常返回最终结果,事件处理在后台完成。
10. 架构总览:执行流、泛型与扩展点
ARCHITECTURE.md 的 “Architecture Overview” 一节浓缩了整个框架的心智模型:
执行流。 Agent.run() → UserPromptNode → ModelRequestNode → CallToolsNode →(循环或结束)。即一次运行是一条节点链:注入用户提示、请求模型、执行模型要求的工具,然后在“还有工具要调用”时回到模型请求节点形成循环,直到模型给出最终输出。
关键泛型。
Agent[AgentDepsT, OutputDataT]—— 绑定依赖类型与输出类型;RunContext[AgentDepsT]—— 在工具与系统提示中可用;AbstractCapability[AgentDepsT]—— 可复用行为束的基类。
Agent 构建的两条路径。 Python 代码路径 Agent(model, instructions=..., tools=..., capabilities=...);声明式路径 Agent.from_file('agent.yaml') 或 Agent.from_spec({...})。
Capability 是首要扩展点——它把工具、生命周期钩子、指令与模型设置捆绑为可复用单元。内置能力包括 Thinking、WebSearch、WebFetch、Hooks、MCP 等(完整清单见第 6 节表格)。
生命周期钩子。 通过 Hooks 或 AbstractCapability 可以拦截运行的每个阶段,顺序为:before_run → before_model_request → before_tool_execute → after_tool_execute → after_model_request → after_run。这套命名与第 7 节 Hooks() 装饰器示例(hooks.on.before_model_request)相互印证:装饰器名与钩子阶段名一一对应。
输出模式小结。 ToolOutput(经工具调用的结构化数据,Pydantic 模型的默认)、NativeOutput(供应商原生结构化输出)、PromptedOutput(基于提示的结构化抽取)、TextOutput(纯文本响应)——与第 3 节决策树形成闭环。
11. 把决策树落到代码:一个完整的选型示例
把文档中的选型结论串起来,一个“带依赖注入 + 结构化输出 + 能力 + 测试”的 Agent 可以这样组织:
from datetime import date
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
from pydantic_ai.capabilities import Thinking, WebSearch
class ResearchResult(BaseModel):
summary: str
sources: list[str]
agent = Agent(
'anthropic:claude-sonnet-4-6', # 决策树三:模型串必须带前缀
deps_type=str, # Agent[AgentDepsT, OutputDataT]
instructions='You are a research assistant. Be thorough and cite sources.',
output_type=ResearchResult, # 决策树二:ToolOutput(MyModel) 默认路径
capabilities=[Thinking(effort='high'), WebSearch()], # 决策树五
)
@agent.instructions
def add_the_users_name(ctx: RunContext[str]) -> str:
return f"The user's name is {ctx.deps}."
@agent.instructions
def add_the_date() -> str:
return f'The date is {date.today()}.'
@agent.tool_plain
def get_now() -> str:
"""Return the current date as text."""
return date.today().isoformat()
result = agent.run_sync('Summarize recent AI safety research', deps='Frank')
print(result.output)
print(result.usage()) # 如 RunUsage(input_tokens=..., output_tokens=..., requests=1)
各要素对应的决策点:需要 RunContext 的指令/工具用 @agent.instructions / @agent.tool(首参 RunContext[str]);不需要上下文的工具用 @agent.tool_plain;结构化输出交给默认 ToolOutput 路径而非 NativeOutput,换取跨供应商一致性;思考与搜索通过 capabilities 装配。测试时按第 7 节决策树用 TestModel + agent.override() 做确定性单测,回归真实 API 行为时用 FunctionModel 或 VCR 回放。
12. 小结与在仓库中的延伸阅读
ARCHITECTURE.md 的价值在于提供“选型而不实现”的一层:六棵决策树回答“该用哪个抽象”,五张对比表给出“为什么用它、在什么约束下用它”(例如 NativeOutput 仅限 OpenAI/Anthropic/Google 且流式受限、Hooks/PrepareTools 等能力不可写入 YAML 规格)。实现细节则由同一 references/ 目录下的八份任务指南承载,入口路由规则写在 SKILL.md 的任务路由表中。对 Docling 贡献者而言,这套技能包配合 AGENTS.md 的说明,构成了在仓库内构建 Pydantic AI Agent 应用时的标准参考路径;技能要求的运行环境为 Python 3.10+,可观测性方面 SKILL.md 推荐 logfire.instrument_pydantic_ai() 追踪 Agent 运行、工具调用与模型请求。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00