ByteNoteByteNote
DeepSeek Harness 系统提示词组装机制解析
字

字节笔记本

2026年10月6日 · 约 6 分钟读完

DeepSeek Harness 系统提示词组装机制解析

API中转
¥120

DeepSeek 在 GitHub 上以 MIT 协议开源了 agent 工程框架 DeepSeek Harness,仓库里有一个专门的 system-prompt 包,负责管理系统提示词的组装。官方文档把它定义为一座桥梁:一侧是各个插件贡献的提示词素材,另一侧是一次组装调用;智能体循环在每个模型步骤之前组装一次,再把结果渲染成完整的模型提示词。本文依据 docs/subsystems/system-prompt.md 与包内 README,梳理这套机制的四个核心类型、组装流水线、严格插值规则与缓存考量。

四个核心类型

AssembleContext 描述一次组装的用途,只有两个可选字段:scope 指定参与本次组装的作用域层,缺省时只有全局提供方参与;signal 是发起这次组装的那一轮的显式中止信号。该类型可通过声明合并扩展,dsh-agent 就追加了可选的 agent 字段,且绝不能在没有 scope 时单独设置,应通过 assembleContextFor(agent, signal) 一起设置显式字段;裸组装既不带作用域也不带信号,提供方必须容忍字段缺席。

PromptSection 是只读的段注册约定:name 必须唯一,重复注册直接抛错;order 决定拼接顺序,各段按升序拼接,项目约定 -100 留给 harness 身份,0 留给部署 persona,工具引导占用 100 到 199;text 既可以是静态文本,也可以是每次组装时用当时的上下文求值的函数,文本中的变量占位符留到渲染阶段才插值;complete 标记整段接管,一个生效的 complete 段会成为唯一的提示词段,出现两个以上生效的 complete 段则组装直接失败。

PromptContext 是 PromptSection 的缓存安全对应物:动态上下文按升序拼接,物化为一条持久化的 user 角色快照。智能体循环只有在完整快照发生变化、或被上下文压缩移除之后,才会把它记录到保留的模型历史之后;空文本的贡献不产生任何输出。

ToolProviderResult 是工具 schema 提供方的返回结构:schemas 是本次组装中对模型可见的集合;knownNames 是限制前的名称全集,专门用于区分「配置里写错了名字」和「已知工具在该作用域被有意隐藏」这两类情况。

组装流水线

DeepSeek Harness 系统提示词组装流水线

注册表服务挂在 ctx.systemPrompt 上,assemble() 的流程固定为五步:合并全局层与目标作用域层的提供方;分离工具参数;按规范顺序排序;运行按作用域过滤的 system-prompt/assemble 事件;最后恢复生效的 complete 段。作用域内的段和变量会遮蔽同名的全局项。waterfall 的返回值就是权威结果,唯一的例外是 complete 段:它在事件之后被恢复为唯一提示词段,因此监听器无法往该作用域的系统提示词里追加或替换内容。事件契约还明确约束,传入的 signal 只控制这一次显式组装请求,监听器不得把它保留下来控制后续轮次。

除 assemble() 外,注册表还有五个常用接口。section() 注册段;context() 注册动态上下文;tools() 注册工具 schema 提供方,返回保留名 TOOL_ORDER_REST 会让组装失败;variable() 注册提示词变量,名称必须匹配 [a-z][a-z0-9_]*,同层重复或非法名称会抛错,提供方可以返回 undefined 表示本轮无值,但渲染引用该值的段时会失败;suppressRuntimeContext() 一键抑制作用域内全部动态上下文贡献,多个抑制器可独立释放,只有全部移除后上下文才会恢复。

严格插值与缓存稳定

段落顺序约定与严格插值规则

组装结果 PromptAssembly 由 sections、tools、variables 三部分组成,段落文本在到达时已求值但尚未插值;工具 schema 按设计属于组装结果,因为「模型获知自己能做什么」与提示词是一个连贯整体,尽管适配器会把 schema 作为独立字段传输。renderPrompt() 负责最后的插值:未知引用抛错,内部用 Object.hasOwn 查找,{{constructor}} 这类原型名同样视为未知;已注册但无值的引用抛错;格式错误的完整占位组抛错;出现左双花括号却没有闭合、而后文又有右双花括号的情况同样抛错。只有后文再无右双花括号的孤立左花括号会按字面量通过,替换值也不会被二次扫描。文档给出的理由很直接:明确失败优于交付格式错误的提示词。

这套设计对 KV Cache 相当敏感。README 明确写道:只要身份、persona、变量、段文本与顺序的渲染完全一致,提示词前缀就保持稳定;任何变更都可能从第一个变化的 token 起让复用失效。动态上下文之所以独立成 user 快照、并且只在变化时落盘,正是为了减少对前缀缓存的破坏。

两个事件与工程启示

system-prompt/assemble 是 waterfall 事件,监听器协作式修改或替换组装出的段落、上下文、工具与变量,返回值为权威结果;作用域过滤保证带作用域的监听器只收到本作用域的组装。system-prompt/change 是 emit 事件,任何提示词提供方变化都会触发,且不做过滤,因为一次全局变化会影响所有作用域。

这个子系统的价值,在于把系统提示词「由谁提供、按什么顺序拼、什么能改、什么不能改」全部变成显式契约:贡献靠注册,顺序靠约定区间,覆盖靠作用域遮蔽,接管靠 complete 标记,防篡改靠事件之后的恢复语义,错误靠尽早抛出。对于自研 agent 框架的开发者来说,这是一份值得对照阅读的实现参考。

相关文章

分享: