AI 컨텍스트 블로트를 80% 줄인 방법, 해결책은 놀라울 정도로 간단했다
요약
AI 에이전트의 성능 저하와 비용 증가의 주원인은 모델 자체가 아닌 '상태 관리' 문제입니다. 모든 원본 도구 출력을 프롬프트에 재사용하는 대신, 결과를 외부의 구조화된 상태(structured state)에 저장하고 다음 단계에 필요한 핵심 정보만 전달해야 합니다.
핵심 포인트
- 원본 도구 출력은 앱 상태에 저장하세요.
- 다음 단계에 필요한 요약/필드만 전송하여 컨텍스트를 줄이세요.
- 긴 프롬프트가 곧 좋은 성능을 의미하지 않습니다.
- 상태 관리가 에이전트 품질의 핵심입니다.
몇 달 전, 저는 에이전트가 정말 기괴한 행동을 하는 것을 목격했습니다.
이미 데이터를 가져왔습니다.
데이터를 이미 파싱했습니다.
유용한 필드를 이미 저장했습니다.
그런데도 다음 모델 호출에 전체 원본 도구 응답을 붙여넣었습니다.
전체 JSON 덩어리들. 로그. 메타데이터. 쓸모없는 필드들. 모든 것이요.
다음 단계가 느려졌습니다.
그 다음 단계는 더 비싸졌습니다.
그러자 모델은 작업의 쉬운 부분만 답변하고 중요한 부분은 조용히 무시하는 짜증 나는 행동을 하기 시작했습니다.
이것이 버그가 명확해진 순간이었습니다:
대부분의 “컨텍스트 창 문제”는 모델 문제가 아닙니다.
상태 관리 문제입니다.
이것을 알게 되면, n8n 플로우, LangGraph 에이전트, MCP 도구 루프, 그리고 사용자 지정 OpenAI 호환 오케스트레이션 코드에서 사방에 그것을 발견하기 시작합니다.
실제 해결책
모든 프롬프트에 원본 도구 출력을 재사용하는 것을 멈추세요.
전체 결과는 모델 외부의 구조화된 상태(structured state)에 유지하세요.
다음으로 필요한 것만 전송하세요:
- 간단한 요약
- 다음 단계에 필요한 정확한 필드
- 또는 검증이 중요한 경우 정확한 증거 슬라이스
그 하나의 변화가 일반적으로 에이전트 품질에 다른 프론티어 모델로 전환하는 것보다 더 많은 영향을 줍니다.
안티 패턴(Anti-pattern)
여기가 나쁜 버전입니다:
result = weather_api.get_forecast("Austin")
messages.append({
...
만약 result가 시간별 예보 데이터 200줄, 제공업체 메타데이터, 타임스탬프, 디버그 필드 및 트레이스 ID를 포함한다면, 당신은 방금 프롬프트를 잡동사니 서랍(junk drawer)으로 만들었습니다.
이제 모델은 다음을 해야 합니다:
- 관련 필드를 찾고
- 관련 없는 필드는 무시하고
- 이전 단계를 기억하며
- 다음 행동을 계획하고
- 노이즈에 산만해지지 않도록 하는 것
모델은 이것을 할 수 있습니다.
단지 그것에 특별히 효율적이지 않을 뿐입니다.
더 나은 패턴(Better pattern)
전체 결과를 앱 상태(app state)에 저장하세요.
다음 단계가 필요한 필드만 전달하세요.
result = weather_api.get_forecast("Austin")
state["weather_raw"] = result
...
동일한 워크플로우.
더 작은 프롬프트.
줄어든 방해 요소.
모델이 불필요한 내용을 재파싱하는 대신 결정에 집중할 확률 향상.
프롬프트 크기가 곧 프롬프트 품질을 의미하지는 않는다
많은 개발자들이 이 두 가지 개념을 혼동한다:
- 모델은 긴 컨텍스트를 담을 수 있다
- 모델에게 긴 컨텍스트를 제공해야 한다
이것들은 같지 않다.
거대한 프롬프트는 종종 트렌치코트를 입은 잘못 관리된 상태 저장소에 불과하다.
GPT-5, Claude Opus, Grok, Qwen, 또는 Llama가 기술적으로 전체 기록을 수용할 수 있다 하더라도, 오래된 내용이 쌓일수록 성능은 여전히 흐릿해진다. 지연 시간(Latency)이 증가하고, 비용도 증가하며, 주의력이 분산된다.
이것이 바로 장기간 실행되는 에이전트들이 5단계에서는 똑똑하게 느껴지다가 20단계쯤 되면 이상하게 불안정해지는 이유다.
순위별 3가지 패턴
| 접근 방식 | 실제 발생하는 일 |
|---|---|
| 프롬프트에 원시 도구 출력 포함 | 가장 큰 프롬프트 증가. 가장 높은 방해 요소. 매 단계마다 지연 시간과 비용 상승. |
| ... | |
| 내 의견: 구조화된 상태를 프롬프트 외부에 두는 것이 기본(default)이어야 한다. |
만약 장난감 봇보다 더 진지한 것을 구축하고 있다면, 원시 도구 출력을 매 단계에 다시 재생하는 것은 게으른 아키텍처다.
무엇이 어디에 있어야 하는가?
이것은 제가 다른 에이전트 개발자들에게 시작했으면 좋겠다고 생각하는 사고 모델이다:
- 단기 대화 상태는 스레드(thread)에 첨부되어 유지된다.
- 장기적인 사실 및 도구 결과는 데이터베이스나 메모리 저장소처럼 프롬프트 외부에 존재한다.
- 다음 모델 호출에는 그 상태의 가장 작고 유용한 조각만 전달된다.
그게 전부다.
복잡하지 않다. 단지 규율이 있을 뿐이다.
실제 예시
LangGraph: 트랜스크립트를 재생하지 말고 체크포인트 상태를 사용하라
LangGraph는 이 부분을 정확하게 처리한다.
이전 도구 출력을 모든 프롬프트에 채워 넣는 대신, 그래프 상태(graph state)를 영속화하고 스레드별로 재개한다.
from langgraph.checkpoint.postgres import PostgresSaver
DB_URI =
n8n은 이 부분에서 놀라울 정도로 실용적입니다.
만약 n8n에서 AI 워크플로우를 구축하고 있다면, 이미 컨텍스트 블로트를 방지할 도구들을 가지고 있습니다:
- Simple Memory
- Redis
- Postgres
- Xata
- Zep
- Motorhead
- Chat Memory Manager
Chat Memory Manager의 존재 자체가 모든 것을 말해줍니다.
만약 워크플로우 플랫폼이 메모리를 검사하고 줄이는 전용 노드를 제공한다면, “채팅 기록에 영원히 그냥 추가하기”는 명백히 최선의 패턴이 아닙니다.
### MCP: 도구 출력은 앱 상태가 먼저고, 프롬프트 자료는 그 다음이다
MCP(Message Context Passing)는 도구가 이미 구조를 가지고 있기 때문에 이를 더 깔끔하게 만듭니다.
예시 도구 정의:
{
"name": "get_weather",
"description": "특정 위치의 현재 날씨 정보를 가져옵니다",
...
이는 다음과 같은 간단한 패턴으로 나아가야 함을 의미합니다:
- 도구를 호출한다.
- 완전하게 구조화된 결과를 저장한다.
- 필요한 필드만 다음 프롬프트에 전달한다.
예를 들어:
tool_result = {
"temperature": "72F",
"conditions": "cloudy",
...
이때 프롬프트는 창고가 아니라 작업 노트가 됩니다.
## 프롬프트 캐싱도 도움이 되지만, 나쁜 컨텍스트 위생을 고치지는 못한다
이 부분은 많이 오해받습니다.
Anthropic의 프롬프트 캐싱은 동일한 안정적인 접두사(prefix)를 계속 재사용할 때 유용합니다.
즉, 다음과 같은 것들입니다:
- 시스템 명령어 (system instructions)
- 도구 정의 (tool definitions)
- 고정된 문서 (fixed documents)
- 반복되는 스캐폴딩(scaffolding)
이것이 지저분한 프롬프트를 마법처럼 좋게 만들지는 않습니다.
Claude 캐싱은 접두사가 동일할 때만 도움이 되며, 기본적으로 캐시 항목은 5분 동안 유지됩니다. 이는 반복되는 접두사에는 훌륭합니다. 하지만 거대한 원본 도구 목록(raw tool dump)을 더 관련성 있게 만드는 데는 아무런 도움이 되지 않습니다.
오히려 캐싱은 품질 문제가 명확해지기 전까지는 나쁜 아키텍처를 잠시 숨겨줄 수 있습니다. 왜냐하면 그때까지 청구 금액이 적기 때문입니다.
예시:
import anthropic
client = anthropic.Anthropic()
...
캐싱은 안정적인 접두사에 사용하세요. 프롬프트에 쓰레기를 계속 밀어 넣을 구실로 사용해서는 안 됩니다.
## 간단한 전/후 리팩터링 비교
일반적인 에이전트 루프에서 이것이 어떻게 보이는지 보여드리겠습니다.
### Before
이것은 처음에는 작동합니다.
그러다가 `search_results`가 엄청나게 커집니다.
그리고 `crm_data`도 엄청나게 커집니다.
결국 매 단계마다 더 많은 불필요한 정보(baggage)를 끌고 다닙니다.
### 이후 (After)
def run_step(state, user_request):
search_results = search_docs(user_request)
crm_data = fetch_customer_record(state["customer_id"])
...
이 버전은 덜 취약합니다.
그리고 나중에 모델이 정확한 증거가 필요할 때, 원본 상태(raw state)를 선택적으로 다시 가져올 수 있습니다.
## 프롬프트에 원본 출력을 유지해야 하는 경우
실제 예외 사례들이 존재합니다.
때로는 다음 단계에서 정확한 원본 자료가 정말로 필요한 경우가 있습니다.
다음과 같은 상황에서는 원본 출력을 컨텍스트에 유지하는 것이 좋습니다:
- 법적 또는 규정 준수 인용(legal or compliance citations)
- 디버깅을 위한 정확한 스택 트레이스(exact stack traces for debugging)
- 전체 diff를 사용한 코드 검토(code review on full diffs)
- 지저분한 텍스트에서 추출(extraction from messy text)
- 원본 증거와의 검증(verification against original evidence)
하지만 그 경우에도, 필요한 정확한 조각(slice)만 전달하세요.
전체 상위 스트림의 모든 정보(entire upstream universe)를 전달할 필요는 없습니다.
## 제가 현재 사용하는 규칙
이것은 제 에이전트 워크플로우 대부분을 정리해 준 경험적 규칙(heuristic)입니다:
- 추론(reasoning)에 필요한 정보라면, 요약하세요.
- 검색(retrieval)에 필요한 정보라면, 저장하세요.
- 검증(verification)에 필요한 정보라면, 정확한 조각만 전달하세요.
이 규칙은 모델을 교체하는 것보다 더 많은 지연 시간(latency), 비용, 그리고 에이전트 품질을 절약해 주었습니다.
## 토큰당 비용을 지불할 때 이것이 더욱 중요한 이유
이 문제는 프로덕션 환경에서 빠르게 비싸집니다.
만약 여러분의 에이전트가 n8n, Make, Zapier, OpenClaw 또는 사용자 정의 루프에서 24/7 실행된다면, 재사용되는 모든 추가 데이터 조각(extra blob)은 반복적인 비용(recurring cost)이 됩니다.
이것이 팀들이 아키텍처를 개선하는 대신 토큰 사용 대시보드에 집착하게 되는 이유 중 하나입니다.
더 깨끗한 상태 설계는 근본적인 곳에서 프롬프트의 부풀림(prompt bloat)을 줄여줍니다.
만약 flat monthly pricing을 제공하는 OpenAI 호환 엔드포인트를 사용하고 있다면, 이 장점은 더욱 커집니다. 왜냐하면 계속해서 도구 호출 하나가 청구서를 급증시킬지 걱정할 필요 없이 에이전트 품질과 처리량에 맞춰 최적화할 수 있기 때문입니다.
제가 Standard Compute에서 좋아하는 부분은 이것입니다. 기존의 OpenAI API 대체품으로 작동하기 때문에, 기존 SDK와 에이전트 코드를 유지하면서도 오케스트레이션을 정리하는 동안 토큰당 비용을 걱정하며 생각하는 것을 멈출 수 있다는 점입니다.
아키텍처 수정은 여전히 중요합니다.
단지 모든 실험에 토큰 불안감이 드리우는 것 없이 이를 적용할 수 있게 된다는 것이죠.
## 실용적인 체크리스트
만약 에이전트가 시간이 지남에 따라 느려지고 멍청해진다고 느껴진다면, 다음 사항들을 먼저 확인하세요:
- 모든 다음 프롬프트에 원시(raw) 도구 출력을 붙여넣고 있나요?
- 모델 외부에서 표준화된 결과(canonical results)를 저장하고 있나요?
- 다음 추론 단계 전에 도구 결과를 요약하고 있나요?
- 검증이 필요할 때만 정확한 증거(exact evidence)를 전달하고 있나요?
- 안정적인 접두사(stable prefixes)에 대해서만 프롬프트 캐싱을 사용하고 있나요?
- n8n 또는 오케스트레이션 레이어에서 메모리를 잘라내고 있나요?
- 모델을 데이터베이스가 아닌 추론 엔진처럼 취급하고 있나요?
이것들만 고쳐도 다른 모델 비교 대회보다 더 큰 개선을 얻을 수 있을 것입니다.
저는 모델 수정책을 찾았습니다.
하지만 제가 발견한 것은 아키텍처 수정책이었습니다.
대부분의 “컨텍스트 창 문제”는 사실 우리가 채팅 기록을 저장 계층으로 사용하고 있기 때문입니다.
원시 도구 출력을 다음 프롬프트에 계속 밀어 넣는 것을 멈추면, 에이전트는 더 빠르고, 저렴하며, 눈에 띄게 혼란스러움이 줄어듭니다.
모델 자체가 변해서가 아닙니다.
우리가 마침내 모델에게 우리의 상태 관리 실수를 떠맡기지 않기로 했기 때문입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기