模型的应答效果依托完整的提示词体系支撑,整套提示词主要由三部分构成,层层衔接、协同保障对话连贯与精准。在 nanobot 的上下文提示词共分为三部分。系统提示词,历史对话,当前轮历史消息。
本文围绕三部分内容主要介绍。
三者结合实现AI连贯、贴合场景的交互输出。
同时重点介绍,如何通过 Dream 系统设计,解决 AI 需要长期记忆来提供个性化服务,但又受限于大模型上下文窗口长度的矛盾。把上下文的内容,及时的通过后台定时任务整理到记忆相关的文件中,让 AI 既"记性好",又"不臃肿",还能"知错就改“。
关注“AI老马” —【获取资源】&【进群交流】
系统提示词共 5 个部分组成,各模块按序拼接,顺序固定。实现代码如下:
def build_system_prompt(self, skill_names=None, channel=None):
parts = []
parts.append( self._get_identity(channel=channel) ) # Part 1: 身份标识
parts.append( self._load_bootstrap_files() ) # Part 2: 引导文件
parts.append( f"# Memory\n\n{memory}" ) # Part 3: 长期记忆
parts.append( f"# Active Skills\n\n{always_content}" ) # Part 4: 常驻技能
parts.append( render_template("skills_section.md", ...) ) # Part 5: 技能目录
return "\n\n---\n\n".join(parts) # 用 --- 分隔各段来源:get_identity() 硬编码生成
内容:Agent名称、运行时环境 (OS/架构/Python版本)、workspace 路径、memory/history 文件位置、平台策略 (POSIX/Windows)、行为准则。
写入方式:每次动态生成,始终存在
属性 | 来源 | 作用 |
|---|---|---|
Agent 名字 | 硬编码模板 identity.md | "你是 nanobot,一个 AI 助手" |
Runtime 信息 | platform.system() / platform.machine() | 让 LLM 知道运行在 macOS arm64 还是 Linux x86 |
Workspace 路径 | self.workspace.resolve() | LLM 知道工作区在哪,才能正确操作文件 |
Platform Policy | platform_policy.md | Windows vs POSIX 差异化指令 |
Format Hint | 根据 channel 条件渲染 | Telegram 用短段落不用表格,WhatsApp 纯文本等 |
Execution Rules | 硬编码模板 | "先做再说"、"读后写再验证"等行为约束 |
Search & Discovery | 硬编码模板 + untrusted_content snippet | 优先用 grep/glob,不信任外部内容 |
来源:_load_bootstrap_files() 读取 workspace 根目录下的固定文件
文件:AGENTS.md、SOUL.md、USER.md、TOOLS.md
内容:用户自定义的 Agent 人格、行为规范、工具说明等
写入方式:文件存在才写入,以##filename为标题拼接
文件 | 内容 | 来源 | 作用 |
|---|---|---|---|
AGENTS.md | 定时提醒规则、心跳任务指南 | 模板复制到 workspace | 教 LLM 使用 cron 工具和 HEARTBEAT |
SOUL.md | Agent 人格/性格 | Dream 自动管理 | 决定回复风格(正式/幽默/简洁) |
USER.md | 用户偏好/画像 | Dream 自动管理 | 了解用户习惯(语言、技术栈、关注点) |
TOOLS.md | 自定义工具说明 | 用户手动创建 | 扩展内置工具的使用指引 |
来源:MemoryStore.get_memory_context() 读取 workspace/memory/MEMORY.md
内容:跨会话积累的长期事实、用户偏好、重要结论
写入方式:文件非空才注入,格式为:#Memory \n\n ## Long-term Memory\n\n {内容}
更新机制:由 MemoryConsolidator 在对话达到 token 上限时调用 LLM 自动归纳写入
作用:让 LLM 拥有跨会话的持久知识。
MemoryStore.get_memory_context()
→ read_memory()
→ read_file(workspace/memory/MEMORY.md) # 返回纯文本
→ 返回 "## Long-term Memory\n{content}"来源:skills.get_always_skills() → load_skills_for_context()
内容:frontmatter 中标记 always:true 的技能的完整正文(去除 frontmatter)
写入方式:依赖满足+always=true 才注入。
用途:核心技能(如 memory 技能)每轮自动可见,无需 Agent 主动请求
作用:Agent 必须始终了解的规则,如记忆系统的文件结构和搜索策略。
1.5,Skills Summary 技能目录摘要
来源:skills.build_skills_summary()
内容:所有技能的 XML 摘要,含名称、描述、文件路径、是否可用、缺少的依赖
写入方式:只要有技能存在,始终注入。
用途:渐进式加载,Agent 先看目录,需要时再用 read_ file 工具读取完整 SKILL.md
来源:session.get_history()
内容:本次会话的 user/assistant/tool 消息列表
写入方式:直接展开插入(*history)
裁剪机制:MemoryConsolidator中的maybe_consolidate_by_tokens() 函数在超 token 预算时归档旧消息,通过 session类中的 last_consolidated 游标指针控制。
详细方式请参考《会话和记忆管理》一节。
一条完整的 user message 由两部分合并而成:
def build_messages(self, history, current_message, media, channel, chat_id, ...):
# 第一部分:运行时上下文
runtime_ctx = self._build_runtime_context(channel, chat_id, self.timezone)
# → "[Runtime Context — metadata only, not instructions]
# Current Time: 2026-05-22 15:30 CST
# Channel: telegram
# Chat ID: 12345"
# 第二部分:用户实际输入(文本 or 文本+图片)
user_content = self._build_user_content(current_message, media)
# → "帮我写一段 Python 代码"
# 或 [{"type":"image_url",...}, {"type":"text","text":"这张图是什么"}]
# 合并为单条 user message
merged = f"{runtime_ctx}\n\n{user_content}"
# 或多模态: [{"type":"text", "text": runtime_ctx}, image_block, text_block]由两部分作为一条消息,避免连续同角色消息。
为什么需要这个保护? LLM 提供商拒绝连续的同角色消息。
场景示例:当注入 user message,如果 history 最后一条也是 user,就会产生两条连续的 user 的报错。
运行时上下文 runtime_ctx 为什么要放在 user message 而不是放 system prompt?
@staticmethod
def _build_runtime_context(channel, chat_id, timezone):
lines = [f"Current Time: {current_time_str(timezone)}"]
if channel and chat_id:
lines += [f"Channel: {channel}", f"Chat ID: {chat_id}"]
return "[Runtime Context — metadata only, not instructions]\n" + "\n".join(lines)小结:上下文的最终格式
messages = [
{"role": "system", "content": "完整 system prompt..."}, ← 系统指令
{"role": "user", "content": "..."}, ← 历史消息 N-2
{"role": "assistant", "content": "..."}, ← 历史消息 N-1
{"role": "user", "content": runtime_ctx + 用户输入}, ← 当前提问 (合并)
]Dream = 用 LLM 定期回顾对话历史,自动提炼知识写入长期记忆文件(SOUL.md / USER.md / MEMORY.md)的后台进程。
类比:就像人睡觉时会整理白天的经历,把重要的存成长期记忆,忘掉无关紧要的细节。
Dream 就是 Agent 的"睡眠整理"机制。
文件 | 内容 | 由谁管理 | 为什么不让用户手动改? |
|---|---|---|---|
SOUL.md | Agent 人格、语气、沟通风格 | Dream 自动更新 | Agent 需要根据交互经验自适应调整风格 |
USER.md | 用户画像、偏好、习惯、技术栈 | Dream 自动更新 | 用户不会主动告诉 Agent 自己的偏好,需要从对话中隐式提取 |
MEMORY.md | 项目上下文、重要事实、关键决策 | Dream 自动更新 | 对话中的知识点会散落在各处,Dream 负责去重和沉淀 |
为什么标记 "Managed by Dream. DO NOT edit"?
/dream-log 和 /dream-restore 可以审查和回滚 Dream 的修改整体流程:
用户对话消息
│
▼ (实时)
session.messages[] ──→ [Token 超限?] ──→ Consolidator.archive()
│
▼
history.jsonl (append-only 日志)
│
┌───────────────────────────────────────┘
│
▼ (定期/demand)
Dream.run()
│
├─→ Phase 1: LLM 分析历史 + 对比当前记忆文件
│
├─→ Phase 2: LLM 用 edit_file 工具修改
│ ├── SOUL.md (更新人格)
│ ├── USER.md (更新画像)
│ └── MEMORY.md (更新知识)
│
└─→ GitStore.auto_commit() (版本快照)
下次构建 System Prompt 时:
context.py 读取最新的 SOUL/USER/MEMORY
→ 注入 Part 2 (Bootstrap) + Part 3 (Memory)
→ Agent 拥有了跨会话的持久记忆 第一阶段 phase1,纯文本解析。
输入是,历史对话和当前的三个文件内容。以下是输入到大模型的一个例子:
Compare conversation history against current memory files.
Also scan memory files for stale content.
Output one line per finding:
[FILE] atomic fact (not already in memory)
[FILE-REMOVE] reason for removal
Files: USER (identity, preferences), SOUL (bot behavior, tone),
MEMORY (knowledge, project context)
Rules:
- Atomic facts: "has a cat named Luna" not "discussed pet care"
- Corrections: [USER] location is Tokyo, not Osaka
Staleness — flag for [FILE-REMOVE]:
- Time-sensitive data older than 14 days: weather, daily status...
- Completed one-time tasks: finished research, resolved incidents
- Superseded: approaches replaced by newer solutions
Do not add: current weather, transient status, temporary errors.
[SKIP] if nothing needs updating.输出结果示例:
[MEMORY] User prefers Chinese communication (not English)
[MEMORY] Project uses Deepseek as primary model
[MEMORY] Debug launch config created at .vscode/launch.json for onboard/agent/telegram
[USER] Language: 中文 (Chinese)
[USER] Tech stack: Python 3.12+, macOS arm64, VSCode
[USER] Editor: VSCode with Python debugging
[SOUL-REMOVE] "Use formal tone in all responses" — user prefers concise casual style based on interaction pattern
[SOUL] Be concise but thorough. Use short paragraphs for messaging apps.
[SKIP] weather/status transient data ignored第二阶段 Phase2,有工具调用能力的详细编辑器。
举例:输入的 prompt
Update memory files based on the analysis below.
- [FILE] entries: add content to appropriate file
- [FILE-REMOVE] entries: delete from memory files
File paths: SOUL.md, USER.md, memory/MEMORY.md
Editing rules:
- Edit directly — file contents provided below
- Use exact text as old_text, include surrounding blank lines
- Batch changes to same file into one edit_file call
- Surgical edits only — never rewrite entire files
- If nothing to update, stop without calling tools大模型返回工具调用的结果:
步骤 | Tool Call | 操作 |
|---|---|---|
1 | edit_file("USER.md", old="- Language: English", new="- Language: 中文 (Chinese)\n- Tech stack: Python 3.12+\n- Editor: VSCode") | 更新用户画像 |
2 | edit_file("SOUL.md", old="Use formal tone...", new="Be concise but thorough...") | 移除过时的正式语气规则 |
3 | edit_file("memory/MEMORY.md", new="- Primary model: qianfan/ernie-4.0-8k\n- User prefers Chinese") | 追加新知识 |
关键设计:阶段二使用 AgentRunner + 工具调用,而非在第一阶段直接生成完整文件内容。
原因:
设计考量 | 说明 |
|---|---|
职责分离 | Phase 1 = "想清楚要改什么",Phase 2 = "执行具体修改" |
可审计性 | Phase 1 的 analysis 是纯文本日志,可以审查"AI 决定了什么" |
精确编辑 | Phase 2 使用 edit_file(old_text, new_text) 做手术式修改,避免整文件覆写丢失内容 |
容错性 | Phase 1 失败 → cursor 不推进(不丢数据);Phase 2 失败 → analysis 已有,下次可重试 |
工具能力 | edit_file 需要读取当前文件内容来精确定位 old_text,这需要真实的 I/O 操作 |
总结:两阶段设计让 Dream 既有了"思考者"的深度分析能力,又有了"执行者"的精确操作能力,同时保证了安全性和可追溯性。
核心问题:如何发现 LLM 修改的不合适,且可以自动的回退到上个版本。
设计方案:收到用户反馈,人工触发审查逻辑,返回结果审查结果和建议,自动回退或者手动触发回退。
实现方式:Git 版本控制,每次 Dream 都留痕
Dream 修改完文件后,通过 GitStore 自动提交:
tracked_files = ["SOUL.md", "USER.md", "memory/MEMORY.md"]
# .gitignore 策略:
# /* ← 忽略所有文件
# !memory/ ← 但跟踪 memory 目录
# !SOUL.md ← 跟踪 SOUL.md
# !USER.md ← 跟踪 USER.md
# !.gitignore每次 Dream 产生实际变更时:
sha = self.store.git.auto_commit(f"dream: 2026-05-09 15:30, 4 change(s)")
# commit author: "nanobot <nanobot@dream>"这带来了完整的审计能力:
/dream-log → 查看历次 Dream 做了什么改动/dream-log <sha> → 查看某次的具体 diff/dream-restore <sha> → 回滚到某个版本维度 | Consolidator | Dream |
|---|---|---|
本质 | 会话级 token 管理 | 跨会话知识提炼 |
触发 | 每次 reply 后自动检查 | /dream 命令或 cron |
输入 | session messages (旧对话) | history.jsonl (事件日志) |
输出 | history.jsonl 新摘要行 | SOUL.md / USER.md / MEMORY.md 修改 |
LLM 调用方式 | 单次 chat (总结) | 两阶段 (分析 + 工具编辑) |
版本控制 | 无 | Git auto-commit |
可回滚 | 否 | 是 (/dream-restore) |
比喻 | 把书桌上的纸张摞起来归档 | 从归档中提取笔记写进笔记本 |
以上详解了 Nanobot 提示词的三大组成及作用,三者协同保障对话流畅自然。同时介绍了 Dream 系统设计,它将上下文压力后置处理,化解上下文窗口局限与长期记忆的矛盾,让 AI 记忆高效、内容精简且可迭代优化。
小结各部分的 Token 开销与可变性。
组成部分 | 预估Token 范围 | 可变性 | 变化原因 |
|---|---|---|---|
Identity | ~800 | 低 | 仅 channel/format hint 不同 |
Bootstrap Files | 500-1500 | 中 | 取决于哪些文件存在及 SOUL/USER 长度 |
Memory (长期) | 200-1000 | 高 | Dream 积累的知识量 |
Active Skills | ~1000 | 低 | 固定为 always skills |
Skills Summary | ~400 | 低 | 内置 skill 数量固定 |
Recent History | 200-2000 | 高 | 取决于自上次 Dream 后的事件数 |
System Prompt 总计 | ~3500-9000 | — | — |
Runtime Context | ~50 | 极低 | 固定格式 |
Conversation History | 2000-50000+ | 极高 | 会话越长越多 |
最大的 token 变量是 conversation history,这也是 consolidator(Dream) 存在的原因,定期将旧对话压缩进 MEMORY.md,控制 history 长度。