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,干掉 dict 和 weakref,让每个属性变成固定偏移量上的 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 能正确推断。
选型时我常对自己说:工具没有高下之分,只有“当前这段数据是跑在血管里还是皮肤上”的区别。血管里用最快的,皮肤上要用最硬的。