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>
This commit is contained in:
@@ -56,3 +56,8 @@ class AnalyzeEvent(Base):
|
||||
# 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)
|
||||
|
||||
@@ -63,6 +63,10 @@ async def record_analyze_event(
|
||||
tier: str | None = None,
|
||||
escalated_to_26b: bool | None = None,
|
||||
suppressed_reason: str | None = None,
|
||||
# PR-B B-2 — /ask 3-state answerability
|
||||
answerability: str | None = None,
|
||||
partial_basis: bool | None = None,
|
||||
suggested_query_count: int | None = None,
|
||||
) -> None:
|
||||
"""analyze_events INSERT. background task에서 호출 — 에러 삼킴.
|
||||
|
||||
@@ -96,6 +100,9 @@ async def record_analyze_event(
|
||||
shadow_would_route_to=shadow_would_route_to,
|
||||
tier=tier,
|
||||
suppressed_reason=suppressed_reason,
|
||||
answerability=answerability,
|
||||
partial_basis=partial_basis,
|
||||
suggested_query_count=suggested_query_count,
|
||||
)
|
||||
session.add(row)
|
||||
await session.commit()
|
||||
|
||||
@@ -17,7 +17,10 @@ from __future__ import annotations
|
||||
|
||||
# ─── ask (/search/ask) 프롬프트 버전 ─────────────────────────
|
||||
# synthesis_service.py 가 로드하는 app/prompts/search_synthesis.txt 기준
|
||||
ASK_PROMPT_VERSION: str = "search_synthesis.v2-600char"
|
||||
# v3-evidence-triage: evidence 추출을 triage(4B Ollama) 로 전환 (B-2). synthesis 는
|
||||
# 여전히 primary(26B MLX) 로 search_synthesis.txt 사용. 프롬프트 자체는 v2-600char
|
||||
# 그대로지만 evidence LLM 경로 변경을 분리 추적하기 위해 bump.
|
||||
ASK_PROMPT_VERSION: str = "search_synthesis.v3-evidence-triage"
|
||||
|
||||
# ─── /analyze 프롬프트 버전 ──────────────────────────────────
|
||||
# documents.py analyze 라우트가 로드하는 app/prompts/document_analyze.txt 기준
|
||||
|
||||
@@ -25,10 +25,10 @@ EvidenceItem 리스트
|
||||
|
||||
## 영구 룰
|
||||
|
||||
- **LLM 호출은 1번만** (batched). 순차 호출 절대 금지 — MLX single-inference
|
||||
큐가 폭발한다.
|
||||
- **모든 MLX 호출은 `get_mlx_gate()` 경유**. analyzer / synthesis 와 동일
|
||||
semaphore 공유.
|
||||
- **LLM 호출은 1번만** (batched). 순차 호출 절대 금지.
|
||||
- **B-2 변경**: evidence 추출은 triage(4B Ollama) 로 전환 — Ollama 는 concurrent
|
||||
OK 라 `get_mlx_gate()` 불필요. primary(26B MLX) 는 synthesis 전용 보호.
|
||||
- 기존 analyzer / synthesis 의 `get_mlx_gate()` 공유는 유지 — 26B 경로에만 적용.
|
||||
- **fallback span 도 query 중심 window**. `full_snippet[:200]` 같은 "앞에서부터
|
||||
자르기" 절대 금지. 조용한 품질 붕괴 (citation 은 멀쩡한데 실제 span 이 query
|
||||
와 무관) 대표 사례.
|
||||
@@ -57,7 +57,6 @@ from typing import TYPE_CHECKING
|
||||
from ai.client import AIClient, _load_prompt, parse_json_response
|
||||
from core.utils import setup_logger
|
||||
|
||||
from .llm_gate import get_mlx_gate
|
||||
from .rerank_service import _extract_window
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -78,7 +77,7 @@ SPAN_ENLARGE_TARGET = 120 # enlarge 시 재윈도우 target_chars
|
||||
SPAN_MAX_CHARS = 300 # 이 초과면 cut (synthesis token budget 보호)
|
||||
|
||||
LLM_TIMEOUT_MS = 15000
|
||||
PROMPT_VERSION = "v1"
|
||||
PROMPT_VERSION = "v2-triage" # B-2: primary(26B MLX) → triage(4B Ollama) 전환
|
||||
|
||||
# 확장 여지 — None 이면 비활성 (baseline). 실측 후 0.8 등으로 켠다.
|
||||
EVIDENCE_FAST_PATH_THRESHOLD: float | None = None
|
||||
@@ -308,10 +307,10 @@ async def extract_evidence(
|
||||
llm_error: str | None = None
|
||||
|
||||
try:
|
||||
# ⚠ semaphore 대기는 timeout 바깥. timeout 은 실제 LLM 호출에만.
|
||||
async with get_mlx_gate():
|
||||
# B-2: evidence 추출은 4B triage (Ollama concurrent OK) — MLX gate 경유 불필요.
|
||||
# primary(26B) 는 synthesis 전용으로 MLX gate 보호.
|
||||
async with asyncio.timeout(LLM_TIMEOUT_MS / 1000):
|
||||
raw = await ai_client._call_chat(ai_client.ai.primary, prompt)
|
||||
raw = await ai_client.call_triage(prompt)
|
||||
except asyncio.TimeoutError:
|
||||
llm_error = "timeout"
|
||||
except Exception as exc:
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
-- 161_analyze_events_answerability.sql
|
||||
-- PR-B B-2: /ask 의 3-state completeness (direct/partial/insufficient) 독립 컬럼화.
|
||||
-- plan: ~/.claude/plans/swirling-swimming-liskov.md §B-2
|
||||
--
|
||||
-- 기존 /ask 응답의 completeness 필드(이미 full/partial/insufficient 3-state 로
|
||||
-- 운영 중인 Phase 3.5a 결과)를 analyze_events 에서도 독립 컬럼으로 집계 가능하게.
|
||||
-- mode 컬럼 문자열 파싱 회피 + "Backlog Suppression" 카드와 동일 패턴.
|
||||
--
|
||||
-- answerability 값 매핑:
|
||||
-- /ask completeness='full' → 'direct'
|
||||
-- /ask completeness='partial' → 'partial'
|
||||
-- /ask completeness='insufficient' → 'insufficient'
|
||||
--
|
||||
-- partial_basis: synthesis 가 partial 답변 본문을 실제로 생성했는지 (unanswered_aspects
|
||||
-- 를 답변 뒤에 명시). completeness=partial 이어도 synthesis 가 스킵되면 false.
|
||||
-- suggested_query_count: insufficient 때 사용자에게 돌려주는 추가 검색어 제안 개수.
|
||||
|
||||
ALTER TABLE analyze_events
|
||||
ADD COLUMN IF NOT EXISTS answerability TEXT,
|
||||
ADD COLUMN IF NOT EXISTS partial_basis BOOLEAN,
|
||||
ADD COLUMN IF NOT EXISTS suggested_query_count INTEGER;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 162_analyze_events_answerability_idx.sql
|
||||
-- PR-B B-2: answerability 분포 조회 인덱스 (대시보드 "에스컬레이션 비율" 카드).
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_analyze_events_answerability
|
||||
ON analyze_events (answerability, created_at DESC)
|
||||
WHERE answerability IS NOT NULL;
|
||||
Reference in New Issue
Block a user