컴팩션

컴팩션 (Compaction)

출처: 문서

본문

개요 (Overview)

장기 실행 상호작용을 지원하기 위해, 이후 턴에 필요한 상태를 보존하면서 컨텍스트 크기를 줄이는 컴팩션을 사용할 수 있어요.

컴팩션은 대화가 커질 때 품질, 비용, 지연 시간의 균형을 잡는 데 도움이 돼요.

서버 측 컴팩션 (Server-side compaction)

context_management에 compact_threshold를 설정하면 Responses 생성 요청(POST /responses 또는 client.responses.create)에서 서버 측 컴팩션을 활성화할 수 있어요.

  • 렌더링된 토큰 수가 구성된 임계값을 넘으면 서버가 서버 측 컴팩션을 실행해요.
  • 이 모드에서는 별도의 /responses/compact 호출이 필요 없어요.
  • 응답 스트림에는 암호화된 컴팩션 아이템이 포함돼요.
  • ZDR 참고: Responses 생성 요청에서 store=false로 설정하면 서버 측 컴팩션은 ZDR 친화적이에요.

반환된 컴팩션 아이템은 더 적은 토큰으로 키가 되는 이전 상태와 추론을 다음 실행으로 이어 가져요. 불투명하며 사람이 해석하도록 의도된 것이 아니에요.

무상태(stateless) 입력 배열 연결에서는 평소처럼 출력 아이템을 추가하세요. previous_response_id를 사용한다면 매 턴 새 사용자 메시지만 전달하세요. 두 경우 모두 컴팩션 아이템이 다음 창에 필요한 컨텍스트를 전달해요.

지연 시간 팁: 출력 아이템을 이전 입력 아이템에 추가한 뒤에는, 가장 최근 컴팩션 아이템보다 앞선 아이템을 버려 요청을 더 작게 유지하고 꼬리 지연 시간을 줄일 수 있어요. 가장 최근 컴팩션 아이템이 대화를 계속하는 데 필요한 컨텍스트를 담고 있어요. previous_response_id 연결을 사용한다면 수동으로 정리하지 마세요.

사용자 여정 (User journey)

  1. 평소처럼 /responses를 호출하되, compact_threshold가 있는 context_management를 포함해 서버 측 컴팩션을 활성화하세요.
  2. 응답이 스트리밍되는 동안 컨텍스트 크기가 임계값을 넘으면 서버가 컴팩션 패스를 트리거하고, 같은 스트림에서 컴팩션 출력 아이템을 내보내며, 추론을 계속하기 전에 컨텍스트를 정리해요.
  3. 한 가지 패턴으로 루프를 계속하세요: 무상태 입력 배열 연결(컴팩션 아이템을 포함한 출력을 다음 입력 배열에 추가) 또는 previous_response_id 연결(매 턴 새 사용자 메시지만 전달하고 그 ID를 이어 나르기).

예시 사용자 흐름 (Example user flow)

import OpenAI from "openai";
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";

const client = new OpenAI();

const conversation = [
  {
    type: "message",
    role: "user",
    content: "Let's begin a long coding task.",
  },
];

const response = await client.responses.create({
  model: "gpt-5.3-codex",
  input: conversation,
  store: false,
  context_management: [{ type: "compaction", compact_threshold: 200_000 }],
});

conversation.push(...toResponseInputItems(response.output));
console.log(response.output_text);
conversation = [
    {
        "type": "message",
        "role": "user",
        "content": "Let's begin a long coding task.",
    }
]

while keep_going:
    response = client.responses.create(
        model="gpt-5.3-codex",
        input=conversation,
        store=False,
        context_management=[{"type": "compaction", "compact_threshold": 200000}],
    )

    conversation.extend(response.output)

    conversation.append(
        {
            "type": "message",
            "role": "user",
            "content": get_next_user_input(),
        }
    )
package main

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"os"

	"github.com/openai/openai-go/v3"
	"github.com/openai/openai-go/v3/responses"
)

func main() {
	client := openai.NewClient()
	conversation := []responses.ResponseInputItemUnionParam{
		responses.ResponseInputItemParamOfMessage("Let's begin a long coding task.", responses.EasyInputMessageRoleUser),
	}
	scanner := bufio.NewScanner(os.Stdin)
	for {
		response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
			Model: "gpt-5.3-codex",
			Store: openai.Bool(false),
			Input: responses.ResponseNewParamsInputUnion{OfInputItemList: conversation},
			ContextManagement: []responses.ResponseNewParamsContextManagement{{
				Type: "compaction", CompactThreshold: openai.Int(200000),
			}},
		})
		if err != nil {
			panic(err)
		}
		conversation = append(conversation, outputAsInput(response.Output)...)
		fmt.Println(response.OutputText())
		if !scanner.Scan() {
			break
		}
		conversation = append(conversation,
			responses.ResponseInputItemParamOfMessage(scanner.Text(), responses.EasyInputMessageRoleUser),
		)
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
}

func outputAsInput(output []responses.ResponseOutputItemUnion) []responses.ResponseInputItemUnionParam {
	input := make([]responses.ResponseInputItemUnionParam, 0, len(output))
	for _, item := range output {
		var converted responses.ResponseInputItemUnion
		if err := json.Unmarshal([]byte(item.RawJSON()), &converted); err != nil {
			panic(err)
		}
		input = append(input, converted.ToParam())
	}
	return input
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.responses.EasyInputMessage;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseInputItem;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

var conversation = new ArrayList<ResponseInputItem>();
conversation.add(
    ResponseInputItem.ofEasyInputMessage(
        EasyInputMessage.builder()
            .role(EasyInputMessage.Role.USER)
            .content("Let's begin a long coding task.")
            .build()));

ResponseCreateParams params =
    ResponseCreateParams.builder()
        .model("gpt-5.3-codex")
        .inputOfResponse(conversation)
        .store(false)
        .putAdditionalBodyProperty(
            "context_management",
            JsonValue.from(List.of(Map.of("type", "compaction", "compact_threshold", 200000))))
        .build();

var response = client.responses().create(params);
response.output().stream()
    .map(item -> JsonValue.from(item).convert(ResponseInputItem.class))
    .forEach(conversation::add);
conversation.add(
    ResponseInputItem.ofEasyInputMessage(
        EasyInputMessage.builder()
            .role(EasyInputMessage.Role.USER)
            .content("Now implement the next step.")
            .build()));

client
    .responses()
    .create(params.toBuilder().inputOfResponse(conversation).build())
    .output()
    .stream()
    .flatMap(item -> item.message().stream())
    .flatMap(message -> message.content().stream())
    .flatMap(content -> content.outputText().stream())
    .forEach(text -> System.out.println(text.text()));
require "openai"

client = OpenAI::Client.new
conversation = [
  {
    type: :message,
    role: :user,
    content: "Let's begin a long coding task."
  }
]

response = client.responses.create(
  model: "gpt-5.3-codex",
  input: conversation,
  store: false,
  context_management: [
    {
      type: :compaction,
      compact_threshold: 200_000
    }
  ]
)
conversation.concat(response.output)
conversation << {
  type: :message,
  role: :user,
  content: "Now implement the next step."
}
next_response = client.responses.create(
  model: "gpt-5.3-codex",
  input: conversation,
  store: false,
  context_management: [
    {
      type: :compaction,
      compact_threshold: 200_000
    }
  ]
)
puts(next_response.output_text)

독립형 compact 엔드포인트 (Standalone compact endpoint)

명시적인 제어를 원한다면 장기 실행 워크플로의 무상태 컴팩션에 독립형 compact 엔드포인트를 사용하세요.

이 엔드포인트는 완전히 무상태이고 ZDR 친화적이에요.

전체 컨텍스트 창(메시지, 도구, 기타 아이템)을 보내면, 엔드포인트가 다음 /responses 호출에 전달할 수 있는 새로 컴팩션된 컨텍스트 창을 반환해요.

반환된 컴팩션된 창에는 더 적은 토큰으로 키가 되는 이전 상태와 추론을 이어 가져오는 암호화된 컴팩션 아이템이 포함돼요. 불투명하며 사람이 해석하도록 의도된 것이 아니에요.

참고: 컴팩션된 창은 일반적으로 컴팩션 아이템보다 더 많은 것을 포함해요. 이전 창에서 유지된 아이템도 포함할 수 있어요.

출력 처리: /responses/compact 출력을 정리하지 마세요. 반환된 창이 정식 다음 컨텍스트 창이므로 다음 /responses 호출에 그대로 전달하세요.

독립형 컴팩션의 사용자 여정 (User journey for standalone compaction)

  1. 평소처럼 /responses를 사용해 사용자 메시지, 어시스턴트 출력, 도구 상호작용을 포함한 입력 아이템을 보내세요.
  2. 컨텍스트 창이 커지면 /responses/compact를 호출해 새로 컴팩션된 컨텍스트 창을 생성하세요. /responses/compact에 보내는 창은 여전히 모델의 컨텍스트 창 안에 들어가야 해요.
  3. 이후 /responses 호출에는 전체 대화 기록 대신 반환된 컴팩션된 창(컴팩션 아이템 포함)을 입력으로 전달하세요.

예시 사용자 흐름 (Example user flow)

import OpenAI from "openai";

const client = new OpenAI();

const conversation = [{ role: "user", content: "Plan a trip to Kyoto." }];

const compacted = await client.responses.compact({
  model: "gpt-6-astra",
  input: conversation,
});

const nextInput = [
  ...compacted.output.map((item) => item),
  { role: "user", content: "Add two more days to the itinerary." },
];

const response = await client.responses.create({
  model: "gpt-6-astra",
  input: nextInput,
  store: false,
});

console.log(response.output_text);
# Full window collected from prior turns
long_input_items_array = [{"role": "user", "content": "Plan a trip to Kyoto."}]

# 1) Compact the current window
compacted = client.responses.compact(
    model="gpt-6-astra",
    input=long_input_items_array,
)

# 2) Start the next turn by appending a new user message
next_input = [
    *compacted.output,  # Use compact output as-is
    {
        "type": "message",
        "role": "user",
        "content": user_input_message(),
    },
]

next_response = client.responses.create(
    model="gpt-6-astra",
    input=next_input,
    store=False,  # Keep the flow ZDR-friendly
)
package main

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"os"

	"github.com/openai/openai-go/v3"
	"github.com/openai/openai-go/v3/responses"
)

func main() {
	client := openai.NewClient()
	longInputItems := []responses.ResponseInputItemUnionParam{
		responses.ResponseInputItemParamOfMessage("Plan a trip to Kyoto.", responses.EasyInputMessageRoleUser),
	}
	compacted, err := client.Responses.Compact(context.Background(), responses.ResponseCompactParams{
		Model: "gpt-6-astra",
		Input: responses.ResponseCompactParamsInputUnion{OfResponseInputItemArray: longInputItems},
	})
	if err != nil {
		panic(err)
	}
	scanner := bufio.NewScanner(os.Stdin)
	if !scanner.Scan() {
		return
	}
	nextInput := append(outputAsInput(compacted.Output),
		responses.ResponseInputItemParamOfMessage(scanner.Text(), responses.EasyInputMessageRoleUser),
	)
	nextResponse, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
		Model: "gpt-6-astra",
		Store: openai.Bool(false),
		Input: responses.ResponseNewParamsInputUnion{OfInputItemList: nextInput},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(nextResponse.OutputText())
}

func outputAsInput(output []responses.ResponseOutputItemUnion) []responses.ResponseInputItemUnionParam {
	input := make([]responses.ResponseInputItemUnionParam, 0, len(output))
	for _, item := range output {
		var converted responses.ResponseInputItemUnion
		if err := json.Unmarshal([]byte(item.RawJSON()), &converted); err != nil {
			panic(err)
		}
		input = append(input, converted.ToParam())
	}
	return input
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.EasyInputMessage;
import com.openai.models.responses.ResponseCompactParams;
import com.openai.models.responses.ResponseCompactionItemParam;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseInputItem;
import java.util.ArrayList;

var compacted =
    client
        .responses()
        .compact(
            ResponseCompactParams.builder()
                .model("gpt-6-astra")
                .input("Plan a trip to Kyoto.")
                .build());
var input = new ArrayList<ResponseInputItem>();
for (var item : compacted.output()) {
  item.message().map(ResponseInputItem::ofResponseOutputMessage).ifPresent(input::add);
  item.reasoning().map(ResponseInputItem::ofReasoning).ifPresent(input::add);
  item.compaction()
      .map(
          value ->
              ResponseInputItem.ofCompaction(
                  ResponseCompactionItemParam.builder()
                      .id(value.id())
                      .encryptedContent(value.encryptedContent())
                      .build()))
      .ifPresent(input::add);
}
input.add(
    ResponseInputItem.ofEasyInputMessage(
        EasyInputMessage.builder()
            .role(EasyInputMessage.Role.USER)
            .content("Add restaurant recommendations.")
            .build()));

client
    .responses()
    .create(
        ResponseCreateParams.builder()
            .model("gpt-6-astra")
            .inputOfResponse(input)
            .store(false)
            .build())
    .output()
    .stream()
    .flatMap(item -> item.message().stream())
    .flatMap(message -> message.content().stream())
    .flatMap(content -> content.outputText().stream())
    .forEach(text -> System.out.println(text.text()));
require "openai"

client = OpenAI::Client.new
long_input = [
  {
    role: :user,
    content: "Plan a trip to Kyoto."
  }
]
compaction = client.responses.compact(
  model: "gpt-6-astra",
  input: long_input
)
next_input = [
  *compaction.output,
  {
    type: :message,
    role: :user,
    content: "Add restaurant recommendations."
  }
]
response = client.responses.create(
  model: "gpt-6-astra",
  input: next_input,
  store: false
)
puts(response.output_text)

더 알아보기 (Learn more)