大数跨境

DeepSeek Harness 如何实现动态修改系统提示词且不破坏 KV Cache

DeepSeek Harness 如何实现动态修改系统提示词且不破坏 KV Cache 独立开发
2026-09-10
6
导读:DeepSeek Harness 的动态系统提示词机制

DeepSeek Harness 如何实现支持动态修改系统提示词且不破坏 KV Cache

最近看到 DeepSeek Harness 的一条更新:支持动态修改系统提示词且不破坏 KV Cache,模型需显式声明支持。


众所周知,harness 设计中,KV Cache 的命中是非常重要的一个约束,命中与否的价格相差了数十倍。

这让我有点好奇。系统提示词通常放在请求最前面,一旦修改,后面的历史对话还能复用缓存吗?带着这个问题,我读了一下 dsh 的代码。

先说结论:dsh 尽量保留已经发给模型的前缀,把更新后的内容追加到历史后面,来实现了提示词的动态更新。其中,运行时上下文以user消息更新,系统提示词以system消息更新;后者需要模型支持特定语义。(这个思路并不罕见,claude code 与 codex 的动态工具加载就是类似的思路,但是 DeepSeek Harness 是我遇到第一个把这个思路应用到提示词,或者说是所有 context 中的)

提示词变化后,Session 如何变化

我们先来看一下系统提示词发生变化后,session 具体发生了哪些变化

目前没有找到手动修改系统提示词的方法,由于系统提示词里有模型的信息,通过切换模型可以实现系统提示词的变更。

可以看到历史中的旧内容仍然存在,最新版本提示词追加到了最后面。

接下来看看,这些内容是怎样拼出来、又怎样进入请求的。

系统提示词如何拼接

DSH 的提示词分成 sections 与 context 两种类型。

• section() 注册系统提示词(例如角色设定、行为规则、工具使用指导,是以 system 的身份发送给 LLM)
• context() 注册运行时重要上下文(比如当前的环境,状态,以 user 身份发送给 LLM)

每个请求步骤开始前,agent-loop 调用systemPrompt.assemble():先合并全局与当前作用域的贡献,同名内容由更近的作用域覆盖;再按顺序排列段落,求值动态文本,并交给组装扩展点处理。随后,渲染阶段替换{{model}}等变量,过滤空段,用空行连接文本。

两类内容不会混成一条 system:sections渲染成完整系统提示词,contexts单独渲染成运行时快照。工具 schema 虽然也参与组装,仍作为独立的tools字段提交。

sections 变化:有条件地追加 system

systemPrompt是一个 cordis 服务,插件通过section()向它注册一段系统提示词。

先看这个方法接收的参数:

参数 作用
name 段落名称,用来区分不同贡献;同一作用域层内不能重复注册同名段落
order 拼接顺序,数值越小越靠前;相同数值再按名称排序
text 提示词内容,可以是固定字符串,也可以是每次组装时调用的函数
complete 可选;设为true时,这一段单独作为完整系统提示词,不再拼入其他 section

order决定的是文本位置。当前内置顺序里,Harness 身份段是-1000,persona 前缀是0,工具指导如 Bash 是1000、Read 是1100。插件可以通过getSectionOrder()获取这些命名位置。

每个 step 开始前,assemble()会收集这些段落、排序,并调用其中的text函数取得当前文本。进入 step 后,再替换文本里的变量、去掉空段,用空行拼成完整的 system prompt。

接下来才判断如何写入 Session:第一次写入开头的system/message;后续如果文本没变,就不重复写。如果文本变了,想把新版追加到历史后面,模型适配器需要声明(目前只有 Flash 模型支持动态变更系统提示词):

systemPromptUpdate'in-history'

它表示模型支持把后出现的 system 当作完整的新系统指令。在这个前提下,只要没有压缩、工具 schema 变化等打断原有请求序列的情况,DSH 就保留旧消息,以system role 追加完整的新提示词

模型不支持或请求序列需要重建时,则归并到开头的 system;清空提示词也走清除已有有效 system 内容的专门处理。

context 变化:追加完整的 user 快照

context()用来注册运行时上下文,参数同样是nameordertext,但没有completetext也支持固定字符串或动态函数,例如由函数返回当前环境状态。

多个 context 会按自己的order从小到大拼接,不与 section 混排。也就是说,context 的order再小,也不会因此跑到系统提示词前面。

它和 section 都在 agent step 前参与assemble(),动态函数也在这里求值。不同的是,context 在这个 pre 阶段就会完成渲染和快照比较:

1. 将当前所有有效 context 排序、替换变量,拼成一份完整快照。
2. 与 Session 中最近保留的上下文快照比较,文本相同就不追加。
3. 文本不同,就准备一条user role 消息;本步被接受后,进入 step 时写入 Session 的user/message

快照开头会说明“当前快照替代之前的运行时上下文快照”;已有上下文被清空时,也会追加明确的清空通知。

动态 context 不需要模型专门支持,所有模型都可以做到,因为 context 就是以一条普通的 user message 发送给了 LLM。经常变化的环境与状态信息适合放在 context 中,系统级行为规则则放在 section 中。

为什么这样能保留 KV Cache

看到这里,这个问题其实就已经回答了。DSH 在系统提示词发生变化时,把新的提示词追加到了 session 的组后面,以此来实现了提示词的动态变更。

旧的系统提示词仍旧在最开头,这肯定需要模型经过专门的训练,让 LLM 更加重视后出现的 system prompt。

deferred tools 与 tool_search

这让我想到 Claude Code/Codex 里的 Tool Search、Deferred Tools:先不把所有工具定义放进模型上下文,需要时再加载。

Claude Code 支持 MCP 工具按需发现;Claude API 会把发现的工具以tool_reference放入对话,再展开成完整定义,避免改动原有提示词前缀。OpenAI 的 tool_search 也明确说明,新发现的工具加载到上下文末尾,以保留缓存。

两者的共同点,是让新增信息尽量出现在已有前缀之后。不过,工具搜索加载的是新发现的定义;DSH 更新的是完整的系统提示词或上下文快照。修改已经加载的旧工具,也不能一概保证缓存不受影响。

我觉得 DSH 有意思的地方在于,它把这种保留前缀的思路用到了更广泛的指令与重要上下文更新中。当前源码还没有内置完整的 Tool Search/Deferred Loading 机制,直接改变工具 schema 仍可能影响缓存。

但有了这样的上下文组织方式,我也更期待 DSH 接下来补上 Deferred Tools、Tool Search。

【声明】内容源于网络
0
0
独立开发
技术变现,实现被动收入,SEO优化技巧,流量获取与变现,独立开发指南,网站搭建,niche站点,独立站,affiliate,广告,订阅
内容 91
粉丝 1
独立开发 技术变现,实现被动收入,SEO优化技巧,流量获取与变现,独立开发指南,网站搭建,niche站点,独立站,affiliate,广告,订阅
总阅读14.1k
粉丝1
内容91