pref(context): 改善 Provider 前缀缓存命中基础#616
Merged
Yumiue merged 4 commits into1024XEngineer:mainfrom May 11, 2026
Merged
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
f4fb13f to
709b97e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
现状问题
当前 NeoCode 发送给 provider 的 system prompt 由十余个来源拼接而成,顺序固定但内容混合——长期稳定的规则、能力说明与每轮变化的 todo、git 状态、进度提醒杂糅在一起。从任何一个 provider 的自动前缀缓存(DeepSeek Context Caching、OpenAI Automatic Prefix Caching 等)角度看,整个 system prompt 几乎每轮都在前缀附近就变了,缓存命中率极低。
具体有 4 个导致缓存前缀失效的问题:
1. 稳定与动态内容未分离
prompt 中的所有 section 被同等对待,统一进入单一
SystemPrompt。像AGENTS.md规则、memo 索引这样跨多轮不变的内容,与 todo state、git dirty、active skills 等每轮变化的内容混在同一个线性序列中。一旦某个动态字段变化(比如新增一个 todo),整个 system prompt 就从那个位置"断裂",后续缓存全部作废。2. Capabilities 内容随 PlanStage 波动
capabilitiesSource的输出依赖PlanStage(""/"plan"/"build"),但它被归入稳定来源。同一个 session 内从 plan 模式切换到 build 模式时,稳定前缀就变了——这本该是动态内容。3. Reminder / Hook notification 破坏拼接边界
prepareTurnBudgetSnapshot中 progress reminder、pending system reminder、hook notification 直接追加到已拼接好的SystemPrompt尾部。虽然语义上在最末尾,但BuildResult只保留最终的SystemPrompt,runtime 无法知道哪部分是稳定的、哪部分是动态的——缓存前缀的 "stable 部分" 被丢失了。4. 工具列表和 repository 未做稳定排序
prioritizeToolSpecsBySkillHints按激活 skill 的 hint 调整工具顺序,同一组可用工具在不同 session(不同 skill 激活)中顺序不同,使 tools 数组在请求间不可复用。同样,RepositoryContext中的 changed-files 和 retrieval hits 按 runtime 传参顺序渲染,无自身排序保证。方案
不引入任何 provider 特定字段(不碰
cache_control、prompt_cache_key、cachedContent),只改善 NeoCode 发出的请求自身的前缀稳定性,让所有支持 automatic prefix caching 的 provider 都能受益。1. 构建时分离 stable/dynamic prompt(
internal/context)BuildResult增加两个新字段:Prompt source 分为两组:
composeSystemPrompt→joinSystemPromptParts:新增拼接 helper,空部分自动跳过,非空部分用双换行分隔。SystemPrompt始终等于 stable + dynamic。2. Runtime reminder 只追加到 dynamic(
internal/runtime)prepareTurnBudgetSnapshot中将withProgressReminder、drainPendingSystemReminder、drainHookNotificationsForTurn的注入目标从builtContext.SystemPrompt改为builtContext.DynamicSystemPrompt,然后用joinRuntimeSystemPromptParts(stable, dynamic)重新拼接。向后兼容:当旧式 builder(不填充
StableSystemPrompt/DynamicSystemPrompt)使用时,回退到原SystemPrompt路径。3. 工具规格稳定排序(
internal/runtime)移除主链路中
prioritizeToolSpecsBySkillHints的调用,替换为stableSortToolSpecsByName——对 tools 数组按Name做sort.SliceStable。skill hints 继续通过skillPromptSource→ dynamic prompt 告知模型,不影响模型的选择能力。4. Repository 内容稳定排序(
internal/context)renderChangedFilesRepositoryContext和renderRetrievalRepositoryContext渲染前分别按 path(+ line_hint)做一次稳定排序副本,消除 runtime 传参顺序的抖动。最终请求结构
改造前:
所有内容在一个扁平序列中,任一部分变化就"断裂"整个前缀。
改造后:
发给 provider 的仍然是单个
SystemPrompt = StableSystemPrompt + DynamicSystemPrompt。provider adapter 零改动。原则与边界
PromptCacheConfig。cache_control、prompt_cache_key、cachedContent、cache usage 统计。Builderinterface 不变,Build()返回值兼容旧调用方。SystemPrompt,不区分 stable/dynamic。影响范围
internal/contexttypes.goBuildResult新增 2 字段prompt.gojoinSystemPromptPartsprompt_test.gobuilder.gonewPromptSources为 stable + dynamic,Build()双路径收集builder_test.gosource_repository.gochangedFiles和retrievalHits渲染前稳定排序internal/runtimerun.goDynamicSystemPrompttoolexec.gostableSortToolSpecsByNameruntime_test.goskills_test.go验收标准
BuildResult输出 stable / dynamic / system 三个字段,SystemPrompt == stable + "\n\n" + dynamicStableSystemPrompt不变StableSystemPrompt可变化Name稳定排序;skill hints 只通过 prompt 文本告知模型go build ./...通过,go test ./internal/context ./internal/runtime ./internal/gateway通过