Harness 怎么适配不同模型:上下文、Token 计算与缓存

我前面一直在想一个问题:同一个 Harness 可以接很多模型,但每个模型的上下文上限、Token 计算方式、输出能力都可能不一样,它到底怎么保证请求不会超限?

我的理解是,不能只在配置里换一个模型名称。Harness 还需要同时解决三件事:

  1. 知道当前模型能接收多少内容。
  2. 计算这次准备发送多少 Token,并给输出和推理预留空间。
  3. 管理模型配置、会话上下文和 Prompt Cache,减少重复获取与重复计算。

整条链路可以先简化成这样:

text 复制代码
用户任务 + 历史消息 + 记忆 + Skill + 工具定义和结果
                         ↓
                 Harness 上下文管理
              选择、裁剪、摘要、组装
                         ↓
                   多模型适配层
       模型上限、Tokenizer、输出预算、压缩阈值
                         ↓
                       模型

1. 上下文到底是什么

上下文不是客户端里保存的全部数据,而是这一次真正交给模型的内容。

常见内容包括:

  • 系统和开发者指令。
  • 当前用户问题。
  • 选中的历史对话。
  • 从长期记忆或知识库中检索出的内容。
  • 当前任务需要的 Skill 说明。
  • 提供给模型的工具名称、描述和参数结构。
  • 前面几轮的工具调用结果。
  • 图片、文件等输入经过模型接口处理后的内容。

客户端可以保存一百轮历史,但 Harness 只选择其中二十轮发送,那么本次模型上下文就是这二十轮和其他输入的组合,不是全部一百轮。

text 复制代码
会话存储                         本次模型上下文
├─ 完整历史 ───────┐             ├─ 系统指令
├─ 长期记忆 ───────┼─ Harness ──→├─ 最近对话
├─ 工具结果 ───────┤   选择组装   ├─ 相关记忆
└─ 附件与资料 ─────┘             └─ 工具定义和结果

所以,模型可以是无状态的,但会话仍然可以连续。连续性由客户端、Harness 或模型厂商的会话服务保存;下一次调用时,再通过完整消息、会话标识或前一响应标识把相关内容接上。

2. 不同模型需要哪些适配信息

Harness 至少需要维护下面这些模型元数据:

配置 用途
model_id 实际调用的模型名称
context_window 输入和生成内容可使用的总窗口
max_output_tokens 单次允许生成的最大 Token
tokenizer 估算或精确计算输入 Token
reasoning_support 是否支持推理及可用的推理档位
tool_support 是否支持 Function、MCP、并行工具等能力
modalities 支持文本、图片、音频中的哪些输入
compact_threshold 到什么位置开始主动压缩
safety_margin 为计算误差和运行开销保留的缓冲区

这些配置可以写在本地,也可以由服务端动态下发。

yaml 复制代码
model_id: example-reasoning-model
context_window: 200000
max_output_tokens: 32000
tokenizer: example_tokenizer
compact_threshold: 0.80
safety_margin_tokens: 8000
supports_tools: true
supports_reasoning: true

这只是通用示意,不对应某个真实模型。

关键点是:切换模型,不只是换 model_id,还要一起切换这套计算和能力配置。

3. Harness 怎么计算 Token

在调用模型前,可以先做一个预算:

text 复制代码
可用输入预算
= 模型上下文上限
- 预留输出 Token
- 预留推理和安全缓冲

例如某个模型的上下文窗口是 200K,Harness 准备给输出和推理保留 32K,再留下 8K 安全空间,那么本次输入最好控制在 160K 以内。

text 复制代码
200K 总窗口
├─ 160K:输入内容
├─  32K:输出和推理预算
└─   8K:安全缓冲

这里的数字不是统一标准。普通问答可以少留一些输出空间,代码生成、长报告或高强度推理则可能需要多留一些。

Token 数从哪里来

通常有三种方式:

  1. 使用对应模型或模型族的 Tokenizer 在本地计算。
  2. 使用模型厂商提供的计数接口或 SDK。
  3. 没有精确能力时,按字符数、消息结构和图片规格进行保守估算。

调用前得到的是预算值,调用后还要读取模型响应中的实际用量。实际用量一般会区分输入、缓存输入、输出和推理等字段,但各厂商返回结构不完全一样。

工具定义、消息角色、图片和结构化输入也会占用 Token,不能只计算用户输入的文字。

4. 什么时候压缩上下文

Harness 不需要等请求已经超过模型上限才处理。更稳妥的做法是提前设置有效上限:

text 复制代码
预计输入 + 输出预留 + 安全缓冲 >= 有效上下文上限
                         ↓
                     触发压缩

压缩通常不是简单删除最早的消息,可以分成几步:

  1. 删除重复日志、重复工具输出和已经失效的中间过程。
  2. 把较早的多轮对话总结成结构化摘要。
  3. 保留当前目标、约束、重要决定、关键证据和未完成事项。
  4. 最近几轮对话尽量保留原文。
  5. 需要时再从长期记忆或知识库检索原始资料。
text 复制代码
压缩前
历史原文 + 多次工具输出 + 当前任务
                  ↓
压缩后
历史摘要 + 关键证据 + 最近对话 + 当前任务

摘要也可能丢信息,所以完整审计记录不应该只保存在摘要里。会话原始记录、任务状态和发送给模型的上下文,最好分开保存。

部分模型接口支持自动截断。以 OpenAI Responses API 为例,truncation=auto 可以在超出窗口时从会话开头丢弃内容;关闭自动截断时,请求可能直接失败。对工程型 Agent 来说,Harness 主动选择和压缩通常更可控,因为它知道哪些目标、授权和证据不能丢。OpenAI Responses API

5. 模型切换后为什么要重新计算

假设一段上下文在模型 A 中是 80K Token,切换到模型 B 后,不能直接认为仍然是 80K。

原因包括:

  • 两个模型可能使用不同的 Tokenizer。
  • 上下文窗口和最大输出不同。
  • 对图片、工具定义和结构化消息的计算方式可能不同。
  • 推理模型需要不同的输出和推理预算。
  • 新模型不一定支持原有工具或消息格式。

因此,模型切换流程应该是:

text 复制代码
选择新模型
   ↓
加载新模型元数据
   ↓
使用对应 Tokenizer 重新计算
   ↓
重新分配输入、输出和安全预算
   ↓
必要时重新压缩
   ↓
发起模型调用

如果 Harness 只做模型路由,却不重新计算上下文,最常见的结果就是请求超限、上下文被意外截断,或者预留的输出空间不够。

6. 这里至少有三种"缓存"

讨论缓存时,最容易把不同机制混到一起。我目前把它分成三层。

6.1 模型配置缓存

这一层保存模型目录和适配信息,例如:

  • 有哪些可用模型。
  • 每个模型的上下文窗口。
  • 输出上限和推理档位。
  • 工具、图片等能力。
  • Tokenizer 和压缩阈值。

它可以使用 TTL + ETag + 版本号

text 复制代码
Harness 需要模型配置
          ↓
     本地缓存有效?
      ├─ 是 → 直接使用
      └─ 否 → 携带 ETag 请求配置服务
                 ├─ 未变化 → 续期缓存
                 ├─ 有变化 → 更新本地缓存
                 └─ 请求失败 → 使用旧缓存降级

触发刷新可以是启动时、缓存过期时、每次任务前、后台轮询、服务端推送或手工刷新。不同 Harness 可以采用不同组合。

这里的"过期"通常表示需要重新校验,不等于立刻删除旧配置。保留一份最近成功的配置,可以在网络异常时继续提供降级能力。

6.2 会话与上下文缓存

这一层保存历史消息、摘要、工具结果和检索结果。严格来说,其中一部分是会话状态,不只是缓存。

Harness 可以保存完整历史,同时缓存已经生成的摘要。下一轮优先复用摘要;只有摘要失效或任务范围变化时,才重新整理。

但不能把会话 ID 当成模型天然记住了历史。会话 ID 只是索引,真正的历史仍由客户端、Harness 或服务端存储和重新关联。

6.3 模型厂商的 Prompt Cache

Prompt Cache 是模型服务端对重复提示前缀的复用。它主要降低重复输入的延迟和成本,不等于会话记忆,也不会替 Harness 决定本轮应该发送哪些历史。

要提高命中率,通常会把稳定内容放在前面,把每次变化的用户问题和动态数据放在后面。OpenAI 的模型接口也提供 Prompt Cache 相关参数和用量字段,具体支持范围随模型变化。OpenAI 模型指南

三层缓存可以这样区分:

层次 主要保存什么 主要目的
模型配置缓存 模型能力、上限、Tokenizer、版本 正确适配不同模型
会话与上下文缓存 历史、摘要、记忆、工具结果 保持任务连续,减少重复整理
Prompt Cache 重复的模型输入前缀 降低服务端重复计算、延迟和费用

7. 一个完整的运行过程

假设用户继续追问一个已经进行多轮的代码问题,Harness 可以这样处理:

text 复制代码
1. 收到用户新问题
        ↓
2. 根据任务和规则选择模型
        ↓
3. 读取模型配置缓存
   过期则向配置服务校验
        ↓
4. 读取会话历史、摘要和相关记忆
        ↓
5. 使用该模型的 Tokenizer 计算输入
        ↓
6. 超过有效阈值则裁剪或压缩
        ↓
7. 组装指令、消息、Skill 和工具定义
        ↓
8. 调用模型,模型可能回答或请求工具
        ↓
9. 工具结果回到 Harness,再进入下一轮模型调用
        ↓
10. 保存实际用量、会话状态和新的摘要

这里可能调用模型一次,也可能调用很多次。Token 计算、缓存读取和权限校验不一定需要调用模型;生成摘要通常需要模型,但也可以使用规则或程序处理一部分内容。

8. Codex 的本机观察案例

在我本机的 Codex Desktop 0.151.0 中,模型目录缓存包含:

  • fetched_at:上次获取或续期时间。
  • etag:判断服务端模型目录是否变化。
  • client_version:缓存对应的客户端版本。
  • 模型上下文、输出、推理档位等元数据。

本机客户端中的模型缓存 TTL 是 300 秒,也就是大约 5 分钟。超过时间后并不是删除缓存,而是重新校验;服务端内容没变化时,只需要给缓存续期。

这个值是对当前本机版本的观察,不是 Harness 标准,也不是 OpenAI 对所有 Codex 版本的固定承诺。其他 Harness 完全可以使用不同 TTL,甚至不用轮询,改成服务端推送。

OpenAI 官方提供独立的模型目录,用来描述不同模型的上下文、输出和能力;具体 Harness 如何缓存这些信息,仍然属于客户端实现。OpenAI 模型目录

9. 实现时我会重点守住哪些边界

如果自己做一套 Harness,我觉得下面几条比较重要:

  1. 模型配置要带版本,不能只按模型名称硬编码。
  2. 模型切换后必须重新计算 Token 和输出预算。
  3. 调用前做估算,调用后记录模型返回的实际用量。
  4. 压缩不能丢失目标、约束、授权、决定和关键证据。
  5. Prompt Cache、会话历史和长期记忆必须分开理解。
  6. 配置服务不可用时,可以使用最近成功的缓存降级。
  7. 缓存刷新和模型调用分开,刷新配置通常不需要调用模型。
  8. 对每个模型做真实测试,不能只相信配置中的理论上限。

10. 最后总结

我的理解可以压缩成一句话:

模型决定怎么回答,Harness 决定给模型什么;要接入不同模型,Harness 就必须知道各自的上限和计算方式,在调用前完成 Token 预算、上下文压缩与缓存管理。

最核心的关系是:

text 复制代码
模型配置缓存 → 告诉 Harness 当前模型能做什么
会话上下文   → 告诉模型这次任务已经发生了什么
Token 计算   → 判断这些内容是否放得下
上下文压缩   → 在放不下时保留真正重要的信息
Prompt Cache → 减少重复前缀的服务端计算

它们组合起来,才是一套能稳定适配多模型的上下文管理机制。

相关推荐
爱丶不疚1 小时前
Eval: Agent 说的 Eval 是什么?从单测、TDD 到 Sentry 聊起
前端·ai编程·vibecoding
杨杨杨大侠1 小时前
Agent 是怎么被组织起来的:六种编排方式与选择
aigc·openai·ai编程
jimidou1 小时前
少点几次“允许”,Claude Code 为什么反而更安全?
ai编程
jimidou1 小时前
从 20 人试点到全员使用:Agentic Coding 扩容前,先看团队能不能接住更多代码
ai编程
程序员老刘1 小时前
为了一盘醋吃顿饺子:我把手头的免费AI订阅全榨干了
ai编程
ServBay2 小时前
OpenClaw 2.0 意外更新,龙虾协同能力更强了
aigc·ai编程
打呵欠的猫2 小时前
一个 Hook 让 AI 每次写文件前自动检查编码规范,违规代码再也提交不进来
前端·ai编程·代码规范
NingBo3 小时前
让非技术人员也能一键使用 DeepSeek Harness
ai编程·deepseek
全栈弄潮儿3 小时前
真实案例:用 AI 重构一段难维护的旧代码
aigc·openai·ai编程