Files
hyungi_document_server/app/services/prompt_versions.py
T
Hyungi Ahn 6fdc48e5b6 feat(ai): B-1 summary tier 분할 — triage(4B) + deep_summary(26B)
PR-A policy 레이어를 재사용하여 classify_worker 에 tier triage 경로를 추가.
Legacy ai_summary / ai_domain / ai_suggestion 은 유지 (회귀 0), tldr/bullets/
detail/inconsistencies 는 별도 필드로 분리.

Migrations (156~160):
- 156 documents: ai_tldr, ai_bullets, ai_detail_summary, ai_inconsistencies,
  ai_analysis_tier 5컬럼
- 157 process_stage 에 'deep_summary' ADD VALUE 단독 (Postgres 동일 트랜잭션
  제약 회피)
- 158 processing_queue.payload JSONB (envelope 전달)
- 159 analyze_events 에 tier + suppressed_reason
- 160 suppressed_reason partial index

Models/ORM:
- Document: 5컬럼 Mapped 추가
- ProcessingQueue: deep_summary enum 확장 + payload 필드, enqueue_stage 에
  payload 옵션
- AnalyzeEvent: PR-A shadow 6컬럼 + PR-B tier/suppressed_reason

Workers:
- classify_worker: 기존 legacy 경로 뒤에 _run_tier_triage 추가.
  - _match_subject_domain(doc, text): source_channel + 본문 keywords + ai_domain
    prefix 로 PR-A policy 의 subject_domain 이름 결정 (category 매칭 금지).
  - R1 TriageOutput pydantic + JSON 깨짐 fallback (triage_json_invalid).
  - R2 _check_backlog_guard(): 30분 window ratio > threshold OR pending 초과면
    soft escalate suppress. hard escalate 는 통과.
  - R3 _slice_text_ranges(): 260k 초과 시 head 120k + mid 20k + tail 120k 3조각.
  - escalate 시 EscalationEnvelope 구성 + {envelope, subject_domain} payload 로
    deep_summary enqueue.
- deep_summary_worker (신규): queue payload 에서 envelope + subject_domain 읽기 →
  render_26b("p3c_deep_summary", subject_domain) + MLX 호출 (llm_gate Semaphore(1)
  경유) → ai_detail_summary + ai_inconsistencies 저장 + ai_analysis_tier='deep'.
  _filter_inconsistencies 로 허용 kind (version_drift / procedure_conflict /
  source_conflict / missing_basis) 만 통과 — 구매/계약 kind drop.
- queue_consumer: workers dict 에 deep_summary 추가 + BATCH_SIZE=1. next_stages
  는 건드리지 않음 — classify → embed/chunk 는 그대로, deep_summary 는 독립 체인.

Telemetry:
- record_analyze_event: subject_domain / risk_flags / escalation_reasons /
  confidence / policy_version / shadow_would_route_to / tier / escalated_to_26b /
  suppressed_reason 파라미터 확장. classify/deep worker 가 mode="summary_triage"
  또는 "summary_deep" 로 기록.

API:
- DocumentResponse 에 ai_tldr / ai_bullets / ai_detail_summary /
  ai_inconsistencies / ai_analysis_tier 5필드 노출.

Prompts:
- classify.txt 에 DEPRECATED 주석만 추가 (파일 유지 — rollback 경로 보존).
- PR-A 의 app/prompts/policy/p3a_short_summary.txt (4B) 와 p3c_deep_summary.txt
  (26B) 를 그대로 사용. 내 소유의 summary_triage.txt / summary_deep.txt 는 중복
  이라 별도 커밋에서 제거하지 않고 바로 생성 전 삭제.

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

69 lines
3.1 KiB
Python

"""프롬프트/모델 버전 상수 — telemetry 기록용 (Phase E.1)
목적: ask_events / analyze_events 에 prompt_version 과 model_name 을 기록해서
튜닝 전/후 비교와 실험 분기를 식별 가능하게 함.
규칙:
- 프롬프트 파일이 의미 있게 바뀌면 해당 상수 문자열을 bump (예: v1-400char → v2-600char)
- 하드코딩 금지. 파이프라인은 여기 상수만 참조.
- 모델명은 런타임 config(settings.ai.primary.model)에서 읽어서 resolve_primary_model() 사용.
E.3 배포 타임라인:
- v1-400char → 현재 (search_synthesis.txt 17행 "400 characters max")
- v2-600char → E.3 배포 시 bump (동일 파일 "600 characters max")
"""
from __future__ import annotations
# ─── ask (/search/ask) 프롬프트 버전 ─────────────────────────
# synthesis_service.py 가 로드하는 app/prompts/search_synthesis.txt 기준
ASK_PROMPT_VERSION: str = "search_synthesis.v2-600char"
# ─── /analyze 프롬프트 버전 ──────────────────────────────────
# documents.py analyze 라우트가 로드하는 app/prompts/document_analyze.txt 기준
ANALYZE_PROMPT_VERSION: str = "document_analyze.v1"
# ─── PR-B B-1: summary tier 분할 task 이름 ─────────────────────
# classify_worker / deep_summary_worker 가 PR-A 정책 템플릿 + policy_version 해시
# 조합으로 analyze_events.prompt_version 을 기록한다. (예: "p3a_short_summary@abc123")
SUMMARY_TRIAGE_TASK: str = "p3a_short_summary" # 4B gemma Ollama
SUMMARY_DEEP_TASK: str = "p3c_deep_summary" # 26B MLX
def resolve_primary_model() -> str | None:
"""런타임 config에서 primary 모델명을 resolve.
settings.ai 가 미구성이면 None.
telemetry 기록은 None 허용 (측정 필드는 nullable).
"""
try:
from core.config import settings
if settings.ai and settings.ai.primary:
return settings.ai.primary.model
except Exception:
pass
return None
# ─── Policy-layer prompt version helper (PR-A) ──────────────────────
# domain_policy.yaml + 정책 template 의 결합 해시로 automatic version 산출.
# analyze_events.policy_version 컬럼에 기록되어 drift 추적.
#
# 기존 ASK_PROMPT_VERSION / ANALYZE_PROMPT_VERSION 상수는 그대로 유지 — PR-B 에서
# 정책 렌더된 프롬프트로 전환 시 compute_policy_version() 결과로 대체할지 병기할지 결정.
def compute_policy_version(
task: str, *, policy_path: str | None = None
) -> str:
"""sha256(yaml_bytes + template_bytes)[:12] — deterministic hash.
task: policy template 이름 (예: 'p3a_short_summary'). app/prompts/policy/ 하위.
policy_path: override (테스트용). None 이면 loader 기본값.
import 지연 — app.policy 는 아직 worker 경로에서 쓰지 않는다 (PR-A 런타임 격리).
"""
from policy.prompt_render import policy_version as _pv
return _pv(task, policy_path=policy_path)