Skip to content

pref(context): 改善 Provider 前缀缓存命中基础#616

Merged
Yumiue merged 4 commits into1024XEngineer:mainfrom
phantom5099:rebuild-verify
May 11, 2026
Merged

pref(context): 改善 Provider 前缀缓存命中基础#616
Yumiue merged 4 commits into1024XEngineer:mainfrom
phantom5099:rebuild-verify

Conversation

@phantom5099
Copy link
Copy Markdown
Collaborator

现状问题

当前 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_controlprompt_cache_keycachedContent),只改善 NeoCode 发出的请求自身的前缀稳定性,让所有支持 automatic prefix caching 的 provider 都能受益。

1. 构建时分离 stable/dynamic prompt(internal/context

BuildResult 增加两个新字段:

type BuildResult struct {
    SystemPrompt        string   // 兼容旧链路,= stable + "\n\n" + dynamic
    StableSystemPrompt  string   // 适合作为缓存前缀
    DynamicSystemPrompt string   // 当前轮运行态
    Messages            []providertypes.Message
}

Prompt source 分为两组:

Stable(缓存前缀) Dynamic(每轮变化)
core prompt(Agent Identity 等) capabilities(依赖 PlanStage)
rules(AGENTS.md) task state
memo 持久记忆索引 plan mode context
todos
active skills / skill hints
repository context
system state(workdir/provider/git)

composeSystemPromptjoinSystemPromptParts:新增拼接 helper,空部分自动跳过,非空部分用双换行分隔。SystemPrompt 始终等于 stable + dynamic。

2. Runtime reminder 只追加到 dynamic(internal/runtime

prepareTurnBudgetSnapshot 中将 withProgressReminderdrainPendingSystemReminderdrainHookNotificationsForTurn 的注入目标从 builtContext.SystemPrompt 改为 builtContext.DynamicSystemPrompt,然后用 joinRuntimeSystemPromptParts(stable, dynamic) 重新拼接。

向后兼容:当旧式 builder(不填充 StableSystemPrompt / DynamicSystemPrompt)使用时,回退到原 SystemPrompt 路径。

3. 工具规格稳定排序(internal/runtime

移除主链路中 prioritizeToolSpecsBySkillHints 的调用,替换为 stableSortToolSpecsByName——对 tools 数组按 Namesort.SliceStable。skill hints 继续通过 skillPromptSource → dynamic prompt 告知模型,不影响模型的选择能力。

4. Repository 内容稳定排序(internal/context

renderChangedFilesRepositoryContextrenderRetrievalRepositoryContext 渲染前分别按 path(+ line_hint)做一次稳定排序副本,消除 runtime 传参顺序的抖动。


最终请求结构

改造前:

SystemPrompt  = core + capabilities(rules) + rules + task state + plan mode
              + todos + skills + repository + system state + reminders

所有内容在一个扁平序列中,任一部分变化就"断裂"整个前缀。

改造后:

StableSystemPrompt:
    core prompt         ← 固定常量
    rules               ← 同一规则版本下不变
    memo 索引           ← 仅规则变更时更新

DynamicSystemPrompt:
    capabilities        ← 随 PlanStage
    task state          ← 随任务进度
    plan mode           ← 随当前模式
    todos               ← 随任务进度
    skills              ← 随激活 skill
    repository context  ← 随检索结果
    system state        ← 随 git 状态
    reminders / hook    ← runtime 注入

发给 provider 的仍然是单个 SystemPrompt = StableSystemPrompt + DynamicSystemPrompt。provider adapter 零改动。


原则与边界

  • 不新增任何配置项:第一阶段不做 PromptCacheConfig
  • 不碰 provider 协议层:不加 cache_controlprompt_cache_keycachedContent、cache usage 统计。
  • 不引入新接口Builder interface 不变,Build() 返回值兼容旧调用方。
  • provider adapter 无感知:继续只读 SystemPrompt,不区分 stable/dynamic。

影响范围

目录 文件 变更
internal/context types.go BuildResult 新增 2 字段
prompt.go 新增 joinSystemPromptParts
prompt_test.go +4 测试
builder.go newPromptSources 为 stable + dynamic,Build() 双路径收集
builder_test.go +6 测试(分离验证、todo/plan 不变性、memo stable)
source_repository.go changedFilesretrievalHits 渲染前稳定排序
internal/runtime run.go reminder 注入目标改为 DynamicSystemPrompt
toolexec.go 新增 stableSortToolSpecsByName
runtime_test.go stub builder 填充新字段
skills_test.go +3 稳定排序测试,更新工具排序预期

验收标准

  1. BuildResult 输出 stable / dynamic / system 三个字段,SystemPrompt == stable + "\n\n" + dynamic
  2. 仅改变 todo、git dirty、repository retrieval、hook notification、progress reminder、plan 时 StableSystemPrompt 不变
  3. 改变 rules 时 StableSystemPrompt 可变化
  4. provider adapter 无修改
  5. 不新增配置项、不新增 cache 统计字段
  6. tools 进入 provider request 前按 Name 稳定排序;skill hints 只通过 prompt 文本告知模型
  7. go build ./... 通过,go test ./internal/context ./internal/runtime ./internal/gateway 通过

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@phantom5099 phantom5099 changed the title pref(contex): 改善 Provider 前缀缓存命中基础 pref(context): 改善 Provider 前缀缓存命中基础 May 10, 2026
Copy link
Copy Markdown

@fennoai fennoai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 1 noteworthy regression. The stable/dynamic split is directionally sound, but the legacy builder compatibility path in prepareTurnBudgetSnapshot now drops stateful reminders/notifications before they can reach the provider request.

Comment thread internal/runtime/run.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 10, 2026

Codecov Report

❌ Patch coverage is 83.67347% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/context/source_repository.go 57.89% 5 Missing and 3 partials ⚠️
internal/runtime/run.go 73.91% 3 Missing and 3 partials ⚠️
internal/context/builder.go 95.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@Yumiue Yumiue merged commit 79ce375 into 1024XEngineer:main May 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants