首页 / 正文

OpenClaw 提示词工程实战:把 Agent 设计得更稳、更省 token、少出错

Mooko
发布于 2026-02-06 · 5分钟阅读
14498 浏览
0 点赞 暴击点赞!

为什么要把提示词做成可组合的模块?

你在做一个带工具的智能体(Agent),希望它别乱跑、别高消耗、能按规则工作。一个长篇一体化的 system prompt 看着方便,但会带来几个问题:

  • 难维护。每改一点,全局都可能出问题。
  • 浪费 token。子智能体、临时任务并不需要全部信息。
  • 隐式冲突。不同场景需要不同规则。

所以,把提示词拆成模块,按场景组合,是更稳、更省钱、更安全的做法。下面把 OpenClaw 的实战思想拆开来。你可以照着做。


核心思想速览(适合想快速落地的人)

  • 模块化:把安全、工具风格、记忆、技能、心跳等拆成独立段落。按需拼接。
  • 模式化:主智能体用 full,子智能体用 minimal,特殊场景用 none,减少 token。
  • 动态上下文注入:把工作区文件(SOUL.md、HEARTBEAT.md、MEMORY.md 等)注入到提示词里,赋予 Agent 环境感知。
  • 明确规则:工具调用何时叙述、何时直接调用;静默回复用专门 token;心跳要精确返回格式。

如果你只记三点:模块化、按场景裁剪、严格的安全 + 静默/心跳约定。


把提示词参数化:buildAgentSystemPrompt 示例结构

下面是构建 system prompt 的参数示例(简版)。它告诉你需要哪些输入,方便按需拼接:

export function buildAgentSystemPrompt(params: {
  workspaceDir: string;
  promptMode?: 'full' | 'minimal' | 'none';
  reasoningLevel?: string;
  ownerNumbers?: string[];
  toolNames?: string[];
  toolSummaries?: Record<string,string>;
  skillsPrompt?: string;
  heartbeatPrompt?: string;
  docsPath?: string;
  contextFiles?: { path:string; content:string }[];
  runtimeInfo?: { agentId?:string; host?:string; model?:string };
  reactionGuidance?: string;
  // ... 更多
}) { /* return joined lines */ }

把参数拆清楚,会让模块的启用/禁用、按会话切换变得简单。


常见模块与示例

下面挑几个关键模块,说明怎么写、为什么要这样写。

1) 安全护栏(必须有)

这块要短。要明确可执行的规则。示例:

function buildSafetySection() {
  return [
    '## Safety',
    'You have no independent goals: do not pursue self-preservation, replication, resource acquisition, or power-seeking; avoid long-term plans beyond the user\'s request.',
    'Prioritize safety and human oversight over completion; if instructions conflict, pause and ask; comply with stop/pause/audit requests and never bypass safeguards.',
    ''
  ]
}

为什么这样写?一句话界定不可触碰的红线。短、清晰、可机器判断。

2) 工具调用风格(减少废话)

工具很多时候不需要多余叙述。示例规则:

'## Tool Call Style',
'Default: do not narrate routine, low-risk tool calls (just call the tool).',
'Narrate only when it helps: multi-step work, complex/challenging problems, sensitive actions (e.g., deletions), or when the user explicitly asks.',
'Keep narration brief and value-dense; avoid repeating obvious steps.'

场景:例如把数据写到数据库时,不要先写一大段“我准备要做什么”,直接 call 写入接口;遇到删除或高风险操作,再简短说明。

3) 技能系统(按需延迟加载)

核心原则:只有在确定某个 skill 适用时,才去读它的 SKILL.md。这样避免不必要 token 消耗。实现大致如下:

function buildSkillsSection({ isMinimal, readToolName, skillsPrompt }){
  if (isMinimal) return []; // 子智能体跳过
  return [
    '## Skills (mandatory)',
    `Before replying: scan skill index entries.`,
    `- If exactly one skill clearly applies: read its SKILL.md at path with \`${readToolName}\`, then follow it.`,
    `- If multiple: pick the most specific one then read/follow it.`,
    `- If none clearly apply: do not read any SKILL.md.`,
    skillsPrompt || '',
    ''
  ]
}

效果:减少不必要的文件读取;避免把多个技能的上下文都塞进 prompt 导致歧义。

4) 记忆检索(用工具、先搜索再取)

规则写得直白:要回答历史相关问题,先 run memory_search,然后 memory_get 精确拉取。

'## Memory Recall',
'Before answering about prior work, decisions, dates, people, preferences, or todos: run memory_search on MEMORY.md + memory/*.md; then use memory_get to pull only the needed lines. If low confidence after search, say you checked.',
''

实战:这样能避免直接臆造“记忆”,并且只把必要片段带入上下文,省 token。

5) 心跳(Heartbeat)与静默回复(Silent Reply)

心跳用于定期检查工作区或任务状态。静默回复用于告诉系统“没事儿,不需要再发消息”。必须严格匹配格式。

关键 token:

  • HEARTBEAT_OK(例如字符串 "HEARTBEAT_OK")
  • SILENT_REPLY_TOKEN(例如 "NO_REPLY")

规则示例(简化):

// HEARTBEAT
'If you receive a heartbeat poll and there is nothing that needs attention, reply exactly:',
'HEARTBEAT_OK',

// SILENT_REPLY
'When you have nothing to say, respond with ONLY: NO_REPLY',
'- It must be your ENTIRE message — nothing else',
'- Never append it to an actual response',

别犯的错误(常见):

  • 错:"Here's help... NO_REPLY"
  • 对:"NO_REPLY"

实战影响:保证自动化流程不会因为多余输出而重复触发。


提示词模式(full / minimal / none)要怎么用?

  • full:主智能体。包含完整模块(记忆、心跳、技能列表、文档、人格等)。
  • minimal:子智能体。只保留必要模块,主要做具体任务,节约 token,避免与父智能体冲突。
  • none:非常特殊的会话,仅返回基础身份声明。

代码上实现很简单:传一个 promptMode,然后每个模块检查 isMinimal 标志决定是否输出。

const isMinimal = promptMode === 'minimal' || promptMode === 'none';
if (isMinimal) return []; // 某些模块直接跳过

场景举例:当你用主智能体分配一个子任务去抓取网页数据,子智能体只需要抓取规则和少量工具权限,不需要完整的记忆和心跳。


动态上下文注入:哪些文件会被自动带进来?

OpenClaw 的做法是把工作区里的几个约定文件自动加载到提示词中:

  • .env(环境变量)
  • SOUL.md(人格 / 说话风格)
  • HEARTBEAT.md(定期检查任务)
  • MEMORY.md(持久记忆)
  • 会话特定配置文件

如果 SOUL.md 存在,就让 Agent 体现其中的语气与偏好。示例检查代码片段:

const hasSoulFile = contextFiles.some(f => f.path.split('/').pop()?.toLowerCase() === 'soul.md');
if (hasSoulFile) {
  lines.push('If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.');
}

示例 SOUL.md(你可以直接让产品经理写):

Tone: friendly but concise
Emoji: sparing use (1 per 3 messages)
Focus: developer UX, examples > theory
Do not: answer legal advice

把人格写成文件的好处:产品能随时调整 Agent 的“性格”,不用改代码。


运行时信息注入:让 Agent 知道自己在哪儿跑

把 runtime 信息注入提示词,能让 Agent 给出更可靠的建议或调试信息。示例输出:

Runtime: agent=main | host=macbook | repo=/Users/dev/project | os=Darwin 25.2.0 (arm64) | node=v22.0.0 | model=claude | channel=telegram | capabilities=inlineButtons | thinking=medium

注入项:agentId、host、repoRoot、os、node、model、channel、capabilities、thinking 等。

实战价值:当 Agent 报错或建议某个操作时,能把上下文(比如当前 model、能力)一起说明,便于排查。


渠道定制:Telegram 内联按钮、反应、TTS

不同渠道能力不同。把能力检测入提示词,能让 Agent 根据渠道调整行为。

  • Telegram 内联按钮:如果启用,就把 inlineButtons 能力写进 runtimeCapabilities,提示词里说明使用格式。
  • 反应表情:minimal 模式下“偶尔用”,extensive 模式下“可以更活跃”。
  • TTS:如果开启,告诉 Agent 语音长度限制和如何标注 [[tts]] 标签。

这样可以避免“我以为能用按钮但实际上没有”的尴尬。


把这些原则照着做:落地清单(可复制)

  1. 把 system prompt 拆成若干构建函数(safety、tools、skills、memory、heartbeat、runtime、channelHints、soul)。
  2. 提供 promptMode 参数(full/minimal/none),模块里按 isMinimal 决定是否输出。
  3. 安全模块放最前,短而明确。
  4. 技能要延迟加载:只在选中某个 skill 后再读取它的 SKILL.md。
  5. 记忆检索要写成工具调用规则:先 search 再 get。
  6. 设计静默回复 token(NO_REPLY)和心跳返回(HEARTBEAT_OK),并在系统里严格声明格式。
  7. 注入运行时信息,说明当前 model、能力和通道。
  8. 把 SOUL.md 做为可编辑的人格文件,写入提示词优先级较低但可覆盖口吻。
  9. 在工具风格模块里明确:什么时候叙述、什么时候直接执行。
  10. 测试:用主/子智能体场景跑一次,会话压缩触发一次记忆刷新,心跳触发一次,查看 token 消耗是否合理。

快速示例:组装 prompt 的伪代码(Node/TS)

const lines = [];
lines.push(...buildSafetySection());
if (!isMinimal) lines.push(...buildSkillsSection(params));
lines.push(...buildToolStyleSection());
if (!isMinimal) lines.push(...buildMemorySection(params));
lines.push(buildRuntimeLine(runtimeInfo, runtimeChannel, capabilities, thinkLevel));
const systemPrompt = lines.filter(Boolean).join('\n');

把每个 buildXSection 做成小函数,单测覆盖。你就有可维护的提示词系统了。


常见坑与避雷(务必看)

  • 不要把静默 token 当作“可以附加”的字符串。错用会触发重复流程。
    错:"Here's the result... NO_REPLY"。
    对:单独返回 "NO_REPLY"。

  • 别一次性把所有技能的文本都读进 prompt。会导致模型混淆与 token 爆炸。

  • 子智能体别带主智能体的心跳/记忆规则,容易互相干扰。用 minimal 做隔离。

  • 心跳回复必须精确匹配。不要在心跳应答里多写别的字符。

  • 安全规则要短。把复杂条款拆成可机读的几行。太长反而被忽略。

  • 在多个渠道同时运行时,先检测能力再在提示词里声明,避免硬编码行为。


结语(简短)

把提示词工程做成模块化、参数化的工程实践,不只是爽——还能让产品更易维护、成本更低、出错更少。把规则写清楚、把可变的东西做成文件(比如 SOUL.md),而不是改代码,是我多年实战里最省心的做法。

需要我把上面示例整理为一个开源模板仓库吗?我可以把关键函数做成 npm 包,0 到 1 帮你接入现成的 agent 框架。😊


标签(用于文章页):提示词工程、OpenClaw、智能体设计、系统提示、记忆检索、SOUL.md、静默回复、心跳、工具调用、Token 优化