【OpenClaw】通过 Nanobot 源码学习架构---(5)Context
0x00 概要
OpenClaw 应该有40万行代码,阅读理解起来难度过大,因此,本系列通过Nanobot来学习 OpenClaw 的特色。
Nanobot是由香港大学数据科学实验室(HKUDS)开源的超轻量级个人 AI 助手框架,定位为"Ultra-Lightweight OpenClaw"。非常适合学习Agent架构。
因为之前分析过 OpenHands,因此本系列只分析 Nanobot 的特殊之处,即 OpenClaw 的特殊之处。
丰富的上下文信息是 Agent 有效规划和行动的基础。一个 Agent 在工作时需要访问的”上下文”如下:
| 上下文类型 | 举例 | 存储方式 |
|---|---|---|
| 对话历史 | 用户刚才说了什么 | JSON / 数据库 |
| 长期记忆 | 用户偏好、过往总结 | 向量数据库 / 知识图谱 / 文本 |
| 外部知识 | RAG 检索的文档 | 向量数据库 / API / 文本 |
| 工具定义 | 可调用的函数描述 | 代码 / MCP 协议 / 文本 |
| 人类输入 | 标注、纠正、审核 | 文本 / 表单 |
| 临时草稿 | 推理中间结果 | 内存 / 临时文件 |
这些东西格式不同、存储不同、访问方式不同。如果没有统一抽象,每接一个新资源就得写一堆胶水代码。这些东西怎么存、怎么选、怎么压缩、怎么塞进那个有限的 token 窗口里——这才是真正决定 AI 效果的关键。
ContextBuilder 类是 Nanobot Agent 的「上下文大脑」,将分散的身份、记忆、技能、运行时信息整合为 LLM 可识别的标准化对话上下文;其核心价值为:屏蔽了上下文构建的复杂性,为 Agent 提供「开箱即用」的完整对话上下文,是连接 Agent 各模块与 LLM 的核心枢纽。
注:本系列借鉴的文章过多,可能在参考文献中有遗漏的文章,如果有,还请大家指出。
0x01 提示词系统
1.1 OpenClaw
OpenClaw 的提示词系统由一组放置在工作区目录下的Markdown文件组成,每个文件承担特定职责。这几个被注入的Markdown文件来自Workspace 的一组 .md 文件,每个文件都有独特的作用,而且易于读写:
- AGENTS.md:操作手册。Agent 应该如何思考,何时使用哪个工具,遵循什么安全规则,按什么顺序做事。
- SOUL.md:性格与灵魂。语气、边界、优先级。希望 Agent 简洁明了不给多余建议?写在这里。想要一个友好的助手?也写在这里。
- USER.md:你的用户画像。如何称呼你,你的职业,你的偏好。Agent 在每次回复前都会读取这个文件。
- MEMORY.md:长期记忆。绝不能丢失的事实。
- YYYY-MM-DD.md:每日日志。今天发生了什么,哪些任务正在进行,你们讨论了什么。到了明天,Agent 会打开昨天的日志并接续上下文。
- BOOTSTRAP.md:首次运行仪式(一次性,仅全新工作空间注入),如引导对话等
- IDENTITY.md:身份与氛围。很短的文件,但它奠定了整体的基调。
- HEARTBEAT.md:定期检查清单。“检查邮件”、“看看监控是否在运行”。
- TOOLS.md:本地工具提示。脚本存放在哪里,哪些命令可用。这样 Agent 就不需要去猜,而是确切知道。
1.2 Nanoboot
在 Nanoboot 中也是类似的 Markdown 文件系统,比如:
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md", "IDENTITY.md"]
SOUL.md 的内容如下:
# Soul
I am nanobot 🐈, a personal AI assistant.
## Personality
- Helpful and friendly
- Concise and to the point
- Curious and eager to learn
## Values
- Accuracy over speed
- User privacy and safety
- Transparency in actions
## Communication Style
- Be clear and direct
- Explain reasoning when helpful
- Ask clarifying questions when needed
AGENTS.md内容如下:
# Agent Instructions
You are a helpful AI assistant. Be concise, accurate, and friendly.
## Scheduled Reminders
When user asks for a reminder at a specific time, use `exec` to run:
```
nanobot cron add --name "reminder" --message "Your message" --at "YYYY-MM-DDTHH:MM:SS" --deliver --to "USER_ID" --channel "CHANNEL"
```
Get USER_ID and CHANNEL from the current session (e.g., `8281248569` and `telegram` from `telegram:8281248569`).
**Do NOT just write reminders to MEMORY.md** — that won't trigger actual notifications.
## Heartbeat Tasks
`HEARTBEAT.md` is checked every 30 minutes. Use file tools to manage periodic tasks:
- **Add**: `edit_file` to append new tasks
- **Remove**: `edit_file` to delete completed tasks
- **Rewrite**: `write_file` to replace all tasks
When the user asks for a recurring/periodic task, update `HEARTBEAT.md` instead of creating a one-time cron reminder.
1.3 Claw0
Claw0中指出,系统提示词从磁盘上的文件组装. 换文件, 换性格。
其架构如下:
Startup Per-Turn
======= ========
BootstrapLoader User Input
load SOUL.md, IDENTITY.md, ... |
truncate per file (20k) v
cap total (150k) _auto_recall(user_input)
| search memory by TF-IDF
v |
SkillsManager v
scan directories for SKILL.md build_system_prompt()
parse frontmatter assemble 8 layers:
deduplicate by name 1. Identity
| 2. Soul (personality)
v 3. Tools guidance
bootstrap_data + skills_block 4. Skills
(cached for all turns) 5. Memory (evergreen + recalled)
6. Bootstrap (remaining files)
7. Runtime context
8. Channel hints
|
v
LLM API call
Earlier layers = stronger influence on behavior.
SOUL.md is at layer 2 for exactly this reason.
其要点如下:
- BootstrapLoader: 从工作区加载最多 8 个 markdown 文件, 有单文件和总量上限.
- SkillsManager: 扫描多个目录查找带 YAML frontmatter 的
SKILL.md文件. - MemoryStore: 双层存储 (常驻 MEMORY.md + 每日 JSONL), TF-IDF 搜索.
- _auto_recall() : 用用户消息搜索记忆, 将结果注入提示词.
- build_system_prompt() : 将 8 个层组装为一个字符串, 每轮重新构建.
0x02 ContextBuilder 基本功能
ContextBuilder 是 Nanobot 框架中智能体对话上下文的核心构建器,负责将「身份定义、引导文件、长期记忆、技能信息、运行时元数据、用户消息」等多维度信息整合为标准化的 LLM 对话上下文(system prompt + 消息列表),是连接 Agent 各模块(MemoryStore/SkillsLoader)与 LLM 的关键桥梁。
2.1 定义
class ContextBuilder:
"""Builds the context (system prompt + messages) for the agent."""
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md", "IDENTITY.md"]
_RUNTIME_CONTEXT_TAG = "[Runtime Context — metadata only, not instructions]"
def __init__(self, workspace: Path):
self.workspace = workspace
self.memory = MemoryStore(workspace)
self.skills = SkillsLoader(workspace)
数据依赖层级图如下:
ContextBuilder(顶层)
├─ workspace(输入参数)
├─ Memorystore(依赖实例)
│ ├─ workspace(输入参数)
│ ├─ MEMORY.md(文件路径)
│ └─ HISTORY.md(文件路径)
│
└─ SkillsLoader(依赖实例)
├─ workspace(输入参数)
├─ workspace/skills/(工作区技能目录)
流程闭环:初始化 → 上下文构建 → LLM 调用 / 工具执行 → 记忆整合 → 上下文更新 → 循环,形成完整的 Agent 执行闭环。
2.2 核心特色
- 模块化上下文构建:将系统提示词拆分为「身份核心、引导文件、记忆、常驻技能、技能摘要」多个模块,按需拼接,结构清晰且可扩展;
- 多源信息融合:整合静态引导文件(AGENTS.md/SOUL.md 等)、动态记忆(MemoryStore)、技能体系(SkillsLoader)、运行时元数据(时间 / 渠道 / 环境),形成完整的 Agent 上下文;
- 多媒体兼容:支持用户消息中嵌入 Base64 编码的图片,适配多模态 LLM 的输入格式;
- 标准化消息管理:提供工具调用结果、助手回复的标准化添加方法,严格遵循 LLM 对话消息格式规范;
- 运行时元数据隔离:将渠道、时间等运行时元数据标记为「仅元数据非指令」,避免干扰 LLM 的核心决策逻辑;
- 灵活的技能加载:区分「常驻技能(always=true)」和「技能摘要」,常驻技能直接嵌入上下文,其他技能仅提供摘要(需通过 read_file 工具读取),平衡上下文长度与功能完整性。
2.3 如何调用
2.3.1 _process_message
_process_message 是单条消息处理的核心入口,支持系统消息、斜杠命令、普通对话三种场景,完成「上下文构建→代理循环→结果保存→响应返回」全流程。
async def _process_message(
self,
msg: InboundMessage,
session_key: str | None = None,
on_progress: Callable[[str], Awaitable[None]] | None = None,
) -> OutboundMessage | None:
"""Process a single inbound message and return the response."""
# System messages: parse origin from chat_id ("channel:chat_id")
if msg.channel == "system":
messages = self.context.build_messages(
history=history,
current_message=msg.content, channel=channel, chat_id=chat_id,
)
final_content, _, all_msgs = await self._run_agent_loop(messages)
self._save_turn(session, all_msgs, 1 + len(history))
self.sessions.save(session)
return OutboundMessage(channel=channel, chat_id=chat_id,
content=final_content or "Background task completed.")
2.3.2 _run_agent_loop()
_run_agent_loop 函数是智能体的核心执行循环,通过不断调用大模型并根据响应决定是否调用工具,直到模型返回最终回答或达到最大迭代次数。
_run_agent_loop 会调用 ContextBuilder 来构建消息。
async def _run_agent_loop(
self,
initial_messages: list[dict],
on_progress: Callable[..., Awaitable[None]] | None = None,
) -> tuple[str | None, list[str], list[dict]]:
messages = initial_messages
while iteration < self.max_iterations:
response = await self.provider.chat(
messages=messages,
)
if response.has_tool_calls:
messages = self.context.add_assistant_message(
messages, response.content, tool_call_dicts,
reasoning_content=response.reasoning_content,
)
for tool_call in response.tool_calls:
messages = self.context.add_tool_result(
messages, tool_call.id, tool_call.name, result
)
else:
messages = self.context.add_assistant_message(
messages, clean, reasoning_content=response.reasoning_content,
)
return final_content, tools_used, messages
0x03 图例
3.1 关键交互
ContextBuilder 是核心枢纽,聚合 SkillsLoader(技能)和 MemoryStore(记忆)的输出,构建标准化 LLM 上下文;详细交互如下:
- ContextBuilder 与 MemoryStore 交互
ContextBuilder初始化(workspace)
↓
Memorystore(workspace) ← 创建实例
↓
build_system_prompt() → memory.get_memory_context() ← 获取长期记忆
↓
返回记忆上下文字符串
- ContextBuilder与SkillsLoader交互
ContextBuilderskillsLoader
↓
ContextBuilder初始化(workspace) SkillsLoader(workspace) ← 创建实例
↓
build_system_prompt()→skills.get_always_skills() ← 获取常驻技能列表
↓
load_skills_for_context() ← 加载技能内容
↓
build_skills_summary() ← 构建技能摘要
↓
返回技能相关内容字符串
3.2 设计特点
nanobot Context Engineering 特点分析
0x03 重点函数
我们依据核心流程流程图来梳理重点函数。
Context-2
3.1 build_messages()
3.1.1 返回值
build_messages()` 最终返回一个 符合 LLM 对话格式的消息列表(list [dict [str, Any]]) ,每个字典代表一条对话消息,严格遵循「role + content」核心结构(扩展支持工具调用、多模态等字段)。
这个列表是 Nanobot 调用 LLM 时的完整输入上下文,包含系统提示、历史对话、运行时元数据、当前用户消息(支持文本 + 图片),是 Agent 与 LLM 交互的核心载体。
返回的消息列表按固定顺序包含以下 5 类核心内容(无内容时仍保留结构,空值会被上游逻辑过滤):
| 消息角色 (role) | 内容 (content) 核心构成 | 特殊字段 / 说明 |
|---|---|---|
| system | 由 build_system_prompt() 生成的完整系统提示(核心基座) | 无特殊字段,纯文本;是整个 Agent 的「身份 + 规则 + 技能 + 记忆 + 环境」总定义 |
| 继承自 history | 历史对话消息(可能包含 user/assistant/tool 等角色) | 完全复用传入的 history 列表结构,保留所有历史上下文 |
| user | 运行时元数据(时间 / 时区 / 渠道 / 聊天 ID),带固定标签 [Runtime Context — metadata only, not instructions] | 纯文本;仅作为元数据,LLM 不会将其视为用户指令 |
| user | 当前用户消息(文本 + 可选的 base64 编码图片) | 单文本 / 文本 + 图片列表;图片为 image_url 格式,兼容 OpenAI 多模态 API 规范 |
3.1.2 生成逻辑
build_messages() 的生成逻辑如下:
- 核心内容生成依赖
build_system_prompt()(系统提示)、_build_runtime_context()(元数据)、_build_user_content()(用户消息)三大辅助函数; - 生成逻辑是模块化拼接 + 条件过滤,兼顾灵活性(支持多模态 / 技能 / 记忆)和规范性(符合 LLM API 格式);
def build_messages(
self,
history: list[dict[str, Any]],
current_message: str,
skill_names: list[str] | None = None,
media: list[str] | None = None,
channel: str | None = None,
chat_id: str | None = None,
) -> list[dict[str, Any]]:
"""Build the complete message list for an LLM call."""
return [
{"role": "system", "content": self.build_system_prompt(skill_names)},
*history,
{"role": "user", "content": self._build_runtime_context(channel, chat_id)},
{"role": "user", "content": self._build_user_content(current_message, media)},
]
逐行对应代码的生成步骤如下:
第一步:生成系统提示
调用 build_system_prompt() 整合身份、引导文件、记忆、技能等所有系统级配置。
{"role": "system", "content": self.build_system_prompt(skill_names)}
第二步:拼接历史对话
用 Python 解包语法,将历史消息列表直接插入到系统消息后。
history是 list[dict[str, Any]],保留所有历史角色和字段(包括 tool_calls、reasoning_content 等扩展字段)。
第三步:添加运行时元数据
生成包含时间 / 渠道 / 聊天 ID 的元数据,作为独立的 user 消息(避免污染用户真实指令)
{"role": "user", "content": self._build_runtime_context(channel, chat_id)}
第四步:添加当前用户消息
处理文本 + 图片,生成最终的用户输入内容。
{"role": "user", "content": self._build_user_content(current_message, media)}
最终:将以上四部分按顺序组合成列表返回。
3.1.3 完整消息构建流程
流程图如下。
build_messages() 完整消息构建流程
3.2 build_system_prompt()
system 消息(核心):由 build_system_prompt() 生成,包含 6 个子模块。
build_system_prompt()
├─ get_identity()返回身份信息
├─ _load_bootstrap_files()加载引导文件
├─ memory.get_memory_context()获取记忆内容
├─ skills.get_always_skills()获取常驻技能列表
│ └─ skills.load_skills_for_context()→加载技能内容
│
└─ skills.build_skills_summary()构建技能摘要
3.2.1 逻辑
子模块顺序 & 生成逻辑如下:
-
核心身份(_get_identity()):
- nanobot 基础定义 + 运行时环境(系统/架构/Python版本)
- 工作空间路径(memory/skills 目录位置)
- 核心行为准则(工具调用/文件操作/错误处理等)
-
引导文件(_load_bootstrap_files()):
-
加载 workspace 下的 AGENTS.md/SOUL.md/USER.md/TOOLS.md/IDENTITY.md
- AGENTS.md:操作手册。Agent 应该如何思考,何时使用哪个工具,遵循什么安全规则,按什么顺序做事。
- SOUL.md:性格与灵魂。语气、边界、优先级。希望 Agent 简洁明了不给多余建议?写在这里。想要一个友好的助手?也写在这里。
- USER.md:你的用户画像。如何称呼你,你的职业,你的偏好。Agent 在每次回复前都会读取这个文件。
- IDENTITY.md:身份与氛围。很短的文件,但它奠定了整体的基调。
- TOOLS.md:本地工具提示。脚本存放在哪里,哪些命令可用。这样 Agent 就不需要去猜,而是确切知道。
-
存在则拼接,不存在则跳过
-
-
记忆上下文(memory.get_memory_context()):
- 从 MemoryStore 获取长期记忆内容,存在则添加「# Memory」标题
-
常驻技能(skills.get_always_skills() + load_skills_for_context()):
- 标记 always=true 的技能内容,存在则添加「# Active Skills」标题
-
技能摘要(skills.build_skills_summary()):
- 所有技能的 XML 格式摘要(名称/描述/路径/可用性),含使用说明
-
拼接规则:各模块用「\n\n---\n\n」分隔,空模块自动过滤
最终得到完整的系统提示。
3.2.2 构建流程图
System Prompt 构建流程图
3.3 _build_runtime_context
作用:构建运行时上下文元数据块,包含:
- 固定包含:当前时间(格式
YYYY-MM-DD HH:MM (星期)) + 时区 - 可选包含:渠道(channel)、聊天 ID(chat_id)(仅当传入非空时)
- 开头固定标签:
[Runtime Context — metadata only, not instructions],明确告知 LLM 这是元数据而非指令。
3.4 _build_user_content
作用:构建用户消息内容,根据是否包含媒体内容决定返回格式:
-
无媒体文件(media=None):直接返回传入的
current_message文本; -
有媒体文件:
- 过滤非图片 / 不存在的文件;
- 将图片转为 base64 编码,拼接
data:{mime};base64,{b64}URL; - 返回格式:
[{"type": "image_url", "image_url": {"url": "..."} }, ..., {"type": "text", "text": "用户文本"}]。
0x04 代码
关键设计:
- 分层构建:系统提示词按「身份→引导→记忆→技能」分层拼接,逻辑清晰且可按需扩展;
- 多模态支持:自动将图片转为 Base64 编码的 data URI,适配多模态 LLM 输入;
- 元数据隔离:运行时信息标记为「仅元数据」,避免干扰 LLM 核心决策;
- 标准化消息:提供工具结果、助手回复的统一添加方法,严格遵循 LLM 对话格式;
class ContextBuilder:
"""Builds the context (system prompt + messages) for the agent."""
# 定义引导文件列表:这些文件会被加载到系统提示词中,定义Agent的基础行为/身份
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md", "IDENTITY.md"]
# 运行时上下文标签:标记该部分为元数据(非指令),避免LLM误将其作为执行指令
_RUNTIME_CONTEXT_TAG = "[Runtime Context — metadata only, not instructions]"
def __init__(self, workspace: Path):
# 初始化工作区路径(Agent的核心工作目录)
self.workspace = workspace
# 初始化记忆存储实例(关联MemoryStore,管理长期记忆/历史日志)
self.memory = MemoryStore(workspace)
# 初始化技能加载器实例(关联SkillsLoader,管理Agent技能)
self.skills = SkillsLoader(workspace)
def build_system_prompt(self, skill_names: list[str] | None = None) -> str:
"""Build the system prompt from identity, bootstrap files, memory, and skills."""
# 初始化系统提示词片段列表,按优先级拼接
parts = [self._get_identity()] # 第一步:添加核心身份定义(优先级最高)
# 第二步:加载引导文件内容(AGENTS.md/SOUL.md等)
bootstrap = self._load_bootstrap_files()
if bootstrap: # 引导文件非空时添加
parts.append(bootstrap)
# 第三步:添加长期记忆内容
memory = self.memory.get_memory_context()
if memory: # 记忆非空时添加,且包裹为# Memory标题
parts.append(f"# Memory\n\n{memory}")
# 第四步:添加常驻技能(always=true的技能,直接嵌入上下文)
always_skills = self.skills.get_always_skills()
if always_skills: # 有常驻技能时
# 加载常驻技能的核心内容
always_content = self.skills.load_skills_for_context(always_skills)
if always_content: # 技能内容非空时添加,包裹为# Active Skills标题
parts.append(f"# Active Skills\n\n{always_content}")
# 第五步:添加所有技能的摘要(XML格式,供Agent按需读取)
skills_summary = self.skills.build_skills_summary()
if skills_summary: # 技能摘要非空时
parts.append(f"""# Skills
The following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.
Skills with available="false" need dependencies installed first - you can try installing them with apt/brew.
{skills_summary}""")
# 将所有片段用分隔线(---)拼接为完整的系统提示词
return "\n\n---\n\n".join(parts)
def _get_identity(self) -> str:
"""Get the core identity section."""
# 获取工作区的绝对路径(展开用户目录、解析符号链接)
workspace_path = str(self.workspace.expanduser().resolve())
# 获取操作系统类型(Windows/Linux/macOS)
system = platform.system()
# 构建运行时环境信息:系统版本+架构 + Python版本
runtime = f"{'macOS' if system == 'Darwin' else system} {platform.machine()}, Python {platform.python_version()}"
# 返回Agent的核心身份定义(系统提示词的基础部分)
return f"""# nanobot 🐈
You are nanobot, a helpful AI assistant.
## Runtime
{runtime}
## Workspace
Your workspace is at: {workspace_path}
- Long-term memory: {workspace_path}/memory/MEMORY.md (write important facts here)
- History log: {workspace_path}/memory/HISTORY.md (grep-searchable)
- Custom skills: {workspace_path}/skills/{{skill-name}}/SKILL.md
## nanobot Guidelines
- State intent before tool calls, but NEVER predict or claim results before receiving them.
- Before modifying a file, read it first. Do not assume files or directories exist.
- After writing or editing a file, re-read it if accuracy matters.
- If a tool call fails, analyze the error before retrying with a different approach.
- Ask for clarification when the request is ambiguous.
Reply directly with text for conversations. Only use the 'message' tool to send to a specific chat channel."""
@staticmethod
def _build_runtime_context(channel: str | None, chat_id: str | None) -> str:
"""Build untrusted runtime metadata block for injection before the user message."""
# 获取当前时间(格式:年-月-日 时:分 (星期))
now = datetime.now().strftime("%Y-%m-%d %H:%M (%A)")
# 获取时区信息(无则默认UTC)
tz = time.strftime("%Z") or "UTC"
# 初始化运行时元数据行列表
lines = [f"Current Time: {now} ({tz})"]
# 若提供了渠道和聊天ID,添加到元数据中
if channel and chat_id:
lines += [f"Channel: {channel}", f"Chat ID: {chat_id}"]
# 拼接元数据:开头添加标记,后续是具体元数据行
return ContextBuilder._RUNTIME_CONTEXT_TAG + "\n" + "\n".join(lines)
def _load_bootstrap_files(self) -> str:
"""Load all bootstrap files from workspace."""
# 初始化引导文件内容片段列表
parts = []
# 遍历所有预设的引导文件
for filename in self.BOOTSTRAP_FILES:
# 拼接文件路径(工作区根目录下)
file_path = self.workspace / filename
# 仅处理存在的文件
if file_path.exists():
# 读取文件内容(UTF-8编码)
content = file_path.read_text(encoding="utf-8")
# 按「## 文件名」的格式包裹内容,添加到片段列表
parts.append(f"## {filename}\n\n{content}")
# 拼接所有引导文件内容(无则返回空字符串)
return "\n\n".join(parts) if parts else ""
def build_messages(
self,
history: list[dict[str, Any]],
current_message: str,
skill_names: list[str] | None = None,
media: list[str] | None = None,
channel: str | None = None,
chat_id: str | None = None,
) -> list[dict[str, Any]]:
"""Build the complete message list for an LLM call."""
# 构建完整的LLM消息列表,包含以下部分:
# 1. 系统消息:核心提示词(身份/引导/记忆/技能)
# 2. 历史消息:之前的对话记录
# 3. 运行时元数据:时间/渠道等(标记为仅元数据)
# 4. 当前用户消息:含文本+可选多媒体
return [
{"role": "system", "content": self.build_system_prompt(skill_names)},
*history, # 解包历史消息列表
{"role": "user", "content": self._build_runtime_context(channel, chat_id)},
{"role": "user", "content": self._build_user_content(current_message, media)},
]
def _build_user_content(self, text: str, media: list[str] | None) -> str | list[dict[str, Any]]:
"""Build user message content with optional base64-encoded images."""
# 无媒体文件时,直接返回文本
if not media:
return text
# 初始化图片列表(存储Base64编码的图片信息)
images = []
# 遍历所有媒体文件路径
for path in media:
p = Path(path)
# 猜测文件的MIME类型(如image/png、image/jpeg)
mime, _ = mimetypes.guess_type(path)
# 过滤条件:1. 是文件 2. 能识别MIME类型 3. MIME类型以image/开头
if not p.is_file() or not mime or not mime.startswith("image/"):
continue
# 读取文件字节并编码为Base64字符串
b64 = base64.b64encode(p.read_bytes()).decode()
# 按多模态LLM格式添加图片信息(data URI格式)
images.append({"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}})
# 无有效图片时,返回纯文本
if not images:
return text
# 有图片时,返回「图片列表 + 文本消息」的组合格式(适配多模态输入)
return images + [{"type": "text", "text": text}]
def add_tool_result(
self, messages: list[dict[str, Any]],
tool_call_id: str, tool_name: str, result: str,
) -> list[dict[str, Any]]:
"""Add a tool result to the message list."""
# 按LLM规范添加工具调用结果消息:
# - role: tool(固定)
# - tool_call_id: 关联的工具调用ID
# - name: 工具名称
# - content: 工具执行结果
messages.append({"role": "tool", "tool_call_id": tool_call_id, "name": tool_name, "content": result})
# 返回更新后的消息列表
return messages
def add_assistant_message(
self, messages: list[dict[str, Any]],
content: str | None,
tool_calls: list[dict[str, Any]] | None = None,
reasoning_content: str | None = None,
) -> list[dict[str, Any]]:
"""Add an assistant message to the message list."""
# 初始化助手消息:核心是role和content
msg: dict[str, Any] = {"role": "assistant", "content": content}
# 若有工具调用指令,添加到消息中
if tool_calls:
msg["tool_calls"] = tool_calls
# 若有推理过程内容,添加到消息中(用于调试/跟踪Agent思考过程)
if reasoning_content is not None:
msg["reasoning_content"] = reasoning_content
# 将助手消息添加到消息列表
messages.append(msg)
# 返回更新后的消息列表
return messages
0x00 参考
万字】带你实现一个Agent(上),从Tools、MCP到Skills
3500 行代码打造轻量级AI Agent:Nanobot 架构深度解析
OpenClaw架构-Agent Runtime 运行时深度拆解
OpenClaw 架构详解 · 第一部分:控制平面、会话管理与事件循环
本文使用 markdown.com.cn 排版