Skip to content

迁移到 ContextChef v4

v4 是压缩管道 v2 版本:压缩现在提前触发("腐烂前")、可以固定(pin)、可以校验、可以归档并可逆、可以后台运行,也可以整体委托给 provider 的服务端上下文管理(Anthropic compact_20260112、OpenAI /responses/compact)。它还带来了新的 openai-responses target adapter、面向 @tanstack/ai ^0.44 的 @context-chef/tanstack-ai 完全重写,以及结构清理:废弃的 namespace 包装器被删除,src/index.ts 变成纯 barrel(facade 移到 src/chef.ts —— 公共 API 表面除此之外没有变化)。

v3 线v4 版本
@context-chef/core3.9.04.0.0
@context-chef/ai-sdk-middleware2.1.03.0.0
@context-chef/tanstack-ai0.6.01.0.0

破坏性变更

1. 移除的 API

废弃的 namespace 包装器被删除。底层函数本来就有直接导出;唯一要改的是 import。

移除替代
TokenUtilsestimateestimateObject
XmlGeneratorobjectToXml
AdapterFactorygetAdapteradapterRegistry
JanitorConfig.onBudgetExceededJanitorConfig.onBeforeCompress
ts
// v3
import { TokenUtils, XmlGenerator, AdapterFactory } from '@context-chef/core';
TokenUtils.estimate(text);
XmlGenerator.objectToXml(obj);
AdapterFactory.getAdapter('anthropic');

// v4
import { estimate, objectToXml, getAdapter } from '@context-chef/core';
estimate(text);
objectToXml(obj);
getAdapter('anthropic');
ts
// v3
janitor: { onBudgetExceeded: (history, info) => firstPassCompact(history) }

// v4 — same signature, same semantics, new name
janitor: { onBeforeCompress: (history, info) => firstPassCompact(history) }

2. 压缩在窗口 70% 处触发(triggerRatio 默认 0.7)

原因:模型质量早在硬窗口上限之前就开始退化,而且在 100% 处压缩容易引发死循环(Gemini CLI 正是因此把触发线从 0.7 移到 0.5)。v4 在 contextWindow * triggerRatio 处触发压缩,默认 0.7preserveRatio 现在作用于这个有效预算,而不是原始窗口。

ts
// v3 behavior: compress only when contextWindow itself is exceeded
janitor: { contextWindow: 200_000 }

// v4: compression now fires at 140k tokens. To restore v3 behavior:
janitor: { contextWindow: 200_000, triggerRatio: 1 }

如果你在 v3 中按原始窗口调过 preserveRatio,请重新检查 —— 它现在是 contextWindow * triggerRatio 的比例。

3. 压缩失败让历史保持不变

原因:v3 在压缩模型失败时用占位符截断历史,静默摧毁上下文。v4 中模型失败、缩减闸门触发(见下一条)、或 validateCompression 拒绝,都会原样返回历史并使熔断计数 +1。失败也不再设置压缩后抑制标志(E10)—— 该标志现在只在成功压缩后抑制重新检查。

ts
// v3: compressionModel throws → history replaced with a truncation placeholder
// v4: compressionModel throws → history returned as-is, breaker++ (3 strikes → compress() no-ops)

如果你依赖有损兜底把 payload 压在窗口内,请在 onBeforeCompress / compact() 里加自己的第一道兜底。

4. 缩减闸门:minShrinkRatio 默认 0.5

原因:会复读输入的摘要器过去会"成功"并永远循环。v4 中,对 ≥ 2000 字符的被压缩片段,摘要必须让它缩小至少 50%,否则压缩按失败处理(历史不变,熔断 +1)。

ts
// A summarizer that returns near-verbatim output now trips the breaker.
// To disable the guard:
janitor: { contextWindow: 200_000, minShrinkRatio: 0 }

5. withGuardrails 延迟应用、替换语义、独立消息

原因:v3 中护栏被立即应用并合并进动态状态消息 —— 在 withGuardrails() 之后调用 setDynamicState() 会静默丢弃护栏。v4 中 options 被存储、在 compile() 期间应用:

  • 顺序无关 —— withGuardrailssetDynamicState 之前还是之后调用,输出相同。
  • 替换语义 —— 每次调用替换上一次的 options(不累积)。
  • withGuardrails(null) 清除存储的 options。
  • 独立尾部消息 —— 护栏作为独立消息落在三明治末端,不再合并进动态状态消息。payload 会出现字节级差异,属于预期。
  • 持久化 —— ChefSnapshot 增加 guardrailOptions;快照/恢复完整往返。
ts
// v3 — order mattered, this silently dropped the guardrail:
chef.withGuardrails({ enforceXML: { outputTag: 'answer' } });
chef.setDynamicState(state); // guardrail gone

// v4 — same code works in any order; to remove a guardrail, be explicit:
chef.withGuardrails(null);

6. 事件 handler 错误隔离

原因:观察者永远不该打断编译。v3 中抛异常的事件 handler 会从 compile() 传播出去。v4 中错误被记录,compile() 继续。如果你(滥)用抛异常的 handler 做控制流闸门,把逻辑移到真正的钩子里(onBeforeCompilevalidateCompressiontransformContext)。

7. compile() 调用串行化

同一实例上的并发 compile() 现在排队(snapshot + serialize),不再交错执行、破坏共享状态(轮次计数、熔断器、signal 暂存)。输入在调用时快照 —— 两次排队的 compile 之间调 setHistory(),第一次看到旧历史、第二次看到新历史。被 reject 的 compile 不会污染队列。推荐模式仍然是每个并发调用方一个 chef;队列的目的是让意外共享变得安全,不是变快。

8. @context-chef/ai-sdk-middleware 3.0.0

  • 服务端上下文管理会跳过 middleware 压缩。 当某次调用带 providerOptions.anthropic.contextManagement 且 middleware 也配置了压缩,该次调用的 middleware 压缩会被跳过(一次性警告)—— 否则你和 Anthropic 会压缩同一段历史。逃生舱:

    ts
    createContextMiddleware({ ..., allowDoubleCompression: true })
  • 继承 core v4 压缩行为,包括 triggerRatio 0.7 默认值 —— middleware 压缩现在在 contextWindow 的 70% 处触发。传 triggerRatio: 1 恢复 v2 时机。

9. @context-chef/tanstack-ai 1.0.0(面向 @tanstack/ai ^0.44 重写)

旧的 ^0.10 上游 API 已不存在;包围绕 0.44 的 ChatMiddleware 重写,采用基于返回值的 onConfig(初始化 + 每次迭代触发;注入幂等,钩子永不向 host 抛异常)。

  • Peer dependency@tanstack/ai ^0.44.0

  • contextWindow 变为可选 —— 但一旦配置 compressonCompressonBeforeCompress 就必填(否则抛出)。

  • ctx.threadId 做会话 keyconversationId 是仍可用的废弃别名)。没有显式的 sessionId 选项;不传 threadId 的调用只有 run 内隔离。

  • compact 窗口语义对齐 AI SDK pruneMessagesreasoning 支持 'all' | 'before-last-message' | 'none'toolCalls 接受带按工具粒度的数组形式。

  • emptyMessages 默认 'remove'(v0.6 保留空消息)。

  • clear: ['thinking'] 现在生效 —— v0.6 中它是带警告的 no-op。

  • transformContext 增加第三个 ctx 参数,systemPrompts 拓宽为 SystemPrompt[]

    ts
    // v0.6
    transformContext?: (messages, systemPrompts: string[]) => {...}
    
    // 1.0
    transformContext?: (messages, systemPrompts: SystemPrompt[], ctx: ChatMiddlewareContext) => {...}
  • 压缩 adapter 不再限制摘要器的输出 token —— 0.44 移除了顶层 maxTokens。需要上限的话在自己的模型调用里限制。

v4 新特性

特性配置 / API
约束固定(固定消息原文穿过压缩,按轮次保护)Message.pinned: true
可逆压缩归档 + 召回工具janitor.archive: 'vfs' | CompressionArchiveConfiggetRecallToolDefinition()chef.resolveRecall(uri)
服务端上下文管理(Anthropic compaction,betas 自动推导)contextManagement: { strategy: 'server' }
压缩 prompt 里的领域指南janitor.compressionGuidelines: string[]
增量 anchored 压缩(持久 anchor 文档)janitor.compressionMode: 'incremental-anchored'janitor.getAnchorDoc()
后台(非阻塞)压缩janitor.compressionScheduling: 'background'
摘要后校验闸门janitor.validateCompression(summary, { compressed, kept })
压缩前统一 tool-result 重写(卸载、PII 脱敏)transformToolResult(content, { toolName, toolCallId })
细粒度事件compress:startcompress:endoffload:createdpruner:tool-blocked
OpenAI Responses API target(reasoning 条目逐字节保留)compile({ target: 'openai-responses' })fromOpenAIResponses()
Gemini 3.x thought signatures(往返保留,穿过 compact(['thinking'])ToolCall.thoughtSignature(自动)
跨 provider 以文本重放 thinkingnew OpenAIAdapter({ preserveThinkingAsText: true })GeminiAdapter
Anthropic Tool Search 注解ToolDefinition.deferLoading: true
新的 compact() targetclear: ['reasoning-tags']{ target: 'tool-result', toolFilter, exemptTools }
每次 compile 只扫一次 memory store(原来 4 次)Memory.compileArtifacts()(公开)

迁移清单

  1. 升级包版本:core ^4.0.0、ai-sdk-middleware ^3.0.0、tanstack-ai ^1.0.0(如适用再加 @tanstack/ai ^0.44)。
  2. 替换被移除的 import:TokenUtilsestimate/estimateObjectXmlGeneratorobjectToXmlAdapterFactorygetAdapter/adapterRegistry
  3. 重命名 janitor.onBudgetExceededonBeforeCompress(签名不变)。
  4. 决定压缩时机:保留新的 0.7 腐烂前触发,或设 triggerRatio: 1 恢复 v3 时机。重新检查调过的 preserveRatio
  5. 如果你依赖压缩模型失败时的占位符截断,在 onBeforeCompress 里加自己的兜底 —— v4 失败时历史保持不变。
  6. 如果你的摘要器在大片段上确实产出低缩减输出,调低或关闭 minShrinkRatio
  7. 审计 withGuardrails 用法:移除顺序 workaround,在依赖"被遗忘"行为的地方显式加 withGuardrails(null)。payload diff 中护栏会以独立尾部消息出现。
  8. 把控制流逻辑移出事件 handler(它们不再让 compile() 失败)。
  9. ai-sdk-middleware:如果你有意组合客户端压缩与 Anthropic 服务端上下文管理,设 allowDoubleCompression: true;否则删掉你自己的去重防护。
  10. tanstack-ai:升级 @tanstack/ai 到 ^0.44,为跨 run 会话连续性传 threadId,凡配置压缩处补上 contextWindow,更新 transformContext 签名,并重新检查依赖 emptyMessages: 'keep'clear: ['thinking'] no-op 的流程。
  11. 然后在划算的地方采用新特性 —— 固定你的策略消息(pinned: true),考虑 archive: 'vfs' + 召回工具,并在 Anthropic 上评估 contextManagement: { strategy: 'server' }