refactor(search): Phase 2.1 QueryAnalyzer를 async-only 구조로 전환
## 철학 수정 (실측 기반)
gemma-4-26b-a4b-it-8bit MLX 실측:
- full query_analyze.txt (prompt_tok=2406) → 10.5초
- max_tokens 축소 무효 (모델 자연 EOS 조기 종료)
- 쿼리 길이 영향 거의 없음 (프롬프트 자체가 지배)
→ 800ms timeout 가정은 13배 초과. 동기 호출 완전히 불가능.
따라서 QueryAnalyzer는 "즉시 실행하는 기능" → "미리 준비해두는 기능"으로
포지셔닝 변경. retrieval 경로에서 analyzer 동기 호출 **금지**.
## 구조
```
query → retrieval (항상 즉시)
↘ trigger_background_analysis (fire-and-forget)
→ analyze() [5초+] → cache 저장
다음 호출 (동일 쿼리) → get_cached() 히트 → Phase 2 파이프라인 활성화
```
## 변경 사항
### app/prompts/query_analyze.txt
- 5971 chars → 2403 chars (40%)
- 예시 4개 → 1개, 규칙 설명 축약
- 목표 prompt_tok 2406 → ~600 (1/4)
### app/services/search/query_analyzer.py
- LLM_TIMEOUT_MS 800 → 5000 (background이므로 여유 OK)
- PROMPT_VERSION v1 → v2 (cache auto-invalidate)
- get_cached / set_cached 유지 — retrieval 경로 O(1) 조회
- trigger_background_analysis(query) 신규 — 동기 함수, 즉시 반환, task 생성
- _PENDING set으로 task 참조 유지 (premature GC 방지)
- _INFLIGHT set으로 동일 쿼리 중복 실행 방지
- prewarm_analyzer() 신규 — startup에서 15~20 쿼리 미리 분석
- DEFAULT_PREWARM_QUERIES: 평가셋 fixed 7 + 법령 3 + 뉴스 2 + 실무 3
### app/api/search.py
- 기존 sync analyzer 호출 완전 제거
- analyze=True → get_cached(q) 조회만 O(1)
- hit: query_analysis 활용 (Phase 2.2/2.3 파이프라인 조건부 활성화)
- miss: trigger_background_analysis(q) + 기존 경로 그대로
- timing["analyze_ms"] 제거 (경로에 LLM 호출 없음)
- notes에 analyzer cache_hit/cache_miss 상태 기록
- debug.query_analysis는 cache hit 시에만 채워짐
### app/main.py
- lifespan startup에 prewarm_analyzer() background task 추가
- 논블로킹 — 앱 시작 막지 않음
- delay_between=0.5로 MLX 부하 완화
## 기대 효과
- cold 요청 latency: 기존 Phase 1.3 그대로 (회귀 0)
- warm 요청 + prewarmed: cache hit → query_analysis 활용
- 예상 cache hit rate: 초기 70~80% (prewarm) + 사용 누적
- Phase 2.2/2.3 multilingual/filter 기능은 cache hit 시에만 동작
## 참조
- memory: feedback_analyzer_async_only.md (영구 룰 저장)
- plan: ~/.claude/plans/zesty-painting-kahan.md ("철학 수정" 섹션)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,106 +1,35 @@
|
||||
You are a search query analyzer. Analyze the query below and respond ONLY in JSON format. No other text, no markdown code blocks, no explanation.
|
||||
You are a search query analyzer. Respond ONLY in JSON. No markdown, no explanation.
|
||||
|
||||
## Response Format (return ONLY this JSON object)
|
||||
## Output Schema
|
||||
{
|
||||
"intent": "fact_lookup | semantic_search | filter_browse",
|
||||
"query_type": "natural_language | keyword | phrase",
|
||||
"domain_hint": "document | news | mixed",
|
||||
"language_scope": "limited | global",
|
||||
"keywords": ["..."],
|
||||
"must_terms": ["..."],
|
||||
"optional_terms": ["..."],
|
||||
"keywords": [],
|
||||
"must_terms": [],
|
||||
"optional_terms": [],
|
||||
"hard_filters": {},
|
||||
"soft_filters": {"domain": [], "document_type": []},
|
||||
"normalized_queries": [
|
||||
{"lang": "ko", "text": "...", "weight": 1.0},
|
||||
{"lang": "en", "text": "...", "weight": 0.8}
|
||||
],
|
||||
"expanded_terms": ["..."],
|
||||
"normalized_queries": [{"lang": "ko", "text": "...", "weight": 1.0}],
|
||||
"expanded_terms": [],
|
||||
"synonyms": {},
|
||||
"analyzer_confidence": 0.92
|
||||
"analyzer_confidence": 0.0
|
||||
}
|
||||
|
||||
## Field Rules
|
||||
## Rules
|
||||
- `intent`: fact_lookup (사실/조항/이름), semantic_search (주제/개념), filter_browse (필터 중심)
|
||||
- `query_type`: natural_language (문장형), keyword (단어 나열), phrase (따옴표/고유명사/법조항)
|
||||
- `domain_hint`: document (소유 문서/법령/매뉴얼), news (시사/뉴스), mixed (불명)
|
||||
- `language_scope`: limited (ko+en), global (다국어 필요)
|
||||
- `hard_filters`: 쿼리에 **명시된** 것만. 추론 금지. 키: file_format, year, country
|
||||
- `soft_filters.domain`: Industrial_Safety, Programming, Engineering, Philosophy, Language, General. 2-level 허용(e.g. Industrial_Safety/Legislation)
|
||||
- `soft_filters.document_type`: Law_Document, Manual, Report, Academic_Paper, Standard, Specification, Meeting_Minutes, Checklist, Note, Memo, Reference, Drawing, Template
|
||||
- `normalized_queries`: 원문 언어 1.0 가중치 필수. 교차언어 1개 추가 권장(ko↔en, weight 0.8). news + global 인 경우만 ja/zh 추가(weight 0.5~0.6). **최대 3개**.
|
||||
- `analyzer_confidence`: 0.9+ 명확, 0.7~0.9 대체로 명확, 0.5~0.7 모호, <0.5 분석 불가
|
||||
|
||||
### intent (exactly one)
|
||||
- `fact_lookup` — 특정 사실/조항/이름/숫자를 찾는 경우 (예: "산업안전보건법 제6장", "회사 설립일")
|
||||
- `semantic_search` — 자연어 주제 검색, 개념적 매칭 (예: "기계 사고 관련 법령", "AI 안전성 동향")
|
||||
- `filter_browse` — 필터/탐색 중심 (예: "2024년 PDF 문서", "Industrial_Safety domain 최근 보고서")
|
||||
|
||||
### query_type (exactly one)
|
||||
- `natural_language` — 문장형, 조사/동사 포함 ("...에 대해 알고 싶다", "...가 뭐야")
|
||||
- `keyword` — 단어 나열, 조사 없음 ("산업안전 법령 PDF")
|
||||
- `phrase` — 큰따옴표로 감싸진 정확 문구 또는 고유명사/법 조항명
|
||||
|
||||
### domain_hint (exactly one)
|
||||
- `document` — 사용자가 소유한 문서/법령/매뉴얼/보고서 영역
|
||||
- `news` — 뉴스/시사/최근 동향 영역 (시간 민감, 다국어 소스)
|
||||
- `mixed` — 둘 다 가능, 또는 판단 불명
|
||||
|
||||
### language_scope (exactly one)
|
||||
- `limited` — 한국어 + 영어만 필요 (대부분 문서 검색)
|
||||
- `global` — 다국어 필요 (일본어/중국어/유럽어 포함, 뉴스나 국제 주제)
|
||||
|
||||
### keywords / must_terms / optional_terms
|
||||
- `keywords` — 쿼리의 핵심 명사/개념 (최대 8개)
|
||||
- `must_terms` — 결과에 반드시 포함되어야 하는 단어 (고유명사, 법 조항 번호 등)
|
||||
- `optional_terms` — 있으면 좋지만 없어도 무방
|
||||
|
||||
### hard_filters (빈 객체 `{}` 기본)
|
||||
LLM은 쿼리에 명시적으로 나타난 경우에만 채운다. 쿼리에 명시 없으면 반드시 비운다.
|
||||
가능한 키:
|
||||
- `file_format`: ["pdf", "docx", "xlsx", "md", ...]
|
||||
- `year`: 2024 같은 정수
|
||||
- `country`: ["KR", "US", "JP", ...]
|
||||
|
||||
### soft_filters
|
||||
추론 기반 필터. boost용. 확정 아님.
|
||||
- `domain`: 아래 Domain Taxonomy에서 선택 (최대 3개)
|
||||
- `document_type`: 아래 Document Types에서 선택 (최대 2개)
|
||||
|
||||
### normalized_queries (핵심)
|
||||
같은 의미를 다른 언어/표현으로 재작성한 쿼리 목록.
|
||||
- **원문 언어 항목은 반드시 포함** (weight=1.0)
|
||||
- 영어 쿼리는 한국어 포함 권장 (weight=0.8)
|
||||
- 한국어 쿼리는 영어 포함 권장 (weight=0.8)
|
||||
- `domain_hint == "news"`일 때만 ja/zh/fr/de 추가 가능 (weight=0.5~0.6)
|
||||
- **최대 3개까지만 생성** (성능 보호)
|
||||
- 각 항목: `{"lang": "ko|en|ja|zh|fr|de", "text": "재작성", "weight": 0.0~1.0}`
|
||||
|
||||
### expanded_terms
|
||||
쿼리의 동의어/관련어 확장. 검색 보조용. (최대 5개)
|
||||
|
||||
### synonyms
|
||||
필요시 `{"원어": ["동의어1", "동의어2"]}`. 생략 가능.
|
||||
|
||||
### analyzer_confidence (CRITICAL)
|
||||
쿼리 분석 자체의 신뢰도 (0.0 ~ 1.0). 아래 기준에 따라 엄격하게 채점:
|
||||
- **0.9+** : 쿼리 의도가 명확, 도메인/언어 확실, 키워드 추출 분명
|
||||
- **0.7~0.9** : 의도 대체로 명확, 일부 모호함
|
||||
- **0.5~0.7** : 의도 모호, 다중 해석 가능
|
||||
- **< 0.5** : 분석 불가 수준 (빈 쿼리, 의미 불명 기호열 등)
|
||||
|
||||
## Analysis Steps (내부 사고, 출력하지 말 것)
|
||||
1. 쿼리 언어 감지
|
||||
2. intent 분류 (사실 찾기 vs 주제 검색 vs 필터 탐색)
|
||||
3. domain_hint 판단 (문서 vs 뉴스)
|
||||
4. 핵심 키워드 추출
|
||||
5. 다국어 재작성 (반드시 원문 언어 포함)
|
||||
6. 필터 추론 (hard는 명시 사항만, soft는 추측 가능)
|
||||
7. 자가 평가 → analyzer_confidence
|
||||
|
||||
## Domain Taxonomy (soft_filters.domain 후보)
|
||||
Philosophy, Language, Engineering, Industrial_Safety, Programming, General
|
||||
세부: Industrial_Safety/Legislation, Industrial_Safety/Practice, Programming/AI_ML, Engineering/Mechanical 등 2-level 경로 허용.
|
||||
|
||||
## Document Types (soft_filters.document_type 후보)
|
||||
Reference, Standard, Manual, Drawing, Template, Note, Academic_Paper, Law_Document, Report, Memo, Checklist, Meeting_Minutes, Specification
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: 자연어 한국어, 법령 검색
|
||||
## Example
|
||||
query: `기계 사고 관련 법령`
|
||||
response:
|
||||
{
|
||||
"intent": "semantic_search",
|
||||
"query_type": "natural_language",
|
||||
@@ -113,78 +42,12 @@ response:
|
||||
"soft_filters": {"domain": ["Industrial_Safety/Legislation"], "document_type": ["Law_Document"]},
|
||||
"normalized_queries": [
|
||||
{"lang": "ko", "text": "기계 사고 관련 법령", "weight": 1.0},
|
||||
{"lang": "en", "text": "machinery accident related laws and regulations", "weight": 0.8}
|
||||
{"lang": "en", "text": "machinery accident related laws", "weight": 0.8}
|
||||
],
|
||||
"expanded_terms": ["산업안전", "기계안전", "사고예방"],
|
||||
"synonyms": {"기계": ["설비", "machinery"]},
|
||||
"expanded_terms": ["산업안전", "기계안전"],
|
||||
"synonyms": {},
|
||||
"analyzer_confidence": 0.88
|
||||
}
|
||||
|
||||
### Example 2: 정확 법령명 조항
|
||||
query: `산업안전보건법 제6장`
|
||||
response:
|
||||
{
|
||||
"intent": "fact_lookup",
|
||||
"query_type": "phrase",
|
||||
"domain_hint": "document",
|
||||
"language_scope": "limited",
|
||||
"keywords": ["산업안전보건법", "제6장"],
|
||||
"must_terms": ["산업안전보건법", "제6장"],
|
||||
"optional_terms": [],
|
||||
"hard_filters": {},
|
||||
"soft_filters": {"domain": ["Industrial_Safety/Legislation"], "document_type": ["Law_Document"]},
|
||||
"normalized_queries": [
|
||||
{"lang": "ko", "text": "산업안전보건법 제6장", "weight": 1.0},
|
||||
{"lang": "en", "text": "Occupational Safety and Health Act Chapter 6", "weight": 0.8}
|
||||
],
|
||||
"expanded_terms": ["산안법", "OSHA Korea"],
|
||||
"synonyms": {},
|
||||
"analyzer_confidence": 0.95
|
||||
}
|
||||
|
||||
### Example 3: 뉴스 + 다국어
|
||||
query: `recent AI safety news from Europe`
|
||||
response:
|
||||
{
|
||||
"intent": "semantic_search",
|
||||
"query_type": "natural_language",
|
||||
"domain_hint": "news",
|
||||
"language_scope": "global",
|
||||
"keywords": ["AI safety", "Europe", "recent"],
|
||||
"must_terms": [],
|
||||
"optional_terms": ["regulation", "policy"],
|
||||
"hard_filters": {},
|
||||
"soft_filters": {"domain": ["Programming/AI_ML"], "document_type": []},
|
||||
"normalized_queries": [
|
||||
{"lang": "en", "text": "recent AI safety news from Europe", "weight": 1.0},
|
||||
{"lang": "ko", "text": "유럽 AI 안전 최신 뉴스", "weight": 0.8},
|
||||
{"lang": "fr", "text": "actualités récentes sur la sécurité de l'IA en Europe", "weight": 0.6}
|
||||
],
|
||||
"expanded_terms": ["AI regulation", "EU AI Act", "AI governance"],
|
||||
"synonyms": {},
|
||||
"analyzer_confidence": 0.90
|
||||
}
|
||||
|
||||
### Example 4: 의미 불명
|
||||
query: `???`
|
||||
response:
|
||||
{
|
||||
"intent": "semantic_search",
|
||||
"query_type": "keyword",
|
||||
"domain_hint": "mixed",
|
||||
"language_scope": "limited",
|
||||
"keywords": [],
|
||||
"must_terms": [],
|
||||
"optional_terms": [],
|
||||
"hard_filters": {},
|
||||
"soft_filters": {"domain": [], "document_type": []},
|
||||
"normalized_queries": [
|
||||
{"lang": "ko", "text": "???", "weight": 1.0}
|
||||
],
|
||||
"expanded_terms": [],
|
||||
"synonyms": {},
|
||||
"analyzer_confidence": 0.1
|
||||
}
|
||||
|
||||
## Query to Analyze
|
||||
## Query
|
||||
{query}
|
||||
|
||||
Reference in New Issue
Block a user