Spaces:
Running
Running
| # 영웅문 S# AI 메뉴 검색 시스템 — 아키텍처 & 워크플로우 | |
| > 사용자의 자연어 입력을 이해하고, 증권 HTS 앱(영웅문 S#)의 관련 메뉴를 추천하는 AI 검색 시스템 | |
| --- | |
| ## 목차 | |
| 1. [프로젝트 개요](#1-프로젝트-개요) | |
| 2. [전체 아키텍처](#2-전체-아키텍처) | |
| 3. [데이터 파이프라인](#3-데이터-파이프라인) | |
| 4. [핵심 모듈](#4-핵심-모듈) | |
| 5. [검색 요청 상세 흐름](#5-검색-요청-상세-흐름) | |
| 6. [LangGraph 에이전트](#6-langgraph-에이전트) | |
| 7. [주요 설정값](#7-주요-설정값) | |
| 8. [프롬프트 구조](#8-프롬프트-구조) | |
| 9. [데이터 구조](#9-데이터-구조) | |
| 10. [성능 지표](#10-성능-지표) | |
| 11. [웹 UI](#11-웹-ui) | |
| 12. [Quick Start](#12-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 기법 | |
| ```python | |
| # 쿼리 → 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 기법** — 유사 메뉴 간 혼동 감소 | |
| ```python | |
| # 같은 카테고리 sibling 메뉴들을 GPT에 제공 | |
| # GPT가 해당 메뉴만의 고유 기능·차이점을 1~2문장으로 생성 | |
| # embedding_text 앞에 prepend → 재임베딩 | |
| # 예시 (해외주식 > 주문 > 잔고) | |
| 【식별문맥】 해외주식 주문 카테고리 내 잔고 메뉴는 보유 해외주식의 | |
| 실시간 잔고와 수익 현황을 원화 및 외화 기준으로 확인하는 기능으로, | |
| 단순 주문 실행이나 미체결 내역 조회와 구별됩니다. | |
| ``` | |
| | 항목 | 내용 | | |
| |------|------| | |
| | 처리 방식 | 오프라인 1회 처리 (per-query 비용 없음) | | |
| | 마커 | `【식별문맥】` (재실행 시 기존 문맥 자동 교체) | | |
| | 동시처리 | concurrency=15, 약 10분 소요 | | |
| ### 4-6. 벡터 스토어 (`core/vectorstore.py`) | |
| ```python | |
| 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 가중치 인덱싱** | |
| ```python | |
| 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 검색에는 미적용) | |
| ```python | |
| 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` | |
| ```json | |
| { | |
| "menu_id": "SCR_1000", | |
| "menu_name": "국내관심", | |
| "menu_path": "국내주식 > 관심종목 > 국내관심", | |
| "category": "국내주식", | |
| "screen_num": "1000", | |
| "search_count": 12739646, | |
| "keywords": ["관심종목", "관심그룹", "즐겨찾기"] | |
| } | |
| ``` | |
| ### `data/generated/menu_descriptions.jsonl` | |
| ```json | |
| { | |
| "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` | |
| ```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개) | | |
| **실행:** | |
| ```bash | |
| .venv\Scripts\streamlit.exe run app/streamlit_app.py | |
| # 접속: http://localhost:8501 | |
| # 외부 공유: ngrok http 8501 | |
| ``` | |
| --- | |
| ## 12. Quick Start | |
| ### 최초 환경 세팅 | |
| ```bash | |
| 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회) | |
| ```bash | |
| # 메뉴 설명 생성 | |
| .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) | |
| ```bash | |
| # 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 | |
| ``` | |
| ### 고도화 파이프라인 (선택 실행) | |
| ```bash | |
| # 현업 키워드 반영 + 이미지 기반 재생성 | |
| .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 | |
| ``` | |
| ### 검색 품질 평가 | |
| ```bash | |
| # 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 | |
| ``` | |
| ### 데모 앱 실행 | |
| ```bash | |
| .venv\Scripts\streamlit.exe run app/streamlit_app.py | |
| # 외부 공유: ngrok http 8501 | |
| ``` | |
| --- | |
| *최종 업데이트: 2026-06-02* | |