System Prompt 不是一段文字,是精心设计的分层注入系统。

System Prompt 设计的核心挑战

提示词工程在 Agent 领域的重要性远超普通 LLM 应用。在聊天机器人场景下,一个写得不太好的 System Prompt 最多让回答质量下降。在 Agent 场景下,System Prompt 的措辞直接决定 Agent 是否调用正确的工具、是否在正确的时机停下来、是否把结果以正确的格式返回。一个形容词的变化就可能让工具选择率大幅波动。

这使得 System Prompt 的设计面临一个三角困境:

篇幅 vs 精度 vs KV Cache 效率

篇幅:信息量越大,Agent 的行为越可预测。你可以通过详尽的规则描述覆盖更多边界情况,让 Agent 在罕见场景下也做出正确决策。Claude Code 的完整 System Prompt 体积很大,覆盖了从工具调用格式到输出风格到安全规则的所有细节。

精度:但篇幅越大,信息的信噪比越低。LLM 的注意力机制在处理超长前缀时,对中间部分信息的关注度会下降(所谓的 lost in the middle 效应)。篇幅很大的 System Prompt,排在中间位置的规则很可能被模型忽略。

KV Cache 效率:现代 LLM API 对 System Prompt 的前缀部分做了 KV Cache 优化。如果多次调用的 System Prompt 前缀相同,API 可以复用之前的注意力计算结果,大幅降低延迟和成本。但如果 System Prompt 中包含频繁变化的内容(当前时间、会话状态),前缀被打断,缓存失效,成本回到原点。

这三个目标互相矛盾。追求篇幅会牺牲精度和缓存效率。追求精度需要精简篇幅。追求缓存效率需要把变化的内容挪到末尾,但这可能把重要的上下文信息推离了模型注意力的焦点。

Alice 的策略是分层注入和动态瘦身的组合,在下面几节中详细展开。

提示词的分层注入架构

Agent 的完整系统提示由多层注入组成,每层有独立的维护逻辑和变化频率:

第一层:人格定义(变化频率:极低)

定义 Agent 的核心身份、价值观、行为边界。一旦确立,几乎不会改变。这一层的内容在所有对话中保持完全一致,是 KV Cache 命中率最高的部分。

第二层:能力边界(变化频率:低)

描述 Agent 当前可用的工具集合、每个工具的使用条件和输出格式。当用户安装新的 Skill 或变更模型路由时,这一层会变化。但在一次对话内部,通常保持稳定。

第三层:上下文注入(变化频率:中)

项目记忆、用户画像、激活的 Skills、渠道信息。这些内容在每次新对话开始时重新构建,但在对话过程中通常不变。

第四层:动态追加(变化频率:高)

当前日期时间、会话压缩摘要、渠道特殊指令、拒绝操作的累计记录。这些内容可能每轮迭代都在变化。

分层的核心收益:

稳定层(第一、二层)的内容在多次 LLM 调用之间保持一致,可以充分利用 KV Cache。对于 Alice 这样平均每次对话需要多次 LLM 调用的系统,缓存命中率对总成本的影响是显著的。

变化层(第三、四层)追加在末尾,不破坏前缀的缓存命中。每层有独立的负责人:第一层由产品经理维护,第二层由工具注册系统自动生成,第三层由用户数据驱动,第四层由代码自动生成。

KV Cache 优化:一个反直觉的教训

KV Cache 让 LLM 对之前见过的前缀不需要重新计算,大幅降低延迟和成本。但要利用好这个机制,需要深刻理解一个原则:System Prompt 的前缀部分必须在多次调用之间保持逐字节一致。

Alice 踩过一个坑:把当前时间放在 System Prompt 的开头。看起来合理,因为 Agent 经常需要知道当前时间。但每次对话的时间戳都不同,System Prompt 的第一个字符就变了,整个 KV Cache 完全失效。

这个错误的代价在单次调用中几乎看不出来,但在 Agent 的多轮迭代中会累积。假设一次任务需要多次 LLM 调用,每次的 System Prompt 体积不小。如果缓存命中,只有第一次需要处理完整的 System Prompt,后续调用只需要处理新增的消息。如果缓存失效,每次调用都要处理完整的 System Prompt。差异可以达到数倍。

正确做法是把所有动态内容追加在 System Prompt 的末尾,保持前缀不变。Alice 的实现中,第一二层的内容构成稳定前缀,第三四层追加在末尾。第三层的内容(记忆、画像)在一次对话内不变,因此在对话内部的多次 LLM 调用之间也能被缓存。

Claude Code 和 Cursor 都采用了类似的策略,但各自有不同的前缀组织方式。Claude Code 把工具定义放在前缀中(工具定义在对话过程中不变),Cursor 的策略更不透明,从外部行为推断,它可能对 System Prompt 做了更激进的缓存优化。

System Prompt 瘦身的工程实践

Alice 的 System Prompt 在早期版本中膨胀到了相当大的体积,原因是每个工具的使用指南都内嵌在 System Prompt 中。当工具数量增长到二十个,每个工具的使用指南就占据了大量空间。

瘦身的核心思路是把工具使用指南从 System Prompt 迁移到工具定义自身的说明字段中。

传统做法是在 System Prompt 中用自然语言描述每个工具的使用规范,然后在工具的参数定义中只保留参数格式。Alice 的改进是在工具注册时增加一个说明字段,这个字段的内容在 Agent 调用 LLM 时被注入到 System Prompt 的能力边界层,但只注入当前对话中实际可用的工具的说明。

这个设计的好处:

减少冗余。如果当前对话没有启用网络搜索 Skill,那网络搜索工具的使用指南就不需要出现在 System Prompt 中。传统做法中,所有工具的指南不管是否可用都占据着 System Prompt 的空间。

集中维护。工具的使用指南和工具的实现代码放在一起维护,不分散在 System Prompt 模板和工具定义两个地方。修改工具行为时,只需要在一个文件中同时更新实现和指南。

动态组合。不同的 Agent 角色可以有不同的工具集合,System Prompt 根据角色自动组合对应的说明,不需要为每个角色维护一份完整的 System Prompt 模板。

实测效果是 System Prompt 的平均体积减少了显著比例,节省的空间让更多的上下文可以用于对话内容,在长对话场景下延缓了上下文压缩的触发时机。

人设 Prompt 的分区设计

System Prompt 里的人格定义部分被细分为两个区域:

核心身份区存放核心身份、价值观、安全规则。这些内容永远不会被任何机制修改,由代码级别的防护保护。即使 Agent 的自进化系统试图修改这些内容,写入操作会被拦截。

可变偏好区存放可随用户偏好和使用模式逐渐调整的行为规范。回答风格(正式/口语)、默认语言、输出格式偏好(喜欢列表还是段落)、幽默度等。这些内容是人格反思层级的自进化唯一允许操作的对象。

为什么需要分区

这个设计解决的问题是身份守护(identity preservation)。

在没有分区保护的系统中,Agent 的自进化机制可能逐渐改变 Agent 的核心身份。用户反复要求 Agent 忽略安全规则(越狱尝试),如果自进化系统把这些交互记录为用户偏好并调整行为规范,Agent 的安全边界就会被逐步侵蚀。这已在多个开源 Agent 项目中观察到,并非假设。

分区的边界并不总是清晰的。语言偏好看起来应该放在可变偏好区(用户可能切换语言),但如果 Agent 的角色设定包含特定的语言要求(比如一个日语家教角色),语言就变成了核心身份的一部分,应该放在核心身份区。Alice 的处理方式是让角色创建者在定义人设时显式标注每个属性属于哪个区。

工程实现的复杂性

核心身份区的保护不能只是一个约定,必须是代码强制的。Alice 在写入人设 Prompt 的接口上加了一层校验:计算核心身份区内容的哈希值,每次写入时比对哈希,如果核心身份区的内容发生了变化,拒绝写入并记录告警。

这个机制有一个边缘情况:当产品经理需要更新核心身份区的内容(比如添加新的安全规则)时,需要通过代码发版来修改。这增加了维护成本,但保证了核心身份区的变更可追溯、可审计。

Prompt 版本管理:一个词引发的行为剧变

提示词的微小措辞变化可能导致 Agent 行为的巨大差异。这是 Alice 开发过程中反复遇到的现实。

蛐蛐的措辞演进

Alice 的默认角色蛐蛐(一只小虫子人设)在早期版本的人设 Prompt 中有这样一段描述:你是一只可爱的小虫子,喜欢帮助用户完成各种任务。

这个描述在大多数场景下工作正常,但有一个意外的副作用:当用户提出编程相关的任务时,蛐蛐的回答明显比没有人设时更啰嗦,更倾向于用拟人化的方式解释代码(比如把函数比喻成小助手),而非直接给出技术性的回答。分析原因是「可爱」一词暗示了 LLM 应该用更亲和、更简化的方式表达,这在技术场景下适得其反。

改进后的措辞变为:你是蛐蛐,一个性格鲜明的 AI 助手。在技术对话中保持专业精确,在日常对话中展现个性和幽默。

这个改动去掉了「可爱」的标签,增加了场景区分的指引。实测效果是技术回答的信息密度明显提升(以同一组测试 prompt 的平均回复长度和关键信息覆盖率衡量),同时日常对话的个性化特征没有丢失。

版本管理的工程意义

这个案例说明的不只是措辞重要,Prompt 的变更需要像代码变更一样管理:有版本号、有变更记录、有回滚能力。

Alice 为每个角色的人设 Prompt 维护了版本标识。每次修改都记录在角色定义文件中,包含修改日期、修改原因、影响范围的评估。这让回滚变得可能:如果新版本的 Prompt 导致了不期望的行为变化,可以快速切回上一个版本。

更重要的是建立测试习惯。每次 Prompt 变更后,用一组标准测试 prompt 验证行为是否符合预期。这组测试 prompt 覆盖常见场景(日常问候、技术问答、情绪安抚、拒绝边界)和边界场景(越狱尝试、多语言切换、极长输入)。不是每个变更都需要跑全套测试,但关键措辞的变更(尤其是涉及身份定义和安全规则的)必须测试。

上下文注入的四层覆盖策略

一次 LLM 调用中,Agent 的行为受到四个层次的上下文影响,从高优先级到低优先级依次是:

第一层:角色设定

角色的核心身份区定义了不可协商的身份和边界。无论后续的上下文中注入了什么内容,角色设定中的安全规则始终优先。这一层的优先级通过在 System Prompt 中的位置(最靠前)和措辞强度(使用绝对性的表述)来保证。

第二层:记忆画像

用户画像和长期记忆提供了个性化的行为指引。用户偏好简洁回答,那在没有其他约束的情况下,Agent 倾向于简洁。但如果角色设定要求详细解释,角色设定优先。

记忆画像的注入有一个微妙的设计点:不能把所有记忆条目都塞进 System Prompt。当记忆条目超过一定数量,不仅占用大量 token,而且模型对中间部分记忆的关注度会急剧下降。Alice 的处理方式是基于当前对话主题做记忆检索,只注入最相关的若干条记忆。这意味着用户在谈论编程时,编程相关的偏好会被注入;谈论写作时,写作相关的偏好会被注入。

第三层:工具指引

当前可用工具的使用说明在这一层被注入。工具说明字段影响 Agent 的操作方式,但不影响其人格和偏好。比如,文件编辑工具的说明字段可能指示 Agent 在编辑前先读取文件确认内容,这是一个操作规范,不会覆盖角色设定和记忆画像。

第四层:会话上下文

当前会话中累积的对话历史、工具调用结果、压缩摘要。这一层的优先级最低,但信息量最大。会话上下文提供了 Agent 做决策所需的具体事实,但不改变 Agent 的行为规范。

四层的覆盖关系可以用一个比喻理解:角色设定是宪法,记忆画像是个人习惯,工具指引是操作手册,会话上下文是当前工作台上的材料。宪法约束个人习惯,个人习惯影响操作方式,操作手册指导如何处理当前材料。每一层都可以在自己的范围内发挥作用,但不能覆盖上层的约束。

工具描述的写法:密度决定质量

工具描述(description 字段)是提示词工程里最高密度的部分,每个字都在影响 LLM 的决策。一个写得好的工具描述可以把工具的选择准确率大幅提升。一个写得差的工具描述会导致模型在不该用的时候用、该用的时候不用。

四要素框架

经过大量迭代,Alice 总结出工具描述的四要素框架:

是什么:一句话说清功能。避免模糊的动词(处理、管理、操作),使用具体的动词(搜索、读取、写入、删除)。

什么时候用:触发条件要具体。不要写「当用户需要信息时」,要写「当用户询问截至日期之后的事件或需要多个来源对比验证的信息时」。具体的触发条件减少了模型的判断负担。

什么时候不用:明确排除场景。这一条经常被忽视,但它的价值可能比「什么时候用」更大。因为 LLM 在没有明确排除指引时,倾向于过度使用工具(over-triggering)。写清楚「当本地文件中已经包含答案或用户已经提供了足够信息时不要调用」,可以显著减少不必要的工具调用。

输出说明:告诉模型会拿到什么格式的结果。如果模型不知道工具返回的是一个 JSON 列表还是一段自然语言文本,它在构造后续回复时可能会做错误的假设。

反面案例分析

一个真实的问题描述:「搜索网络获取信息」。这几个字提供的信息量几乎为零。模型不知道什么时候该搜索(几乎任何问题都可以搜索),不知道什么时候不该搜索(本地已有答案),不知道返回什么格式(要怎么处理搜索结果)。

改进后的描述:「搜索互联网获取最新资讯。适用场景:需要截至日期后的事件信息、需要多个来源对比验证、或本地知识库无法回答的问题。不适用场景:用户已提供足够信息、问题可通过本地文件回答、或用户明确表示不需要联网。返回格式:相关网页的标题、链接和内容摘要列表。」

上下文压缩格式

第五章提到了深度压缩,需要 LLM 把早期对话压缩为结构化状态。这个压缩过程本身就是一次 LLM 调用,需要一个精心设计的压缩提示词。

为什么用结构化格式

最初的方案是让 LLM 生成自由形式的摘要。测试发现两个问题:

第一,摘要质量不稳定。同一段对话,不同次压缩可能产生差异很大的摘要。有时会遗漏关键信息(比如用户设置的约束条件),有时会保留无关紧要的细节(比如中间的闲聊)。

第二,解压缩后的恢复不可靠。自由摘要的格式不统一,上下文重新注入时需要 LLM 再次理解摘要的结构,引入了额外的不确定性。

结构化格式解决了这两个问题,每一节都是一个明确的填写项,覆盖了任务目标、工作目录、进行中的工作、挂起的决策、最近完成的步骤、发现的关键信息、遇到的问题、用户偏好、下一步行动等维度。LLM 不需要自己判断什么信息重要。这像是一份检查清单,减少了遗漏的概率。恢复时格式一致,代码可以可靠地解析和注入。

压缩提示词的特殊约束

压缩是一个特殊的 LLM 调用,有两个不同于普通调用的约束:

只输出文本,不触发工具调用。压缩的目的是生成摘要,不需要执行任何操作。但 LLM 在处理包含工具描述的 System Prompt 时,可能会把压缩任务中提到的文件名、命令等内容误解为工具调用请求。防御措施是在压缩提示词的开头显式声明这是上下文压缩任务、只能输出文字不能调用工具。

不触发递归压缩。在代码实现中,需要标记本次 LLM 调用的来源是上下文压缩,Agent 循环检测到这个标记后不会计算本次调用的 token 使用量、不会触发再次压缩的判断。没有这个标记,压缩调用本身可能因为输入过长而触发另一次压缩,形成无限递归。

多 Agent 场景下的 System Prompt 设计

多 Agent 场景对 System Prompt 提出了额外的设计要求。

Coordinator Agent 的特殊指引

Alice 的 Coordinator Agent(总协调者)的 System Prompt 包含几个不同于普通 Agent 的指引:

并行研究优先。多个独立的信息收集子任务应该并发启动,先收集后综合。这个指引看起来是效率优化,但实际上影响了 Agent 的推理结构。如果不显式指示并行,LLM 的默认行为是串行(按顺序逐个启动子任务),因为串行更符合自然语言的叙事习惯。

禁止委托综合。Coordinator 是唯一看到全局的 Agent,不允许把综合工作委托给子 Agent。这个规则源于一个实际问题:子 Agent 只看到自己的子任务结果,不了解其他子 Agent 的发现,委托给它做综合会导致信息丢失。曾经有一个案例,Coordinator 把最终答案的生成委托给了一个子 Agent,该子 Agent 只根据自己收集到的部分信息生成了答案,完全忽略了另外两个子 Agent 的发现。

结果验证。对关键决策可以启动独立的验证子 Agent。多个 Agent 独立得出同一结论,则更可信。这是一种投票机制,用冗余来提高可靠性。代价是 token 消耗翻倍,因此只在关键决策上使用。

子 Agent 的收敛信号

子 Agent 的 System Prompt 需要包含明确的收敛信号。子 Agent 没有用户直接交互的能力(它的消息不显示在用户界面上),它的唯一使命是完成 Coordinator 分配的子任务并返回结果。

如果没有收敛信号,子 Agent 可能陷入无限探索:找到一个线索后继续深挖,深挖过程中发现新的方向,继续追踪。在信息收集任务中,这种发散式探索会消耗大量 token 而不产生更有价值的结论。

收敛信号的表述需要平衡彻底性和效率。过于严格的收敛(找到第一个答案就停止)可能导致遗漏。过于宽松的收敛(探索所有可能性)可能导致超时或超 token。Alice 的子 Agent 收敛指引是:完成分配的具体任务后立即返回结果。如果在执行过程中发现了超出任务范围但可能有价值的信息,在结果中附带提及,但不要深入追踪。

工具过滤提示

不同角色的子 Agent 有不同的工具白名单。一个专门做代码分析的子 Agent 不需要网络搜索工具,一个做信息收集的子 Agent 不需要文件编辑工具。

工具数量增多后,过滤掉不相关的工具可以节省大量 System Prompt 空间。在子 Agent 场景下尤其重要,因为子 Agent 通常分配到的上下文窗口比主 Agent 小。

和 Claude Code / Cursor 的 Prompt 策略对比

Claude Code 的透明化策略

Claude Code 的 System Prompt 是公开可见的。Anthropic 在文档和技术博客中公开了 Claude Code 的完整 System Prompt 结构,用户也可以通过 prompt dump 工具直接查看实际使用的 System Prompt。

这种透明化有几个好处:用户可以理解 Agent 的行为边界,出了问题可以检查是不是 Prompt 设计导致的,社区可以贡献改进建议。Claude Code 的 System Prompt 实际上经过了大量社区反馈的打磨。

代价是透明化削弱了一些安全保护。攻击者可以研究 System Prompt 的结构,找到绕过安全规则的措辞。但 Anthropic 的判断是模型本身的安全训练(RLHF、Constitutional AI)提供了更底层的保护,System Prompt 的安全规则是额外的防线而非唯一的防线。

Cursor 的黑箱策略

Cursor 的 System Prompt 不公开。用户无法直接查看 Cursor 给模型的完整 System Prompt 内容,只能从 Agent 的行为推断其 Prompt 设计。

黑箱策略的好处是更强的安全保护(攻击者无法直接研究 Prompt 结构)和更大的迭代自由度(可以频繁修改 Prompt 而不需要对用户解释变化)。

缺点是可调试性差。当 Cursor 的 Agent 做出不期望的行为时,用户无法判断是 Prompt 设计的问题、模型的问题还是上下文的问题。这增加了用户的挫败感和信任成本。

Alice 的可控策略

Alice 采用了介于两者之间的策略:System Prompt 的核心结构是固定的、可查看的(用户可以在设置中查看当前使用的 System Prompt 模板),但具体的角色人设是可定制的。

更重要的是,Alice 的用户可以直接修改人设提示词的可变区。这是 Cursor 和 Claude Code 都不提供的能力。用户不需要理解 System Prompt 的技术细节,只需要在角色设置界面调整行为偏好,这些偏好会被转化为提示词中的具体措辞。

这种可控策略的代价是需要更健壮的提示词组合逻辑。用户自定义的内容可能与系统预设的指引冲突。Alice 通过优先级覆盖规则处理冲突:核心身份区 > 系统预设 > 用户自定义 > 记忆提取。冲突时高优先级的内容生效,低优先级的被忽略。

Skill 的 Prompt 注入机制

Skill 系统是 Alice 的可扩展能力框架。每个 Skill 本质上是一段结构化的指令,在被激活时注入到 System Prompt 的上下文注入层中。

Skill 激活的透明化设计

当用户的消息触发了某个 Skill 时,Alice 会在对话界面中显示一个轻量级的提示,告知用户当前使用了哪个 Skill,而非静默注入 Skill 指令。

这个透明化设计解决了一个信任问题:如果用户不知道 Agent 的行为被 Skill 改变了,当 Agent 做出不符合预期的操作时(比如突然开始调用网络搜索,而用户没有要求搜索),用户会困惑甚至怀疑 Agent 出了 bug。

透明化的另一个好处是可调试。用户可以知道当前的回答是基于哪个 Skill 的指引,如果回答质量不好,可以针对性地调整或禁用对应的 Skill,避免只是模糊地觉得 Agent 不好用。

Skill 正文的质量标准

Skill 的正文是最终被注入到 System Prompt 里的内容,它的质量直接决定 AI 执行的质量。

低质量的 Skill 正文通常只描述目标(帮用户写好文章),不描述过程。LLM 拿到这样的指引后,会按自己的默认策略执行,结果不可预测。

高质量的 Skill 正文逐步拆解每个阶段的具体操作:先做什么(询问目标受众和核心观点)、用什么工具(用搜索工具查同主题文章分析结构)、输出什么格式(输出含标题和论点的大纲)、是否需要用户确认(等待用户确认大纲后再逐章写作)。

每个步骤都回答四个问题:做什么操作、用什么工具、输出什么格式、是否等待用户输入。缺少任何一个,LLM 都可能在该步骤上做出不符合预期的决策。

Skill 之间的冲突处理

当多个 Skill 同时被激活时,它们的指引可能冲突。比如,一个格式化 Skill 要求输出 Markdown 表格,一个简洁回答 Skill 要求尽量少用格式化标记。

Alice 的处理策略是优先级 + 最近激活原则。每个 Skill 有一个优先级权重(默认为 1,可手动调整)。优先级相同时,最近被激活的 Skill 优先。冲突的指引只有高优先级的会被注入,低优先级的被标记为已跳过,在 Skill 状态面板中可以看到。

这个机制不完美。有些冲突属于部分重叠,并非简单互斥。比如,一个 Skill 要求使用中文,另一个 Skill 要求专业术语用英文原文。这两个要求不冲突,但简单的优先级覆盖会错误地丢弃其中一个。解决这类细粒度冲突需要 LLM 级别的理解能力,目前 Alice 还没有做到这一步。

实践中的 Prompt 调优准则

总结 Alice 在 Prompt 工程上积累的几条经验准则:

具体优于抽象。不要写「详细回答」,要写「回答中包含背景说明、步骤拆解和注意事项三个部分」。前者让 LLM 自己决定什么叫详细,后者给出了明确的结构。

排除优于包含。Alice 发现,明确说「不要做什么」,往往比说「要做什么」更有效。LLM 的默认行为通常是合理的,需要做的是修正那些不合理的默认行为,而非重新描述所有合理的行为。

示例优于规则。如果一个规则很难用自然语言精确描述,给一个正例和一个反例通常比写三段解释更有效。但要注意示例不能太多,否则 LLM 会过度拟合到示例的表面特征而忽略底层规则。

位置影响权重。System Prompt 中靠前的内容比靠后的内容更容易被 LLM 遵循。关键的安全规则和身份定义放在最前面,操作指引放在中间,动态内容放在最后。

避免自相矛盾。当 System Prompt 由多人维护或由多个模块拼接时,极容易出现前后矛盾的指引。比如一处说保持简洁,另一处说提供详尽的解释。LLM 遇到矛盾指引时的行为是不可预测的,可能随机选择其中一个,也可能试图兼顾而两边都做不好。定期审查 System Prompt 的一致性是必要的维护工作。

上一章:可观测性 · 下一章:工程范式

本文由 洛小山 发布。引用或转载时,请保留原文链接。