跳转至

dataclass 与 slots 如何配合优化 Agent 数据模型,以及和 Pydantic 的选型策略?

在 Agent 系统里,数据模型既要承载高频的请求记录(性能敏感),又要处理外部交互的序列化校验(类型安全)。我通常会先把需求分层,再决定用 dataclass(slots=True) 还是 Pydantic——没有银弹,只有最合适的组合。


📦 先看痛点:为什么普通类不够用?

一个典型的 Agent 消息模型如果写成这样:

class Message:
    def __init__(self, role, content, timestamp):
        self.role = role
        self.content = content
        self.timestamp = timestamp

当每秒产生上千个 Message 实例时,每个对象携带的 dict 会带来:

  • 额外的字典内存(每个字典至少 64 字节 + 键值对开销)

  • 属性访问的散列表查找(相对慢)

  • 内存碎片和 GC 压力

在压测中你会发现,即使逻辑不变,内存水位也高得离谱。


dataclass + slots:压榨性能的最佳组合

从 Python 3.10 开始,dataclass 可以直接传入 slots=True,相当于自动生成 slots,干掉 dictweakref,让每个属性变成固定偏移量上的 C 结构体式存储。

from dataclasses import dataclass

@dataclass(slots=True)
class AgentMessage:
    role: str
    content: str
    timestamp: float
    token_count: int = 0

查看内嵌表格

内存直降 60%~70%,访问速度提升 20%~30%,对于 Agent 内部快速创建并销毁的工具调用记录、LLM 响应包装,效果显著。


🔍 配合 frozen=True 实现不可变对象(适合状态传递)

有些场景我们需要把 Agent 的决策步骤作为历史传递,且不希望中途被篡改:

@dataclass(slots=True, frozen=True)
class ToolCallResult:
    tool_name: str
    output: str
    duration_ms: float

frozen 让所有属性只读,结合 slots,对象变得更轻且线程/协程安全,可以放心地在队列中共享。


🧩 什么时候用 slots 要注意的坑

  • 不能有 dict:动态添加属性会报错,但这对数据模型反而是约束,避免意外赋值。

  • 多继承受限:所有父类必须都定义了 slots,且冲突难处理。Agent 模型通常不复杂,单继承够用。

  • 不能和某些 ORM/框架混用:如果模型需要动态扩展,或者要集成 Django ORM,slots 可能导致元类冲突。


🛡️ Pydantic:当类型校验和序列化成刚需

Pydantic 的核心优势是运行时的数据验证 + 强序列化能力。如果你的 Agent 消息需要从 API 接收、写入数据库、生成 JSON Schema,Pydantic 自带全套工具链。

from pydantic import BaseModel, PositiveInt

class AgentRequest(BaseModel):
    prompt: str
    max_tokens: PositiveInt = 256
    temperature: float = 0.7

Pydantic v2 也用 Rust 内核做了性能优化,且 model_validate 的速度比之前快很多,但内存占用依然比 slots dataclass 大(内部维护着字段信息、验证器等)。 而且默认支持动态属性(可通过 model_config = {'frozen': True, 'extra': 'forbid'} 控制)。


⚖️ 选型决策表:什么时候用哪个?

查看内嵌表格


🧪 我们团队的真实落地模式

在 Agent 引擎中,流程是这样:

用户请求 (JSON)
Pydantic 模型做校验 (AgentInput)
   ▼ 通过验证后,转换为
dataclass(slots=True) 内部消息 (AgentMessage)
   ▼ 各个工具调用、LLM 返回
dataclass 记录 (ToolCallRecord)
   ▼ 最后输出
Pydantic 模型序列化为 JSON 给前端

边界处用 Pydantic 做“守门人”,内部高速公路上用 slots dataclass 跑赛车。既保证了健壮性,又没有在核心循环中引入性能损耗。


⚠️ 容易忽视的协同问题

  • Pydantic v1 不支持 slots,v2 支持 model_config = {'slots': True},但开启后不能动态添加字段,需谨慎。

  • 弱引用:slots 默认移除了 weakref,如果需要在其他对象中弱引用这些实例,记得在 slots 中显式加上 "weakref"

  • IDE 支持:dataclass(slots=True) 的类型提示完美,与普通 dataclass 无异,Pylance 能正确推断。


选型时我常对自己说:工具没有高下之分,只有“当前这段数据是跑在血管里还是皮肤上”的区别。血管里用最快的,皮肤上要用最硬的。