ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

0基础深入理解DeepSeek Harness 架构【5】系统提示词是怎么拼出来的

0基础深入理解DeepSeek Harness 架构【5】系统提示词是怎么拼出来的 第 5 章 系统提示词是怎么拼出来的摘要本章深入解析 DeepSeek Harness 中系统提示词的组装机制。系统提示词并非硬编码而是由插件通过段落PromptSection、动态上下文PromptContext和工具 schema 提供方三类贡献拼接而成。核心设计在于系统提示词作为「历史中的消息」而非请求顶层字段传递以保障请求可从日志重建。工具 schema 通过显式允许列表仅暴露 name、description、parameters 三个字段其余执行细节对模型完全不可见。本章还涵盖更新规则、作用域组装上下文及实操示例帮助读者理解并编写自己的提示词段落插件。标签系统提示词PromptSectionPromptContextToolSchema可重建性插件机制DeepSeek Harness模型每次开口前都会先看到一段「系统提示词」。这段文字不是硬编码在某个文件里的而是由一堆插件各自贡献一段、然后按规则拼起来的。这一章讲清楚拼接规则——顺便解决一个经典困惑「为什么我在系统提示词里改的东西有时候没生效」。5.1 三个贡献类型段落、上下文、工具 schema组装这件事官方交给ctx.systemPrompt这个服务。它接受三类输入类型注册方法它进入请求的哪一部分段落PromptSectionctx.systemPrompt.section()拼成系统提示词文本。第一条追加为 surface 第 0 号节点动态上下文PromptContextctx.systemPrompt.context()作为一条**持久的 user-role 快照某一瞬间的完整状态副本。拿到快照之后别人再改也不会影响你手上这一份**被物化。只在完整当前快照发生变化或被压缩移除时才重新记录工具 schema 提供方ctx.systemPrompt.tools()贡献这次组装里模型可见的工具 schema 集合提示词变量ctx.systemPrompt.variable()给段落文本里的{{变量}}占位符提供值图 18排序、遮蔽、waterfall、渲染——四步之后一段文本变成了一条 surface 节点。5.2 段落的字段五个就够了一个PromptSection的字段非常少但每个都有明确用途字段必填说明name是唯一名称。重复注册会抛异常所以起名要带上你的插件前缀。而且它还有一个副作用order 相同时按名称排序所以名字也参与排序order是排序权重升序拼接。同一个 order 时按名称的代码单元顺序排text是静态文本或者一个每次组装时求值的函数参数是这次组装的AssembleContext。用函数就能做到「提示词随当前 agent 变化」interpolate否是否插值{{变量}}。默认 true设成 false 就保留字面文本写代码示例时有用complete否把这个贡献当作完整的系统提示词。多个生效的 complete 会让组装失败还有一个配套的结构PromptContext字段更少name、order、text。区别在于它的产物是一条持久的 user-role 快照而不是拼进系统提示词文本里。怎么选段落还是上下文问自己一句「这段信息是每个请求都该在的稳定说明还是『当前状态』」·稳定说明我是谁、怎么用工具、项目规范→ 用段落section()·当前状态现在打开了哪些文件、有哪些可选项→ 用上下文context()因为它是快照变了才重新记录5.3 最容易困惑的一点系统提示词是「消息」不是「请求字段」这是本章最重要的一节。官方在这个设计上专门写了一篇决策记录标题就叫「系统提示词作为 surface 节点」。传统做法是把系统提示词放在请求的一个system字段里。DeepSeek Harness 的做法不同——渲染后的系统提示词是通过system/message历史传递的。提示词仅通过system/message历史传递空渲染文本清除所有生效的系统节点模型不再看到旧提示词具备能力的路由可在缓存前缀之后追加非空更新……不具备能力的路由与新请求序列将非空提示词文本归并到首个系统节点并为非空的后续系统节点记录空内容替换。再看官方在 LLM 一页里的说法请求的system字段不设置——GenerateOptions.system服务于标题提供方等直接单次调用方。也就是说**循环构建的请求里系统提示词是「派生历史里的第一条消息」不是请求的顶层字段。**请求的system字段是留给那些不走循环的直接调用方比如自动生成会话标题用的。为什么要这么设计官方给出的理由是「可重建性」。回顾第 4 章请求必须是日志的纯函数。如果系统提示词放在请求的顶层字段里那么它就不在日志的事件流中重建请求时就没有依据。而把它作为system/message事件放进日志它就成了历史的一部分可以被完整重建。这是一处很典型的「用一个约束可重建统一了一片设计」的例子。你在读官方文档时看到的各种看起来多余的规则多半都能追溯到类似的一条底层约束。由此带来的更新规则因为系统提示词是历史节点它的「变化」就有了三种处理方式取决于路由是否声明支持「历史内更新」情况处理方式路由声明了systemPromptUpdate: in-history模型会把messages里任意位置最新的system消息读作完整的有效系统提示词。所以循环可以把变化后的非空提示词追加到已缓存历史之后而不是改写第 0 条消息——这样前面的缓存前缀就保住了路由没有声明该模式模型只读开头的 system 消息。所以循环必须把非空提示词文本归并到首个系统节点并为后续的系统节点记录「空内容替换」渲染文本变成空清除所有生效的系统节点模型不再看到任何旧提示词。注意「空节点」不会去恢复旧文本图 19三种更新方式的存在是因为不同模型路由的能力不同。更新规则要跟着路由能力走。陷阱新手常犯的错误「我把系统提示词清空了但模型好像还记得之前的指令」。请对照上表情况 C空渲染文本会清除所有生效的系统节点。如果你观察到旧指令仍在起作用那多半不是提示词的问题——想想它是不是被写进了 user 消息、或者被某个 skill 注入进了对话历史。5.4 工具 schema 是怎么进提示词的工具需要让模型知道自己的存在和参数格式。这件事通过ToolSchema完成它的结构很简洁interfaceToolSchema{/** 请求把工具定义「延迟加载」进模型上下文独立于是否用工具添加块记录它 */deferLoading?:truename:stringdescription:string/** 参数的 JSON Schema 对象 */parameters:Recordstring,unknown}官方特别说明了它为什么声明在dsh-llm而不是dsh-tools因为它是一个模型请求GenerateOptions的一部分。dsh-tools的ToolDefinition和dsh-system-prompt的PromptAssembly都从那里 import 它。这是一个「按职责而非按直觉放置类型」的例子。还有一个关键的安全细节官方在工具一页里写得很明确注册表的schemas()通过显式允许列表构建面向模型的ToolSchema[]outputexecuteprojectContentfinalizeContenttimeoutMsisConcurrencySafepresentCallpresentResult绝不能泄漏到模型请求中。也就是说你写工具时声明的那些执行逻辑、超时预算、UI 卡片函数模型一个字节都看不到。它只看到name、description、parameters这三个字段。这是显式允许列表不是黑名单——新增字段默认是不会被发出去的。关键你在description里写的东西是模型理解「这个工具是干什么的」的唯一依据。所以写工具时description 的质量直接决定模型用得对不对。这不是文档这是提示词。5.5 工具提供方结果里的两个字段如果某个插件想让工具集合随作用域变化它注册一个「工具 schema 提供方」。返回值是两个字段字段作用schemas这个提供方对本次组装贡献的工具 schemaknownNames提供方在「限制之前」的名称全集。用途是区分两种情况一个名字是「配置里拼错了」还是「这个工具在此作用域中被有意隐藏了」。没有这个字段配置校验只能报一句含糊的「工具不存在」knownNames这个设计值得单独夸一句它解决的是「错误信息质量」问题。很多框架到这里就报个「未知工具」了事用户得自己猜是拼错了还是被策略挡了。5.6 一个完整的组装上下文每次组装都会有一个AssembleContext它标识这次组装解析的作用域层并可以携带该请求的显式控制信号。它也是可合并扩展的——dsh-agent给它加了一个可选的agent字段用来携带当前 agent 实例辅助函数assembleContextFor(agent, signal)则一起设置这些字段。官方对「裸组装」的说明是**它既没有作用域也没有信号。**这时只有全局提供方和「无主体」的监听器参与。这个设计让「同一个进程里不同 agent 看到不同的提示词」成为可能——这正是第 9 章「作用域」要展开的内容。5.7 实操写一个自己的提示词段落把这一章的知识串起来一个真实可用的提示词段落插件长这样importtype{Context}fromdeepseek-ai/cordisexportconstnamemy-guidelinesexportconstinject[systemPrompt]exportfunctionapply(ctx:Context){// 1) 一段静态说明ctx.systemPrompt.section({name:my-plugin:guidelines,// 名字带上前缀避免和别人撞车order:250,// 排在 AGENTS.md 之后、skill 之前text:回答时优先给出可执行的命令不要只讲原理。,})// 2) 一段动态说明随当前 agent 变化ctx.systemPrompt.section({name:my-plugin:workspace,order:260,text:(assembly)assembly.agent?当前工作目录是${assembly.agent.session.header.cwd}。:,})// 3) 一个变量供上面或别人的段落引用 {{repo_name}}ctx.systemPrompt.variable(repo_name,()my-repo)}三点值得注意注册是副作用。插件卸载时这些段落会自动消失你不需要写清理代码。inject: [systemPrompt]让 Cordis 等到提示词服务就绪再启动你这个插件。名字冲突会直接抛异常。所以永远带上前缀。5.8 这一章要带走的三句话这一章要带走的三句话**系统提示词是拼出来的不是写死的。**顺序由 order 决定同名会被作用域遮蔽。**它是历史里的消息不是请求的顶层字段。**这是「请求可从日志重建」这条约束的直接后果。**模型的输入面只有三个字段name、description、parameters。**其他全是执行期的私有信息不会外泄。
返回列表