AI Agent

AI 에이전트 메모리 관리 실전 — 세션 간 상태 유지, 컨텍스트 압축, 레포 재탐색 방지

cell-devlog 2026. 5. 21. 09:49
반응형

에이전트가 어제 분석한 코드베이스를 오늘 또 처음부터 읽습니다. 지난주에 정한 컨벤션을 이번 주에 다시 묻습니다. 컨텍스트 윈도우가 1M 토큰이어도 세션이 끝나면 모든 것을 잊습니다. 메모리는 모델 선택보다 더 중요한 엔지니어링 문제입니다. "천재적인 AI + 기억 상실" 조합은 프로덕션에서 쓸 수 없습니다.

[핵심 요약]
→ 에이전트 메모리 = 4계층: 인컨텍스트(즉시) / 세션(단기) / 외부 DB(장기) / 감사 로그(영구)
→ 10M 토큰 컨텍스트도 해결책이 아님 — Lost-in-the-middle 60% 이하 정확도, 비용 폭탄
→ 실무 황금비율: 최근 K턴 원문 + 이전 내용 요약 + 외부 DB 검색 3단 조합
→ Mem0: 오픈소스 에이전트 메모리 레이어, user_id 기반 세션 간 지속 — 21개 프레임워크 지원
→ LangGraph checkpointing: PostgreSQL/Redis 기반 체크포인트 — 크래시 복구 + 세션 재개
→ 레포 재탐색 방지: 파일 구조 맵을 외부 DB에 캐시 → 매 세션 재인덱싱 제거
→ 10개 이상 청크 컨텍스트: 로직 에러 22% 증가 (2026년 실측) — 선별적 로딩 필수
→ 규모별 전략: 소규모 → SQLite / 성장기 → PostgreSQL+Redis / 엔터프라이즈 → AWS AgentCore

 


왜 긴 컨텍스트 윈도우로는 부족한가

[1M 토큰 컨텍스트 = 메모리 문제 해결 아님]

Lost-in-the-middle 문제:
→ 컨텍스트 앞뒤: 높은 주의력
→ 컨텍스트 중간: 주의력 급락
→ 10M 토큰에서 특정 사실 검색 정확도: 60% 이하
→ 중간에 묻힌 중요 정보를 에이전트가 무시

비용 문제:
→ Gemini 3.5 Flash 1M 토큰 입력: $1.50
→ 매 턴마다 전체 컨텍스트 재전송: 10턴이면 $15+
→ 캐시 가능하지 않은 동적 컨텍스트 → 캐싱 효과 없음

속도 문제:
→ 컨텍스트 길이와 TTFT 선형 증가
→ 100만 토큰 → 수십 초 첫 응답 대기

결론:
→ 긴 컨텍스트 = 복잡한 단일 세션 작업용 확장된 워킹 메모리
→ 외부 메모리 시스템의 대체재가 아님
→ 장기 기억은 반드시 외부 저장소

 


메모리 4계층 아키텍처

[프로덕션 표준 4계층]

Layer 1: 인컨텍스트 워킹 메모리 (즉시)
→ 현재 세션의 최근 K턴 원문
→ 활성 툴 호출 결과
→ 현재 추론 중인 상태
→ 저장소: 메모리(RAM)
→ 유지 기간: 세션 내

Layer 2: 세션 메모리 (단기)
→ 세션 간 요약 (압축된 과거 대화)
→ 현재 태스크 체크포인트
→ 크래시 복구용 상태
→ 저장소: Redis / SQLite
→ 유지 기간: 수일~수주

Layer 3: 외부 장기 메모리 (장기)
→ 사용자 선호도, 학습된 패턴
→ 프로젝트 컨텍스트 (코드베이스 구조 등)
→ 과거 성공/실패 패턴
→ 저장소: PostgreSQL + pgvector / Chroma
→ 유지 기간: 영구

Layer 4: 감사 로그 (영구)
→ 전체 실행 히스토리
→ 컴플라이언스용 불변 기록
→ 저장소: S3 / BigQuery
→ 유지 기간: 영구 (법적 의무)

실전 1 — 세션 내 컨텍스트 압축

import anthropic
from dataclasses import dataclass, field
from typing import Optional

client = anthropic.Anthropic()

@dataclass
class AgentMemory:
    """
    3단 메모리 구조:
    1. 최근 K턴 원문 (정밀)
    2. 이전 대화 요약 (압축)
    3. 외부 DB 검색 결과 (선별)
    """
    raw_messages: list = field(default_factory=list)
    summary: str = ""
    key_facts: list = field(default_factory=list)  # 중요 사실 추출본

    # 설정
    WINDOW_SIZE: int = 8        # 원문 유지 최근 N턴
    SUMMARY_THRESHOLD: int = 16  # 이 턴 수 초과 시 압축
    MAX_FACTS: int = 20         # 핵심 사실 최대 개수

    def add_turn(self, role: str, content: str):
        self.raw_messages.append({"role": role, "content": content})

        # 임계값 초과 시 자동 압축
        if len(self.raw_messages) > self.SUMMARY_THRESHOLD:
            self._compress()

    def _compress(self):
        """오래된 메시지를 요약으로 압축"""
        to_compress = self.raw_messages[:-self.WINDOW_SIZE]
        keep_recent = self.raw_messages[-self.WINDOW_SIZE:]

        # 요약 생성 (저렴한 모델로)
        compress_response = client.messages.create(
            model="claude-haiku-4-5",  # 압축엔 저렴한 모델
            max_tokens=800,
            system="""대화를 요약할 때:
1. 결정된 사항, 합의된 내용 보존
2. 사용자 선호도·제약 조건 보존
3. 에러 발생 위치와 해결 방법 보존
4. 반복 잡담 제거
한국어로 간결하게.""",
            messages=[{
                "role": "user",
                "content": "다음 대화를 요약해줘:\n\n" +
                          "\n".join([f"{m['role']}: {m['content'][:300]}"
                                    for m in to_compress])
            }]
        )
        new_summary_piece = compress_response.content[0].text

        # 기존 요약과 합치기
        if self.summary:
            self.summary = f"{self.summary}\n\n[추가 요약]\n{new_summary_piece}"
        else:
            self.summary = new_summary_piece

        # 최근 메시지만 유지
        self.raw_messages = keep_recent

    def _extract_key_facts(self, content: str):
        """응답에서 중요 사실 추출 (비동기로 처리 권장)"""
        if len(self.key_facts) >= self.MAX_FACTS:
            # 오래된 사실 제거 (LRU)
            self.key_facts.pop(0)

        # 단순 패턴 기반 추출 (LLM 호출 없이)
        keywords = ["결정:", "합의:", "주의:", "에러:", "해결:"]
        for line in content.split("\n"):
            if any(kw in line for kw in keywords):
                self.key_facts.append(line.strip()[:200])

    def build_context(self) -> tuple[str, list]:
        """
        에이전트에게 주입할 시스템 프롬프트 + 메시지 반환
        """
        system_parts = []

        # 요약 주입
        if self.summary:
            system_parts.append(f"[이전 대화 요약]\n{self.summary}")

        # 핵심 사실 주입
        if self.key_facts:
            facts_str = "\n".join(f"- {f}" for f in self.key_facts[-10:])
            system_parts.append(f"[주요 결정 및 사실]\n{facts_str}")

        system = "\n\n".join(system_parts) if system_parts else ""
        return system, self.raw_messages

# 사용
memory = AgentMemory()

def chat_with_memory(user_input: str) -> str:
    memory.add_turn("user", user_input)

    system, messages = memory.build_context()

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        system=system,
        messages=messages
    )

    reply = response.content[0].text
    memory.add_turn("assistant", reply)
    memory._extract_key_facts(reply)
    return reply

# 50턴 대화에서 토큰 비교
# 단순 누적: ~500,000 토큰
# 이 방식:   ~30,000 토큰 (약 16배 절감)

실전 2 — Mem0으로 세션 간 장기 메모리

# pip install mem0ai anthropic
from mem0 import Memory
import anthropic

# Mem0 초기화
m = Memory()
client = anthropic.Anthropic()

def agent_with_persistent_memory(
    user_id: str,
    session_id: str,
    user_input: str
) -> str:
    """
    세션이 끊겨도 사용자 선호도·이전 맥락 유지
    user_id: 사용자 식별자 (세션 간 공유)
    session_id: 현재 세션 식별자
    """

    # 1. 관련 메모리 검색
    relevant_memories = m.search(
        query=user_input,
        user_id=user_id,
        limit=5  # 상위 5개만 (10개 이상 → 에러율 22% 증가)
    )

    # 2. 메모리를 시스템 프롬프트에 주입
    memory_context = ""
    if relevant_memories["results"]:
        mem_list = "\n".join([
            f"- {r['memory']}"
            for r in relevant_memories["results"]
        ])
        memory_context = f"[이 사용자에 대해 알고 있는 것]\n{mem_list}\n\n"

    # 3. LLM 호출
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        system=f"""{memory_context}사용자의 질문에 정확하게 답변하세요.
이전에 알고 있는 정보를 자연스럽게 활용하되 언급하지 마세요.""",
        messages=[{"role": "user", "content": user_input}]
    )

    reply = response.content[0].text

    # 4. 새로운 메모리 추출 후 저장
    # Mem0이 자동으로 중요 정보 추출·저장
    m.add(
        messages=[
            {"role": "user", "content": user_input},
            {"role": "assistant", "content": reply}
        ],
        user_id=user_id,
        metadata={
            "session_id": session_id,
            "timestamp": "2026-05-21"
        }
    )

    return reply

# 첫 번째 세션
reply1 = agent_with_persistent_memory(
    user_id="user-alice",
    session_id="sess-001",
    user_input="나는 Python을 주로 쓰고, 타입 힌트는 항상 추가해야 해"
)

# --- 세션 종료 ---

# 두 번째 세션 (다른 날)
reply2 = agent_with_persistent_memory(
    user_id="user-alice",
    session_id="sess-002",
    user_input="이 함수 좀 개선해줘: def add(a, b): return a+b"
)
# → Mem0이 "Python 선호, 타입 힌트 필수"를 기억하고 자동 적용
# → def add(a: int, b: int) -> int: return a + b
# ── Mem0 멀티스코프 메모리 ──────────────────────────
# user_id: 사용자별 / agent_id: 에이전트별 / run_id: 세션별

m = Memory()

# 조직 공유 메모리 (팀 전체 공유)
m.add(
    messages=[{"role": "user", "content": "우리 팀은 PostgreSQL 14+, FastAPI, 비동기 위주"}],
    user_id="org-mycompany",  # 조직 ID로 공유
    metadata={"scope": "organization", "type": "tech_stack"}
)

# 프로젝트별 메모리
m.add(
    messages=[{"role": "user", "content": "결제 모듈은 Stripe v7 사용, PCI DSS 준수 필수"}],
    user_id="project-payments",
    metadata={"scope": "project", "type": "constraints"}
)

# 검색 시 여러 스코프 조합
def get_combined_context(user_id: str, query: str) -> str:
    contexts = []

    # 사용자 개인 메모리
    user_mem = m.search(query=query, user_id=user_id, limit=3)
    # 조직 공유 메모리
    org_mem = m.search(query=query, user_id="org-mycompany", limit=3)
    # 프로젝트 메모리
    proj_mem = m.search(query=query, user_id="project-payments", limit=2)

    for scope, results in [
        ("개인", user_mem),
        ("팀", org_mem),
        ("프로젝트", proj_mem)
    ]:
        if results["results"]:
            items = "\n".join([f"  - {r['memory']}"
                              for r in results["results"]])
            contexts.append(f"[{scope} 컨텍스트]\n{items}")

    return "\n\n".join(contexts)

실전 3 — LangGraph 체크포인팅으로 크래시 복구

# pip install langgraph langgraph-checkpoint-postgres psycopg
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.postgres import PostgresSaver
from typing import TypedDict, Annotated
import operator
import anthropic

client = anthropic.Anthropic()

class AgentState(TypedDict):
    messages: Annotated[list, operator.add]
    task: str
    completed_steps: Annotated[list, operator.add]
    current_step: int

# ── PostgreSQL 체크포인터 설정 ─────────────────────────
DB_URI = "postgresql://user:password@localhost:5432/agentdb"

with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
    checkpointer.setup()  # 테이블 자동 생성

# ── 에이전트 노드 정의 ────────────────────────────────
def planning_node(state: AgentState) -> dict:
    """계획 수립 — 체크포인트 저장"""
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{"role": "user",
                  "content": f"태스크 계획 수립: {state['task']}"}]
    )
    plan = response.content[0].text
    return {
        "messages": [{"role": "assistant", "content": plan}],
        "completed_steps": ["planning"],
        "current_step": 1
    }

def execution_node(state: AgentState) -> dict:
    """실행 — 각 스텝마다 체크포인트"""
    plan = state["messages"][-1]["content"]

    # 긴 작업 중 크래시 가정
    # → 체크포인트에서 자동 복구
    result = execute_long_task(plan, state["current_step"])

    return {
        "messages": [{"role": "user", "content": f"결과: {result}"}],
        "completed_steps": [f"step_{state['current_step']}"],
        "current_step": state["current_step"] + 1
    }

def should_continue(state: AgentState) -> str:
    return END if state["current_step"] >= 5 else "execution"

# ── 그래프 구성 ───────────────────────────────────────
workflow = StateGraph(AgentState)
workflow.add_node("planning", planning_node)
workflow.add_node("execution", execution_node)
workflow.set_entry_point("planning")
workflow.add_edge("planning", "execution")
workflow.add_conditional_edges("execution", should_continue)

with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
    app = workflow.compile(checkpointer=checkpointer)

    # 설정: thread_id로 세션 식별
    config = {"configurable": {"thread_id": "task-2026-05-21-001"}}

    # 첫 번째 실행
    try:
        result = app.invoke(
            {"task": "코드베이스 전체 리팩토링", "messages": [],
             "completed_steps": [], "current_step": 0},
            config=config
        )
    except Exception as e:
        print(f"크래시 발생: {e}")

    # 크래시 후 재시작 — 같은 thread_id로 자동 복구
    # → completed_steps에서 중단 지점 확인
    # → 그 지점부터 재실행 (처음부터 아님)
    current_state = app.get_state(config)
    print(f"완료된 스텝: {current_state.values['completed_steps']}")
    print(f"현재 스텝: {current_state.values['current_step']}")

    # 이어서 실행
    result = app.invoke(None, config=config)  # None = 기존 상태에서 이어서
# ── Redis 체크포인터 (더 빠른 읽기) ──────────────────
from langgraph.checkpoint.redis import RedisSaver

REDIS_URL = "redis://localhost:6379"

with RedisSaver.from_url(REDIS_URL) as checkpointer:
    app = workflow.compile(checkpointer=checkpointer)
    # → 세션 상태 Redis에서 수밀리초 내 로드
    # → 고트래픽 환경 권장

실전 4 — 레포 재탐색 방지 (코딩 에이전트 핵심)

import json
import hashlib
import redis
from pathlib import Path
import anthropic

client = anthropic.Anthropic()
redis_client = redis.Redis(host="localhost", port=6379, db=0)

class CodebaseMemory:
    """
    코드베이스 구조를 캐시 — 매 세션 재탐색 방지
    Reddit 1위 pain point 해결
    """

    def __init__(self, repo_path: str, ttl_seconds: int = 86400):
        self.repo_path = Path(repo_path)
        self.ttl = ttl_seconds
        self.cache_key = f"codebase:{self._compute_hash()}"

    def _compute_hash(self) -> str:
        """레포 구조의 해시 (파일 변경 감지용)"""
        structure = sorted([
            str(p.relative_to(self.repo_path))
            for p in self.repo_path.rglob("*.py")
            if ".git" not in str(p)
        ])
        return hashlib.md5(str(structure).encode()).hexdigest()[:8]

    def get_or_build_map(self) -> dict:
        """캐시에서 코드베이스 맵 로드, 없으면 생성"""
        cached = redis_client.get(self.cache_key)
        if cached:
            print(f"✅ 캐시 히트: {self.cache_key}")
            return json.loads(cached)

        print(f"🔄 코드베이스 맵 생성 중...")
        codebase_map = self._build_map()

        # 캐시 저장 (TTL: 24시간)
        redis_client.setex(
            self.cache_key,
            self.ttl,
            json.dumps(codebase_map, ensure_ascii=False)
        )
        return codebase_map

    def _build_map(self) -> dict:
        """실제 코드베이스 분석 (비용 발생 — 캐시 미스 시만)"""
        py_files = list(self.repo_path.rglob("*.py"))

        # 파일별 메타데이터 수집
        file_summaries = {}
        for f in py_files[:50]:  # 최대 50개 파일
            if ".git" in str(f):
                continue
            try:
                content = f.read_text(encoding="utf-8")[:3000]
                rel_path = str(f.relative_to(self.repo_path))
                file_summaries[rel_path] = {
                    "size": f.stat().st_size,
                    "lines": content.count("\n"),
                    "imports": self._extract_imports(content),
                    "classes": self._extract_classes(content),
                    "functions": self._extract_functions(content)[:10],
                }
            except Exception:
                continue

        # LLM으로 구조 요약 (캐시 미스 시 한 번만)
        summary_response = client.messages.create(
            model="claude-haiku-4-5",  # 저렴한 모델로
            max_tokens=2000,
            messages=[{
                "role": "user",
                "content": f"""이 Python 프로젝트 구조를 분석해줘:

파일 목록: {list(file_summaries.keys())}

주요 파일 내용:
{json.dumps({k: v for k, v in list(file_summaries.items())[:10]},
            ensure_ascii=False, indent=2)}

분석 결과를 JSON으로:
{{
  "main_purpose": "프로젝트 목적",
  "key_modules": {{"모듈명": "역할"}},
  "entry_points": ["진입점 파일들"],
  "test_location": "테스트 디렉토리",
  "conventions": ["코딩 컨벤션들"]
}}"""
            }]
        )

        try:
            analysis = json.loads(summary_response.content[0].text)
        except json.JSONDecodeError:
            analysis = {"raw": summary_response.content[0].text}

        return {
            "files": file_summaries,
            "analysis": analysis,
            "cached_at": "2026-05-21",
            "hash": self.cache_key
        }

    def _extract_imports(self, content: str) -> list:
        return [l.strip() for l in content.split("\n")
                if l.startswith("import ") or l.startswith("from ")][:10]

    def _extract_classes(self, content: str) -> list:
        return [l.split("class ")[1].split(":")[0].split("(")[0].strip()
                for l in content.split("\n") if l.startswith("class ")]

    def _extract_functions(self, content: str) -> list:
        return [l.split("def ")[1].split("(")[0].strip()
                for l in content.split("\n") if l.startswith("def ")]

def code_agent_with_codebase_memory(
    repo_path: str,
    task: str,
    user_id: str
) -> str:
    """
    코드베이스 맵을 캐시에서 로드해서
    매 세션 재탐색 없이 바로 작업
    """
    # 코드베이스 맵 로드 (캐시 히트: ~5ms, 미스: ~30초)
    cb_memory = CodebaseMemory(repo_path)
    codebase_map = cb_memory.get_or_build_map()

    # 사용자 메모리 로드 (이전 세션 컨텍스트)
    from mem0 import Memory
    m = Memory()
    user_context = m.search(query=task, user_id=user_id, limit=5)
    user_mem_str = "\n".join([
        f"- {r['memory']}"
        for r in user_context.get("results", [])
    ])

    # 태스크에 관련된 파일만 선별 로드
    relevant_files = find_relevant_files(task, codebase_map)

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=4096,
        system=f"""프로젝트 구조:
{json.dumps(codebase_map['analysis'], ensure_ascii=False, indent=2)}

관련 파일: {relevant_files}

{f"사용자 컨텍스트:{chr(10)}{user_mem_str}" if user_mem_str else ""}""",
        messages=[{"role": "user", "content": task}]
    )

    return response.content[0].text

def find_relevant_files(task: str, codebase_map: dict) -> list:
    """태스크 키워드로 관련 파일만 선별"""
    task_lower = task.lower()
    keywords = set(task_lower.split())

    relevant = []
    for filepath, meta in codebase_map.get("files", {}).items():
        file_lower = filepath.lower()
        funcs = " ".join(meta.get("functions", [])).lower()
        classes = " ".join(meta.get("classes", [])).lower()

        # 파일명이나 함수명에 키워드 포함되면 관련 파일
        score = sum(1 for kw in keywords
                   if kw in file_lower or kw in funcs or kw in classes)
        if score > 0:
            relevant.append((filepath, score))

    return [f for f, _ in sorted(relevant, key=lambda x: -x[1])[:5]]

# 사용
result = code_agent_with_codebase_memory(
    repo_path="/path/to/project",
    task="결제 모듈의 에러 핸들링 개선",
    user_id="user-alice"
)
# → 첫 실행: 코드베이스 분석 (30초)
# → 이후 세션: 캐시 로드 (5ms) + 바로 작업

실전 5 — 메모리 오염 방지

# 오래된·잘못된 메모리가 에이전트를 오염시키는 문제 해결
from mem0 import Memory
from datetime import datetime, timedelta

m = Memory()

def update_memory_with_staleness_check(
    user_id: str,
    key: str,
    new_value: str
):
    """
    메모리 업데이트 시 오래된 정보 교체
    (사용자가 직장을 바꿨는데 AI가 옛날 직장 기억하는 문제)
    """
    # 기존 관련 메모리 검색
    existing = m.search(query=key, user_id=user_id, limit=5)

    # 충돌하는 메모리 삭제
    for item in existing.get("results", []):
        # 같은 카테고리의 오래된 정보 제거
        if is_conflicting(item["memory"], new_value):
            m.delete(memory_id=item["id"])
            print(f"🗑️ 오래된 메모리 삭제: {item['memory'][:50]}")

    # 새 메모리 추가 + 타임스탬프
    m.add(
        messages=[{
            "role": "user",
            "content": f"[업데이트 {datetime.now().strftime('%Y-%m-%d')}] {new_value}"
        }],
        user_id=user_id,
        metadata={
            "key": key,
            "updated_at": datetime.now().isoformat(),
            "version": "latest"
        }
    )

def is_conflicting(old_memory: str, new_value: str) -> bool:
    """두 메모리가 충돌하는지 간단 휴리스틱 확인"""
    # 같은 종류의 사실 (직장, 언어 선호 등)이면 충돌
    conflict_patterns = [
        ("직장", "회사", "근무"),
        ("선호", "좋아하는", "쓰는"),
        ("언어", "프레임워크", "스택"),
    ]
    for pattern_group in conflict_patterns:
        if (any(p in old_memory for p in pattern_group) and
            any(p in new_value for p in pattern_group)):
            return True
    return False

# 사용
update_memory_with_staleness_check(
    user_id="user-alice",
    key="직장",
    new_value="현재 Startup B에서 일함 (전 직장 Startup A에서 이직)"
)
# → 기존 "Startup A 근무" 메모리 자동 삭제
# → 새 "Startup B 근무" 메모리 추가

팀 규모별 메모리 아키텍처 선택

[Solo / 소규모 팀 (1~5명)]
→ 세션 상태: SQLite (파일 기반)
→ 장기 메모리: Mem0 로컬 or JSON 파일
→ 체크포인팅: LangGraph + SQLiteSaver
→ 코드베이스 캐시: 로컬 파일
→ 월 비용: ~$0

[성장 단계 (5~20명)]
→ 세션 상태: Redis (고속 읽기)
→ 장기 메모리: Mem0 + PostgreSQL + pgvector
→ 체크포인팅: LangGraph + PostgresSaver
→ 코드베이스 캐시: Redis
→ 옵저버빌리티: Langfuse 셀프호스팅
→ 월 비용: ~$50~200

[엔터프라이즈 (20명+)]
→ AWS AgentCore: 세션당 격리된 microVM
→ 세션 TTL: 8시간, 비활성 15분 후 종료
→ 메모리: 2티어 (워킹 + 장기 지능형)
→ 데이터 격리: 세션 종료 시 메모리 완전 소거
→ 월 비용: 사용량 기반

마무리

✅ 지금 당장 해야 할 것
→ 히스토리 전체 재전송 여부 확인 — 슬라이딩 윈도우로 교체
→ Mem0 도입으로 사용자 선호도 세션 간 유지
→ LangGraph checkpointing으로 크래시 복구 구현
→ 코드베이스 탐색 결과 Redis 캐시로 재탐색 제거

❌ 흔한 실수
→ "1M 토큰이면 다 들어가잖아" → 비용·속도·정확도 모두 문제
→ 메모리 스키마 없이 시작 → 나중에 마이그레이션 지옥
→ 메모리 오염 방지 로직 없음 → 오래된 정보가 계속 영향
→ 10개 이상 청크 로딩 → 로직 에러 22% 증가 (실측)
→ 모든 대화를 다 저장 → 중요한 정보가 노이즈에 묻힘

관련 글


 

반응형