ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

08. 系统提示装配:模型看见的前缀是被“拼“出来的

2026/9/15 12:03:35 拓冰建站 浏览量
08. 系统提示装配:模型看见的前缀是被“拼“出来的 你在哪运行示例的第 5 步。上一篇讲了循环在什么时候调它这一篇讲它内部怎么把一堆插件的贡献拼成一段前缀。读完你会知道PromptSection的 order 约定、complete段落如何独占整个系统提示、PromptContext为什么必须和 section 分开这关系到 KV Cache 命中率以及工具 schema 如何被挑选、遮蔽、过滤。缩写对照表缩写英文全称中文KV CacheKey-Value Cache键值缓存推理侧复用相同前缀的机制LLMLarge Language Model大语言模型APIApplication Programming Interface应用程序编程接口一、角色回顾拥有把各插件注册的提示段落、动态上下文、工具 schema、变量按序装配成一次请求的前缀。刻意不做不发请求、不缓存——每次装配都重新求值。在示例中出场第 5 步每个 step 各一次。服务 key 是ctx.systemPrompt四个注册方法 一个装配方法section(section:PromptSection):()void// 提示段落context(context:PromptContext):()void// 动态上下文variable(name,provider):()void// 变量tools(provider):()void// 工具 schema 提供者assemble(context?):PromisePromptAssembly// 装配注意每个方法都返回一个 disposer——第 4 篇讲的注册即可撤销效果。二、Section有序拼接的提示段落interfacePromptSection{readonlyname:string// 唯一重复注册直接抛错readonlyorder:number// 升序拼接readonlytext:string|((context:AssembleContext)string)readonlycomplete?:boolean}order 约定文档写死了这几档order放什么-100harness 身份“你是一个跑在 XX 里的 agent”负数其他也在人格之前渲染比如harness:source告诉 agent DSH 源码在磁盘哪0部署人格persona100–199工具指引标准模式那份 persona 就注册在 order 0-id:personaname:deepseek-ai/dsh-personaconfig:text:-You are a coding agent powered by the{{model}}model. Your working directory is{{cwd}}.text 可以是函数text既可以是静态字符串也可以是每次装配都用当次AssembleContext求值的函数。这就是为什么不缓存是设计而不是偷懒——段落内容可能依赖当前作用域、当前工作区、当前权限策略。complete独占整个系统提示readonlycomplete?:boolean标了complete的段落会成为唯一的系统提示段落。但注意它的执行顺序很讲究“Assembly still runs the cooperative waterfall so tools, contexts, and variables can be resolved,then restores this exact section as the sole prompt section. More than one effective complete section makes assembly fail.”即waterfall 照跑工具 schema、上下文、变量都要解析跑完之后把这个段落恢复成唯一段落。所以 waterfall 监听器改不了这个作用域的系统提示——这是给评测用的强隔离。极简模式的配置正是这么写的-id:personaname:deepseek-ai/dsh-personaconfig:text:You are a helpful software engineer assistant.complete:trueincludeRuntimeContext:false一句人格 独占 连运行时上下文都不要。这就是第 1 篇说的把变量按死的具体实现。三、Context为什么它必须和 Section 分开interfacePromptContext{readonlyname:stringreadonlyorder:numberreadonlytext:string|((context:AssembleContext)string)}字段几乎和PromptSection一样只少了complete。那为什么要两个类型答案在文档的一句话里“PromptContextis thecache-safe counterparttoPromptSection. The assembly resolves and orders these contributions, while agent-looplogs their complete current snapshot after retained model historyonly when it changed or compaction removed it.”关键在位置一次模型请求的构成系统提示由 section 拼成很少变保留的模型历史只在末尾增长动态上下文快照由 context 拼成变了才追加这张图回答的问题为什么当前时间当前权限策略这类会变的东西不能写进系统提示。如果把当前审批策略是 ask 还是 never塞进系统提示那么每次策略一变整个前缀就变了——KV Cache 全部失效每一次请求都要从头算。把它放在保留历史之后作为一条user/message快照追加前缀就稳定了缓存能一直命中。这条设计在仓库里是有纪律的几乎每个包的 README 都有一节“KV Cache effect”说明这个插件对缓存的影响。比如app-boot的那节写着addHarnessSourceSection放的是系统提示头部的一行短文本在每请求内容之前所以跨回合不会让缓存失效。顺便这也解释了第 6 篇那条request/context事件为什么只在路由或容量变化时才记——同一个思路不变的东西不要制造噪音。四、Variable{{model}}是怎么解析的variable(name:string,provider:(context:AssembleContext)string|undefined):()void名字规则[a-z][a-z0-9_]*重复或非法直接抛错。段落文本里写{{variable}}在renderPrompt阶段插值不是注册时。provider 可以返回undefined但渲染一个引用了它的段落就会失败——刻意不给你静默的空字符串。作用域规则和其他注册一样scoped 变量遮蔽同名全局变量。所以{{model}}在不同会话里解析成不同的值是作用域链的自然结果第 4、5 篇。五、工具 schema装配期才决定模型能看见哪些工具tools(provider:(context:AssembleContext)ToolProviderResult):()voidinterfaceToolProviderResult{readonlyschemas:readonlyToolSchema[]// 本次装配可见的readonlyknownNames?:readonlystring[]// 限制前的名字全集}knownNames这个字段体现了很好的产品直觉它是限制前的名字全集用来区分两种情况——你配置里写的工具名打错了这个名字压根不存在这个工具存在但在当前作用域里被刻意隐藏了。两种情况的报错应该不一样。遮蔽与过滤工具可见性由三层机制共同决定术语表里的定义机制效果shadowing遮蔽最具体的赢作用域内的同名工具替换全局的同名工具仅对该作用域生效。这是每个 agent 一套人格 / 一套工具变体的实现方式restriction限制tools.restrict过滤全局工具集多个限制按交集叠加作用域内注册的工具在过滤之后合并进来scope-local 注册只属于一个作用域的工具不向下继承给子代理有一条特别重要的一致性保证“A filtered-away global tool isabsent from the prompt AND refuses execution, indistinguishably from a nonexistent one.”被过滤掉的工具既不出现在提示里也拒绝执行——和不存在的工具无法区分。没有模型看不见但能调的后门。谁在装配时被排除第 10 篇会讲ToolDefinition有一堆宿主专用字段execute、timeoutMs、isConcurrencySafe、presentCall……。注册表的schemas()用显式白名单构造给模型看的ToolSchema[]“output/execute/finalizeContent/timeoutMs/isConcurrencySafe/presentCall/presentResultmust never leak into a model request.”白名单而不是黑名单——加新字段时默认是安全的。六、装配流程是否assemble(context)收集全局 作用域的section / context/ variable / tools把工具参数拆出来应用规范排序system-prompt/assemblewaterfall有 complete 段落恢复它为唯一段落用 waterfall 的返回值PromptAssembly这张图回答的问题从注册的碎片到一次请求的前缀中间发生了什么。system-prompt/assemble是一个scope-filtered作用域过滤的 waterfall作用域内的监听器只收到该作用域的装配。返回值是权威的——除了complete段落会在之后被恢复。还有一个小而有用的开关suppressRuntimeContext():()void在调用方作用域里压制所有动态运行时上下文贡献但不改变拥有或强制这些事实的服务。极简模式的includeRuntimeContext: false走的就是这条路——压制的是呈现不是能力。七、失败行为出什么事怎么办两个插件注册同名 section抛错同一层内重复order 是 NaN / Infinity抛错有效的complete段落多于一个装配失败变量 provider 返回undefined而某段落引用了它渲染失败不给静默空串工具 provider 返回了保留名TOOL_ORDER_REST装配失败任何提供者变化发system-prompt/change不做作用域过滤——全局变化影响每个作用域⚓ 回到示例第 5 步你那句话进入step/start之后assemble()被调用产出物大致是[order -100] harness 身份段落 [order 0] 人格You are a coding agent powered by deepseek-chat model. Your working directory is /Users/you/my-project. ← {{model}} {{cwd}} 已插值 [order 100] 工具指引段落bash 怎么用、编辑器怎么用…… ───────────── 工具 schemaread_file / str_replace_editor / bash / grep / glob / subagent / todo_write / ... 标准模式 preset 注册的那一套 经过本作用域的遮蔽与限制之后 ───────────── 保留的模型历史第一步时是空的 ───────────── 动态上下文快照当前工作区、当前权限策略workspace-write ask、 当前时间…… ← 这部分在历史之后保护 KV Cache整份东西作为request/header落进日志第 6 篇的seq 4。到了第 9 步第二个 step装配又跑了一次。这次section 部分一个字节都没变→ 前缀稳定 → KV Cache 命中历史长了多了第一步的助手消息和工具结果动态上下文只在变了的时候才重新追加一份快照——你没改权限策略所以没变。到了第 10 步之后如果你在审批弹窗里选了以后都别问把策略改成never那么权限策略这个动态上下文变了——于是一份新的完整快照会追加在保留历史之后。注意它不会去改系统提示。这就是把 section 和 context 分成两个类型的全部理由。上一篇← 07 · Agent Loop下一篇→ 09 · LLM 接缝把厂商协议关进一个可替换的盒子回到→ 系列索引 返回专栏目录