跳转至

PydanticAI:类型安全的 AI Agent 框架

为什么要学 PydanticAI

构建 AI Agent 时,最常遇到的痛点是:LLM 的输出不可控、工具调用缺乏类型校验、调试困难、多模型切换成本高。LangChain 虽然功能全面但过度抽象,简单任务也需要大量样板代码。

PydanticAI 由 Pydantic 团队开发,核心设计哲学是用 Python 类型系统驯服 LLM 的不确定性。它借鉴了 FastAPI 的设计理念——通过类型注解自动完成验证、序列化和文档生成。如果你熟悉 FastAPI,PydanticAI 的 Agent 定义方式会让你立刻上手。

核心优势: - 类型安全的结构化输出(基于 Pydantic Model) - 模型无关设计(OpenAI、Anthropic、Gemini、Groq、Ollama 一行切换) - 依赖注入系统(类似 FastAPI 的 Depends) - 与 Logfire 深度集成(可观测性开箱即用) - 流式响应支持 - 原生 Python,不发明新概念


核心概念

白话解释

概念白话说明
Agent一个封装了系统提示、工具、输出类型的智能体定义
ToolAgent 可调用的函数,框架自动从类型注解生成 schema
Structured Result用 Pydantic Model 定义的输出格式,LLM 必须按格式返回
Dependencies注入到工具/系统提示中的运行时上下文(如DB连接、用户信息)
System Prompt可以是静态字符串,也可以是动态函数(基于依赖注入)
ModelRetry当输出不符合 schema 时自动重试
RunContext运行时上下文对象,携带依赖和重试信息
Usagetoken 用量统计
Message History对话历史,可持久化和恢复

与其他框架对比

特性PydanticAILangChainCrewAI
类型安全原生 Pydantic
学习曲线低(类FastAPI)
抽象层级薄(接近原生)
多模型支持
可观测性Logfire集成LangSmith
流式输出支持支持有限
适用场景生产级Agent快速原型多Agent协作

安装配置

安装

# 基础安装
pip install pydantic-ai

# 安装特定模型支持
pip install pydantic-ai[openai]      # OpenAI
pip install pydantic-ai[anthropic]   # Anthropic/Claude
pip install pydantic-ai[groq]        # Groq
pip install pydantic-ai[google]      # Gemini

环境变量配置

# .env 文件
OPENAI_API_KEY=sk-xxx
ANTHROPIC_API_KEY=sk-ant-xxx
GEMINI_API_KEY=xxx
GROQ_API_KEY=gsk_xxx

验证安装

from pydantic_ai import Agent

agent = Agent("openai:gpt-4o-mini")
result = agent.run_sync("说一句话证明你在工作")
print(result.data)

快速上手

最简 Agent

from pydantic_ai import Agent

# 创建一个最简单的 Agent
agent = Agent(
    "openai:gpt-4o-mini",
    system_prompt="你是一个乐于助人的助手,回答要简洁。"
)

# 同步运行
result = agent.run_sync("Python 的 GIL 是什么?")
print(result.data)
print(f"Token 用量: {result.usage()}")

结构化输出

from pydantic import BaseModel
from pydantic_ai import Agent

class CityInfo(BaseModel):
    name: str
    country: str
    population: int
    famous_for: list[str]

agent = Agent(
    "openai:gpt-4o-mini",
    result_type=CityInfo,
    system_prompt="提取城市信息,population 用实际数字。"
)

result = agent.run_sync("告诉我关于东京的信息")
city = result.data  # 类型是 CityInfo
print(f"{city.name}, {city.country}")
print(f"人口: {city.population:,}")
print(f"闻名于: {', '.join(city.famous_for)}")

添加工具

from pydantic_ai import Agent, RunContext
import httpx

agent = Agent(
    "openai:gpt-4o-mini",
    system_prompt="你是天气助手,使用工具查询天气。"
)

@agent.tool_plain
async def get_weather(city: str) -> str:
    """获取指定城市的当前天气。"""
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"https://wttr.in/{city}?format=3"
        )
        return resp.text

# 运行
result = agent.run_sync("北京今天天气怎么样?")
print(result.data)

进阶用法

1. 依赖注入

from dataclasses import dataclass
from pydantic_ai import Agent, RunContext

@dataclass
class UserContext:
    user_id: int
    user_name: str
    is_premium: bool

agent = Agent(
    "openai:gpt-4o-mini",
    deps_type=UserContext,
)

@agent.system_prompt
async def build_prompt(ctx: RunContext[UserContext]) -> str:
    tier = "高级会员" if ctx.deps.is_premium else "免费用户"
    return f"你正在为{tier} {ctx.deps.user_name} 提供服务。"

@agent.tool
async def get_user_orders(ctx: RunContext[UserContext], limit: int = 5) -> str:
    """查询用户最近的订单。"""
    # 这里可以访问 ctx.deps.user_id 来查数据库
    return f"用户 {ctx.deps.user_id} 最近有 {limit} 条订单"

# 使用时注入依赖
user = UserContext(user_id=42, user_name="张三", is_premium=True)
result = agent.run_sync("查看我的最近订单", deps=user)
print(result.data)

2. 多模型切换

from pydantic_ai import Agent

# 定义 Agent 时不绑定模型
agent = Agent(
    system_prompt="简洁回答问题。",
    result_type=str,
)

# 运行时指定模型
r1 = agent.run_sync("什么是量子计算?", model="openai:gpt-4o-mini")
r2 = agent.run_sync("什么是量子计算?", model="anthropic:claude-3-5-sonnet-20241022")
r3 = agent.run_sync("什么是量子计算?", model="groq:llama-3.1-70b-versatile")

# 比较输出和成本
for r in [r1, r2, r3]:
    print(f"Token: {r.usage().total_tokens}, 回答: {r.data[:50]}")

3. 流式输出

import asyncio
from pydantic_ai import Agent

agent = Agent("openai:gpt-4o-mini")

async def main():
    async with agent.run_stream("用500字解释相对论") as response:
        async for chunk in response.stream_text():
            print(chunk, end="", flush=True)

    print(f"\n\n总 token: {response.usage().total_tokens}")

asyncio.run(main())

4. 结构化流式输出

import asyncio
from pydantic import BaseModel
from pydantic_ai import Agent

class StoryOutline(BaseModel):
    title: str
    genre: str
    characters: list[str]
    plot_points: list[str]

agent = Agent(
    "openai:gpt-4o-mini",
    result_type=StoryOutline,
    system_prompt="生成故事大纲"
)

async def main():
    async with agent.run_stream("写一个科幻故事大纲") as response:
        async for partial in response.stream_structured():
            # partial 是逐步填充的 StoryOutline
            print(f"当前状态: {partial}")

        # 最终完整结果
        final = await response.get_data()
        print(f"\n完整大纲: {final.title}")

asyncio.run(main())

5. 对话历史管理

from pydantic_ai import Agent
from pydantic_ai.messages import ModelMessage

agent = Agent("openai:gpt-4o-mini", system_prompt="你是一个记忆力很好的助手。")

# 第一轮对话
result1 = agent.run_sync("我叫小明,我喜欢Python。")
print(result1.data)

# 获取消息历史
history: list[ModelMessage] = result1.all_messages()

# 第二轮对话(带上历史)
result2 = agent.run_sync("我叫什么?我喜欢什么?", message_history=history)
print(result2.data)  # 应该记住 "小明" 和 "Python"

6. 错误处理与重试

from pydantic import BaseModel, field_validator
from pydantic_ai import Agent, ModelRetry

class SafeResponse(BaseModel):
    answer: str
    confidence: float

    @field_validator("confidence")
    @classmethod
    def check_confidence(cls, v):
        if not 0 <= v <= 1:
            raise ValueError("confidence 必须在 0-1 之间")
        return v

agent = Agent(
    "openai:gpt-4o-mini",
    result_type=SafeResponse,
    retries=3  # 最多重试3次
)

@agent.result_validator
async def validate_result(ctx, result: SafeResponse) -> SafeResponse:
    if len(result.answer) < 10:
        raise ModelRetry("回答太短了,请更详细一些")
    return result

result = agent.run_sync("解释什么是微服务架构")
print(f"答案: {result.data.answer}")
print(f"置信度: {result.data.confidence}")

7. 与 Logfire 集成实现可观测性

import logfire
from pydantic_ai import Agent

# 初始化 Logfire
logfire.configure()

# Agent 自动被 Logfire 追踪
agent = Agent("openai:gpt-4o-mini")

# 所有调用都会出现在 Logfire 仪表板
result = agent.run_sync("Hello!")
# 可以在 Logfire UI 看到:请求耗时、token用量、工具调用链路

常见问题

Q1: 结构化输出经常失败/重试次数用尽

解决方案: - 增加 retries 参数值 - 在 system_prompt 中明确说明输出格式要求 - 简化 Pydantic Model(减少嵌套层级) - 使用更强的模型(gpt-4o 比 gpt-4o-mini 格式遵循更好)

Q2: 如何使用本地模型(Ollama)

from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel

# Ollama 兼容 OpenAI API 格式
model = OpenAIModel(
    model_name="llama3.1",
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # Ollama 不需要真实 key
)

agent = Agent(model)

Q3: Tool 定义的 docstring 有什么作用

PydanticAI 会将 tool 函数的 docstring 作为工具描述发送给 LLM。LLM 根据描述决定何时调用工具。所以 docstring 要写清楚工具的用途和参数含义。

Q4: 与 FastAPI 如何配合

from fastapi import FastAPI
from pydantic_ai import Agent

app = FastAPI()
agent = Agent("openai:gpt-4o-mini")

@app.post("/chat")
async def chat(message: str):
    result = await agent.run(message)
    return {"response": result.data, "usage": result.usage().model_dump()}

Q5: 如何处理超长对话导致的 token 超限

  • 手动截断 message_history(只保留最近 N 轮)
  • 使用摘要策略(让 LLM 总结历史再继续)
  • 选择上下文窗口更大的模型

参考资源

资源链接
GitHub 仓库https://github.com/pydantic/pydantic-ai
官方文档https://ai.pydantic.dev
PyPIhttps://pypi.org/project/pydantic-ai
Pydantic 文档https://docs.pydantic.dev
Logfire(可观测性)https://pydantic.dev/logfire
示例代码https://ai.pydantic.dev/examples

小结: PydanticAI 不追求大而全,而是在"类型安全+开发体验"这个切面做到极致。如果你用 Python 构建生产级 AI Agent,希望代码可维护、输出可预测、调试有迹可循,PydanticAI 是当前最值得投入的框架之一。