乐于分享
好东西不私藏

OpenClaw 源码漫游指南(九):大脑的解剖学 —— Prompt 动态组装与 Context 压缩算法

OpenClaw 源码漫游指南(九):大脑的解剖学 —— Prompt 动态组装与 Context 压缩算法

核心观点:LLM 本质上是一个“文本补全机”。Agent 的智能程度,很大程度上取决于你如何构建那个庞大的 Prompt。OpenClaw 在这方面展示了极高的工程造诣——从动态 System Prompt 到上下文压缩(Compaction)。


在前面的文章中,我们搭建了身体(Gateway)、感官(Voice)、四肢(Skills)、面孔(Canvas)和海马体(Memory)。
今天,我们终于要解剖它的前额叶皮层——位于 src/agents/pi-embedded-runner 的核心逻辑。

1. 动态 System Prompt:千人千面

在 OpenClaw 中,System Prompt 是动态组装的。

打开 src/agents/pi-embedded-runner/system-prompt.ts,你会看到一个复杂的构建流水线:

// src/agents/pi-embedded-runner/system-prompt.ts

exportfunctionbuildSystemPrompt(ctx: Context){
const parts =[];

// 1. 基础人设 (Persona)
  parts.push(ctx.persona.instructions);

// 2. 环境感知 (Environment)
  parts.push(`Current Time: ${newDate().toISOString()}`);
  parts.push(`OS: ${process.platform}`);
  parts.push(`Working Directory: ${process.cwd()}`);

// 3. 技能注入 (Skills)
// 只有当前启用的 Skill 才会出现在这里,节省 Token
for(const skill of ctx.activeSkills){
    parts.push(skill.prompt);
}

// 4. 记忆注入 (Short-term Memory)
// 最近检索到的相关知识
  parts.push(`Relevant Context:\n${ctx.retrievedDocs}`);

return parts.join("\n\n");
}

这意味着什么?
Agent 清楚地知道“我是谁”、“我在哪”、“现在几点了”、“我手里有什么工具”。这种极强的情境感知(Situational Awareness),是 OpenClaw 显得比其他 Bot 更“聪明”的原因。


2. Context Guard:上下文守卫

我们在系列三中简单提过 Context Guard。今天我们深入 src/agents/pi-embedded-runner/context.ts 看看它是如何工作的。

LLM 的 Context Window(如 128k)是有限的,且 Token 越多,推理越慢,费用越高。
OpenClaw 引入了 预算管理 的概念:

  1. 1. Reserve (预留): System Prompt 和最新的几轮对话是“VIP”,必须保留。
  2. 2. Sliding Window (滑动窗口): 中间的对话历史采用滑动窗口。
  3. 3. Eviction (驱逐): 当 Token 超标时,Context Guard 会启动驱逐策略。
  4. // src/agents/pi-embedded-runner/context.ts

    if(tokenCount > maxContext){
    // 策略 A: 丢弃最早的 User Message (Lossy)
      history.shift();

    // 策略 B: 触发压缩 (Smart)
    awaitcompactHistory(history);
    }

3. Compaction:记忆压缩算法

OpenClaw 不满足于简单的“丢弃”,它实现了语义压缩
代码位于 src/agents/pi-embedded-runner/compaction.ts

当对话过长时,后台会启动一个独立的 LLM 任务,对旧的对话进行 Summarization(摘要):

原始对话
User: 帮我查下明天的天气。
Agent: 调用工具 weather… 结果是晴天。
User: 那我要去爬山,帮我写个攻略。

压缩后
[System]: User checked weather (Sunny) and asked for hiking guide.

这种递归摘要机制,使得 OpenClaw 理论上可以支持无限长度的对话。旧的细节逐渐模糊成概念,而新的细节依然清晰——这简直和人类的记忆机制一模一样!


4. 思考链 (Chain of Thought) 的代码实现

OpenClaw 是如何让 Agent “多想一步”的?
在 src/agents/pi-embedded-runner/run.ts 的主循环中:

// src/agents/pi-embedded-runner/run.ts

while(true){
// 1. 推理
const response =await llm.generate();

// 2. 检查是否有工具调用
if(response.hasToolCalls){
// 3. 执行工具
const results =awaitexecuteTools(response.toolCalls);
// 4. 将结果追加到历史,继续循环 (ReAct Loop)
    history.push(results);
continue;
}

// 5. 没有工具调用,输出最终回复给用户
yield response.text;
break;
}

这个 while(true) 循环就是 Agent 的思维流。它允许 Agent 连续执行多个工具(查天气 -> 订票 -> 发邮件),直到它认为任务完成。


5. 深度解析:System Prompt 的组装流水线

在 src/agents/pi-embedded-runner/system-prompt.ts 中,buildEmbeddedSystemPrompt 函数不仅仅是字符串拼接,它更像是一个即时编译 (JIT) 的上下文工厂

这个工厂在每次对话发生的那一毫秒,实时采集 Agent 的所有“生理指标”和“环境参数”,组装成一个独一无二的 Prompt。

5.1 环境感知的源码实现

Agent 如何知道自己运行在什么机器上?请看 src/agents/pi-embedded-runner/run/attempt.ts 中的 runtimeInfo 构建逻辑:

// src/agents/pi-embedded-runner/run/attempt.ts

const runtimeInfo ={
  host: machineName,// e.g., "Production-Gateway-01"
  os:`${os.type()}${os.release()}`,// e.g., "Linux 5.15.0"
  arch: os.arch(),// e.g., "x64"
  node: process.version,// e.g., "v20.10.0"
  model:`${provider}/${modelId}`,// e.g., "openai/gpt-4o"
  channel: runtimeChannel,// e.g., "telegram"
  capabilities: runtimeCapabilities,// e.g., ["voice", "image"]
};

这些信息会被注入到 System Prompt 的头部。这解释了为什么当你问 OpenClaw “你在哪里运行”时,它能准确回答出操作系统和机器名,而不是瞎编。

5.2 技能 (Skills) 的按需注入

为了节省 Token,OpenClaw 不会将所有 Skill 的说明书都塞进 Prompt。

// src/agents/pi-embedded-runner/run/attempt.ts

const skillsPrompt =resolveSkillsPromptForRun({
  skillsSnapshot: params.skillsSnapshot,
  entries: shouldLoadSkillEntries ? skillEntries :undefined,
  config: params.config,
  workspaceDir: effectiveWorkspace,
});

resolveSkillsPromptForRun 会根据当前配置,只加载激活状态的 Skill。这意味着你可以安装 100 个插件,但如果只启用了 3 个,Prompt 中就只会包含这 3 个的定义。这种动态剪裁 (Dynamic Pruning) 是保持 Agent 轻量且专注的关键。

5.3 文档上下文的自动注入

Agent 为什么能回答关于 OpenClaw 自身的配置问题?因为它自带“说明书”。

// src/agents/pi-embedded-runner/run/attempt.ts

const docsPath =awaitresolveOpenClawDocsPath({
  workspaceDir: effectiveWorkspace,
  argv1: process.argv[1],
// ...
});

// 在 buildEmbeddedSystemPrompt 中注入
if(docsPath){
    parts.push(`Documentation Reference: ${docsPath}`);
}

系统会自动定位本地文档路径,并将其作为 Context 注入。这赋予了 Agent 自省 (Introspection) 的能力——它不仅知道如何使用外部工具,还知道自身的配置规范。


6. 深度解析:上下文溢出的自动恢复策略

当对话过长,超出 LLM 的 Context Window(如 128k Tokens)时,普通的 Bot 会直接报错崩溃。
OpenClaw 在 src/agents/pi-embedded-runner/run.ts 中实现了一套自我修复 (Self-Healing) 机制。

6.1 溢出重试循环

// src/agents/pi-embedded-runner/run.ts

constMAX_OVERFLOW_COMPACTION_ATTEMPTS=3;
let overflowCompactionAttempts =0;

while(true){
try{
// 尝试运行 Agent
const attempt =awaitrunEmbeddedAttempt({...});

// 检查是否有 Prompt Error
if(attempt.promptError){
const errorText =describeUnknownError(attempt.promptError);

// 核心逻辑:检测是否为上下文溢出错误
if(isContextOverflowError(errorText)){
if(overflowCompactionAttempts <MAX_OVERFLOW_COMPACTION_ATTEMPTS){
          overflowCompactionAttempts++;

// 触发压缩!
          log.warn(`context overflow detected... attempting auto-compaction`);
awaitcompactEmbeddedPiSessionDirect({...});

// 压缩完成后,continue 继续循环,重试 runEmbeddedAttempt
continue;
}
}
}
// 成功或不可恢复的错误,跳出循环
return attempt;
}catch(err){
// ...
}
}

6.2 流程图解

这个机制保证了 Agent 的永续性 (Immortality)。用户感知到的只是回复稍慢了一点(因为后台在疯狂压缩历史),而不是服务中断。

6.3 压缩时的环境重建

压缩本身也是一个 Agent 任务,它需要理解原始对话中的工具调用。如果压缩时丢失了 Skill 定义,LLM 就看不懂 git.commit 是什么意思,容易产生幻觉。

因此,compactEmbeddedPiSessionDirect 必须重建现场

// src/agents/pi-embedded-runner/compact.ts

// 1. 恢复沙箱上下文
const sandbox =awaitresolveSandboxContext({...});

// 2. 恢复 Skill 环境
restoreSkillEnv = params.skillsSnapshot
?applySkillEnvOverridesFromSnapshot({...})
:applySkillEnvOverrides({...});

// 3. 执行压缩
const result =await session.compact(params.customInstructions);

这段代码展示了极高的工程严谨性——即使是后台的维护性任务,也必须在严格一致的运行时环境中执行。


7. 深度解析:执行层的沙箱与技能注入

在 Agent 开始“思考”之前,OpenClaw 必须先为它准备好一个安全的“游乐场”。这发生在 src/agents/pi-embedded-runner/run/attempt.ts 的初始化阶段。

7.1 沙箱环境构建

// src/agents/pi-embedded-runner/run/attempt.ts

const sandbox =awaitresolveSandboxContext({
  config: params.config,
  sessionKey: sandboxSessionKey,
  workspaceDir: resolvedWorkspace,
});

// 确定有效的工作目录
const effectiveWorkspace = sandbox?.enabled
? sandbox.workspaceAccess ==="rw"
? resolvedWorkspace   // 读写模式:使用真实目录
: sandbox.workspaceDir // 只读/沙箱模式:使用隔离目录
: resolvedWorkspace;

这段代码体现了 OpenClaw 的安全分层。如果是不可信的 Agent,它可以被限制在一个临时的沙箱目录中,无法触碰宿主机的真实文件系统。

7.2 Bootstrap 文件注入

除了 System Prompt,OpenClaw 还会自动读取当前目录下的特定文件(如 README.mdpackage.json),将它们的内容直接作为 Context 注入给 Agent。

// src/agents/pi-embedded-runner/run/attempt.ts

const{ contextFiles }=awaitresolveBootstrapContextForRun({
  workspaceDir: effectiveWorkspace,
// ...
});

这就是为什么你在 OpenClaw 项目根目录下问它“这个项目是做什么的”,它能立刻回答——因为它在启动的那一刻,实际上已经“阅读”了你目录下的 README 文件。这种零配置的上下文感知 (Zero-Config Context Awareness) 极大地提升了开发者体验。


8. 深度解析:历史消息的精细化裁剪

并非所有的对话历史都等价。OpenClaw 在 src/agents/pi-embedded-runner/history.ts 中实现了精细化的裁剪策略。

8.1 基于会话维度的限制

// src/agents/pi-embedded-runner/history.ts

exportfunctiongetDmHistoryLimitFromSessionKey(
  sessionKey:string|undefined,
  config: OpenClawConfig |undefined,
):number|undefined{
// 解析 sessionKey,例如 "agent:telegram:dm:123456789"
// 提取 provider (telegram) 和 userId (123456789)

// 查找配置中是否有针对该用户的特殊限制
if(userId && providerConfig.dms?.[userId]?.historyLimit !==undefined){
return providerConfig.dms[userId].historyLimit;
}
// 否则返回该 Provider 的默认限制
return providerConfig.dmHistoryLimit;
}

8.2 为什么需要这个?

这允许管理员为不同的用户群体设置不同的“记忆深度”。

  • • VIP 用户:设置 historyLimit: 100,让他们享受超长记忆。
  • • 普通用户:设置 historyLimit: 20,节省 Token 成本。
  • • 调试模式:可以针对特定 User ID 开启无限记忆,方便排查问题。

这种配置驱动的策略控制 (Config-Driven Policy Control) 是企业级 Agent 必不可少的能力。


9. 深度解析:Auth Profile Failover (自动换号机制)

OpenClaw 的高可用性不仅体现在软件逻辑上,还体现在对不稳定基础设施的容错 (Resilience) 上。当某个 API Key 被限流 (Rate Limit) 或欠费时,系统不会直接报错。

9.1 轮询与冷却

在 src/agents/pi-embedded-runner/run.ts 中,runEmbeddedPiAgent 维护了一个 Key 的候选列表:

// src/agents/pi-embedded-runner/run.ts

const advanceAuthProfile =async():Promise<boolean>=>{
// 遍历候选 Key 列表
while(nextIndex < profileCandidates.length){
const candidate = profileCandidates[nextIndex];

// 检查是否在冷却期 (Cooldown)
if(candidate &&isProfileInCooldown(authStore, candidate)){
      nextIndex +=1;
continue;// 跳过坏的 Key
}

// 切换到下一个 Key
awaitapplyApiKeyInfo(candidate);
returntrue;
}
returnfalse;
};

9.2 故障分类

系统能精准区分错误的类型。如果是 rate_limit 或 billing 错误,它会标记当前 Key 为“不健康”并设定冷却时间(如 5 分钟),然后自动重试下一个 Key。

这使得 OpenClaw 可以像负载均衡器一样,在多个 OpenAI/Anthropic 账号之间自动调度,极大地提高了生产环境的稳定性。


10. 性能与成本优化

  • • Context 精细化管理:
    • • Compaction: 启用 agents.defaults.compaction。当 Token 达到阈值时,系统会自动触发 Summarization,将旧的历史记录压缩为摘要,释放 Context Window。
    • • Skill 动态加载: 不要将所有 Skill 都在 agents.defaults.skills 中全局启用。利用 openclaw.plugin.json 按需加载,避免 System Prompt 过于臃肿,浪费 Input Token。
  • • Thinking Level:
    • • 对于代码生成、复杂推理任务,建议在 Request 中指定 thinking: "high"。虽然延迟增加,但 One-shot 成功率显著提升,反而节省了反复修正的 Token 消耗。

11. 局限性

  • • Context Window 瓶颈:
    • • 尽管有 Compaction 机制,但当对话长度超过 LLM 上限(如 128k)时,早期的细节仍会不可避免地丢失或模糊化。
  • • Prompt 脆弱性 (Butterfly Effect):
    • • Prompt Engineering 仍是一门“玄学”,微小的措辞变化可能导致输出质量剧烈波动。

12. 结语

下一期,我们将进入工程化免疫系统,探讨 安全沙箱与权限控制 —— 看看如何防止 AI “暴走”,详解权限审批机制。

敬请期待:《OpenClaw 源码漫游指南 (十):安全沙箱与权限控制》

GitHub 传送门: https://github.com/openclaw/openclaw

关注公众号【这山有AI】,不错过下一篇硬核源码解读! 👇

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » OpenClaw 源码漫游指南(九):大脑的解剖学 —— Prompt 动态组装与 Context 压缩算法

评论 抢沙发

1 + 5 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮