Skip to content

关于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 定义了一个“字面量类型别名”,用来严格限制消息角色的取值只能是引号内的四个字符串之一

python
# 定义消息角色的类型,限制其取值
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(抽象基类) ​

python
"""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。

基于 VitePress 构建