Spaces:
Running
영웅문 S# AI 메뉴 검색 시스템 — 아키텍처 & 워크플로우
사용자의 자연어 입력을 이해하고, 증권 HTS 앱(영웅문 S#)의 관련 메뉴를 추천하는 AI 검색 시스템
목차
- 프로젝트 개요
- 전체 아키텍처
- 데이터 파이프라인
- 핵심 모듈
- 검색 요청 상세 흐름
- LangGraph 에이전트
- 주요 설정값
- 프롬프트 구조
- 데이터 구조
- 성능 지표
- 웹 UI
- Quick Start
1. 프로젝트 개요
| 항목 | 내용 |
|---|---|
| 목적 | 사용자의 구어체 자연어 질문을 메뉴로 매핑 |
| 대상 메뉴 | 영웅문 S# 앱 내 901개 메뉴 |
| 검색 방식 | Dense (의미) + BM25 (키워드) + QueryIndex (쿼리↔쿼리) Weighted RRF |
| 고도화 | HyDE · Contextual Retrieval · Cross-Encoder 리랭킹 · 쿼리 인덱스 |
| AI 에이전트 | LangGraph 기반, 품질 미달 시 GPT 쿼리 재작성 후 재검색 |
| 임베딩 모델 | BAAI/bge-m3 (100개 언어, ~570MB) |
| 리랭킹 모델 | BAAI/bge-reranker-v2-m3 (로컬) / Cohere Rerank 4 (API) |
| LLM | Azure OpenAI gpt-4.1-mini |
| 웹 UI | Streamlit |
기술 스택
Python 3.11+
├── AI/ML
│ ├── sentence-transformers # bge-m3 임베딩 + bge-reranker 리랭킹
│ ├── chromadb # 벡터 DB (HNSW 코사인 유사도)
│ │ # herogun_menus : 901개 메뉴 벡터
│ │ # herogun_queries: 1,688개 샘플 쿼리 벡터 (Q-index)
│ ├── rank-bm25 # BM25 키워드 검색
│ ├── langgraph # 멀티턴 검색 에이전트 그래프
│ ├── openai # Azure OpenAI (GPT-4.1-mini, HyDE·쿼리재작성)
│ └── cohere # Cohere Rerank 4 (선택, API key 필요)
├── Web
│ └── streamlit # 데모 웹 UI
└── Data
├── jsonlines # JSONL 파일 처리
├── openpyxl # Excel 리포트 생성
└── kiwipiepy # 한국어 형태소 분석 (BM25 옵션, 현재 비활성)
2. 전체 아키텍처
2-1. 데이터 파이프라인 (오프라인, 단계별 실행)
원본 CSV (menu.csv)
1,568행 (전체 화면)
│
▼ [00_import_csv.py]
│ IS_VISIBLE_MENU=true, 팝업 제외
│
data/raw/real_menus.json (901개)
│
├──▶ [12_update_keywords.py] 현업 키워드 Excel → keywords 갱신
│
▼ [01_generate_descriptions.py] (또는 13·15 재생성)
│ Azure GPT-4.1-mini (비동기 병렬, concurrency=10)
│ · 기본 생성: 텍스트 전용
│ · 이미지 있는 메뉴: Vision 멀티모달 (concurrency=5)
│ · 현업 피드백 있는 메뉴: feedback_map 주입
│
data/generated/menu_descriptions.jsonl (901개 × 8필드)
│
▼ [18_contextual_retrieval.py]
│ 같은 카테고리 sibling 메뉴들을 컨텍스트로 제공
│ GPT가 각 메뉴별 1~2문장 식별문맥 생성
│ embedding_text 앞에 【식별문맥】 prepend
│
├──▶ [02_build_vectordb.py] ├──▶ [06_rebuild_bm25.py]
│ bge-m3 임베딩 (배치, ~12분) │ BM25 Multi-field 인덱싱
│ 29 배치 × ~25초 │ keywords×2 + name×2 + text×1
│ │ (USE_KIWI=False, 공백 분리)
▼ ▼
data/chroma_db/herogun_menus data/bm25_index.pkl
901개 벡터 (HNSW, 코사인) BM25 역인덱스
│
▼ [20_build_query_index.py --from-pool]
현업 검토 샘플 쿼리 임베딩 (eval 300개 제외)
human 쿼리 + LLM top200 쿼리 (취소선 제외)
▼
data/chroma_db/herogun_queries
1,688개 샘플 쿼리 벡터 (Q-index)
2-2. 검색 요청 흐름 (온라인, 실시간)
사용자 입력: "주문 잘못 넣었는데 어떻게 취소해?"
│
▼ [app/streamlit_app.py]
│
▼ [core/agent.py — LangGraph]
│
├── 1. analyze_query (규칙 기반, LLM 없음)
│ 카테고리 감지: "국내주식"
│ 의도 감지: "주문"
│
├── 2. search_menus [core/search_engine.py]
│ ├── 쿼리 정규화 (조사·어미 제거)
│ ├── HyDE: GPT가 가상 메뉴 설명 생성 → Dense 검색 벡터로 활용
│ ├── Dense 검색 (ChromaDB herogun_menus, bge-m3)
│ ├── Q-index 검색 (ChromaDB herogun_queries, 원본 쿼리 벡터)
│ │ └── 유사 샘플 쿼리 → 해당 메뉴 RRF 부스트
│ ├── BM25 검색 (동의어 확장 후)
│ ├── Weighted RRF 결합 (Dense×1.5 + BM25×0.5 + QIdx×0.4)
│ ├── Search Count Additive Boost
│ └── Cross-Encoder 리랭킹 (use_reranker=True 시)
│
├── 3. evaluate_results
│ quality_score = top1×0.7 + top3avg×0.3
│ 임계값: 0.55
│
├── [만족] ──▶ 4. generate_response → 결과 반환
│
└── [불만족, 최대 2회] ──▶ rewrite_query (GPT) ──▶ 2번 루프
3. 데이터 파이프라인
스크립트 목록
| 스크립트 | 역할 |
|---|---|
00_import_csv.py |
CSV → JSON 변환, 유효 메뉴 필터링 (901개) |
01_generate_descriptions.py |
GPT로 메뉴별 설명 생성 (비동기 병렬) |
02_build_vectordb.py |
bge-m3 임베딩 생성 & ChromaDB herogun_menus 저장 |
06_rebuild_bm25.py |
BM25 인덱스 재구축 |
07_export_top50_review.py |
상위 100개 메뉴 검토용 Excel 출력 |
12_update_keywords.py |
현업 키워드 Excel → real_menus.json 갱신 |
13_regen_with_images.py |
변경 메뉴 이미지(Vision) 기반 재생성 |
14_export_regen_review.py |
재생성 결과 검토용 Excel 출력 (부서별 시트) |
15_import_review_feedback.py |
현업 피드백 Excel → 201개 메뉴 재생성 |
16_eval_with_answer.py |
구 정답셋 기반 Top5 정확도 평가 |
17_eval_comparison.py |
bge-m3 / +HyDE / +HyDE+Rerank 3-way 비교 평가 |
18_contextual_retrieval.py |
카테고리별 식별문맥 생성 → embedding_text prepend |
20_build_query_index.py |
현업 검토 샘플 쿼리 → herogun_queries 컬렉션 구축 |
21_build_eval300.py |
300개 eval 셋 구축 (top200×270 + 201+×30, 누수 방지) |
22_eval300.py |
300개 eval 셋 기반 품질 평가 (Acc@1/3/5, MRR@5) |
23_weight_grid.py |
Dense/BM25 가중치 그리드 서치 |
24_bm25_ablation.py |
BM25 토크나이저 ablation 분석 |
현업 협업 워크플로우
[현업] 키워드 Excel 작성
│
▼ 12_update_keywords.py
│ 키워드(현업) + 키워드(채널기획) union → real_menus.json
│
▼ 13_regen_with_images.py
│ 변경 메뉴 재생성 (이미지 있는 메뉴 → GPT Vision)
│
[현업] 화면 설명서 검토 (14_export_regen_review.py로 생성한 Excel)
│
▼ 15_import_review_feedback.py
│ 수정 의견 201개 → feedback_map으로 GPT에 전달 → 재생성
│
▼ 18_contextual_retrieval.py
│ sibling 메뉴 컨텍스트로 식별문맥 생성 → embedding_text 강화
│
[현업] 샘플 쿼리 검토 (MenuSearch_review_all.xlsx)
│ sample_queries(LLM 생성): 취소선으로 나쁜 쿼리 제거
│ Sample_queries(자체 생성): 직접 작성한 쿼리 추가
│
▼ 21_build_eval300.py
│ 300개 eval 셋 분리 저장 (data leakage 방지)
│
▼ 20_build_query_index.py --reset --from-pool
│ 나머지 1,688개 쿼리 → herogun_queries 인덱싱
│
▼ 02_build_vectordb.py + 06_rebuild_bm25.py
인덱스 재구축 완료
eval 셋 구성 (data/eval_queries_300.json)
| 구분 | 수량 | 설명 |
|---|---|---|
| top200 메뉴 | 270개 | 메뉴별 균등 stratified 샘플링, human 우선 |
| 201+ 메뉴 | 30개 | 랜덤 샘플링 |
| 합계 | 300개 | human 100개 + llm 200개 |
data/eval_queries_300.json: eval 쿼리 셋data/query_index_pool.json: eval 제외 후 Q-index 구축용 풀 (1,688개)data/eval_results_300.xlsx: 평가 결과 (건별 O/~/X 색상, 요약 시트)
4. 핵심 모듈
4-1. 검색 엔진 (core/search_engine.py)
Weighted RRF 3-way 하이브리드 + HyDE + 리랭킹
사용자 쿼리
│
▼ 쿼리 정규화 (조사·어미 제거)
│
▼ HyDE (use_hyde=True 시)
│ GPT-4.1-mini → 가상 메뉴 설명 생성 (temperature=0.1)
│ 가상 문서를 Dense 검색 벡터로 활용
│ BM25 · Q-index는 원본 쿼리 벡터 사용
│
├─────────────────┬──────────────────┬──────────────────
▼ ▼ ▼
Dense 검색 Q-index 검색 BM25 검색
(ChromaDB, (ChromaDB, (동의어 확장 후)
herogun_menus, herogun_queries, 전체 901개 대상
전체 901개) 원본 쿼리 벡터)
유사도 ≥ 0.85만
RRF 반영
│ │ │
└─────────────────┴──────────────────┘
▼
Weighted RRF 결합
score = Dense×1.5/(k+rank) + BM25×0.5/(k+rank) + QIdx×0.4/(k+rank)
k = 60 (Cormack et al. 2009)
▼
Search Count Additive Boost
score += 0.005 × (log1p(count)/log1p(MAX))²
▼
Threshold 필터 (≥ 0.3)
상위 10개 후보 선별
▼
Cross-Encoder 리랭킹 (use_reranker=True 시)
bge-reranker-v2-m3 또는 Cohere Rerank 4
▼
Top-N 결과 반환 (기본 5개)
4-2. 쿼리 인덱스 (core/query_vectorstore.py)
Query↔Query 매칭 — 어휘 갭 해소 핵심 신호
현업이 직접 작성하거나 검토한 샘플 쿼리를 벡터로 저장, 사용자 입력과 매칭해 해당 메뉴를 RRF에서 부스트.
herogun_queries 컬렉션 (ChromaDB)
총 1,688개 샘플 쿼리
├── human 118개 (현업 직접 작성)
└── llm_top200 1,570개 (현업 취소선 검토 후 유지된 LLM 쿼리)
소스: MenuSearch_review_all.xlsx top200 메뉴
eval300 쿼리 제외 (data leakage 방지)
| 파라미터 | 값 | 설명 |
|---|---|---|
W_QUERY_IDX |
0.4 |
Q-index RRF 가중치 (Dense/BM25 결과 뒤집지 않는 수준) |
QUERY_IDX_THRESHOLD |
0.85 |
코사인 유사도 임계값 (유사 메뉴 오탐 방지) |
4-3. 임베더 (core/embedder.py)
| 항목 | 내용 |
|---|---|
| 모델 | BAAI/bge-m3 |
| 크기 | ~570MB |
| 특징 | 100개 언어 지원, 한국어·영어 혼용 처리, MIRACL 벤치마크 SOTA |
| 배치 처리 | 29배치, ~12분 (CPU 기준) |
임베딩 대상 = embedding_text 필드
(Contextual Retrieval 적용 시: 【식별문맥】 {1~2문장}\n\n{원본 250~400자})
4-4. HyDE 모듈 (core/hyde_generator.py)
Hypothetical Document Embedding — Gao et al. 2022 기법
# 쿼리 → GPT → 가상 메뉴 설명 → 임베딩 → Dense 검색
가상 문서 예시 (쿼리: "주문 잘못 넣었어"):
"주문 내역을 확인하고 미체결 주문을 취소하거나 정정할 수 있습니다.
체결 전 주문을 수정·취소하는 기능을 제공합니다."
| 항목 | 내용 |
|---|---|
| 사용 모델 | gpt-4.1-mini (temperature=0.1, 일관된 전문 용어) |
| 효과 | 쿼리-문서 어휘 갭 해소, HyDE OFF 대비 Acc@1 +7.6%p |
| 기본값 | use_hyde=True |
| Q-index와의 관계 | HyDE는 Dense 검색 벡터만 변경, Q-index는 항상 원본 쿼리 벡터 사용 |
4-5. Contextual Retrieval (scripts/18_contextual_retrieval.py)
Anthropic 2024 기법 — 유사 메뉴 간 혼동 감소
# 같은 카테고리 sibling 메뉴들을 GPT에 제공
# GPT가 해당 메뉴만의 고유 기능·차이점을 1~2문장으로 생성
# embedding_text 앞에 prepend → 재임베딩
# 예시 (해외주식 > 주문 > 잔고)
【식별문맥】 해외주식 주문 카테고리 내 잔고 메뉴는 보유 해외주식의
실시간 잔고와 수익 현황을 원화 및 외화 기준으로 확인하는 기능으로,
단순 주문 실행이나 미체결 내역 조회와 구별됩니다.
| 항목 | 내용 |
|---|---|
| 처리 방식 | 오프라인 1회 처리 (per-query 비용 없음) |
| 마커 | 【식별문맥】 (재실행 시 기존 문맥 자동 교체) |
| 동시처리 | concurrency=15, 약 10분 소요 |
4-6. 벡터 스토어 (core/vectorstore.py)
ChromaDB PersistentClient
컬렉션: "herogun_menus"
유사도: 코사인 (HNSW 인덱싱)
저장 메타데이터:
- menu_name, menu_path, category
- search_count_norm # log1p 정규화
- keywords_text # 키워드 문자열
4-7. BM25 인덱스 (core/bm25_index.py)
Multi-field 가중치 인덱싱
BM25 문서 = keywords × 2 + menu_name × 2 + embedding_text × 1
# 키워드 정확 매칭 시 BM25 점수 대폭 상승
| 파라미터 | 값 | 설명 |
|---|---|---|
USE_KIWI |
False |
kiwipiepy 형태소 분석 사용 여부 |
| 토크나이저 | 공백 분리 | kiwipiepy 복합명사 분리가 금융 도메인에서 역효과 (Acc@1 -10%p) |
참고:
USE_KIWI=True로 변경 후06_rebuild_bm25.py실행 시 형태소 분석 적용 가능. 단, eval 기준 공백 분리(47.7%)가 kiwipiepy(37.3%)보다 우수.
4-8. 리랭커 (core/reranker.py / core/cohere_reranker.py)
폴백 체인: Cohere Rerank 4 → bge-reranker-v2-m3
| 항목 | Cohere Rerank 4 | bge-reranker-v2-m3 |
|---|---|---|
| 모델명 | rerank-v3.5 |
BAAI/bge-reranker-v2-m3 |
| 방식 | API (유료) | 로컬 (무료) |
| API Key | 필요 (COHERE_API_KEY) |
불필요 |
| 크기 | — | ~300MB |
| 특징 | SOTA 상용 리랭커 | bge-m3 동일 패밀리, 100개 언어 |
| 활성화 | .env에 키 입력 시 자동 |
Cohere 실패 시 자동 폴백 |
입력: RRF Top-10 후보 → (query, document) 쌍 → Cross-Encoder 재점수 → 재정렬
4-9. 쿼리 확장기 (core/query_expander.py)
BM25 전용 HTS 도메인 동의어 확장 (Dense 검색에는 미적용)
SYNONYM_MAP 예시:
"취소" → ["취소", "정정", "주문변경", "미체결", "정정취소"]
"주문" → ["주문", "매수", "매도", "거래", "체결"]
"잔고" → ["잔고", "보유", "수량", "평가금액", "자산"]
"차트" → ["차트", "일봉", "주봉", "캔들", "그래프"]
"수익" → ["수익", "손익", "수익률", "평가손익", "누적손익"]
4-10. LLM 클라이언트 (core/llm_client.py)
| 항목 | 내용 |
|---|---|
| 모델 | Azure OpenAI gpt-4.1-mini |
| 엔드포인트 | kiwoom-ai3-prod-oai-korea-central.openai.azure.com |
| temperature | 0.3 (설명 생성) / 0.1 (식별문맥) |
| max_tokens | 1,500 (설명 생성) / 160 (식별문맥) |
| 배치 처리 | 비동기 병렬, concurrency=10 (Vision 시 5) |
| feedback_map | {menu_id: feedback_text} — 현업 피드백 GPT 힌트 전달 |
5. 검색 요청 상세 흐름
① 사용자 입력 (Streamlit)
예: "내 주식 얼마나 올랐어?"
② 쿼리 정규화 (search_engine.py)
조사·어미 제거 → "내 주식 얼마나 올랐"
③ 카테고리 감지 (agent.py — analyze_query)
CATEGORY_RULES 키워드 2개 이상 매칭 시 필터 적용
④ HyDE (use_hyde=True)
GPT-4.1-mini → 가상 메뉴 설명 생성 (temperature=0.1)
가상 문서를 bge-m3로 임베딩 → Dense 검색 벡터로 사용
⑤ Dense 검색 (ChromaDB herogun_menus, bge-m3)
가상 문서 벡터로 코사인 유사도 검색 (전체 901개)
카테고리 필터 적용 (감지된 경우)
⑥ Q-index 검색 (ChromaDB herogun_queries)
원본 쿼리 벡터로 1,688개 샘플 쿼리 검색
유사도 ≥ 0.85인 쿼리의 메뉴 → RRF 부스트 대상 선정
⑦ BM25 검색 (bm25_index.py)
쿼리 동의어 확장 → BM25 점수 (전체 901개)
⑧ Weighted RRF 결합
Dense 기여(×1.5) + BM25 기여(×0.5) + Q-index 기여(×0.4) → RRF 통합 점수
+ Search Count Additive Boost (0.005 × sc_norm²)
⑨ Cross-Encoder 리랭킹 (use_reranker=True 시)
RRF Top-10 후보 → Cohere/bge-reranker → 최종 재정렬
⑩ 품질 평가 (agent.py — evaluate_results)
quality_score = top1_sim × 0.7 + avg(top3_sim) × 0.3
임계값 0.55 미달 시 → GPT 쿼리 재작성 (최대 2회)
⑪ 결과 반환 (Top-5, threshold ≥ 0.3)
6. LangGraph 에이전트
그래프 구조 (core/agent.py)
START
│
▼
[analyze_query] 규칙 기반 (LLM 없음)
카테고리·의도 감지
│
▼
[search_menus] MenuSearchEngine.search() 호출
RRF 3-way 하이브리드 + HyDE + Q-index + 리랭킹
│
▼
[evaluate_results] quality_score 계산
│
├── 만족(≥0.55) 또는 재시도 2회 초과 ──▶ [generate_response] ──▶ END
│
└── 불만족 + 재시도 < 2 ──▶ [rewrite_query] GPT-4.1-mini 쿼리 재작성 (temperature=0.3)
└── 루프백 ──▶ [search_menus]
AgentState 구조
| 필드 | 타입 | 설명 |
|---|---|---|
original_query |
str | 사용자 원본 입력 (불변) |
refined_query |
str | 현재 검색 쿼리 (재작성 시 갱신) |
detected_category |
str|None | 감지된 카테고리 |
intent |
str | 감지된 의도 |
search_results |
list | 검색 결과 |
quality_score |
float | 검색 품질 점수 (0.0~1.0) |
retry_count |
int | 재시도 횟수 (최대 2) |
rewrite_history |
list | 쿼리 재작성 이력 |
use_hyde |
bool | HyDE 활성 여부 (기본 True) |
use_reranker |
bool | 리랭킹 활성 여부 |
is_satisfactory |
bool | 품질 만족 여부 |
7. 주요 설정값
파일: config.py / core/search_engine.py
| 설정 | 값 | 설명 |
|---|---|---|
EMBEDDING_MODEL_NAME |
BAAI/bge-m3 |
다국어 임베딩 모델 |
CHROMA_COLLECTION_NAME |
herogun_menus |
메뉴 벡터 ChromaDB 컬렉션 |
LLM_MODEL |
gpt-4.1-mini |
Azure 배포명 |
AZURE_API_VERSION |
2025-01-01-preview |
API 버전 |
COHERE_API_KEY |
.env 설정 |
Cohere Rerank 4 활성화 키 |
DEFAULT_TOP_N |
5 |
기본 검색 결과 수 |
SIMILARITY_THRESHOLD |
0.3 |
최소 유사도 임계값 |
RRF_K |
60 |
RRF 상수 (Cormack et al. 2009) |
W_DENSE |
1.5 |
Dense 검색 RRF 가중치 |
W_BM25 |
0.5 |
BM25 검색 RRF 가중치 |
W_QUERY_IDX |
0.4 |
Q-index RRF 가중치 |
QUERY_IDX_THRESHOLD |
0.85 |
Q-index 코사인 유사도 임계값 |
SEARCH_COUNT_BOOST_WEIGHT |
0.005 |
인기도 additive boost 가중치 |
SEARCH_COUNT_MAX |
20,260,108 |
log1p 정규화 기준 최대값 |
8. 프롬프트 구조
파일: prompts/description_prompt.txt
GPT가 각 메뉴에 대해 생성하는 필드:
| 필드 | 내용 | 용도 |
|---|---|---|
function_desc |
메뉴 주요 기능 2~3문장 | 현업 검토용 설명서 |
action_types |
이 메뉴에서 할 수 있는 행위 목록 | 의도 매핑 참고 |
user_verbs |
사용자가 쓰는 구어체 동사 | BM25 확장 참고 |
keywords |
핵심 검색어 (전문용어+일상어) | BM25 가중치 필드 |
related_concepts |
연관 금융 개념 | 검색 커버리지 확보 |
sample_queries |
실제 사용자 질의 8개 이상 | 현업 검토용 / Q-index 소스 |
embedding_text |
벡터 검색 핵심 문서 (250~400자) | Dense 검색 대상 |
동적 변수:
{keywords_section}: 현업 제공 키워드 (있을 때만 포함){search_count_section}: 인기 메뉴 안내 (10,000회 초과 시){feedback_section}: 현업 검토 의견 (있을 때만 포함, 반드시 반영 지시)
9. 데이터 구조
data/raw/real_menus.json
{
"menu_id": "SCR_1000",
"menu_name": "국내관심",
"menu_path": "국내주식 > 관심종목 > 국내관심",
"category": "국내주식",
"screen_num": "1000",
"search_count": 12739646,
"keywords": ["관심종목", "관심그룹", "즐겨찾기"]
}
data/generated/menu_descriptions.jsonl
{
"menu_id": "SCR_1000",
"menu_name": "국내관심",
"menu_path": "국내주식 > 관심종목 > 국내관심",
"function_desc": "사용자가 설정한 관심종목과 관심그룹을 한눈에 조회...",
"action_types": ["조회", "설정", "수정", "관리"],
"user_verbs": ["보고 싶어", "확인해", "찾아줘"],
"keywords": ["관심종목", "관심그룹", "즐겨찾기", "시세", "변동률"],
"related_concepts": ["시세", "변동률", "수익률"],
"sample_queries": ["내 관심종목 보여줘", "관심그룹 어떻게 설정해", "..."],
"embedding_text": "【식별문맥】 국내주식 관심종목 카테고리에서 국내관심 메뉴는...\n\n주식 투자할 때 자주 보는 종목이나 그룹을 빠르게 확인하고 싶을 때..."
}
※ embedding_text는 Contextual Retrieval 적용 후 【식별문맥】 prefix가 prepend됨
data/eval_queries_300.json
{
"id": 1,
"query": "지금 내 주식 얼마야?",
"menu_id": "SCR_1300",
"menu_name": "국내잔고",
"menu_path": "국내주식 > 잔고 > 국내잔고",
"rank": 3,
"source": "human"
}
10. 성능 지표
평가 기준: 300개 쿼리 eval 셋 (human 100개 + llm 200개)
평가 스크립트: scripts/22_eval300.py
최신 결과 (HyDE OFF / ON 비교)
| 지표 | HyDE OFF | HyDE ON | 향상 |
|---|---|---|---|
| Acc@1 | 47.7% | 55.3% | +7.6%p |
| Acc@3 | 70.3% | 77.0% | +6.7%p |
| Acc@5 | 75.7% | 84.0% | +8.3%p |
| MRR@5 | 0.591 | 0.667 | +0.076 |
| Top5 오답 | 73건 | 48건 | -25건 |
소스별 (HyDE ON)
| 출처 | n | Acc@1 | Acc@3 | Acc@5 |
|---|---|---|---|---|
| human (현업 직접 작성) | 100 | 41.0% | 66.0% | 77.0% |
| llm (현업 검토 LLM 쿼리) | 200 | 62.5% | 82.5% | 87.5% |
순위 구간별 (HyDE ON)
| 구간 | n | Acc@1 | Acc@5 |
|---|---|---|---|
| top200 메뉴 | 270 | 54.4% | 83.3% |
| 201+ 메뉴 | 30 | 63.3% | 90.0% |
고도화 기법별 기여
| 기법 | 주요 효과 |
|---|---|
| bge-m3 임베딩 | 다국어 이해력, 한국어 구어체 처리 |
| HyDE | 쿼리-문서 어휘 갭 해소 (+7.6%p Acc@1 vs OFF) |
| Contextual Retrieval | 유사 메뉴명 간 혼동 감소 |
| Q-index (Query↔Query) | 현업 쿼리 패턴 직접 매칭, 어휘 갭 해소 |
| Weighted RRF (1.5:0.5:0.4) | 3-way 신호 결합, BM25 보조 강화 |
| Search Count Boost | 인기 메뉴 우선 노출 (0.005×sc_norm²) |
| Cross-Encoder 리랭킹 | RRF 상위 후보 정밀 재정렬 |
BM25 토크나이저 비교 (HyDE OFF)
| 토크나이저 | Acc@1 | 비고 |
|---|---|---|
| 공백 분리 (현재) | 47.7% | 기본 방식 |
| kiwipiepy 형태소 | 37.3% | 금융 복합명사 분리로 역효과 |
11. 웹 UI
파일: app/streamlit_app.py
┌──────────────────────────────────────────────────────────┐
│ 영웅문 S# AI 메뉴 검색 │
│ 자연어로 원하는 기능을 입력하면 관련 메뉴를 찾아드립니다. │
├─────────────────┬────────────────────────────────────────┤
│ 검색 옵션 │ [검색창: 자연어 입력] │
│ │ │
│ 카테고리 필터 │ 빠른 검색 (12개 버튼, 2행): │
│ [전체 ▼] │ [내 주식 얼마야?] [주문 취소] ... │
│ │ [내 수익률 어때?] [달러 환전] ... │
│ 등록된 메뉴 수 │ │
│ 901 │ "주문 취소" 검색 결과 (5개) │
│ │ │
│ │ ▼ 1위 주문 취소/정정 `94.2%` │
│ │ 경로: 국내주식 > 주문 > ... │
│ │ 메뉴 ID: SCR_xxxx │
│ │ ▼ 2위 미체결 주문조회 `87.1%` │
│ │ ▷ 3위 ... │
└─────────────────┴────────────────────────────────────────┘
주요 설정:
| 설정 | 값 |
|---|---|
| Top-N | 5개 |
| 최소 유사도 | 0.3 |
| HyDE | 활성 (use_hyde=True) |
| 리랭킹 | 비활성 (use_reranker=False) |
| 빠른 검색 | 12개 (2행 × 6개) |
실행:
.venv\Scripts\streamlit.exe run app/streamlit_app.py
# 접속: http://localhost:8501
# 외부 공유: ngrok http 8501
12. Quick Start
최초 환경 세팅
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt
# .env 파일 설정
AZURE_KEY=...
AZURE_GPT41_MINI_ENDPOINT=...
AZURE_GPT41_MINI_API_VERSION=2025-01-01-preview
COHERE_API_KEY= # 선택: Cohere Rerank 4 활성화
데이터 파이프라인 (최초 1회)
# 메뉴 설명 생성
.venv\Scripts\python.exe scripts/01_generate_descriptions.py
# 벡터 DB 구축 (~12분)
.venv\Scripts\python.exe scripts/02_build_vectordb.py --reset
# BM25 인덱스 구축
.venv\Scripts\python.exe scripts/06_rebuild_bm25.py
쿼리 인덱스 구축 (Q-index)
# 1. eval 300개 분리 (최초 1회)
.venv\Scripts\python.exe scripts/21_build_eval300.py
# 2. eval 제외 나머지로 Q-index 구축
.venv\Scripts\python.exe scripts/20_build_query_index.py --reset --from-pool
고도화 파이프라인 (선택 실행)
# 현업 키워드 반영 + 이미지 기반 재생성
.venv\Scripts\python.exe scripts/12_update_keywords.py
.venv\Scripts\python.exe scripts/13_regen_with_images.py
# 현업 피드백 반영 재생성
.venv\Scripts\python.exe scripts/15_import_review_feedback.py
# Contextual Retrieval 적용
.venv\Scripts\python.exe scripts/18_contextual_retrieval.py
# 인덱스 재구축
.venv\Scripts\python.exe scripts/02_build_vectordb.py --reset
.venv\Scripts\python.exe scripts/06_rebuild_bm25.py
검색 품질 평가
# 300개 eval 셋 기반 평가 (HyDE OFF)
.venv\Scripts\python.exe scripts/22_eval300.py --no-hyde
# HyDE ON 평가 (LLM API 호출, 느림)
.venv\Scripts\python.exe scripts/22_eval300.py
# 결과 엑셀 저장
.venv\Scripts\python.exe scripts/22_eval300.py --no-hyde --save-xlsx
# → data/eval_results_300.xlsx
데모 앱 실행
.venv\Scripts\streamlit.exe run app/streamlit_app.py
# 외부 공유: ngrok http 8501
최종 업데이트: 2026-06-02