system prompt 怎么组装 · 一个会随工具集变形的模板
grok 的 system prompt 不是一段死字符串,而是每次会话按「有哪些工具、是否交互、什么受众」现场渲染出来的。这一章拆它的模板引擎、按 kind 间接引用工具、以及 AGENTS.md 为什么不进 system prompt 而是当用户消息注入。
system prompt 怎么组装 · 一个会随工具集变形的模板
模型的「人设与规矩」全在 system prompt 里。grok 的 system prompt 有意思在:它不是死字符串,而是每次会话现场渲染。
三个模板,还被混淆了
模板在 xai-grok-agent/src/prompt/template.rs。有三份:base(主)、apply-patch(codex 风格编辑)、subagent(子代理精简版),外加一个压缩后用的极短 COMPACT_SYSTEM_PROMPT。
一个反常的细节:明文模板放在 crate 的 templates/ 目录(prompt.md、apply_patch_prompt.md、subagent_prompt.md),但编进二进制的是 xai-grok-agent/scripts/encrypt_templates.py 把它们 XOR 混淆后生成的 prompt_encrypted.rs 字节数组(有测试盯着两边不走样),运行时按需解密成 Zeroizing<String>(drop 时抹掉内存明文)。作者自己在注释里写明「这是混淆不是安全措施——种子就在仓库里」,目的只是别让 prompt 在 strings 输出里一眼可见。想看真身:grok prompt --section template,或者直接读 templates/prompt.md。
模板引擎:按 kind 间接引用工具
渲染由 xai-grok-tools 里的 TemplateRenderer 完成(基于 MiniJinja,定界符换成 ${{ }} / ${% %},免得和工具描述里字面的 {{ }} 撞车),两样东西是关键:
- 工具按 kind 引用:模板里写
${{ tools.by_kind.read }},渲染时才替换成实际工具名(read_file)。所以你改工具名、禁用某工具,prompt 会跟着变。 - 按条件裁剪段落:
${%- if tools.by_kind.execute or tools.by_kind.monitor %}…${%- endif %}——没有 execute / monitor 工具就不渲染<background_tasks>段;没有 task 工具,「用户要你派 subagent 就得真派」那条规矩也不会出现。system prompt 因此只描述当前真实可用的能力,不会承诺不存在的工具。
还有一批占位符:system_prompt_label(开头「You are … released by xAI」里的身份名,默认 Grok)、os_name、shell_path、working_directory、current_date、is_non_interactive……其中 is_non_interactive 很关键:headless/SDK 会话没人盯着 TUI,于是 <user_guide> 之类的段落自动消失,身份也从「interactive CLI tool」切成「autonomous agent」。按开关出现的还有 v2 记忆的 <memory> 段、plan agent 用的 <browser_verification> 段。
base 模板有个 16 KB 软上限测试(PROMPT_SIZE_SOFT_CEILING_BYTES),还有个静态守卫测试(test_template_vars_are_always_guarded)确保每个 ${{ tools.by_kind.X }} 都被要求 X 的 if 包着(否则可能渲染成空)。眼下 templates/prompt.md 还不到 10 KB——grok 的 prompt 是刻意精简的,这本身就是一个值得学的取向:prompt 越短越省 token、越不容易「指令过载」。
PromptContext:所有输入的载体
真正驱动渲染的是 prompt/context.rs 的 PromptContext,它把所有输入收在一处并可序列化(便于 dump 检查)。几个维度:
PromptAudience:Primary(全量 base 模板)vsSubagent(精简模板、隐藏 personas)。PromptMode:Extend(base 模板 + 可选追加正文)vsFull(正文即整个 prompt)。TemplateOverride:None(内置)/Codex(apply-patch)/Custom(自带)。
render(&tool_bridge) 就是把选中的模板过一遍 renderer,填占位符、解析工具名,产出最终 system prompt,由 shell 装到历史第 0 项 System。
AGENTS.md 不进 system prompt
一个反直觉但很聪明的决定:项目规则(AGENTS.md / CLAUDE.md / 规则文件)不拼进 system prompt,而是当一条独立的合成用户消息注入(打标 ProjectInstructions)。发现顺序:~/.grok/ → 兼容目录(~/.claude/、~/.cursor/,可在配置里分别关掉)→ 从 git 根走到 cwd(越深越优先;各级 .grok/rules/、.claude/rules/、.cursor/rules/ 下的 *.md 规则也算),.gitignore 过滤;工作目录没被信任时,项目级文件一律不读。
为什么这么做?system prompt 要字节稳定才能命中 KV 缓存;而 AGENTS.md 每个项目不同、还可能中途变。把它拆成用户消息,system prompt 就能保持稳定复用,项目指令另走一路(源码注释也强调:这条消息一旦放下就不再替换,否则会打穿 KV 缓存前缀)。还有个 <user_info> + <git_status> + 规则/技能/MCP 的首条用户消息前缀,同理独立于 system prompt。
和《从 LLM 到 Coding Agent》对照
对照 context-injection:最小实现里,system prompt 常是一个模板字符串 f"...{tools}..."。grok 的增量是——它把「prompt 随工具集/交互态/受众变形」做成了一个带条件、带守卫、带缓存友好切分的渲染系统,还顺手把项目指令从 system prompt 里拆出去以保缓存。
上一篇:会话状态 · chat-state · 下一篇:上下文压缩