首页
/ Docling 仓库中的 Pydantic AI 架构决策指南:从决策树到架构总览

Docling 仓库中的 Pydantic AI 架构决策指南:从决策树到架构总览

2026-09-05 10:03:24作者:乔或婵

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.mdagent_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”文件,并列出姊妹文档——若读者已明确知道要做什么,应改读更窄的任务指南:

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_plainget_player_name 需要读取注入的用户名,用 @agent.tool 并以 ctx: RunContext[str] 作为首参读取 ctx.deps。SKILL.md 的 “Common Gotchas” 同时提醒:@agent.tool 要求首参是 RunContext,而 @agent.tool_plain 绝不能带这个参数,混用会引发运行时错误。

从技能包结构看,工具进阶特性(审批、重试、校验器、超时、ToolReturn、动态 ToolPrepareFuncFunctionToolset 等)的完整写法被拆分在 TOOLS-CORE.mdTOOLS-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 子类;
  • 从配置文件定义 AgentAgent.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 实例)的能力(HooksPrepareToolsToolsetHistoryProcessor)无法写入声明式规格,只能以代码方式装配——这解释了为什么 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.modelm.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()UserPromptNodeModelRequestNodeCallToolsNode →(循环或结束)。即一次运行是一条节点链:注入用户提示、请求模型、执行模型要求的工具,然后在“还有工具要调用”时回到模型请求节点形成循环,直到模型给出最终输出。

关键泛型。

  • Agent[AgentDepsT, OutputDataT] —— 绑定依赖类型与输出类型;
  • RunContext[AgentDepsT] —— 在工具与系统提示中可用;
  • AbstractCapability[AgentDepsT] —— 可复用行为束的基类。

Agent 构建的两条路径。 Python 代码路径 Agent(model, instructions=..., tools=..., capabilities=...);声明式路径 Agent.from_file('agent.yaml')Agent.from_spec({...})

Capability 是首要扩展点——它把工具、生命周期钩子、指令与模型设置捆绑为可复用单元。内置能力包括 ThinkingWebSearchWebFetchHooksMCP 等(完整清单见第 6 节表格)。

生命周期钩子。 通过 HooksAbstractCapability 可以拦截运行的每个阶段,顺序为:before_runbefore_model_requestbefore_tool_executeafter_tool_executeafter_model_requestafter_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 运行、工具调用与模型请求。

登录后查看全文
热门项目推荐
相关项目推荐