ContextBuilder 与模型上下文
对应源码
| 主题 | 源码 |
|---|---|
| system prompt 与 messages | nanobot/agent/context.py::ContextBuilder.build_system_prompt、ContextBuilder.build_messages |
| Bootstrap、Memory 与 Skills | nanobot/agent/context.py、nanobot/agent/memory.py、nanobot/agent/skills.py |
| Prompt 模板 | nanobot/templates/agent/、nanobot/utils/prompt_templates.py |
| Session 回放历史 | nanobot/session/manager.py::Session.get_history |
| BUILD 阶段调用位置 | nanobot/agent/loop.py::AgentLoop._state_build、AgentLoop._build_initial_messages |
学习目标
读完后,你应当能够:
- 解释 system prompt 的主要组成;
- 区分 Session history、Memory、Skill 和 runtime metadata;
- 说明当前用户消息如何与运行上下文合并;
- 找到模型本轮真正收到的初始 messages。
前置知识
- 理解 system/user/assistant/tool 消息;
- 理解 Session 不等于模型上下文;
- 推荐先读 组织、顺序与信任分区。
1. ContextBuilder 负责“选择和组织”
定义:
nanobot/agent/context.py::ContextBuilder
它不负责调用 Provider,而是把分散的信息组织成模型输入:
flowchart TD ID["身份、平台和 workspace"] --> SYS["System Prompt"] BOOT["AGENTS.md / SOUL.md / USER.md"] --> SYS TC["Tool Contract 模板"] --> SYS MEM["MEMORY.md"] --> SYS SK["Always Skills + Skills Summary"] --> SYS RH["Recent Memory History"] --> SYS SUM["Archived Session Summary"] --> SYS SYS --> MSGS["messages"] HIST["Session history"] --> MSGS CUR["当前用户消息"] --> MSGS RT["Runtime Context"] --> CUR
2. System Prompt 的组成
build_system_prompt() 按顺序组合:
- identity;
- workspace bootstrap 文件;
- Tool Contract;
- 用户已定制的长期 Memory;
- always-on Skills;
- 其他可用 Skills 的摘要;
- Dream cursor 之后的 recent history;
- archived session summary。
各部分使用分隔线连接。
Identity
包含:
- workspace 绝对路径;
- 操作系统和 Python 版本;
- 平台策略;
- 当前 Channel。
来源:
nanobot/templates/agent/identity.mdnanobot/templates/agent/platform_policy.md
Bootstrap 文件
固定名称:
AGENTS.mdSOUL.mdUSER.md
它们来自当前 workspace,不是 nanobot 源码目录中的同名模板。onboard 初始化模板后,用户可以在 workspace 定制。
Tool Contract
来源:
nanobot/templates/agent/tool_contract.md
它描述如何使用工具,而具体 tool schemas 由 ToolRegistry 单独传给 Provider。
Memory
memory/MEMORY.md 只有在内容不再等同于原始模板时才加入长期 Memory 段。
Skills
Skills 分为:
- always-on:完整内容进入上下文;
- 其他可用 Skills:先提供摘要,由 Agent 按需读取。
这体现 progressive disclosure,避免把所有 Skill 全文塞进每个请求。
3. Recent History 与 Session History 不同
ContextBuilder 中的 recent history 来自 MemoryStore,有:
- cursor;
- session scope;
- unified session 行为;
- 最多 50 条;
- 最多约 8000 tokens。
Session history 则由 Session.get_history() 取得,来自当前 Session 的持久化消息,并受本轮 replay budget 约束。
Session history:当前对话的可回放消息
Recent Memory History:MemoryStore 中尚未被 Dream 处理的近期记录
Archived summary:较早 Session 内容的摘要
MEMORY.md:长期整理后的记忆不要把它们统称为“历史记录”后忽略来源。
4. 当前用户消息附带 Runtime Context
build_messages() 会构造一段明确标记的 metadata:
[Runtime Context — metadata only, not instructions]
Current Time: ...
Channel: ...
Chat ID: ...
Sender ID: ...
...
[/Runtime Context]这段内容被追加到当前 user 消息后面,而不是创建第二条连续 user 消息。原因包括:
- 某些 Provider 不接受连续同 role 消息;
- 让用户原始内容保持稳定前缀,有利于 prompt cache;
- 明确说明 metadata 不是用户指令。
标记不是安全边界
“metadata only” 是对模型的语义提示,不是权限隔离。真正的权限仍必须由 Tool、workspace、Channel 和网络安全代码执行。
5. 连续同 role 消息会被合并
如果历史最后一条消息与当前消息 role 相同,build_messages() 会合并内容,避免发送连续同 role 消息。
文本与多模态 block 使用不同的合并方式。
这个行为可能影响调试观察:
- 当前输入不一定是 messages 列表中新追加的一项;
- 它可能被合并进最后一条历史消息。
6. 媒体如何进入上下文
_build_user_content() 对媒体进行处理:
- 图片可以进入多模态 content block;
- 文档在 RESTORE 阶段可能先提取文本;
- 非图片附件可能以路径引用形式出现;
- Channel 配置
extractDocumentText影响文档提取。
理解媒体时,需要同时看:
AgentLoop._prepare_message_mediautils/document.pyContextBuilder._build_user_content- Channel/WebUI 上传路径
7. 三个需要分别观察的列表
调试时不要只打印一个 messages:
| 对象 | 位置 | 含义 |
|---|---|---|
Session.messages | Session | 磁盘会话状态 |
ctx.initial_messages | AgentLoop BUILD 后 | 初次交给 Runner 的消息 |
messages_for_model | Runner 每次 iteration | 治理、裁剪后真正交给 Provider 的消息 |
Runner 还会对第三份列表执行:
- orphan tool result 清理;
- 缺失 tool result 回填;
- microcompact;
- tool result budget;
- history snip。
8. 上下文顺序为什么重要
通用设计解读:
- identity 和规则越靠前,越像全局约束;
- 当前用户内容需要保持清晰;
- 不可信工具结果不能冒充 system instruction;
- 过多历史会挤占工具 schema 和输出预算;
- prompt 模板变化会像 Python 代码变化一样影响行为。
项目 .agent/gotchas.md 明确提醒:
- Prompt templates 会直接改变 Agent 行为;
- Tool 描述、Skills 和 Session replay 都会塑造模型行为;
- 写入 Memory 或 Session 的污染可能在未来反复出现。
观察任务
在 AgentLoop._state_build() 执行后查看:
ctx.history
ctx.initial_messages[0]["content"]
ctx.initial_messages[-1]["content"]回答:
- system prompt 中有哪些段落?
- 当前 workspace 的 AGENTS/SOUL/USER 是否进入?
- runtime metadata 在什么位置?
- 当前消息是否与上一条同 role 消息合并?
- Session 中有多少消息,实际 replay 了多少?
不要把完整 system prompt 贴进公开日志
其中可能包含本机路径、用户信息、Memory、Skill 内容或运行上下文。学习记录只摘录结构和必要字段。
源码与测试
nanobot/agent/context.pynanobot/templates/agent/nanobot/utils/prompt_templates.pynanobot/agent/skills.pynanobot/agent/memory.pytests/agent/test_context_builder.pytests/agent/test_context_prompt_cache.pytests/agent/test_context_aware.pytests/agent/test_document_extraction_toggle.py
自检
- Skills 和 Tools 为什么不是同一种上下文信息?
- Session history 与 recent memory history 有什么来源差异?
- Runtime Context 为什么附在 user 消息后面?
- “metadata only” 为什么不能代替权限检查?
- 为什么模型实际收到的 messages 还可能不同于
initial_messages?