Files
gpu-services/nanoclaude/db/database.py
Hyungi Ahn 6b36063010 feat: Conversation sqlite 영구화 — 재시작에도 컨텍스트 유지
- db/database.py: conversation_messages 테이블 + save/load/cleanup 헬퍼
- conversation.py: write-through (memory + DB) + lazy load on first access
  - 메모리 캐시 1시간 TTL, DB 7일 보관
  - add/get/format_for_prompt가 async로 변경
- worker.py: 모든 conversation_store 호출에 await 추가
- main.py lifespan에 startup cleanup 호출 (7일 이상 정리)

서버 재시작 후 "방금 그거 더 자세히" 같은 후속 질문이 컨텍스트 유지

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:02:38 +09:00

127 lines
4.4 KiB
Python

"""aiosqlite DB — 요청/응답 로깅 + 대화 영구화."""
from time import time
import aiosqlite
from config import settings
SCHEMA = """
CREATE TABLE IF NOT EXISTS request_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_id TEXT NOT NULL,
message TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued',
model TEXT NOT NULL,
response_chars INTEGER DEFAULT 0,
latency_ms REAL DEFAULT 0,
created_at REAL NOT NULL,
completed_at REAL,
rewrite_model TEXT,
reasoning_model TEXT,
rewritten_message TEXT,
rewrite_latency_ms REAL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_logs_job ON request_logs(job_id);
CREATE INDEX IF NOT EXISTS idx_logs_created ON request_logs(created_at);
CREATE TABLE IF NOT EXISTS conversation_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at REAL NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_conv_user ON conversation_messages(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_conv_created ON conversation_messages(created_at);
"""
# Phase 1 → Phase 2 마이그레이션 (이미 존재하면 무시)
MIGRATIONS = [
"ALTER TABLE request_logs ADD COLUMN rewrite_model TEXT",
"ALTER TABLE request_logs ADD COLUMN reasoning_model TEXT",
"ALTER TABLE request_logs ADD COLUMN rewritten_message TEXT",
"ALTER TABLE request_logs ADD COLUMN rewrite_latency_ms REAL DEFAULT 0",
]
async def init_db():
async with aiosqlite.connect(settings.db_path) as db:
await db.execute("PRAGMA journal_mode=WAL")
await db.executescript(SCHEMA)
for migration in MIGRATIONS:
try:
await db.execute(migration)
except Exception:
pass # 이미 존재하는 컬럼
await db.commit()
async def log_request(job_id: str, message: str, model: str, created_at: float):
async with aiosqlite.connect(settings.db_path) as db:
await db.execute(
"INSERT INTO request_logs (job_id, message, model, created_at) VALUES (?, ?, ?, ?)",
(job_id, message, model, created_at),
)
await db.commit()
async def save_conversation_message(user_id: str, role: str, content: str, created_at: float):
"""대화 메시지 저장."""
async with aiosqlite.connect(settings.db_path) as db:
await db.execute(
"INSERT INTO conversation_messages (user_id, role, content, created_at) VALUES (?, ?, ?, ?)",
(user_id, role, content, created_at),
)
await db.commit()
async def load_conversation_messages(user_id: str, since: float, limit: int = 40) -> list[dict]:
"""user_id의 최근 메시지 로드 (오래된 순)."""
async with aiosqlite.connect(settings.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute(
"SELECT role, content, created_at FROM conversation_messages "
"WHERE user_id=? AND created_at >= ? ORDER BY created_at DESC LIMIT ?",
(user_id, since, limit),
)
rows = await cursor.fetchall()
# 오래된 순으로 뒤집기
return [{"role": r["role"], "content": r["content"], "created_at": r["created_at"]} for r in reversed(rows)]
async def cleanup_old_conversations(days: int = 7) -> int:
"""N일 이상 오래된 대화 삭제. 삭제된 행 수 반환."""
cutoff = time() - (days * 86400)
async with aiosqlite.connect(settings.db_path) as db:
cursor = await db.execute("DELETE FROM conversation_messages WHERE created_at < ?", (cutoff,))
await db.commit()
return cursor.rowcount
async def log_completion(
job_id: str,
status: str,
response_chars: int,
latency_ms: float,
completed_at: float,
*,
rewrite_model: str | None = None,
reasoning_model: str | None = None,
rewritten_message: str | None = None,
rewrite_latency_ms: float = 0,
):
async with aiosqlite.connect(settings.db_path) as db:
await db.execute(
"""UPDATE request_logs
SET status=?, response_chars=?, latency_ms=?, completed_at=?,
rewrite_model=?, reasoning_model=?, rewritten_message=?, rewrite_latency_ms=?
WHERE job_id=?""",
(status, response_chars, latency_ms, completed_at,
rewrite_model, reasoning_model, rewritten_message, rewrite_latency_ms,
job_id),
)
await db.commit()