关于BaseModel
简单来说,BaseModel 是 Pydantic 库提供的“数据基类”。它的核心作用就是帮你把“原始数据(字典/JSON)”变成“自带校验和智能行为的 Python 对象”。
在你的代码中,Message 继承自 BaseModel,意味着它瞬间拥有了以下 4 个极其强大的能力:
1. 自动类型校验
这是它最核心的价值。当你创建 Message 对象时,它会自动检查字段类型是否匹配。
你定义了 content: str,如果你传入了 123(整数),它会自动帮你转换成字符串 "123"。
你定义了 role: MessageRole(只能是 "user" 等四个词),如果你传了 role="admin",程序立刻报错并抛出 ValidationError,不会让它带着错误数据往后运行。
对比普通类:如果你用普通 class 并手动写 if 判断,代码会非常臃肿,而 BaseModel 一行注解就搞定了。
2. 智能数据解析
在代码中,timestamp: datetime = None
如果你从数据库或前端拿到一个时间字符串 "2026-08-28 10:00:00",直接传给 Message,Pydantic 会自动把它解析成 Python 的 datetime 对象,你不需要手动写 datetime.strptime() 去转换。同样,metadata: Optional[Dict] 也会自动校验你传进去的必须是字典。
3. 开箱即用的序列化
BaseModel 自带官方的 .model_dump() 方法(在旧版本是 .dict()),能一键把对象转成字典。
更重要的是,它还有 .model_dump_json(),一键转成 JSON 字符串。这对于把消息发给大模型 API 极其方便。
4. IDE 智能提示
因为 BaseModel 利用了 Python 的类型注解,当你创建 msg = Message(content="hi", role="user") 后,输入 msg. 时,IDE 会立刻弹出 content、role、timestamp、to_dict 等所有属性和方法,绝对不会打错字。
Literal[]的用法
为 MessageRole 定义了一个“字面量类型别名”,用来严格限制消息角色的取值只能是引号内的四个字符串之一
# 定义消息角色的类型,限制其取值
MessageRole = Literal["user", "assistant", "system", "tool"]
class Message(BaseModel):
"""消息类"""
content: str
role: MessageRole- MessageRole:这是一个类型别名。以后你在代码里写 MessageRole,就等价于在写那一串字符串组合。
- Literal[...]:这是 Python 的 typing 模块提供的类型。它的含义是“必须是这几个字符串中的某一个,一个字都不能差”。
- 代码里的 role: MessageRole 意味着 role 这个变量只能赋值为 "user"、"assistant"、"system" 或 "tool"
为什么不用str或者枚举类
如果用普通 str,你写 role="usr"(拼写错误)程序不会报错,直到运行到调用 API 时才崩溃。而使用 Literal 带来两个立竿见影的好处:
- 静态检查(自动纠错):在 VSCode/PyCharm 等 IDE 里,你打 "user" 时会有代码补全。如果你打错了写成 "usr",IDE 会立即画上红色波浪线提示你(前提是配置了 Pyright/mypy)。
- Pydantic 运行时校验:因为用在 BaseModel 中,如果你尝试实例化 Message(role="guest", content="hi"),Pydantic 会在程序刚运行时就主动抛出 ValidationError,而不是等到发给大模型 API 时才报错。
代码要对接 OpenAI API,API 接口要求传入的必须是裸字符串 JSON(如 {"role": "user"})。
- 如果用 Enum(class Role(Enum): USER = "user"),调用 to_dict() 时必须额外写 .value,代码冗余。
- 而用 Literal,变量的类型看起来是那四个字符串,同时 role 在运行时就是原生的 str。当你执行 message.to_dict() 时,直接拿到 "user" 字符串,无需任何转换,完美贴合 OpenAI 官方格式。
python中的abc(抽象基类)
"""Agent基类"""
from abc import ABC, abstractmethod
from typing import Optional, Any
from .message import Message
from .llm import HelloAgentsLLM
from .config import Config
class Agent(ABC):
"""Agent基类"""
def __init__(
self,
name: str,
llm: HelloAgentsLLM,
system_prompt: Optional[str] = None,
config: Optional[Config] = None
):
self.name = name
self.llm = llm
self.system_prompt = system_prompt
self.config = config or Config()
self._history: list[Message] = []
@abstractmethod
def run(self, input_text: str, **kwargs) -> str:
"""运行Agent"""
pass
def add_message(self, message: Message):
"""添加消息到历史记录"""
self._history.append(message)
def clear_history(self):
"""清空历史记录"""
self._history.clear()
def get_history(self) -> list[Message]:
"""获取历史记录"""
return self._history.copy()
def __str__(self) -> str:
return f"Agent(name={self.name}, provider={self.llm.provider})"abc 是 Python 标准库中的模块,全称是 Abstract Base Classes(抽象基类)。
它就像装修时用的“设计蓝图”——规定了房子必须有门和窗,但它本身不是一栋能住的房子。在代码中,abc 主要做了两件关键的事情:
1. 具体作用拆解
class Agent(ABC):这意味着 Agent 类继承自 ABC,正式成为了一个“抽象基类”。这样一来,Python 就禁止你直接实例化(创建)Agent 对象。如果你试图写 a = Agent(...),程序会直接报错,因为它只是一个概念,不是具体实现。
@abstractmethod:这个装饰器装饰在 run 方法上,意味着这个方法只有声明,没有具体代码。它强制规定:任何继承 Agent 的子类,必须自己实现 run 方法,否则子类也无法被实例化。
2. 为什么要在 Agent 里用它?
在你的设计中,Agent 是基类,后续会有 ChatAgent、ToolAgent 等子类。每个 Agent 执行的“运行逻辑”(run 方法)千差万别,基类根本没法写出通用的代码。
使用 abc 带来了两个立竿见影的好处:
强制约束(防懒防忘):如果你写了一个 MyAgent 继承自 Agent,但忘了写 run 方法,Python 会在你创建 MyAgent 对象时立刻抛出错误提醒你,而不是等到你调用 run 时才报 NotImplementedError。这能把错误提前扼杀在摇篮里。
统一接口(多态基石):它确保了所有 Agent 子类都长得一样。无论底层实现多么复杂,外部调用者都可以放心地写 agent.run(input_text),因为抽象基类保证了每个子类都有这个方法。
3. 不用 abc 行不行?
你也可以不用 abc,直接在 Agent 的 run 方法里写 raise NotImplementedError。但那种方式只在运行时报错,属于“事后追责”。而使用 abc 是在类定义阶段做校验,属于“事前预防”,IDE(如 PyCharm)也会在你写代码时直接画红线提醒你,体验好了不止一个档次。
总结
abc 就是 Python 用来定义“接口规范”的工具。它在你的代码里强行规定:所有具体的 Agent 实现,都必须先交出 run 方法的完整代码,否则就不配称为一个真正的 Agent。