Commit Graph

6 Commits

Author SHA1 Message Date
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
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
Hyungi Ahn 99672292d3 fix(policy): use container-compatible imports (drop app. prefix)
프로덕션 컨테이너는 /app 을 cwd 로 실행하고 import 는 `from api...`,
`from core...`, `from workers...` 처럼 무접두 스타일을 사용한다.
PR-A 내부 import 가 `from app.policy...`, `from app.ai.envelope` 로
되어 있어서 컨테이너에서 ModuleNotFoundError 발생.

변경:
- app/policy/*.py: `from app.policy.X` → `from policy.X`
- app/services/prompt_versions.py: lazy import 도 `from policy.prompt_render`
- app/ai/envelope.py: 영향 없음 (내부 import 없음)
- tests/policy/*.py: 모두 `from policy.X` / `from ai.envelope` 로 통일
- tests/policy/conftest.py: 로컬 pytest 용 sys.path.insert(app/) 추가
  (MacBook 에서 repo-root 기준 실행 시 app/ 를 package root 로 취급)

CI: pytest tests/policy/ -q → 98 passed (로컬, 동일 결과)
프로덕션: docker exec fastapi python -c "from policy.loader import load_policy" → OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 09:42:24 +09:00
Hyungi Ahn f51583f9d6 feat(policy): prompt_render + policy_version hash
app/policy/prompt_render.py:
- render_4b(task, subject) / render_26b(task, subject) — template + yaml
  excerpt 주입. {forbidden_block} / {subject_description} /
  {confidence_threshold} / {context_cap} placeholder 치환.
- policy_version(task) → sha256(yaml_bytes + template_bytes)[:12].
  deterministic — yaml 이나 template 이 바뀌면 hash 변경, analyze_events.
  policy_version 컬럼으로 drift 추적.
- KNOWN_4B_TASKS / KNOWN_26B_TASKS — 잘못된 task 호출 ValueError.
- 미정의 subject_domain 은 fallback_domain.description 사용.

app/services/prompt_versions.py:
- compute_policy_version(task) helper 추가. app.policy 지연 import 로
  worker 경로에 정책 dependency 유입 방지 (런타임 격리).
- 기존 ASK_PROMPT_VERSION / ANALYZE_PROMPT_VERSION 상수 미변경.

plan: ~/.claude/plans/wise-gliding-hippo.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 09:34:48 +09:00
Hyungi Ahn eb9dc94604 feat(search): E.3 — ask synthesis prompt v2-600char bump
한도 400 → 600 자. baseline 관찰(partial avg 168자 / full 10%)에서
길이 제약이 실제 출력 제약이 되는 현상 확인, 절차·비교 카테고리
답변 깊이 확보 목적.

변경 4 라인:
- search_synthesis.txt:17  answer 400→600 characters max
- prompt_versions.py:20    v1-400char → v2-600char (telemetry)
- synthesis_service.py:42  PROMPT_VERSION v1→v2 (cache key 의미론 동기화)
- synthesis_service.py:46  MAX_ANSWER_CHARS 400→600 (hard clip 동기화)

v1 post-tier0 baseline: 225 rows, partial 51% / insufficient 49% / full 0%
(Tier 0 fix 로 full+refused=True 모순 0 건). E.6 는 이 clean baseline 을
compare-against 로 사용.

향후 티켓: PROMPT_VERSION 과 ASK_PROMPT_VERSION 단일 소스 통합.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:02:51 +09:00
Hyungi Ahn 59e38d80b0 feat(api): Phase E.1 — ask_events 측정 필드 확장 (answer_length/prompt_version)
E.3 400→600자 튜닝 전후 비교 + 단계 5 failure mode 분석의 기준 필드 추가.

- migrations/135: answer_length/covered_aspects/missing_aspects/model_name/prompt_version 컬럼 + prompt_version 인덱스
- ORM: ask_event.py에 동일 5개 필드 매핑
- prompt_versions.py: ASK_PROMPT_VERSION="search_synthesis.v1-400char" 상수 + resolve_primary_model() helper
- search_telemetry.record_ask_event: 시그니처에 keyword-only 필드 5개 추가 (하위 호환)
- search.py: refused + success 두 호출사이트에서 새 필드 전달. answer_length는 len(sr.answer or ""), model_name/prompt_version은 상수 모듈 기반

기존 호출 구조(이미 search_telemetry+background_tasks로 DB insert 중)는 유지. 순수 확장 커밋.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:52:14 +09:00