feat(my-deepagent): v0.3 PR #2 — context compaction (auto + manual /compact)
Claude Code의 auto-compact + `/compact` 슬래시 등가. 핵심 동작: - 세션 누적 토큰 (`total_input_tokens + total_output_tokens`)이 활성 모델 컨텍스트 윈도우의 70%를 넘으면 자동으로 가장 오래된 비-system / 비-archived 메시지를 cheap 모델 (`openrouter:deepseek/deepseek-chat` 기본)로 1회 요약 → `MessageRow(is_summary=True, role=system)` 1줄 삽입 + 원본은 `archived=True` + negative seq band (-(original.seq + 1))으로 옮김. - LangGraph thread는 `thread_suffix` bump로 새 컨텍스트 시작 (재인입 비용 회피). 세션 자체는 살아있음 — `sessions show <id> --all`로 archived 메시지 조회 가능. - 수동 `/compact` 슬래시도 동일 함수 호출. 메시지가 부족하면 (`< MIN_COMPACTABLE`) 사유 출력하고 no-op. 데이터·라이브러리: - `monitoring/token_budget.py` (신규): `tiktoken cl100k_base`로 추정 (DeepSeek/ Anthropic 모델 정확한 토크나이저가 없으므로 보수적 over-count). `MODEL_CONTEXT_LIMITS` (DeepSeek 64k, Claude Sonnet/Haiku/Opus 200k, GPT-4o 128k), 미등록 모델은 32k 기본값. `COMPACTION_THRESHOLD = 0.7`. - `compaction.py` (신규): `should_compact()` / `compact_session()` / `CompactionResult`. `_SESSION_LOCKS: dict[str, asyncio.Lock]` 세션별 직렬화 — 동시 compaction은 두 번째가 첫 번째를 기다림. `KEEP_RECENT_K = 10`, `MIN_COMPACTABLE = 4`. LLM 호출은 DB session 바깥 (asyncpg connection 점유 회피). - `pyproject.toml`: `tiktoken>=0.7` 명시 (이전엔 langchain-openai 경유 transitive). REPL 통합 (`cli/interactive.py`): - `_approx_token_count`를 tiktoken-based로 교체. - 매 ainvoke 후 `should_compact(session_row)` → 임계 초과 시 자동 `compact_session()` → 성공 시 `clear_agent_cache()`로 thread bump + 한 줄 알림. - `/compact` 슬래시 등록 (`_register_compaction_slash`). 테스트 (`tests/integration/test_compaction.py`, 7 케이스): 1. `should_compact` 70% 임계 아래/위/미등록 모델 (3개) 2. `MIN_COMPACTABLE` 미만 → LLM 호출 없이 거부 3. Happy path: 14 메시지 → 4 archive(negative seq) + summary at seq=1 + 10 live 유지 + 토큰 카운터 산술 검증 4. 동일 session_id 동시 호출 2개 → Lock 직렬화 검증 5. 없는 session_id → `session_not_found` 게이트: - ruff check / format --check / mypy: PASS - pytest -q --ignore=tests/integration/test_e2e_workflow.py --ignore=tests/integration/test_openrouter_smoke.py: 611 passed (7 신규 포함) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,42 @@
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **v0.3 PR #2 — context compaction (auto + manual `/compact`)**.
|
||||
Claude Code의 auto-compact + `/compact` 슬래시 등가. 세션 누적 토큰이
|
||||
활성 모델 윈도우의 70%를 넘으면 자동으로 가장 오래된 비-system, 비-archived
|
||||
메시지를 cheap 모델(`openrouter:deepseek/deepseek-chat` 기본)로 1회 요약 →
|
||||
`MessageRow(is_summary=True, role=system)` 1줄 삽입 + 원본은 archive.
|
||||
LangGraph thread는 `thread_suffix` bump로 새 컨텍스트 시작 (재인입 비용 회피).
|
||||
- `monitoring/token_budget.py` (신규): `tiktoken cl100k_base`로 추정.
|
||||
`MODEL_CONTEXT_LIMITS` 모델별 윈도우 (DeepSeek 64k, Claude Sonnet/Haiku/Opus
|
||||
200k, GPT-4o 128k). 미등록 모델은 32k 기본값 — 보수적으로 compaction
|
||||
조기 트리거. `COMPACTION_THRESHOLD = 0.7` 상수. `count_tokens()`는 빈
|
||||
문자열·예외 모두 안전 (실패 시 char/4 fallback).
|
||||
- `compaction.py` (신규): `should_compact()` / `compact_session()` /
|
||||
`CompactionResult`. `_SESSION_LOCKS: dict[str, asyncio.Lock]`로
|
||||
세션별 직렬화 — 동시 compaction 호출 시 두 번째는 첫 번째 종료를 기다림.
|
||||
`KEEP_RECENT_K = 10`, `MIN_COMPACTABLE = 4`. LLM 호출은 DB session
|
||||
바깥에서 (asyncpg connection 점유 회피). archived rows는 negative seq
|
||||
band (`-(original.seq + 1)`)로 옮겨 summary가 `to_compact[0].seq`
|
||||
자리에 자연스럽게 들어감 (UNIQUE constraint 충돌 회피).
|
||||
- `cli/interactive.py`:
|
||||
- `_approx_token_count`를 tiktoken-based로 교체 (이전: 단순 `len // 4`).
|
||||
- 매 ainvoke 후 `should_compact(session_row)` 체크 → 임계 초과 시 자동
|
||||
`compact_session()` 호출 → 성공 시 `clear_agent_cache()`로 thread bump.
|
||||
한 줄 stdout 알림 (`context compacted — N messages archived, summary K tokens`).
|
||||
- `/compact` 슬래시 등록 (`_register_compaction_slash`). 수동 강제 compaction.
|
||||
충분한 메시지가 없으면 (`< MIN_COMPACTABLE`) 사유 출력.
|
||||
- `tests/integration/test_compaction.py` (신규, 7 케이스):
|
||||
1. `should_compact` 70% 임계 아래/위/미등록 모델 분기 (3개)
|
||||
2. `MIN_COMPACTABLE` 미만이면 LLM 호출 없이 거부 (stub-call 카운트 검증)
|
||||
3. Happy path: 14개 메시지 → 4개 archive(negative seq) + summary at seq=1 +
|
||||
10개 live 유지 + 토큰 카운터 산술 (1000 - 4*20 + summary_tokens) 검증
|
||||
4. 동일 `session_id`에 동시 호출 2개 → Lock 직렬화 (LLM 호출 윈도우 겹침
|
||||
없음 또는 두 번째 short-circuit) 검증
|
||||
5. 없는 `session_id` → `session_not_found`
|
||||
- `pyproject.toml`: `tiktoken>=0.7` 명시 (이전엔 langchain-openai 경유
|
||||
transitive였음 — 직접 의존 표시).
|
||||
|
||||
- **v0.3 PR #1 — interactive session persistence + LangGraph saver wiring**.
|
||||
v0.3의 토대. REPL/GUI 모두 장기 대화 영속 가능하도록 데이터 모델·CLI·HTTP
|
||||
API를 함께 도입. Claude Code의 `claude --resume` 등가.
|
||||
|
||||
Reference in New Issue
Block a user