8a8096a444
POST /documents/{id}/analyze 호출을 DB에 기록. failure mode 분류 + source 식별.
- migrations/137: analyze_events 테이블 (doc_id FK, mode, truncated, layers_returned JSONB, cached, latency_ms, error_code, source TEXT NOT NULL DEFAULT 'document_server', prompt_version)
- ORM: models/analyze_event.py 신규
- services/document_telemetry.py: record_analyze_event() + sanitize_source() 서버 fallback 강제 (enum 외 → unknown, None → document_server)
- app/api/documents.py:
· X-Source 헤더 + BackgroundTasks 의존성 추가
· try/finally 패턴으로 성공/cache/에러 모든 exit에서 background insert
· error_code: None(성공) | not_found | no_text | timeout | llm | parse | missing_summary
Phase F에서 nanoclaude가 X-Source: synology_chat 헤더로 호출하면 source 구분 가능.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
80 lines
2.2 KiB
Python
80 lines
2.2 KiB
Python
"""document 관련 telemetry — Phase E.2 (analyze_events).
|
|
|
|
/documents/{id}/analyze 호출을 background task로 DB에 기록.
|
|
search_telemetry.py 패턴 동일 (단독 세션 + 에러 흡수).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
from core.database import async_session
|
|
from models.analyze_event import AnalyzeEvent
|
|
|
|
logger = logging.getLogger("document_telemetry")
|
|
|
|
# source enum validation — 서버 강제 fallback
|
|
VALID_SOURCES: set[str] = {
|
|
"document_server",
|
|
"synology_chat",
|
|
"ui_search",
|
|
"ui_detail",
|
|
"eval",
|
|
"unknown",
|
|
}
|
|
DEFAULT_SOURCE = "document_server"
|
|
|
|
|
|
def sanitize_source(raw: str | None) -> str:
|
|
"""source 값 서버 강제. enum 외 값은 unknown, None은 document_server."""
|
|
if raw is None:
|
|
return DEFAULT_SOURCE
|
|
lowered = raw.strip().lower()
|
|
if lowered in VALID_SOURCES:
|
|
return lowered
|
|
return "unknown"
|
|
|
|
|
|
async def record_analyze_event(
|
|
doc_id: int,
|
|
user_id: int | None,
|
|
mode: str,
|
|
text_limit: int | None,
|
|
truncated: bool,
|
|
layers_returned: list[str],
|
|
cached: bool,
|
|
latency_ms: int,
|
|
model_name: str | None,
|
|
prompt_version: str | None,
|
|
error_code: str | None,
|
|
source: str,
|
|
) -> None:
|
|
"""analyze_events INSERT. background task에서 호출 — 에러 삼킴.
|
|
|
|
layers_returned: 성공 시 ["evidence","summary"] 등 layer 문자열 리스트. 실패 시 [].
|
|
error_code: None (성공) | "timeout" | "llm" | "parse" | "missing_summary" | "no_text" | "not_found"
|
|
"""
|
|
try:
|
|
async with async_session() as session:
|
|
row = AnalyzeEvent(
|
|
doc_id=doc_id,
|
|
user_id=user_id,
|
|
mode=mode,
|
|
text_limit=text_limit,
|
|
truncated=truncated,
|
|
layers_returned=layers_returned,
|
|
cached=cached,
|
|
latency_ms=latency_ms,
|
|
model_name=model_name,
|
|
prompt_version=prompt_version,
|
|
error_code=error_code,
|
|
source=source,
|
|
)
|
|
session.add(row)
|
|
await session.commit()
|
|
except SQLAlchemyError as exc:
|
|
logger.warning(f"analyze_event insert failed: {exc}")
|