Files
hyungi_document_server/app/models/analyze_event.py
T
Hyungi Ahn 34f79f84f2 feat(search): B-2 evidence LLM → 4B triage 전환 + answerability 컬럼
Plan 본래 의도: 근거 선별은 4B, 합성은 26B.

- evidence_service: LLM 호출을 primary(26B MLX) → triage(4B Ollama) 로 전환.
  Ollama concurrent 가능하므로 get_mlx_gate() 제거. synthesis 는 여전히
  llm_gate Semaphore(1) 경유로 MLX 보호.
- prompt_version v3-evidence-triage bump (synthesis 프롬프트 자체는 v2-600char
  그대로, evidence LLM 경로 변경을 분리 추적).
- migrations 161/162: analyze_events 에 answerability / partial_basis /
  suggested_query_count 컬럼 + partial index. /ask 는 이미 ask_events 에
  completeness (full/partial/insufficient) 기록 운영 중이므로, analyze_events
  쪽은 향후 문서 분석에서 answerability 개념 도입 시 활용 예비.
- telemetry record_analyze_event 에 answerability / partial_basis /
  suggested_query_count 파라미터 확장.

기존 /ask 3-state completeness 로직 (classifier_service + 7-tier gate) 은
그대로 유지 — 이미 Phase 3.5a 에서 완성된 상태. B-2 는 LLM 부하 재분배와
관측성 확장에 집중.

MLX 부하 감소 효과: 이전엔 쿼리 1건당 evidence(26B) + synthesis(26B) 2번
MLX 호출. 이제는 evidence(4B Ollama) + synthesis(26B MLX) 로 MLX 호출 절반.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:33:32 +09:00

64 lines
3.3 KiB
Python

"""analyze_events 테이블 ORM — POST /documents/{id}/analyze 호출 관측 (Phase E.2)
목적: 분석 failure mode 분류 (timeout / parse / llm / missing_summary) +
source 별 사용 패턴 (document_server / synology_chat / ui_search / ui_detail / eval).
단계 3 snapshot DB 설계 입력이 됨.
"""
from datetime import datetime
from typing import Any
from sqlalchemy import ARRAY, BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
class AnalyzeEvent(Base):
__tablename__ = "analyze_events"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
doc_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("documents.id", ondelete="CASCADE"), nullable=False
)
user_id: Mapped[int | None] = mapped_column(
BigInteger, ForeignKey("users.id", ondelete="SET NULL")
)
mode: Mapped[str] = mapped_column(Text, default="quick", nullable=False) # quick / full / summary_triage / summary_deep / retrieval_select / synthesis
text_limit: Mapped[int | None] = mapped_column(Integer)
truncated: Mapped[bool] = mapped_column(Boolean, default=False)
layers_returned: Mapped[list[Any] | None] = mapped_column(JSONB, default=list)
cached: Mapped[bool] = mapped_column(Boolean, default=False)
latency_ms: Mapped[int | None] = mapped_column(Integer)
model_name: Mapped[str | None] = mapped_column(Text)
prompt_version: Mapped[str | None] = mapped_column(Text)
# None (success) | "timeout" | "llm" | "parse" | "missing_summary" | "no_text"
error_code: Mapped[str | None] = mapped_column(Text)
# document_server / synology_chat / ui_search / ui_detail / eval / unknown
source: Mapped[str] = mapped_column(Text, default="document_server", nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.now, nullable=False
)
# PR-A (migration 153) — routing shadow observability
subject_domain: Mapped[str | None] = mapped_column(Text)
risk_flags: Mapped[list[str] | None] = mapped_column(ARRAY(Text))
high_impact_task: Mapped[bool | None] = mapped_column(Boolean)
escalated_to_26b: Mapped[bool | None] = mapped_column(Boolean)
escalation_reasons: Mapped[list[str] | None] = mapped_column(ARRAY(Text))
confidence: Mapped[float | None] = mapped_column(Float)
policy_violation: Mapped[bool | None] = mapped_column(Boolean)
policy_violation_ids: Mapped[list[str] | None] = mapped_column(ARRAY(Text))
shadow_would_route_to: Mapped[str | None] = mapped_column(Text)
policy_version: Mapped[str | None] = mapped_column(Text)
# PR-B (migration 159) — 실제 호출 tier 와 R2 backlog guard 이벤트
tier: Mapped[str | None] = mapped_column(Text) # 'triage' | 'primary' | 'fallback'
suppressed_reason: Mapped[str | None] = mapped_column(Text) # 'backlog_guard(ratio=0.42,pending=7)'
# PR-B B-2 (migration 161) — /ask 3-state answerability 독립 컬럼
answerability: Mapped[str | None] = mapped_column(Text) # 'direct' | 'partial' | 'insufficient'
partial_basis: Mapped[bool | None] = mapped_column(Boolean) # partial 답변이 실제 생성됐는지
suggested_query_count: Mapped[int | None] = mapped_column(Integer)