博客 • AI / API

Claude API 2026 新变化:Conversation Compaction 是什么?AI Agent 如何使用 JSON 保存长上下文与压缩后的状态?

Agent 跑得越久,Messages 数组就越长。工具结果、思考块、用户来回,都会把上下文窗口顶满。2026 年 Messages API 把这件事收成官方能力:Conversation Compaction。

Compaction 不是你在客户端自己写一段摘要再贴回去。API 会签发一块type: "compaction"的 JSON。下一次请求必须把这块原样放回messages,会话才从压缩后的状态继续。

本文将介绍:

先记住这一点:落盘的不是「对话全文」,而是最新的 compaction 块,加上块后面还没被吃掉的轮次。块里的content、signature、encrypted_content是契约,不是注释。下面按「两种压缩 → 块长什么样 → 落盘清单 → 换位规则」拆开。

Compaction 压缩的是哪一段

官方定位很明确:服务器端压缩是长对话和 Agent 工作流的推荐做法,不必自己在客户端写摘要代码。窗口被旧内容填满时,响应质量会掉;压缩用一段摘要换掉更早的内容,让有效上下文还能往前走。说明见

Claude API Compaction。

阈值压缩打开后,API 做四步:输入 token 碰到触发值;生成摘要;写出compaction块;用压缩后的上下文继续回答。你下次把整段 assistant 响应追加进messages,API 会丢掉这块前面的内容,从摘要接着聊。

它是有损的。具体用过的工具参数、原话、早期思考,不会逐字留下。留下的是目标、决定和后面还用得上的事实。这和「把整个 messages 数组存进数据库」不是同一件事。Agent 技术栈里上下文怎么涨,见

2026 AI Agent 技术栈。

两条路:阈值压缩和按需压缩

2026 年有两套 beta,不要混在同一次请求里。官方写明:compaction参数不能和context_management一起发。

路径 Beta 头 你在 JSON 里写什么 块出现在哪
阈值压缩 compact-2026-01-12 context_management.edits里加compact_20260112 普通回复开头;后面还可以跟 text
按需压缩 compact-2026-09-04 顶层compaction: {"type":"summarize"} 单独一条,stop_reason为compaction,没有普通回复

阈值压缩的触发默认是input_tokens150000,最低 50000。可选pause_after_compaction:摘要写完先停,让你把最近几轮或指令性消息插回块后面,再继续生成。自定义instructions会整段替换默认摘要提示,最长 16384 字符。

按需压缩是 9 月这条线的重点。你决定何时压;摘要请求可以在后台跑,对话仍用完整历史继续;也可以把最近几轮原文留在摘要后面。签名块必须放在messages最前面,被摘要过的消息要从块前面拿掉,否则会 400,错误名是compaction_block_misplaced。Bedrock 和 Google Cloud 没有按需这条。

压缩前后的 Messages JSON

先看压缩前:就是普通的 user / assistant 轮次。

按需压缩请求(节选自官方文档)
{
  "model": "claude-opus-5",
  "max_tokens": 4096,
  "messages": [
    {
      "role": "user",
      "content": "I am building a recipe app. Help me name the main entities in the data model."
    },
    {
      "role": "assistant",
      "content": "Start with Recipe, Ingredient, and Step."
    },
    {
      "role": "user",
      "content": "Good. Now suggest field names for Recipe."
    }
  ],
  "compaction": { "type": "summarize" }
}

返回只有一块签名过的 compaction,stop_reason是compaction。之后每次请求,这块必须在最前面,后面只跟「没被这次摘要吃掉」的轮次。

压缩后继续对话:块在最前,旧消息已去掉
{
  "model": "claude-opus-5",
  "max_tokens": 2048,
  "messages": [
    {
      "role": "assistant",
      "content": [
        {
          "type": "compaction",
          "content": "Summary of the conversation: the user is designing the data model for a recipe app...",
          "signature": "EuYBCkQY..."
        }
      ]
    },
    {
      "role": "assistant",
      "content": "For Recipe, use title, description, servings..."
    },
    {
      "role": "user",
      "content": "Now do the same for Ingredient."
    }
  ]
}

阈值压缩的块长得更「夹在回复里」:assistant 的content数组先是 compaction,再是 text。多次压缩时,以最后一块为准,它代表当前提示的最终状态。API 见到这块,会忽略它前面的内容块。你可以整段回传,也可以自己丢掉旧消息、只从这块往后发。

字段 它是什么 能不能改
type 固定compaction 不能
content 摘要文本;失败时为null 不能改字;空字符串不合法
signature 按需压缩返回的签名 必须原样回传
encrypted_content 不透明元数据,SDK 类型要求 verbatim 回传 有就原样存,不要解析
cache_control 可选,给摘要单独做 cache 断点 你自己加,不是签发字段

SDK 把encrypted_content写成「先前压缩留下的不透明元数据,必须原样 round-trip」。客户端不要拆它,也不要当摘要正文展示。失败块的content为null时,服务器当空操作;仍然可以回传,但不会当有效摘要用。

恢复 session 必须落盘的字段

Agent 一重启,内存里的messages就没了。你要恢复的不是「压缩前那一整本聊天记录」,而是压缩之后还能继续请求的状态。

  1. 1
    最新的 compaction 块

    整块 JSON,包括content、signature、encrypted_content。有多块时只需要最新一块:再压一次会把旧摘要和后面的内容一并吃掉。

  2. 2
    块后面的 messages

    没被这次摘要覆盖的 user / assistant 轮次,按原顺序。后台压缩期间新长出来的轮次也在这里。

  3. 3
    当时的 system 和 tools

    按需压缩时,摘要请求要带上对话其余部分同一套system和tools。带保留思考的模型上,块后面那些轮次的 thinking 能不能不失效,取决于这两项有没有改,以及尾部是否紧挨着被摘要的消息。

  4. 4
    你走的是哪条 beta

    阈值压缩继续带compact-2026-01-12。按需压缩的块,之后每次请求都要带compact-2026-09-04。头不对,块可能不被认。

一份最小的 session 落盘形状(示意,不是官方 schema)
{
  "beta": "compact-2026-09-04",
  "model": "claude-opus-5",
  "system": "You are a coding agent.",
  "tools": [],
  "compaction_block": {
    "type": "compaction",
    "content": "Summary of the conversation: ...",
    "signature": "EuYBCkQY...",
    "encrypted_content": "..."
  },
  "tail": [
    {
      "role": "assistant",
      "content": "For Recipe, use title, description..."
    },
    {
      "role": "user",
      "content": "Now do the same for Ingredient."
    }
  ]
}

重建请求时:messages[0]放带这块的 assistant(或把块放进第一条消息的第一个 content),然后拼接tail。不要把压缩前的旧 user 消息再塞到块前面。

这和权限评估不是一层。那边管 tool call 能不能跑;这边管窗口被吃掉之后,状态还在不在。权限那篇见

Claude Managed Agents 如何自动判断 Tool Call。长流程里 Agent 怎么协作,见

Claude 如何参与 AI 研发工作流。

后台压缩和保留尾部时怎么换

按需压缩的请求只摘要它收到的消息。所以你可以只把旧轮次送去压,最近几轮原文留在本地,压完把块插到这些轮次前面。官方叫 keep-tail。

后台压缩(async)更进一步:摘要还在跑时,对话继续用完整历史往下走。回来之后不能把整段历史换成一块,只能从队头去掉「当时送去压的那几条」,把返回的 assistant 消息放进去,后面新长出来的轮次原样留下。

后台压缩换位(官方示意)
if response.stop_reason == "compaction":
    compaction_message = {
        "role": "assistant",
        "content": response.content,
    }
    history = [compaction_message] + history[sent_count:]
# sent_count = number of messages sent in the compaction request

中间不要改已经在历史里的消息。换位要发生在块到达后的第一次请求上,这样摘要期间产生的 thinking 才还有效。没有块回来就保持完整历史,稍后再试。

阈值压缩若打开pause_after_compaction,响应里往往只有这块,stop_reason也是compaction。你把要保留的原文接在块后面,再发下一次请求。注意:在 Claude Fable 5.1 上,如果把旧 assistant 轮次重新插回块后,要去掉其中的thinking / redacted_thinking,或带thinking-binding-controls-2026-08-01并设置prefix_mismatch_behavior: "drop_block",否则可能 400。那些思考块是对着完整历史生成的,对不上压缩后的对话。

和 prompt cache 怎么叠

压缩和 prompt caching 可以一起用。可以在 compaction 块上加cache_control: {"type":"ephemeral"},让摘要单独进缓存。系统提示末尾再加一个断点,压缩发生时系统提示的缓存还能命中,只用重写摘要这一段。

计费时不要只看顶层usage。压缩迭代的 token 可能记在usage.iterations里。落盘侧仍然只关心块和尾部,不必把 usage 当成恢复会话的契约。

用 JSONNote 核对块和历史

压缩前后的 JSON 体积差很大,字段又不能靠眼睛扫。调试时对位置,不对文采。

  1. 1
    先格式化落盘文件

    把 session JSON 丢进JSON 格式化,确认compaction_block.type是compaction,signature还在。

  2. 2
    再校验块的形状

    用JSON Schema写一份最小 schema:type枚举只有compaction;content允许 string 或 null;signature / encrypted_content有就当 string。禁止空字符串。

  3. 3
    对比压缩前后的 messages

    用JSON Diff看队头少了哪些轮次、块后面多了什么。按需压缩后,块前面不应该再出现被摘要过的 user 消息。

  4. 4
    复盘时打码签名

    摘要正文可以给人看。签名和不透明字段用Hash 分享前先打码。它们能续上会话,不要当普通文本发出去。

常见问题

Conversation Compaction 是把整段对话删掉吗?

不是。API 用一段摘要替换已经被压缩的旧消息。摘要装在 type 为 compaction 的内容块里。你必须把这块原样回传,会话才能从摘要继续。

恢复 session 最少要存哪些 JSON?

至少存最新的 compaction 块(含 content、signature、encrypted_content)、块后面的全部 messages,以及当时的 system 和 tools。少存签名或不透明字段,下一次请求接不上。

阈值压缩和 9 月的按需压缩有何不同?

阈值压缩走 compact-2026-01-12,在普通请求里按 token 触发。按需压缩走 compact-2026-09-04,单独发 compaction: {type: summarize},返回带签名的块,必须放在 messages 最前面。

自己写一份摘要文本能不能代替 compaction 块?

不能当同一条契约用。平台认的是它签发的块。自己写的 user 文本只是另一条消息,没有 signature,也不能让 API 丢掉块前面的旧内容。

compaction 块能改 content 再发回去吗?

不要改。官方要求原样回传。content 为空字符串不合法;content 为 null 表示压缩失败,服务器当空操作。改签名或 encrypted_content 会让后续请求失败或丢掉摘要。

小结

Conversation Compaction 把「窗口要爆了」收成一块可回传的 JSON。阈值压缩在普通请求里自动发生;9 月的按需压缩让你决定何时压、能不能后台压、最近几轮要不要留原文。

恢复 session 靠的是最新 compaction 块加它后面的 tail,不是压缩前的全文。content、signature、encrypted_content 原样存、原样发。块的位置错了,API 不会假装你还带着那段历史。

把块和 tail 当成两份契约来读。需要人盯的长任务,摘要instructions里写清必须留下的实体和未决问题;需要少打断的 Agent,用按需压缩在后台换块,不要在内存里只留一段自己写的说明。

返回博客