RSS

第 10 章 / 共 21 章

系统提示词组装:按运行时状态拼装身份

约 6 分钟 更新于

上一章留下的问题:数一数现在系统提示词里有多少东西——身份说明、工具使用建议、工作目录、技能目录、记忆索引。这还只是第10章,后面还有任务系统、团队、MCP。如果继续用一段硬编码的长字符串,你会遇到三个具体麻烦:

  1. 换个项目就要重写整段,还说不清哪几行必须改;
  2. 改一处,可能悄悄破坏了另一处依赖它的表述;
  3. 每次调用都带上全部内容,哪怕这次根本没有记忆、没有技能。

10.1 分段 + 条件装配

把提示词切成互相独立的片段,每段有自己的装载策略:

片段策略内容
identity总是你是谁、你的工作方式
tools总是当前启用了哪些工具、什么时候用哪个
workspace总是工作目录、当前分支等环境事实
skills有技能才带技能目录
memory有记忆才带记忆索引
PROMPT_SECTIONS = {
    "identity": "你是一个命令行开发助手。先理解,再动手;改动前先读现状。",
    "tools":    "你可以使用工具完成任务。文件操作优先用专用工具,复杂操作再用 bash。",
}

def assemble_system_prompt(context: dict) -> str:
    sections = [PROMPT_SECTIONS["identity"], PROMPT_SECTIONS["tools"]]
    sections.append(f"工作目录:{context['workdir']}")

    if context.get("skills_catalog"):
        sections.append(context["skills_catalog"])
    if context.get("memory_index"):
        sections.append(f"已知的长期事实(索引):\n{context['memory_index']}")
    if context.get("mcp_servers"):
        sections.append(f"已连接的外部服务:{', '.join(context['mcp_servers'])}")

    return "\n\n".join(sections)

注意条件判断的依据是”真实状态”,不是”关键词”。if context.get("memory_index") 问的是”现在到底有没有记忆索引”,而不是”用户这句话里有没有提到’记住’“。用关键词猜意图,是这一层最常见的错误——用户说”按老规矩来”时不会出现任何关键词,但记忆恰恰是这时候最该带上的。

第10章:提示词的分段装配
图 10.1:常驻段与条件段的分层。要看的是右侧那几段的”条件”标签——它们的开关来自运行时的真实状态(有没有记忆文件、连没连 MCP),而不是对用户措辞的猜测。

10.2 缓存:别每轮都重新拼

拼装本身很便宜,但如果状态没变,重复拼装是无谓的。更重要的是,提示词的字节稳定性直接影响 API 侧的提示词缓存命中率——系统提示词哪怕改一个字符,后面所有缓存都会失效。

import json

_prompt_cache: dict[str, str] = {}

def get_system_prompt(context: dict) -> str:
    key = json.dumps(context, sort_keys=True, default=str)
    if key not in _prompt_cache:
        _prompt_cache[key] = assemble_system_prompt(context)
    return _prompt_cache[key]

用 json.dumps(..., sort_keys=True) 而不是 hash(),有两个理由:hash() 对字符串在不同进程间不稳定(Python 默认开启哈希随机化),而且字典本身不可哈希。sort_keys=True 保证同样的状态一定序列化出同样的字节。

顺带记住一条实践准则:**稳定的内容放前面,易变的内容放后面。**系统提示词里如果混进了 datetime.now() 或者每次都变的请求 ID,缓存命中率会直接归零,而你在账单上才会发现。

10.3 走一遍:三种状态下的同一个 Agent

场景装配出的片段大致长度
全新项目,无技能无记忆identity + tools + workspace短
老项目,有 4 个技能、12 条记忆上面三段 + 技能目录 + 记忆索引中
老项目 + 已连接 2 个 MCP 服务再加一段外部服务说明长

同一份代码,三种身份。你没有为”全新项目”这个场景付技能目录和记忆索引的费用,也不用为它单独维护一份提示词。

10.4 提示词分段的写作建议

分段之后,每一段的写法也值得讲究:

  • 一段只讲一件事。identity 里不要混进工具说明,否则改工具时要在两个地方找。
  • 写行为约束,不写抽象品质。“改动文件前先读一遍现状”比”你是一个严谨的助手”有用得多。
  • **给冲突定优先级。**当片段变多,指令难免打架。在 identity 里写清”发生冲突时以哪一层为准”。
  • **别写”不要做 X”却不给替代方案。**只说禁止,模型容易卡住;说”不要 A,改用 B”,它才知道怎么走。

10.5 工程现实

真实系统里,进程内缓存只是第一层。API 侧还有自己的提示词缓存机制,命中它能显著降低重复前缀的成本。这两层的关注点不同:进程内缓存省的是 CPU,API 缓存省的是钱。

还有一个后面会踩到的坑先预告:**第19章连接 MCP 服务之后,工具列表会变,系统提示词也得跟着变。**如果那时你的缓存键里没包含”已连接哪些服务”,模型会拿着一份过期的提示词去用新工具。这就是为什么 context 要如实反映所有影响提示词的状态。

10.6 常见坑

10.7 用三种状态装配同一个身份

10.8 分清哪些片段总是装载、哪些看状态