根据对 ZCode 源代码(特别是 core/src/compact/、core/src/context/builder.ts 和 core/src/runtime/methods/turn-loop.ts)的深度审查,我们可以清晰地看到 ZCode 处理超长上下文的底层机制,以及为何在调用自带 GLM 5.3 和第三方 DeepSeek V4 Flash 时会出现截然不同的表现。
以下是基于源码的详细分析解读:
一、 ZCode 是如何处理超长上下文的?
ZCode 并不是简单地将对话无脑追加发给 LLM,而是构建了一套主动的上下文管理系统。其核心机制包含以下三个层面:
1. 上下文构建与缓存排序 (Context Builder)
在 core/src/context/builder.ts 中,ZCode 会将系统提示词、环境信息、Git 状态、工具定义和历史对话拼装起来。源码显示,ZCode 为每个上下文段落打上了 stable(稳定)和 dynamic(动态)的缓存标签:
- 注入顺序 :ZCode 强制按
stable -> dynamic的顺序将上下文注入 Prompt。 - 缓存复用:这种设计是为了最大化利用 LLM 的缓存机制(Cache Control)。ZCode 的架构分析显示,其缓存命中率极高(据第三方统计可达 98.6%)。通过冻结环境、Git 和项目上下文作为快照复用,它能极大减少重复计算和 Token 浪费。
2. 微压缩与自动压缩机制 (Micro-Compact & Auto-Compact)
在每次与模型交互前,turn-loop.ts 都会强制执行检查:
- Micro-Compact(微压缩) :针对工具调用(如读取了大文件),ZCode 会通过
microcompact策略清理旧的工具输出结果,防止单次长文本撑爆上下文。 - Auto-Compact(自动压缩) :这是防止上下文超长的核心防线 。系统会实时计算当前历史记录的 Token 预估值。一旦预估 Token 数超过了
autoCompactThreshold,ZCode 就会在发送请求前,调用一个专门的压缩模型(或当前模型)对早期历史进行"摘要总结",然后丢弃原始历史,用摘要替代。
3. Token 估算机制 (Token Estimation)
ZCode 并没有为每一个接入的第三方模型下载独立的 Tokenizer(分词器)。在 core/src/compact/manual.ts 中,源码揭示了它的估算逻辑:
typescript
export function estimateMessageTokens(messages) {
// ...
return Math.ceil(estimatedCharacterCount / ESTIMATED_TOKEN_CHAR_DIVISOR);
}
它采用的是基于字符数的启发式估算(即:总字符数 / 固定常量)。
二、 为什么 GLM 5.3 没问题,而 AMD 的 DeepSeek V4 Flash 会报错"上下文超长"?
你遇到的"自带模型没问题,但接第三方 1M 上下文模型报错"的现象,在源码架构层面是由以下 三个致命偏差 导致的:
1. Context Window 配置与 API 硬性限制不匹配 (最核心原因)
ZCode 触发自动压缩的阈值公式是:阈值 = 有效上下文窗口 - 缓冲Token (13000)。
- 对于 GLM 5.3 :作为 Z.ai (智谱) 自家的模型,ZCode 内部注册表中为其配置了非常精准的安全 Context Window 值(例如 128K)。因此,当对话达到 10 万 Token 左右时,ZCode 就会自动触发压缩,确保发给 API 的 Payload 永远在安全线内。
- 对于 AMD DeepSeek V4 Flash :你提到它是"1MB上下文(或 1M Token)"的模型。在 ZCode 中作为 OpenAI 兼容模型接入时,你可能(或系统默认)将其 Context Window 设得极大(如 1,000,000)。
- 致命点 :虽然 DeepSeek 模型理论上支持 1M 上下文,但第三方免费 API 代理(如 AMD 的端点)通常有严格的"实际输入 Token 限制" (例如硬编码
max_prompt_tokens为 64K 或 131K)。 - 结果 :ZCode 看着自己设置的 1M 窗口,认为"我还有 90 万 Token 的空间",因此迟迟不触发自动压缩。它直接将几十万字节的 Prompt 发给 AMD 端点,直接触碰了端点的硬限制,被 API 网关直接拦截并抛出 "Context too long"。
- 致命点 :虽然 DeepSeek 模型理论上支持 1M 上下文,但第三方免费 API 代理(如 AMD 的端点)通常有严格的"实际输入 Token 限制" (例如硬编码
2. 启发式 Token 估算的"盲区"
如前所述,ZCode 预估 Token 是用 字符数 / 常量 来算的。
- GLM 模型的分词器与 ZCode 的估算常量高度契合,预估很准。
- 但 DeepSeek 的分词器对代码、特定符号或中文的切割方式不同。1MB 的纯文本,DeepSeek 可能切出 40 万 Token ,但 ZCode 的估算器可能只认为它只有 20 万 Token。
- 这种"估算盲区"导致 ZCode 错误地以为上下文还没满,没有触发压缩,直接发给 API 后被拒。
3. 缓存控制标签 (Cache Control) 的兼容性问题
ZCode 的 ContextBuilder 强依赖 cacheControl: { type: "ephemeral" } 标签来优化上下文体积。智谱的 GLM 原生完美支持这种注入策略。但部分第三方 OpenAI 兼容端点(尤其是免费中转)可能不支持或错误解析这些缓存标签,甚至对单次请求的 System Prompt 长度有严格限制,导致大体积的系统提示词直接触发 API 报错。
4. Provider Usage vs Estimate
ZCode 的源码中 tokenSource 可以是 provider_usage(基于上一轮 API 返回的真实用量)或 estimate(本地估算)。对于原生 GLM,ZCode 能精准读取 API 返回的真实 Token 消耗,从而精准控制下一轮的压缩;而对于未知的 OpenAI 兼容模型,ZCode 往往只能依赖粗糙的本地 estimate,导致控制失准。
三、 如何解决这个问题?
既然明白了是因为 "ZCode 认为你空间很大,但 API 实际给的空间很小" 导致的,你可以通过以下配置在 ZCode 中"欺骗"它的自动压缩机制,让它提前介入:
-
手动调低 DeepSeek 的 Context Window 设置(最有效) :
在 ZCode 的模型设置中,不要 按照 DeepSeek 标称的 1M 来配置。找到你接入的 DeepSeek V4 Flash 模型,手动将其 Context Window(上下文窗口)修改为该 AMD 端点实际支持的安全值 (建议先尝试填入
128000或65536)。原理:一旦设为 128K,ZCode 的 Auto-Compact 就会在达到 ~110K 预估 Token 时强制启动,帮你总结上下文,从而保证发给 AMD API 的数据永远不会超过它的硬限制。
-
善用手动压缩指令 :
在进行长任务(如重构大型项目)时,不要完全依赖自动触发。你可以随时在对话框中输入
/compact,强制 ZCode 立即对当前历史进行摘要截断,手动释放空间。 -
检查工具返回结果限制 :
在 ZCode 设置中,确保针对文件读取或搜索工具设置了合理的
Max Output Tokens(最大输出截断),防止单次读取整个代码库导致上下文瞬间撑爆。