본문으로 건너뛰기

압축

긴 대화나 여러 단계의 에이전트 루프에서는 메시지가 계속 추가됩니다. 어느 시점에는 트랜스크립트가 모델의 컨텍스트 제한을 초과하여 호출이 실패합니다. 이 제한에 도달하지 않고 대화를 계속 실행해야 합니다.

withCompaction은 각 모델 호출 전에 프로바이더 컨텍스트를 줄입니다. 컨텍스트가 maxTokens를 초과하면 전략이 모델에 표시되는 내용을 다시 작성합니다. 정식 트랜스크립트는 변경되지 않습니다. 이 ChatMiddleware를 모든 chat() 호출의 middleware 배열에 추가합니다.

설치

pnpm add @tanstack/ai-compaction

빠른 시작

트랜스크립트가 maxTokens를 초과하면 가장 오래된 메시지를 버리고 최근 메시지를 유지하는 것이 기본 전략입니다.

import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { withCompaction } from "@tanstack/ai-compaction";

export async function POST(request: Request) {
const { messages } = await request.json();

const stream = chat({
adapter: openaiText("gpt-5.5"),
messages,
middleware: [withCompaction({ maxTokens: 100_000 })],
});

return toServerSentEventsResponse(stream);
}

전략 선택

기록을 줄이는 방식을 변경하려면 strategy를 전달합니다. 기본 제공 전략은 세 가지입니다.

전략수행 내용비용
evictOldest (기본값)가장 오래된 메시지를 삭제하고 마커를 남깁니다추가 모델 호출 없음
summarizeOldest가장 오래된 메시지를 LLM 요약으로 대체합니다하나의 요약 호출
clearToolResults이전 도구 결과의 내용을 스텁화하고 메시지를 유지합니다추가 모델 호출 없음

evictOldest

가장 저렴한 전략입니다. 최근 부분은 유지하고 오래된 부분은 제거한 뒤 그 자리에 짧은 마커를 남깁니다. 기본값이므로 keepRecentTokens를 조정할 때만 명시하면 됩니다.

import { withCompaction, evictOldest } from "@tanstack/ai-compaction";

withCompaction({
maxTokens: 100_000,
strategy: evictOldest({ keepRecentTokens: 40_000 }),
});

summarizeOldest

메시지를 제거하는 대신 이전 대화의 요지를 유지하며, 요약 호출 1회가 필요합니다. summarize 콜백을 전달합니다. 제거될 메시지를 받아 요약 텍스트를 반환합니다. summarize() 또는 임의의 모델 호출에 연결합니다.

import { chat, summarize, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText, openaiSummarize } from "@tanstack/ai-openai";
import { withCompaction, summarizeOldest } from "@tanstack/ai-compaction";
import type { ModelMessage } from "@tanstack/ai";

async function summarizeHistory(messages: Array<ModelMessage>): Promise<string> {
const text = messages
.map((m) => `${m.role}: ${typeof m.content === "string" ? m.content : ""}`)
.join("\n");

const { summary } = await summarize({
adapter: openaiSummarize("gpt-5.5"),
text,
});
return summary;
}

export async function POST(request: Request) {
const { messages } = await request.json();

const stream = chat({
adapter: openaiText("gpt-5.5"),
messages,
middleware: [
withCompaction({
maxTokens: 100_000,
strategy: summarizeOldest({ summarize: summarizeHistory }),
}),
],
});

return toServerSentEventsResponse(stream);
}

clearToolResults

에이전트 루프에 가장 적합합니다. 도구 출력(파일 읽기, 명령 출력)이 대개 토큰의 대부분을 차지합니다. 이 전략은 오래된 도구 결과의 콘텐츠를 스텁으로 바꾸고 모든 메시지와 도구 호출 쌍을 그대로 유지합니다. 대화의 형태는 변경되지 않습니다.

import { withCompaction, clearToolResults } from "@tanstack/ai-compaction";

withCompaction({
maxTokens: 100_000,
// Keep the 5 most recent tool results in full, stub the older ones.
strategy: clearToolResults({ keepRecentToolResults: 5 }),
});

직접 작성하기

전략은 함수입니다. 메시지와 예산을 받아 다시 작성한 메시지를 반환하며, 아무것도 변경하지 않을 때는 null을 반환합니다. 추정치가 maxTokens를 초과할 때만 실행됩니다.

import { withCompaction } from "@tanstack/ai-compaction";
import type { CompactionStrategy } from "@tanstack/ai-compaction";

// Keep only the last message.
const keepLastOnly: CompactionStrategy = (messages) => {
if (messages.length <= 1) return null;
return messages.slice(-1);
};

withCompaction({
maxTokens: 100_000,
strategy: keepLastOnly,
strategyKey: "keep-last-v1",
});

사용자 지정 전략을 영속성과 함께 사용할 때는 strategyKey를 설정합니다. 전략이 다른 출력을 생성할 수 있으면 키를 변경합니다. 이렇게 하면 이전 체크포인트가 오래된 동작을 사용하지 않습니다.

전략 조합

composeStrategies는 여러 전략을 순서대로 실행하며 단계적으로 적용합니다. 결과가 다시 maxTokens 이하가 되는 즉시 중지합니다. 저렴하고 대상이 명확한 전략을 먼저 배치하고 포괄적인 대체 전략을 마지막에 둡니다. 다음 예제에서는 먼저 오래된 도구 출력을 삭제하고, 그래도 충분하지 않을 때만 오래된 메시지를 제거합니다.

import {
withCompaction,
composeStrategies,
clearToolResults,
evictOldest,
} from "@tanstack/ai-compaction";

withCompaction({
maxTokens: 100_000,
strategy: composeStrategies(clearToolResults(), evictOldest()),
});

옵션

withCompaction

옵션타입기본값설명
maxTokensnumber-필수입니다. messages 전체의 추정 토큰 수가 이 값을 초과하면 압축합니다.
strategyCompactionStrategyevictOldest()메시지를 줄이는 방법입니다.
estimateTokens(message: ModelMessage) => numbercharacters / 4메시지별 토큰 추정치입니다. 정확한 수가 필요하면 실제 토크나이저를 전달합니다.
strategyKeystringbuilt-in strategy identity안정적인 체크포인트 식별자입니다. 사용자 지정 전략, 추정기 또는 제거 마커에 설정합니다. summarize 함수가 변경될 수 있으면 변경합니다.
onCompact(info: CompactionInfo) => void-각 압축 후 실행됩니다. info{ before, after, messagesBefore, messagesAfter }(토큰 및 메시지 수)입니다.

전략 옵션

전략옵션
evictOldestkeepRecentTokens (기본값 maxTokens / 2), marker
summarizeOldestsummarize (필수), keepRecentTokens, summaryRole (기본값 assistant)
clearToolResultskeepRecentToolResults (기본값 3), stub

토큰 수는 대략적인 characters / 4 추정치입니다. 실행을 트리거하기에는 충분하지만 정확하지는 않습니다. 프로바이더에 맞는 정확한 수가 필요하면 estimateTokens를 전달합니다.

안전하게 유지되는 항목

  • 시스템 프롬프트는 절대 제거되지 않습니다. chat()은 이를 messages와 분리해 유지하므로 압축은 대화에만 적용됩니다.
  • 도구 호출은 결과와 쌍을 유지합니다. 기본 제공 전략은 고립된 도구 결과를 남기지 않으므로 요청이 유효하게 유지됩니다.
  • 모든 모델 호출 전에 실행됩니다. 압축은 init을 건너뛰고 beforeModelstructuredOutput에서 실행됩니다. 이후 호출마다 다시 압축할 수 있습니다.
  • 정식 트랜스크립트는 완전하게 유지됩니다. 압축은 프로바이더 전용 컨텍스트를 기록하며 영속성과 다른 미들웨어는 계속 ctx.messages를 읽습니다.

DevTools

압축 후 채팅 스트림에는 compaction:started, compaction:state, compaction:ended라는 세 가지 CUSTOM 이벤트가 순서대로 포함됩니다. compaction:started는 전략 실행 전에 전송되므로 느린 summarizeOldest 호출도 클라이언트에서 시작된 것으로 표시됩니다. 전략이 반환되면 상태 이벤트와 종료 이벤트가 이어집니다. TanStack AI DevTools는 훅에 압축 탭을 제공합니다. 각 압축에는 다음 항목이 표시됩니다.

  • 시작, 상태, 종료 행
  • 실행 시점과 토큰 및 메시지 수
  • maxTokens 예산
  • 제거된 메시지
  • 모델에 전송된 트랜스크립트

대화 타임라인에도 onCompactStart, onCompactonCompactEnd 단계가 유지됩니다.

DevTools 패널에서 AI 플러그인을 엽니다(ts-react-chat 예제가 이를 마운트합니다). Compaction 훅을 선택한 다음 Compaction 탭을 엽니다.

examples/ts-react-chat/compaction 라우트는 작은 maxTokens를 사용하므로 몇 차례 대화 후 압축이 실행됩니다. 또한 withPersistence(SQLite)를 사용합니다. 압축 후 다시 로드해도 chat에는 모든 메시지가 계속 표시됩니다. 배너는 예제 UI이며 useChat의 일부가 아닙니다.

압축과 영속성

압축 및 서버 측 withPersistence 은 두 가지 메시지 뷰를 사용합니다:

  • messages는 완전한 정식 트랜스크립트입니다. 영속성은 이 뷰를 저장합니다.
  • providerMessages는 임시 모델 컨텍스트입니다. 압축은 이 뷰를 다시 작성합니다.

미들웨어 순서는 이 분리를 변경하지 않습니다. 제거되거나 요약되거나 스텁으로 바뀐 콘텐츠는 메시지 저장소에 남습니다.

영속성 어댑터에 metadata 저장소가 있으면 압축은 작은 체크포인트도 저장합니다. 다음 요청은 정식 접두사를 검증하고 마지막 압축 결과를 복원한 뒤 새 메시지만 추가합니다. 접두사나 전략 키가 변경되면 체크포인트가 무효화됩니다.

체크포인트를 재사용하면 이후 summarizeOldest 단계는 이전 요약과 새 메시지를 함께 확인한 뒤 이전 요약을 새 요약에 통합합니다. 통합하려면 메타데이터 저장소와 전략 키가 필요합니다.

기본 전략과 표준 evictOldest, summarizeOldest, clearToolResults 및 안전한 조합에는 전략 키가 자동으로 부여됩니다. 사용자 지정 전략, 추정기 또는 마커 함수에는 strategyKey를 설정합니다. summarize 함수가 변경될 수 있으면 strategyKey를 변경합니다. 메타데이터 저장소나 안전한 키가 없으면 압축은 상태 없이 동작합니다.

다음 단계