数据截至 (上游 commit e3a5b8994b30)
模型接口与记忆
两个支撑性子系统:Model 让 agent 不关心背后是哪家 LLM;Memory 让 agent 的每一步既能存下来(回放/序列化),又能变回对话喂给模型。它们是循环(
01)两端的适配层。
1. Model:把各家 LLM 抹平成一个接口
1.1 要解决的小问题
OpenAI、Anthropic、HF Inference、本地 Transformers……每家 API 的入参名、消息格式、工具调用返回都不一样。agent 循环不该被这些差异污染。Model 基类(models.py:452)定义一个统一契约:
进:一列
ChatMessage+(可选)停止序列、response_format、可调工具。出:一个ChatMessage。
核心方法就一个 generate(models.py:553),各家子类实现自己那版。
1.2 统一契约
| 子类 | 背后 | 文件位置 |
|---|---|---|
InferenceClientModel | HF Inference Providers | models.py:1456 |
LiteLLMModel | LiteLLM(100+ 家) | models.py:1205 |
OpenAIModel / AzureOpenAIModel | OpenAI 兼容端点 | models.py:1646 / 1799 |
AmazonBedrockModel | AWS Bedrock | models.py:1859 |
TransformersModel / VLLMModel / MLXModel | 本地推理 | models.py:860 / 633 / 751 |
1.3 两处抹平差异的关键点
入参装配 _prepare_completion_kwargs(models.py:502):把内部的 ChatMessage 列表清洗成各家要的 dict(get_clean_message_list,:332,处理角色映射、图片转 URL、内容扁平化),再按明确的优先级合并 stop、tools、response_format 和用户 kwargs。工具在这里被转成 JSON schema 塞进 tools 字段(:539-542)。
出参兜底 parse_tool_calls(models.py:583):有些模型不返回结构化 tool_calls,而是把工具调用写在文本里。这个方法从文本里把工具名和参数解析出来,补成标准 tool_calls——让 ToolCallingAgent 即便对着「不太会 function calling」的模型也能工作(agents.py:1327-1331 会调它)。
还有 supports_stop_parameter(models.py:418)这类小适配:某些模型不接受 stop 参数,就自动不传。这些琐碎补丁正是「统一接口」的成本所在。
2. Memory:存储与上下文两用
2.1 要解决的小问题
agent 每一步产生一堆东西:模型说了什么、调了什么工具、观测是什么、有没有报错。这些既要存起来(回放、调试、序列化到 Hub),又要在下一步变回对话喂给模型。两个用途、一份数据。
2.2 每步是一个结构化对象
AgentMemory(memory.py:214)持有一个 steps 列表,元素是几种 MemoryStep:
| 步类型 | 存什么 | 符号 |
|---|---|---|
SystemPromptStep | 系统提示 | memory.py:199 |
TaskStep | 用户任务(可带图) | memory.py:186 |
PlanningStep | 一次规划的产出 | memory.py:153 |
ActionStep | 一个行动步的全部:模型输出、工具调用、代码、观测、错误、token 用量 | memory.py:51 |
FinalAnswerStep | 最终答案 | memory.py:209 |
2.3 关键机制:同一步,两种投影
每个步类型都实现 to_messages(),把自己变回模型能读的 ChatMessage。循环开头的 write_memory_to_messages(agents.py:758)就是把系统提示 + 每一步的 to_messages() 顺次拼成完整对话历史。
以 ActionStep.to_messages(memory.py:92)为例,它把一步拆成多条消息:
ActionStep
├─ model_output → assistant 消息(模型那段思考+代码)
├─ tool_calls → "Calling tools: [...]"
├─ observations → "Observation:\n..."(工具/执行结果)
└─ error → "Error:\n... Now let's retry: 别重复之前的错"
错误也被翻译成上下文(memory.py:138-148):上一步的报错会变成一条带「别再犯同样错误」提示的消息——这正是 01 讲的「错误即上下文、自我纠错」在数据层的落点。
2.4 关键机制:summary_mode(给规划步用的精简历史)
to_messages(summary_mode=True) 会丢掉一些内容:PlanningStep 在 summary 模式返回空(memory.py:174-176),ActionStep 略过冗长的 model_output。规划步(01 §6)用它拿一份「不被旧计划过度影响」的精简历史。
2.5 序列化与回调
- 存/取:
dict()把每步转成可 JSON 化的结构(处理嵌套 dataclass、图片转 bytes,memory.py:66-90),支撑 agent 存到 Hub、replay()回放(memory.py:248)。 - 回调:
CallbackRegistry(memory.py:280)让你按步类型注册回调,每步结束时触发(agents.py:620-623的_finalize_step)——用于自定义日志、遥测、可视化。
3. 边界与坑
- 上下文只增不减:每步都往历史里加,长任务会逼近上下文上限。观测被
truncate_content截断缓解,但没有自动摘要/淘汰旧步的机制(除了规划步的 summary_mode)。 - 模型接口是「最小公倍数」:统一契约意味着某些模型的独有能力要靠
**kwargs透传,不是一等公民。 - token 统计依赖各家如实上报:某步缺
token_usage时,RunResult的总量会标为不可用(agents.py:512-521)。
4. 代码地图
| 主题 | 文件 | 符号 |
|---|---|---|
| 模型统一基类 | src/smolagents/models.py | Model |
| 生成(子类实现) | src/smolagents/models.py | Model.generate |
| 入参装配 | src/smolagents/models.py | Model._prepare_completion_kwargs |
| 从文本兜底解析工具调用 | src/smolagents/models.py | Model.parse_tool_calls |
| 消息清洗 | src/smolagents/models.py | get_clean_message_list |
| 记忆容器 | src/smolagents/memory.py | AgentMemory |
| 行动步 + 变回消息 | src/smolagents/memory.py | ActionStep / ActionStep.to_messages |
| 记忆回灌 | src/smolagents/agents.py | MultiStepAgent.write_memory_to_messages |
| 步回调 | src/smolagents/memory.py | CallbackRegistry |