system prompt 里拼了时间和知识大纲,每轮缓存都被击穿:静态/动态分离的提示词装配方法

system prompt 里拼了时间和知识大纲,每轮缓存都被击穿:静态/动态分离的提示词装配方法

这两天 Agent 开发圈还在卷"上下文越来越贵":窗口开得越来越大,账单也越来越长。但有个省钱的架构细节很少有人讲透------前缀缓存。它要求 system prompt 字节级稳定,可很多人把当前时间、知识库大纲拼进 system prompt,每轮都在变,缓存永远命中不了,等于全价买单。

我没去研究更便宜的模型。我干了另一件事------去翻了一个开源数据智能体工作台(daw,「寒鸦数据工作台」)的代码,看它的提示词是怎么装配的。它的解法叫"静态/动态分离",写成了一段钉在代码里的 contract。

看完我可以负责任地告诉你三件事。

第一,system prompt 只放静态纪律------品牌名注入后跨轮、跨会话字节稳定,provider 的前缀缓存才能持续命中。

第二,每轮变化的事实(当前时间、知识库大纲)走 <runtime_context> 快照,prepend 到用户消息前面:不进 system prompt,不写持久化历史,下一轮自动被新快照取代。

第三,光分离不够,还得有三条配套纪律锁死它:每个事实只有一个 owner、fail-loud 测试防漂移、提示词里的示例必须通过真实 schema 校验。

1. 学习目标

读完这篇,你会给自己的 Agent 加上一套"提示词装配纪律":静态内容和动态内容分两堆放、前缀缓存持续命中、再用三组测试保证三个月后不漂移。方法跟模型厂商无关,我用 daw 的 Rust 代码做参照。

2. 前置知识:前缀缓存是什么

先给大家科普一个概念「前缀缓存」:主流模型服务商都会缓存请求的公共前缀------如果这一轮的 prompt 开头,和上一轮字节完全一致,那一部分就按缓存价(通常便宜一个数量级)计费。

翻译成人话:system prompt 是所有轮次共享的最长公共前缀,它一变,每一轮都从头全价计费。

daw 的数据分析核心循环,恰恰鼓励每轮写知识(查完表就沉淀口径、用户纠个错就写知识库)。如果知识库大纲拼在 system prompt 里------"每写一条知识,下一轮整个 system prompt 前缀缓存就被击穿"。这就是这套纪律要解决的真实代价:你的产品越"越用越懂",账单涨得越快。

3. 分步骤实操

步骤 1:把提示词内容分成"静态"和"动态"两堆

先做一次分类,标准只有一个:这句话下一轮还在吗?

  • 在的 → 静态:角色定义、行为纪律、工作流程、错误处置决策树。这些东西几个月才变一次。
  • 不在的 → 动态:当前时间、知识库大纲快照、任务 ID。这些每轮都变。

daw 把这条分类标准直接写成了代码注释,钉在装配点的旁边(src-tauri/src/usage.rs:14-20),叫 Prompt-assembly contract:

rust 复制代码
// Prompt-assembly contract (借鉴 deepseek-harness 的静态/动态分离设计):
// - system prompt 只放**静态纪律**------品牌名经 general_preamble /
//   data_analysis_preamble 注入后跨轮/跨会话字节稳定,provider 前缀缓存
//   才能持续命中。禁止把每轮变化的事实(时间、OKF 大纲)拼进来。
// - 每轮变化的事实由 runner 以 `<runtime_context>` 快照随本次用户消息下发
//   (见 runner.rs),preamble 里只描述该块的语义,不承载其内容。

注意最后一句:"preamble 里只描述该块的语义,不承载其内容"。比如 preamble 里只写"每次任务的用户消息开头都带有系统注入的 <runtime_context> 块(当前时间等),仅对本次任务有效,直接使用即可"------告诉模型这块东西是什么、怎么用,但具体内容一个字不写。

步骤 2:动态事实装成快照,随用户消息下发

分类完,动态的那堆要找个新家。daw 的做法是:每轮调用模型前,runner 把 RuntimeContext(当前时间、知识库大纲快照、任务信息)渲染成一个文本块,prepend 到用户消息前面 (src-tauri/src/agent/runner.rs:669):

rust 复制代码
let llm_prompt = format!(
    "<runtime_context>\n{runtime_context}\n</runtime_context>\n\n{prompt}"
);

三条性质很关键,注释里都写明了:

  1. 不进 system prompt------system prompt 的字节稳定性不受影响;
  2. 不写进持久化历史------前端只存用户原文,历史里没有注入块;
  3. 下一轮自动被新快照取代------时间变了、大纲变了,下轮重新算一份,旧的不留痕。

数据分析场景的快照 = 当前时间行 + 知识库大纲;通用场景只有时间行。连交付闸门自纠轮次都复用同一快照拼修正指令,保证"现在几点、知识库长什么样"在全链路一致。

步骤 3:一个事实,只有一个 owner

分离之后还有个新问题:同一条规则,preamble 里写一遍、工具描述里再写一遍,迟早两边打架。daw 的纪律是每个事实只有一个 owner,其余地方只用一句话引用:

  • postgres_query 下推规则的 owner 是 preamble 第三步(usage.rs:148 的"重要:查询外表时必须用 postgres_query 下推聚合");execute_query 的工具描述里只有一句引用:"写法见系统提示第三步"。
  • 错误处置的 owner 是 preamble 的「错误处置」统一决策树;register_table 的运行时错误文案可以同口径复述------因为它带上下文且便宜,允许例外,但权威定义只有一处。
  • 工具参数里的 OKF 类别枚举,必须从 Category::prompt_list() 派生,不许手写字符串 ------因为历史上手写枚举漏过 selections/users,导致 preamble 要求写选表经验、schema 里却无该类别可选,模型想遵守都遵守不了。

有人可能会问:这跟缓存有什么关系?关系大了------重复和漂移是同一枚硬币的两面。规则散在三处,改的时候必然改漏,改漏的那一处要么进 system prompt 破坏字节稳定,要么让模型拿到矛盾指令。单一 owner 是字节稳定的组织保障。

步骤 4:上 fail-loud 测试,把纪律钉死

纪律写在注释里,三个月后没人看。daw 给这套东西配了三组测试,漂移就炸:

  1. preamble_backtick_identifiers_are_real_tools (src-tauri/src/skill/data_analysis/mod.rs:315):preamble 里反引号引起来的 snake_case 标识符,必须是真实工具的 NAME 常量,或者在 allowlist 里。防的是"臆造工具名"------历史上模型照着 preamble 里编造的工具名发调用,被服务端整个请求拒绝。新增/改名工具时测试失败,强制你显式归类。
  2. preambles_leave_no_placeholder_behind (src-tauri/src/usage.rs:373):品牌名替换后,不允许残留任何 {app_name} 占位符。注释写得狠:"残缺模板送达模型比报错更糟"。
  3. prompt_list_covers_all_category_dirs (src-tauri/src/okf/model.rs:305):8 个知识类别的目录必须全出现在 prompt_list() 里------手写漏类别的 bug,用测试堵死。

步骤 5:提示词里的示例,必须通过真实 schema 校验

还有一条相关的血泪教训,来自 deepseek-harness 的 issue #3204:dsh 的 PTC SDK 声明里含一个无条件渲染的 bash 示例,模型看完绕过 run_code,直接调了个根本没注册的 bash。

修复是两件事:显式声明"声明≠可直接调用";示例只在真实 schema 能逐字接受示例参数时才渲染(required/const/enum 全核对),对不上就不写示例。

翻译成人话:提示词里的示例不是装饰,是模型会照抄的调用模板。 模板里的参数形状但凡跟 schema 差一个字段,模型抄过去就是一次失败调用。写示例之前,先让测试跑一遍 schema。

4. 为什么这样设计

这套纪律的底层逻辑就一句话:把"变"和"不变"分开,让不变的部分长到足以覆盖缓存前缀。

system prompt 越长越稳定,缓存命中的 token 就越多;动态部分再怎么变,也只影响用户消息那一段。daw 甚至把缓存命中率做成了可观测指标:usage.rs 里有个 normalize 函数,把各家 provider 五花八门的 usage 字段折叠成统一形状,cache_read_tokens / prompt_tokens 就是真实缓存命中率------省没省下来,看得见才算数。

反过来想:如果不分离,数据分析这种"每轮写知识"的产品形态,会亲手把自己的缓存前缀一块一块击穿。纪律不是洁癖,是账单。

5. 避坑清单

  • 别在 preamble 里写死工具数量("你有两个工具")------工具集一变,这句话静默失真,模型还深信不疑。
  • 类别枚举别手写字符串 ,从代码里的唯一来源派生。手写漏 selections/users 的教训:preamble 要求写,schema 里没有,等于给模型下了一道执行不了的命令。
  • preamble 引用大纲段落名,必须与实际渲染标题一致。 曾经引用过一个不存在的 # 工作区数据记忆,模型照着找,找到天荒地老。
  • 示例不校验 schema 就别写。 一个对不上的示例,等于手把手教模型调一个不存在的工具。
  • 时间真源统一为 runtime_context 注入。 get_current_time 工具只用于"需要精确到时分秒"的核实,日常相对时间一律以注入块为准------两个时间源,必有一个是错的。

6. 给开发者的建议

  1. 先问"这句话下轮还在吗"。 在 → system prompt;不在 → 随消息注入的快照。这是全部纪律里最便宜的一刀。
  2. 把 contract 写在装配点旁边,不是写在文档里。 文档没人看,代码注释在每次改装配逻辑时都会被看到。
  3. 缓存命中率做成指标,不要凭感觉。 按 provider 归一化 usage,命中率掉下来的时候,你能第一时间知道是哪次"顺手拼了个变量"击穿了前缀。
  4. 纪律配测试,否则就是建议。 三个 fail-loud 测试(标识符真实性、占位符残留、类别全覆盖),每一条背后都是一次真实翻车。

一句话收束:system prompt 是你和服务商之间的长期合同,字节稳定就是履约------每一次"顺手拼个变量",都是在合同上撕一道口子。

相关推荐
a93201 小时前
基准真值也会错:我的 AI 审计管线漏报 12 题,先别急着调模型
人工智能
俊哥V1 小时前
每日 AI 研究简报 · 2026-10-10
人工智能·ai
程序人生8881 小时前
PDF 转 Word 版式错乱、表格丢失?DocConverter Web 格式互转引擎的保真实践
前端·人工智能·opencv·机器学习·pdf·word
Hui Baby1 小时前
A2A简述
ai
Dawson Zhu1 小时前
《Agentic Design Patterns》第 8 章导读:记忆管理(Memory Management)
人工智能·语言模型·架构·aigc·agi
m4Rk_1 小时前
【论文阅读】Agent 记忆机制(96):M3-Agent——让 Agent 从视听经历中持续形成情景与语义长期记忆
论文阅读·人工智能·学习·开源·github
xiami_world1 小时前
Xmind、Miro、博思白板AI怎么选?思维导图/流程图自动生成
人工智能·ai·信息可视化·流程图·xmind
wp123_12 小时前
圆盘拨动电位器机电转换原理,国内外产品性能与成本比拼
人工智能·科技·硬件工程
古少侠2 小时前
Mermaid图表和 LaTeX公式怎么无损转成 Word
ai