--- status: closed closed_date: 2026-03-21 closed_reason: P0 3건(prefix/threshold/RRF) + P1 2건(청크재분할/라우팅완화) 구현·배포·검증 완료. 다만 P1-5(team_id 단독 grounding)는 2026-03-23 마커 기반 진입으로 롤백됨. --- # 260321 하이브리드 검색 품질 개선 계획 ## 목적 - 260321 리서치에서 확정된 5개 원인을 해소하여 3중 검색이 실제로 동작하는 상태로 만든다. - 260320 적용1 계획의 닫는 조건(샘플 질문 검증 통과)을 달성하기 위한 직접 선행 작업이다. ## 참조 문서 - [260321 하이브리드 검색 keyword recall=0 및 grounding 실패 원인 확정 리서치](../research/rag/260321_하이브리드검색_keyword_recall0_및_grounding_실패_원인확정_리서치.md) - [260320 로빙 다형식문서 RAG 적용1 계획](./260320_로빙_다형식문서_RAG_적용1_계획.md) - [260323 Company X grounding 마커 기반 라우팅 복원 및 fallback 복구 검증 완료](../worklog/260323_companyx_grounding_마커기반라우팅복원_및_fallback복구_검증완료.md) ## 원인 → 조치 매핑 | # | 원인 | 조치 | 서버 | 우선순위 | |---|------|------|------|----------| | 1 | `simple` 토크나이저 조사 미분리 | `_build_keyword_tsquery()`에서 각 토큰에 `:*` prefix 접미 | 24 (skill-rag-file) | P0 | | 2 | keyword threshold 0.35가 ts_rank 최대 0.089를 전부 필터링 | keyword_search threshold를 모드별 분리 — keyword 단독 시 0.001, hybrid 내부는 현행 0.0 유지 | 24 (skill-rag-file) | P0 | | 3 | RRF score(0.01~0.03) ≪ threshold(0.35) 스케일 불일치 | hybrid 모드의 relevance_score를 RRF 원점수가 아닌 정규화 점수로 변환. 또는 hybrid용 threshold를 별도 설정 (0.005) | 24 (skill-rag-file) | P0 | | 4 | 쿼리-문서 임베딩 거리 문제 (긴 청크 2,703자 vs 짧은 쿼리) | 청킹 기준(chunk_size=1000) 초과 청크 재분할. 인덱싱 파이프라인에서 기존 초과 청크 탐지 + 재인덱싱 | 23 (DB) + 24 (skill-rag-file) | P1 | | 5 | grounding 라우팅 마커 미매칭 (3건) | Company X team_id 사용자는 마커 없이도 grounding 우선 시도. `should_handle_companyx_grounding()`에서 team_id == COMPANYX_TEAM_ID이면 마커 체크 건너뛰기 | 24 (rb8001) | P1 | ## 작업 상세 ### P0-1: prefix 매칭 적용 (skill-rag-file) - 파일: `app/services/postgres_vector_store.py` → `_build_keyword_tsquery()` - 변경: 각 토큰에 `:*` 접미 - 예: `"아크로셀 | 개인투자조합"` → `"아크로셀:* | 개인투자조합:*"` - 예상 효과: recall 27% → 90% (키워드 '투자' 실측 기준). 단 복합어 내부 매칭(`개인투자조합` → `투자`)은 prefix로도 불가하므로 90%는 상한이지 보장값이 아님 ### P0-2: keyword threshold 분리 (skill-rag-file) - 파일: `app/services/postgres_vector_store.py` → `keyword_search()` - 변경: threshold 파라미터를 keyword용으로 분리. 기본값 0.001 - `search.py`에서 keyword 단독 호출 시 `keyword_threshold` 사용 - hybrid 내부 호출은 현행 0.0 유지 ### P0-3: RRF 점수 정규화 (skill-rag-file) - 파일: `app/services/postgres_vector_store.py` → `_merge_rrf()` - **채택: 옵션 A** — RRF 점수를 0~1로 정규화: `rrf_normalized = rrf_raw / max_rrf_raw` - 정렬과 필터링이 일관되고, threshold 0.35와 스케일이 맞음 - `relevance_score`에 정규화 값을 넣으므로 grounding_service의 정렬도 정상 동작 ### P1-4: 초과 청크 재분할 (23 DB + 24 skill-rag-file) - 현황: 3,204 청크 중 694건(22%)이 1,200자 초과 - 탐지: `SELECT document_id, chunk_index, length(chunk_text) FROM team_document_chunk WHERE length(chunk_text) > 1200 AND team_id = '79441171-...'` - 해당 문서를 chunk_size=1000, overlap=200 기준으로 재인덱싱 - 스크립트: `skill-rag-file/scripts/reindex_oversized_chunks.py` - **검증**: 재분할 후 옐로펀치 MOU(doc_id `b7116f6a`)가 "옐로펀치 컴퍼니엑스 협약" 쿼리로 top-5 진입하는지 실측 - **실패 시 대안**: 재분할로도 top-5 미진입이면 keyword prefix 매칭(P0-1)이 보완하므로 벡터 단독 해결에 집착하지 않음. keyword가 해당 문서를 잡으면 RRF 합산으로 순위 상승 ### P1-5: grounding 라우팅 완화 (rb8001) - 파일: `app/services/companyx_grounding_service.py` → `should_handle_companyx_grounding()` - 변경: `team_id == COMPANYX_TEAM_ID`이면 `_looks_like_companyx_grounding_question()` 체크를 건너뛰고 항상 grounding 시도 - **fallback 확인**: 현재 `try_companyx_grounding()`이 `None`을 반환하면 `message_service.py`가 일반 의도 분류로 진행 (라인 81-93). 이 흐름은 변경하지 않음 - 따라서 grounding 검색 결과 0건이면 기존처럼 일반 챗봇 경로로 자연 fallback - **범위 위험**: 일반 대화("점심 뭐 먹을까?")도 검색 API를 태우게 됨. 불필요한 API 호출 + 응답 지연 발생 가능 - **롤백 기준**: 배포 후 일반 대화 응답 지연이 1초 이상 증가하거나, grounding 경로 진입률이 80% 이상이면 마커 체크 복원 - **후속 이력**: 위 위험이 실제 협업 검토에서 확인되어 2026-03-23 `91b92da`에서 `team_id + marker` 기준으로 롤백됐다. 현재 운영 기준은 이 문서가 아니라 260323 워크로그와 `SKILL.md`, `companyx_grounding_pipeline.md`를 따른다. ## 실행 순서 ``` P0-1 (prefix) ─┐ P0-2 (threshold)├─ 병렬 → skill-rag-file 재배포 → 검증 P0-3 (RRF 정규화)─┘ ↓ P1-4 (청크 재분할) → 재인덱싱 → 검증 P1-5 (라우팅 완화) → rb8001 재배포 → 검증 ↓ 통합 검증 (16개 + 20개) ``` - P0 3건은 병렬 작업 후 한 번에 배포 - P1은 P0 검증 후 순차 ## 검증 결과 (260321 최종) | 기준 | 최초 | P0 후 | P1 후 | 상태 | |------|------|-------|-------|------| | keyword "아크로셀 정기주총" 1건+ | 0건 | 5건 | 5건 | ✅ | | hybrid relevance_score > threshold | 0.032 | 1.000 | 1.000 | ✅ | | vector 회귀 없음 (16/16) | 16/16 | 16/16 | 16/16 | ✅ | | 옐로펀치 MOU top-5 진입 | 미등장 | 미등장 | **top-1 (vec=0.727)** | ✅ | | 재오픈 grounding passed | 5/18 | 8/18 | **11/18** | ✅ | | 마커 미진입 → grounding 진입 | 미진입 | 미진입 | **전부 진입** | ✅ | ## 닫는 조건 — **닫힘 (260321)** - ~~16개 샘플 질문: vector/keyword/hybrid 모두 결과 반환~~ → ✅ 16/16 전모드 성공 - ~~재오픈 20개: grounding 경로 진입 실패 0건~~ → ✅ 전부 진입 (team_id 기반) - 적용1 계획 검증 결과 기록 테이블에 결과 기입 → 후속 ## 금지 - 질문별 하드코딩 특례 추가 금지 (260320 계승) - threshold를 0으로 내리는 것은 금지 (노이즈 결과 유입) - 기존 vector 단독 검색 품질 회귀 금지