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>
71 lines
1.8 KiB
TOML
71 lines
1.8 KiB
TOML
[project]
|
|
name = "my-deepagent"
|
|
version = "0.1.0"
|
|
description = "Add your description here"
|
|
requires-python = ">=3.12"
|
|
dependencies = [
|
|
"asyncpg>=0.30",
|
|
"psycopg[binary]>=3.2",
|
|
"alembic>=1.14",
|
|
"greenlet>=3.0",
|
|
"sqlalchemy[asyncio]>=2.0",
|
|
"fastapi>=0.115",
|
|
"uvicorn[standard]>=0.30",
|
|
"sse-starlette>=2.1",
|
|
"httpx>=0.28",
|
|
"jsonschema>=4.23",
|
|
"keyring>=25.7",
|
|
"langchain>=0.3.0,<2.0.0",
|
|
"langchain-core>=0.3.0,<2.0.0",
|
|
"langchain-openai>=0.3.0,<2.0.0",
|
|
"langgraph>=0.2.0",
|
|
"langgraph-checkpoint-postgres>=2.0.0",
|
|
"openai>=1.0.0",
|
|
"platformdirs>=4.9",
|
|
"prompt-toolkit>=3.0",
|
|
"pydantic>=2.9",
|
|
"pydantic-settings>=2.6",
|
|
"pyyaml>=6.0",
|
|
"rich>=13.9",
|
|
"structlog>=24.4",
|
|
"tiktoken>=0.7",
|
|
"typer>=0.14",
|
|
"zstandard>=0.23",
|
|
"deepagents>=0.6.1,<0.7.0",
|
|
]
|
|
|
|
[project.scripts]
|
|
mydeepagent = "my_deepagent.cli.main:app"
|
|
|
|
[build-system]
|
|
requires = ["uv_build>=0.9.28,<0.10.0"]
|
|
build-backend = "uv_build"
|
|
|
|
[tool.pytest.ini_options]
|
|
asyncio_mode = "auto"
|
|
testpaths = ["tests"]
|
|
addopts = "-v --strict-markers"
|
|
markers = [
|
|
"integration: marks tests as integration tests that make real external API calls (deselect with '-m not integration')",
|
|
]
|
|
|
|
[dependency-groups]
|
|
dev = [
|
|
# aiosqlite is a TEST-ONLY dependency: production runs on Postgres
|
|
# (asyncpg, see [project.dependencies]) but the bulk of the test suite uses
|
|
# sqlite+aiosqlite tmp_path URLs for speed + isolation simplicity. Live
|
|
# Postgres validation happens via the E2E suite (real OpenRouter +
|
|
# docker-compose Postgres).
|
|
"aiosqlite>=0.20",
|
|
"mypy>=1.13",
|
|
"pre-commit>=4.0",
|
|
"pytest>=8.3",
|
|
"pytest-asyncio>=0.24",
|
|
"pytest-httpx>=0.34",
|
|
"pytest-timeout>=2.4.0",
|
|
"respx>=0.21",
|
|
"ruff>=0.8",
|
|
"types-jsonschema>=4.26.0.20260508",
|
|
"types-pyyaml>=6.0.12.20260510",
|
|
]
|