---
title: 在背景中進行壓縮
url: https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-background
description: 在對話以完整歷史記錄繼續進行的同時，請求隨需壓縮摘要，並在摘要送達時將區塊換入。
featureMetadata:
  status: beta
  betaHeader: compact-2026-09-04
  supportedModels:
    - claude-fable-5-1
    - claude-mythos-5-1
    - claude-fable-5
    - claude-mythos-5
    - claude-mythos-preview
    - claude-opus-5-5
    - claude-opus-5
    - claude-opus-4-8
    - claude-opus-4-7
    - claude-opus-4-6
    - claude-sonnet-5-5
    - claude-sonnet-5
    - claude-sonnet-4-6
    - claude-haiku-5-5
  supportedPlatforms:
    Claude API: beta
    Claude Platform on AWS: beta
    Google Cloud: beta
    Microsoft Foundry: beta
---

「Background compaction」（背景壓縮），通常稱為「async compaction」（非同步壓縮），會改變[壓縮迴圈](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-on-demand#compact-in-a-loop)中的兩件事：壓縮請求會在對話以完整歷史記錄繼續進行的同時執行，而替換則會等到區塊送達後才進行。[從摘要繼續](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-on-demand#continue-from-the-summary)與[處理缺少摘要或錯誤的情況](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-on-demand#when-no-summary-comes-back)的內容維持不變地適用。

## 工作持續進行時的替換方式

壓縮請求及其傳回的區塊與迴圈中的相同。您的歷史記錄會在送出請求與使用其結果之間持續增長，而替換必須保留這些增長的部分。

1. 以目前的歷史記錄送出[壓縮請求](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-on-demand#request-a-summary)，並記錄其中包含多少則訊息。
2. 在該請求執行期間，以完整歷史記錄讓對話繼續進行。附加每個新輪次，不要編輯歷史記錄中已有的任何內容，並且在此請求被替換進去或失敗之前，不要啟動另一個壓縮請求。
3. 當回應送達且 `stop_reason` 為 `"compaction"` 時，從歷史記錄的開頭精確移除您所送出的那些訊息，並將傳回的訊息放在它們的位置。自步驟 1 以來附加的每個輪次都保留在其後方。
4. 在區塊送達後的第一個請求中送出替換後的歷史記錄，如此一來，在撰寫摘要期間產生的思考內容才能保持有效。

例如，如果壓縮請求包含訊息 1 到 5，而對話在其執行期間新增了訊息 6 到 8，則替換後您的歷史記錄會是該區塊，後面接著訊息 6 到 8。

<Frame>
  ![「Background compaction」（背景壓縮）時間軸：壓縮請求以訊息 1 到 5 送出，同時對話繼續使用完整歷史記錄並新增訊息 6 到 8；當區塊送達時，它會取代歷史記錄開頭的訊息 1 到 5，歷史記錄變為該區塊後接訊息 6 到 8](https://platform.claude.com/docs/zh-TW/images/compaction-background-timeline.svg)
</Frame>

如果回應具有任何其他 `stop_reason`，表示未產生摘要，這在步驟 2 中視為失敗。請保留完整歷史記錄；[處理缺少摘要或錯誤的情況](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-on-demand#when-no-summary-comes-back)列出了各種原因以及每種情況的處理方式。

## 在背景中請求摘要

壓縮請求與其他任何請求一樣，會計入您的「rate limit」（速率限制），而在其執行期間，您的應用程式會同時有兩個開啟中的請求。在替換之前，對話會以完整歷史記錄持續增長，因此請在「context window」（上下文視窗）仍有空間容納期間送達的輪次時，就啟動壓縮請求。

以下程式是[在迴圈中壓縮](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-on-demand#compact-in-a-loop)中的迴圈，只是將壓縮請求從對話的路徑中移出。醒目標示的程式碼行顯示了它與迴圈的不同之處，而下方清單則依程式執行的順序逐一說明。

<CodeGroup exclude="shell, php">
  ```python Python
  from concurrent.futures import Future, ThreadPoolExecutor

  import anthropic
  from anthropic.types.beta import BetaMessage, BetaMessageParam

  client = anthropic.Anthropic()
  executor = ThreadPoolExecutor(max_workers=1)

  # 請將此值設為接近您實際的輸入預算。此處設得較低，讓簡短的對話也會觸發壓縮。
  COMPACT_AT_TOKENS = 2500
  SYSTEM = "You help design a recipe app's data model. Keep answers short."

  QUESTIONS = [
      "What are the main entities in the data model?",
      "Which fields should Recipe have?",
      "Which fields should Ingredient have?",
      "Which fields should RecipeIngredient have?",
      "Which fields should Step have?",
      "Which indexes should these tables have?",
      "Which fields should be required?",
      "Which fields should have default values?",
  ]


  def swap_in(history: list[BetaMessageParam], summary: BetaMessage, sent: int) -> None:
      if summary.stop_reason == "compaction":
          # 僅替換壓縮請求所包含的那些訊息。
          # 後續的輪次保留在該區塊之後。
          history[:sent] = [{"role": "assistant", "content": summary.content}]
          print(f"Swapped {sent} messages")


  history: list[BetaMessageParam] = []
  pending: Future[BetaMessage] | None = None
  sent = 0
  for turn, question in enumerate(QUESTIONS, start=1):
      if pending is not None and pending.done():
          swap_in(history, pending.result(), sent)
          pending = None

      history.append({"role": "user", "content": question})
      response = client.beta.messages.create(
          model="claude-opus-5-5",
          max_tokens=8192,
          system=SYSTEM,
          betas=["compact-2026-09-04"],
          messages=history,
      )
      history.append({"role": "assistant", "content": response.content})

      # 下一個請求也會送出此回覆，因此請將其計入。
      conversation_tokens = response.usage.input_tokens + response.usage.output_tokens
      if (
          conversation_tokens > COMPACT_AT_TOKENS
          and turn < len(QUESTIONS)
          and pending is None
      ):
          sent = len(history)
          pending = executor.submit(
              client.beta.messages.create,
              model="claude-opus-5-5",
              max_tokens=4096,
              system=SYSTEM,
              betas=["compact-2026-09-04"],
              messages=history.copy(),
              compaction={"type": "summarize"},
          )

  # 在儲存之前，請先換入仍在產生中的摘要
  # 或繼續對話。
  if pending is not None:
      swap_in(history, pending.result(), sent)
  executor.shutdown()
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  // 請將此值設為接近您實際的輸入預算。此處設得較低，讓簡短的對話也會觸發壓縮。
  const compactAtTokens = 2500;
  const systemPrompt = "You help design a recipe app's data model. Keep answers short.";

  const questions = [
    "What are the main entities in the data model?",
    "Which fields should Recipe have?",
    "Which fields should Ingredient have?",
    "Which fields should RecipeIngredient have?",
    "Which fields should Step have?",
    "Which indexes should these tables have?",
    "Which fields should be required?",
    "Which fields should have default values?"
  ];

  function swapIn(
    history: Anthropic.Beta.Messages.BetaMessageParam[],
    summary: Anthropic.Beta.Messages.BetaMessage,
    sent: number
  ): Anthropic.Beta.Messages.BetaMessageParam[] {
    if (summary.stop_reason !== "compaction") {
      return history;
    }
    console.log(`Swapped ${sent} messages`);
    // 僅替換壓縮請求所包含的訊息。後續的回合會保留在該區塊之後。
    return [{ role: "assistant", content: summary.content }, ...history.slice(sent)];
  }

  let history: Anthropic.Beta.Messages.BetaMessageParam[] = [];
  let pending: Promise<Anthropic.Beta.Messages.BetaMessage> | undefined;
  let settled = false;
  let sent = 0;
  for (const [index, question] of questions.entries()) {
    const turn = index + 1;
    if (pending && settled) {
      history = swapIn(history, await pending, sent);
      pending = undefined;
      settled = false;
    }

    history.push({ role: "user", content: question });
    const response = await client.beta.messages.create({
      model: "claude-opus-5-5",
      max_tokens: 8192,
      system: systemPrompt,
      betas: ["compact-2026-09-04"],
      messages: history
    });
    history.push({ role: "assistant", content: response.content });

    // 下一個請求也會送出這則回覆，因此請將其計入。
    const conversationTokens = response.usage.input_tokens + response.usage.output_tokens;
    if (conversationTokens > compactAtTokens && turn < questions.length && !pending) {
      sent = history.length;
      pending = client.beta.messages.create({
        model: "claude-opus-5-5",
        max_tokens: 4096,
        system: systemPrompt,
        betas: ["compact-2026-09-04"],
        messages: [...history],
        compaction: { type: "summarize" }
      });
      // 無論結果如何，都將請求標記為已完成。之後 await 它會回傳摘要或擲回例外。
      const markSettled = () => {
        settled = true;
      };
      pending.then(markSettled, markSettled);
    }
  }

  // 在儲存或繼續對話之前，請先換入仍在產生中的摘要。
  if (pending) {
    history = swapIn(history, await pending, sent);
  }
  ```

  ```csharp C#
  using Anthropic.Models.Beta;
  using Anthropic.Models.Beta.Messages;
  using Model = Anthropic.Models.Messages.Model;

  AnthropicClient client = new();

  // 請將此值設定為接近您實際的輸入預算。此處設得較低，以便短對話也會觸發壓縮。
  const int CompactAtTokens = 2500;
  const string SystemPrompt = "You help design a recipe app's data model. Keep answers short.";

  string[] questions =
  [
      "What are the main entities in the data model?",
      "Which fields should Recipe have?",
      "Which fields should Ingredient have?",
      "Which fields should RecipeIngredient have?",
      "Which fields should Step have?",
      "Which indexes should these tables have?",
      "Which fields should be required?",
      "Which fields should have default values?",
  ];

  static List<BetaMessageParam> SwapIn(List<BetaMessageParam> history, BetaMessage summary, int sent)
  {
      if (summary.StopReason != BetaStopReason.Compaction)
      {
          return history;
      }
      Console.WriteLine($"Swapped {sent} messages");
      // 僅替換壓縮請求所包含的訊息。之後的輪次保留在該區塊之後。
      return
      [
          new()
          {
              Role = Role.Assistant,
              Content = summary.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
          },
          .. history[sent..],
      ];
  }

  List<BetaMessageParam> history = [];
  Task<BetaMessage>? pending = null;
  var sent = 0;
  foreach (var (index, question) in questions.Index())
  {
      var turn = index + 1;
      if (pending is { IsCompleted: true })
      {
          history = SwapIn(history, await pending, sent);
          pending = null;
      }

      history.Add(new() { Role = Role.User, Content = question });
      var response = await client.Beta.Messages.Create(new MessageCreateParams
      {
          Model = Model.ClaudeOpus5_5,
          MaxTokens = 8192,
          System = SystemPrompt,
          Betas = [AnthropicBeta.Compact2026_09_04],
          Messages = history,
      });
      history.Add(new()
      {
          Role = Role.Assistant,
          Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
      });

      // 下一個請求也會送出此回覆，因此請將其計入。
      var conversationTokens = response.Usage.InputTokens + response.Usage.OutputTokens;
      if (conversationTokens > CompactAtTokens && turn < questions.Length && pending is null)
      {
          sent = history.Count;
          pending = client.Beta.Messages.Create(new MessageCreateParams
          {
              Model = Model.ClaudeOpus5_5,
              MaxTokens = 4096,
              System = SystemPrompt,
              Betas = [AnthropicBeta.Compact2026_09_04],
              Messages = [.. history],
              Compaction = new BetaCompactionConfig(), // type defaults to "summarize"
          });
      }
  }

  // 在儲存或繼續對話之前，請先換入仍在傳送中的摘要。
  if (pending is not null)
  {
      history = SwapIn(history, await pending, sent);
  }
  ```

  ```go Go
  ctx := context.Background()
  client := anthropic.NewClient()

  // 請將此值設為接近您實際的輸入預算。此處設得較低，讓簡短的對話也會觸發壓縮。
  const compactAtTokens = 2500
  system := []anthropic.BetaTextBlockParam{{Text: "You help design a recipe app's data model. Keep answers short."}}

  questions := []string{
  	"What are the main entities in the data model?",
  	"Which fields should Recipe have?",
  	"Which fields should Ingredient have?",
  	"Which fields should RecipeIngredient have?",
  	"Which fields should Step have?",
  	"Which indexes should these tables have?",
  	"Which fields should be required?",
  	"Which fields should have default values?",
  }

  var history []anthropic.BetaMessageParam
  var pending chan *anthropic.BetaMessage
  var sent int
  swapIn := func(summary *anthropic.BetaMessage) {
  	if summary.StopReason != anthropic.BetaStopReasonCompaction {
  		return
  	}
  	fmt.Printf("Swapped %d messages\n", sent)
  	// 僅替換壓縮請求所包含的訊息。後續的回合會保留在該區塊之後。
  	history = slices.Replace(history, 0, sent, summary.ToParam())
  }

  for i, question := range questions {
  	turn := i + 1
  	// 從 nil channel 接收永遠不會成功，因此沒有待處理項目時會略過此處。
  	select {
  	case summary := <-pending:
  		swapIn(summary)
  		pending = nil
  	default:
  	}

  	history = append(history, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(question)))
  	response, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
  		Model:     anthropic.ModelClaudeOpus5_5,
  		MaxTokens: 8192,
  		System:    system,
  		Betas:     []anthropic.AnthropicBeta{anthropic.AnthropicBetaCompact2026_09_04},
  		Messages:  history,
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  	history = append(history, response.ToParam())

  	// 下一個請求也會送出這則回覆，因此要將其計入。
  	conversationTokens := response.Usage.InputTokens + response.Usage.OutputTokens
  	if conversationTokens > compactAtTokens && turn < len(questions) && pending == nil {
  		sent = len(history)
  		pending = make(chan *anthropic.BetaMessage, 1)
  		go func(messages []anthropic.BetaMessageParam, result chan<- *anthropic.BetaMessage) {
  			summary, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
  				Model:     anthropic.ModelClaudeOpus5_5,
  				MaxTokens: 4096,
  				System:    system,
  				Betas:     []anthropic.AnthropicBeta{anthropic.AnthropicBetaCompact2026_09_04},
  				Messages:  messages,
  				Compaction: anthropic.BetaCompactionConfigUnionParam{
  					OfSummarize: &anthropic.BetaSummarizeCompactionParam{},
  				},
  			})
  			if err != nil {
  				log.Fatal(err)
  			}
  			result <- summary
  		}(slices.Clone(history), pending)
  	}
  }

  // 在儲存或繼續對話之前，請先換入仍在傳送中的摘要。
  if pending != nil {
  	swapIn(<-pending)
  }
  ```

  ```java Java
  import com.anthropic.models.beta.AnthropicBeta;
  import com.anthropic.models.beta.messages.BetaCompactionConfig;
  import com.anthropic.models.beta.messages.BetaMessage;
  import com.anthropic.models.beta.messages.BetaMessageParam;
  import com.anthropic.models.beta.messages.BetaStopReason;
  import com.anthropic.models.beta.messages.MessageCreateParams;

  // 請將此值設為接近您實際的輸入預算。此處設得較低，讓簡短的對話也會觸發壓縮。
  static final long COMPACT_AT_TOKENS = 2500;
  static final String SYSTEM = "You help design a recipe app's data model. Keep answers short.";

  void swapIn(List<BetaMessageParam> history, BetaMessage summary, int sent) {
      if (!summary.stopReason().map(BetaStopReason.COMPACTION::equals).orElse(false)) {
          return;
      }
      IO.println("Swapped " + sent + " messages");
      // 僅替換壓縮請求所包含的訊息。之後的回合保留在該區塊之後。
      history.subList(0, sent).clear();
      history.addFirst(summary.toParam());
  }

  void main() {
      var client = AnthropicOkHttpClient.fromEnv();

      var questions = List.of(
          "What are the main entities in the data model?",
          "Which fields should Recipe have?",
          "Which fields should Ingredient have?",
          "Which fields should RecipeIngredient have?",
          "Which fields should Step have?",
          "Which indexes should these tables have?",
          "Which fields should be required?",
          "Which fields should have default values?"
      );

      var history = new ArrayList<BetaMessageParam>();
      CompletableFuture<BetaMessage> pending = null;
      int sent = 0;
      for (int turn = 1; turn <= questions.size(); turn++) {
          if (pending != null && pending.isDone()) {
              swapIn(history, pending.join(), sent);
              pending = null;
          }

          history.add(BetaMessageParam.builder()
              .role(BetaMessageParam.Role.USER)
              .content(questions.get(turn - 1))
              .build());
          var params = MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(8192)
              .system(SYSTEM)
              .addBeta(AnthropicBeta.COMPACT_2026_09_04)
              .messages(history)
              .build();
          var response = client.beta().messages().create(params);
          history.add(response.toParam());

          // 下一個請求也會送出此回覆，因此請將其計入。
          long conversationTokens = response.usage().inputTokens() + response.usage().outputTokens();
          if (conversationTokens > COMPACT_AT_TOKENS && turn < questions.size() && pending == null) {
              sent = history.size();
              var summaryParams = MessageCreateParams.builder()
                  .model(Model.CLAUDE_OPUS_5_5)
                  .maxTokens(4096)
                  .system(SYSTEM)
                  .addBeta(AnthropicBeta.COMPACT_2026_09_04)
                  .messages(List.copyOf(history))
                  .compaction(BetaCompactionConfig.builder().build()) // type defaults to "summarize"
                  .build();
              pending = client.async().beta().messages().create(summaryParams);
          }
      }

      // 在儲存或繼續對話之前，請先換入仍在產生中的摘要。
      if (pending != null) {
          swapIn(history, pending.join(), sent);
      }
      client.close();
  }
  ```

  <CodeGroupItem language="PHP">
    PHP SDK 以同步方式送出請求，因此無法在對話進行的同時於背景中執行壓縮請求。
  </CodeGroupItem>

  ```ruby Ruby
  client = Anthropic::Client.new

  # 請將此值設為接近您實際的輸入預算。此處設得較低，讓簡短的對話也會觸發壓縮。
  COMPACT_AT_TOKENS = 2500
  SYSTEM = "You help design a recipe app's data model. Keep answers short."

  questions = [
    "What are the main entities in the data model?",
    "Which fields should Recipe have?",
    "Which fields should Ingredient have?",
    "Which fields should RecipeIngredient have?",
    "Which fields should Step have?",
    "Which indexes should these tables have?",
    "Which fields should be required?",
    "Which fields should have default values?"
  ]

  def swap_in(history, summary, sent)
    return history unless summary.stop_reason == :compaction

    puts "Swapped #{sent} messages"
    # 僅替換壓縮請求所包含的訊息，之後的輪次保留在該區塊之後。
    [{ role: "assistant", content: summary.content }, *history[sent..]]
  end

  history = []
  pending = nil
  sent = 0
  questions.each.with_index(1) do |question, turn|
    if pending && !pending.alive?
      history = swap_in(history, pending.value, sent)
      pending = nil
    end

    history << { role: "user", content: question }
    response = client.beta.messages.create(
      model: Anthropic::Model::CLAUDE_OPUS_5_5,
      max_tokens: 8192,
      system_: SYSTEM,
      betas: [Anthropic::AnthropicBeta::COMPACT_2026_09_04],
      messages: history
    )
    history << { role: "assistant", content: response.content }

    # 下一個請求也會送出此回覆，因此請將其計入。
    conversation_tokens = response.usage.input_tokens + response.usage.output_tokens
    if conversation_tokens > COMPACT_AT_TOKENS && turn < questions.length && pending.nil?
      sent = history.length
      pending = Thread.new(history.dup) do |snapshot|
        client.beta.messages.create(
          model: Anthropic::Model::CLAUDE_OPUS_5_5,
          max_tokens: 4096,
          system_: SYSTEM,
          betas: [Anthropic::AnthropicBeta::COMPACT_2026_09_04],
          messages: snapshot,
          compaction: { type: "summarize" }
        )
      end
    end
  end

  # 在儲存或繼續對話之前，請先換入仍在產生中的摘要。
  history = swap_in(history, pending.value, sent) if pending
  ```
</CodeGroup>

* **決定何時壓縮：** 大小檢查還要求沒有待處理的壓縮請求。
* **啟動請求：** 在迴圈等待壓縮回應的地方，此版本會記錄歷史記錄中包含多少則訊息，使用各語言本身的並行工具，以歷史記錄的副本啟動請求，然後不等待就繼續進行下一個輪次。
* **檢查結果：** 在每個輪次開始時，程式會檢查待處理的請求是否已完成。如果已完成，程式會在送出該輪次的請求之前進行替換。
* **進行替換：** 在迴圈以傳回的訊息取代整個歷史記錄的地方，此版本的替換函式只會取代請求所包含的訊息（從開頭算起），並保留之後附加的所有內容。
* **結束迴圈：** 如果迴圈結束時壓縮請求仍在待處理中，程式會等待它完成並進行替換，如此一來，在您儲存或繼續對話之前，仍在傳送途中的摘要就不會遺失。

`stop_reason` 檢查與迴圈中的相同：沒有區塊的回應會讓歷史記錄維持原樣。由於已不再有待處理的請求，程式接著便可以啟動新的壓縮請求。

## 在建立摘要期間保持思考有效

在撰寫摘要期間送達的輪次屬於保留的輪次。如果您在具有[保留思考](https://platform.claude.com/docs/zh-TW/build-with-claude/preserved-thinking)功能的模型上回傳思考區塊，則這些輪次中的思考內容只有在[保留的思考維持有效的條件](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-thinking-blocks#conditions-for-kept-thinking-to-stay-valid)成立時才會保持有效。
