Phase 3.5a(classifier+refusal gate+grounding) 위에 4개 Item 추가: Item 0: ask_events telemetry 배선 - AskEvent ORM 모델 + record_ask_event() — ask_events INSERT 완성 - defense_layers에 input_snapshot(query, chunks, answer) 저장 - refused/normal 두 경로 모두 telemetry 호출 Item 3: evidence 간 numeric conflict detection - 동일 단위 다른 숫자 → weak flag - "이상/이하/초과/미만" threshold 표현 → skip (FP 방지) Item 4: fabricated_number normalization 개선 - 단위 접미사 건/원 추가, 범위 표현(10~20%) 양쪽 추출 - bare number 2자리 이상만 (1자리 FP 제거) Item 1: exaone semantic verifier (판단권 잠금 배선) - verifier_service.py — 3s timeout, circuit breaker, severity 3단계 - direct_negation만 strong, numeric/intent→medium, 나머지→weak - verifier strong 단독 refuse 금지 — grounding과 교차 필수 - 6-tier re-gate (4라운드 리뷰 확정) - grounding strong 2+ OR max_score<0.2 → verifier skip Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
39 lines
1.7 KiB
Python
39 lines
1.7 KiB
Python
"""ask_events 테이블 ORM — /ask 호출 관측 (Phase 3.5a migration 102, Phase 3.5b 배선)
|
|
|
|
threshold calibration + verifier FP 분석 + defense layer 디버깅 데이터.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, String, Text
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from core.database import Base
|
|
|
|
|
|
class AskEvent(Base):
|
|
__tablename__ = "ask_events"
|
|
|
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
query: Mapped[str] = mapped_column(Text, nullable=False)
|
|
user_id: Mapped[int | None] = mapped_column(
|
|
BigInteger, ForeignKey("users.id", ondelete="SET NULL")
|
|
)
|
|
completeness: Mapped[str | None] = mapped_column(Text) # full / partial / insufficient
|
|
synthesis_status: Mapped[str | None] = mapped_column(Text)
|
|
confidence: Mapped[str | None] = mapped_column(Text) # high / medium / low
|
|
refused: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
classifier_verdict: Mapped[str | None] = mapped_column(Text) # sufficient / insufficient
|
|
max_rerank_score: Mapped[float | None] = mapped_column(Float)
|
|
aggregate_score: Mapped[float | None] = mapped_column(Float)
|
|
hallucination_flags: Mapped[list[Any] | None] = mapped_column(JSONB, default=list)
|
|
evidence_count: Mapped[int | None] = mapped_column(Integer)
|
|
citation_count: Mapped[int | None] = mapped_column(Integer)
|
|
defense_layers: Mapped[dict[str, Any] | None] = mapped_column(JSONB)
|
|
total_ms: Mapped[int | None] = mapped_column(Integer)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now, nullable=False
|
|
)
|