# RAG 시스템 전체 흐름도 ## 📊 시스템 아키텍처 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ RAG 시스템 전체 구조 │ └─────────────────────────────────────────────────────────────────────┘ ┌──────────────────┐ │ 외부 API 서버들 │ └──────────────────┘ │ ┌──────────────────┼──────────────────┐ ↓ ↓ ↓ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ LLM (16000) │ │ TEI Embed │ │ vLLM Rerank │ │ SGLang/vLLM │ │ (16001) │ │ (16002) │ │ Qwen3-80B │ │ Qwen3-Embed │ │ Qwen3-Rerank │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ └──────────────────┼──────────────────┘ │ ┌────────▼────────┐ │ RAG 시스템 │ │ (Docker) │ └─────────────────┘ │ ┌──────────────┴──────────────┐ ↓ ↓ ┌──────────────┐ ┌──────────────┐ │ Batch 처리 │ │ API 서비스 │ │ (데이터 준비) │ │ (검색/답변) │ └──────────────┘ └──────────────┘ │ ↓ ┌──────────────┐ │ FAISS/Qdrant │ │ 벡터 저장소 │ └──────────────┘ ``` --- ## 🔄 Phase 1: 데이터 준비 (한 번만 실행) ### 1️⃣ 엑셀 파일 → JSONL 변환 ``` ┌─────────────────────────────────────────────────────────────┐ │ 엑셀 파일 (qa_data.xlsx) │ │ ┌────┬──────┬─────────────────────┬────────────────────┐ │ │ │ A │ B │ C (질문) │ D (답변) │ │ │ ├────┼──────┼─────────────────────┼────────────────────┤ │ │ │ 1 │ 분류 │ 카드 분실 시? │ 모바일 앱 또는... │ │ │ │ 2 │ 충전 │ 충전 후 정지 해제? │ 한국도로공사... │ │ │ └────┴──────┴─────────────────────┴────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ excel_to_jsonl.py │ (3열=질문, 4열=답변) ↓ ┌─────────────────────────────────────────────────────────────┐ │ data/qa_raw.jsonl │ │ {"q":"카드 분실 시?","a":"모바일 앱 또는..."} │ │ {"q":"충전 후 정지 해제?","a":"한국도로공사..."} │ └─────────────────────────────────────────────────────────────┘ ``` **명령어:** ```bash python scripts/excel_to_jsonl.py data/qa_data.xlsx 2 3 ``` --- ### 2️⃣ 질문 요약 (LLM) ``` data/qa_raw.jsonl ↓ ┌─────────────────────────────────────────────────────────────┐ │ preprocess_qa.py │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 긴 질문 → LLM 요약 → 짧은 질문 (q_short) │ │ │ │ │ │ │ │ [LLM API - Port 16000] │ │ │ │ POST http://llm-server:16000/v1/chat/completions │ │ │ │ { │ │ │ │ "messages": [ │ │ │ │ {"role": "system", "content": "질문을 요약..."}, │ │ │ │ {"role": "user", "content": "긴 질문 원문"} │ │ │ │ ] │ │ │ │ } │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ data/qa.jsonl {"q":"카드 분실 시?","q_short":"카드 분실","a":"모바일..."} ``` **명령어:** ```bash docker-compose -f docker-compose-slim.yml up preprocess ``` --- ### 3️⃣ 임베딩 생성 (TEI) ``` data/qa.jsonl ↓ ┌─────────────────────────────────────────────────────────────┐ │ ingest_qa.py │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 각 질문을 벡터로 변환 (Document 모드) │ │ │ │ │ │ │ │ [TEI Embedding API - Port 16001] │ │ │ │ POST http://tei-server:16001/embed │ │ │ │ { │ │ │ │ "inputs": "카드 분실 시 어떻게 해야 하나요?", │ │ │ │ "normalize": true, │ │ │ │ "truncate": true │ │ │ │ } │ │ │ │ │ │ │ │ ⚠️ 주의: Document 모드 (is_query=False) │ │ │ │ → Instruct 문구 없이 원문만 전송 │ │ │ │ │ │ │ │ 응답: [[0.123, -0.456, ...], ...] │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ 메모리에 벡터 저장 all_vectors = [[vec1], [vec2], ...] all_metadatas = [{"q": "...", "a": "..."}, ...] ``` **명령어:** ```bash docker-compose -f docker-compose-slim.yml up embed ``` --- ### 4️⃣ 인덱스 빌드 (FAISS/Qdrant) ``` 메모리의 벡터들 ↓ ┌─────────────────────────────────────────────────────────────┐ │ build_index_qa.py │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ FAISS 사용 시: │ │ │ │ - IndexFlatIP (Inner Product) 생성 │ │ │ │ - 벡터 추가 (add_vectors) │ │ │ │ - 인덱스 저장 → data/qa.index │ │ │ │ - 메타데이터 저장 → data/qa_meta.pkl │ │ │ │ │ │ │ │ Qdrant 사용 시: │ │ │ │ - 실시간 인덱싱 (별도 빌드 불필요) │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ data/qa.index (FAISS) data/qa_meta.pkl (메타데이터) ``` **명령어:** ```bash docker-compose -f docker-compose-slim.yml up index ``` --- ## 🔍 Phase 2: 검색 및 답변 생성 (실시간) ### 전체 흐름 ``` 사용자 질문: "카드를 잃어버렸어요" ↓ ┌─────────────────────────────────────────────────────────────┐ │ Step 1: 질문 임베딩 (TEI - Query 모드) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ [TEI Embedding API - Port 16001] │ │ │ │ POST http://tei-server:16001/embed │ │ │ │ { │ │ │ │ "inputs": "Instruct: Given a web search query, │ │ │ │ retrieve relevant passages that │ │ │ │ answer the query\n │ │ │ │ Query: 카드를 잃어버렸어요" │ │ │ │ } │ │ │ │ │ │ │ │ ⚠️ 주의: Query 모드 (is_query=True) │ │ │ │ → Instruct 문구 자동 추가 │ │ │ │ │ │ │ │ 응답: [[0.789, -0.234, ...]] │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ 질문 벡터: [0.789, -0.234, ...] ↓ ┌─────────────────────────────────────────────────────────────┐ │ Step 2: FAISS 유사도 검색 (TOP_K=30) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ FAISS IndexFlatIP.search(query_vec, k=30) │ │ │ │ │ │ │ │ - Inner Product 계산 (코사인 유사도) │ │ │ │ - FAISS_THRESHOLD=0.55 이상만 필터링 │ │ │ │ - 상위 30개 후보 반환 │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ 30개 후보: [ {"q": "카드 분실 시?", "a": "모바일 앱...", "score": 0.89}, {"q": "카드 재발급?", "a": "고객센터...", "score": 0.85}, ... ] ↓ ┌─────────────────────────────────────────────────────────────┐ │ Step 3: 재랭킹 (vLLM - Qwen3-Reranker) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 상위 20개만 선택 (RERANK_CANDIDATES=20) │ │ │ │ │ │ │ │ [vLLM Reranker API - Port 16002] │ │ │ │ POST http://vllm-server:16002/rerank │ │ │ │ { │ │ │ │ "query": "카드를 잃어버렸어요", │ │ │ │ "documents": [ │ │ │ │ "카드 분실 시 어떻게 해야 하나요?", │ │ │ │ "카드 재발급은 어떻게 하나요?", │ │ │ │ ... │ │ │ │ ] │ │ │ │ } │ │ │ │ │ │ │ │ ⚠️ 주의: "documents" 키 사용 (vLLM 커스텀 서버) │ │ │ │ │ │ │ │ 응답 (score 내림차순 정렬): │ │ │ │ [ │ │ │ │ {"index": 0, "score": 0.9834}, ← 가장 관련 높음 │ │ │ │ {"index": 5, "score": 0.8721}, │ │ │ │ {"index": 2, "score": 0.7543}, │ │ │ │ ... │ │ │ │ ] │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ 상위 5개 선택 (TOP_N_FOR_LLM=5) ↓ ┌─────────────────────────────────────────────────────────────┐ │ Step 4: LLM 답변 생성 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ [LLM API - Port 16000] │ │ │ │ POST http://llm-server:16000/v1/chat/completions │ │ │ │ { │ │ │ │ "messages": [ │ │ │ │ { │ │ │ │ "role": "system", │ │ │ │ "content": "당신은 고객 문의에 답변하는..." │ │ │ │ }, │ │ │ │ { │ │ │ │ "role": "user", │ │ │ │ "content": "고객 질문: 카드를 잃어버렸어요\n │ │ │ │ │ │ │ │ 참고자료: │ │ │ │ [참고자료 1] │ │ │ │ 질문: 카드 분실 시 어떻게 해야 하나요? │ │ │ │ 답변: 모바일 앱 또는 고객센터... │ │ │ │ │ │ │ │ [참고자료 2] │ │ │ │ 질문: 카드 재발급은? │ │ │ │ 답변: 고객센터를 통해... │ │ │ │ │ │ │ │ ... (총 5개) │ │ │ │ │ │ │ │ 위 참고자료를 바탕으로 답변해주세요." │ │ │ │ } │ │ │ │ ], │ │ │ │ "max_tokens": 512, │ │ │ │ "temperature": 0.3 │ │ │ │ } │ │ │ │ │ │ │ │ 응답: │ │ │ │ "카드를 잃어버리셨다면 즉시 모바일 앱이나..." │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ 최종 응답 { "answer": "카드를 잃어버리셨다면 즉시 모바일 앱이나...", "matched_questions": ["카드 분실 시?", "카드 재발급?", ...], "scores": [0.9834, 0.8721, 0.7543], "num_references": 5 } ``` --- ## 🔧 핵심 기술 포인트 ### 1. TEI Embedding - Query/Document 구분 | 시점 | 모드 | 형식 | 예시 | | ---------- | ------------------------- | ----------- | ---------------------------------------- | | **데이터 저장** | Document (is_query=False) | 원문만 | `"카드 분실 시 어떻게 해야 하나요?"` | | **검색** | Query (is_query=True) | Instruct 추가 | `ct: Given...\nQuery: 카드 분실?"``"Instru` | **정확도 향상:** 10-20% ↑ --- ### 2. vLLM Reranker - 빠른 재랭킹 ```python # 요청 { "query": "질문", "documents": ["문서1", "문서2", ...] # "texts" 아님! } # 응답 (score 내림차순 정렬) [ {"index": 0, "score": 0.98}, # 가장 관련 높음 {"index": 5, "score": 0.87}, ... ] ``` **특징:** - ✅ vLLM 기반 빠른 추론 - ✅ Yes/No 확률 내부 계산 - ✅ score 내림차순 정렬 --- ### 3. 파이프라인 파라미터 ```bash # .env 파일 FAISS_TOP_K=30 # 빠른 임베딩 검색 (넓은 범위) RERANK_CANDIDATES=20 # 재랭킹 후보 (비용 절감) TOP_N_FOR_LLM=5 # LLM 참고자료 (정확도 + 비용) # 흐름 30개 검색 → 20개 재랭킹 → 5개 LLM 입력 ``` --- ## 📋 전체 명령어 순서 ### 1회 설정 (데이터 준비) ```bash cd /Users/parkjiwon/src/rag # 1. 엑셀 → JSONL (3열=질문, 4열=답변) python scripts/excel_to_jsonl.py data/qa_data.xlsx 2 3 # 2. 환경 설정 cp env.template .env vi .env # 서버 주소 입력 # 3. 전처리 (LLM 요약) docker-compose -f docker-compose-slim.yml up preprocess # 4. 임베딩 (TEI, Document 모드) docker-compose -f docker-compose-slim.yml up embed # 5. 인덱싱 (FAISS) docker-compose -f docker-compose-slim.yml up index ``` ### 서비스 시작 (실시간 검색) ```bash # API 서버 시작 docker-compose -f docker-compose-slim.yml up api # 테스트 curl -X POST http://localhost:28012/ask \ -H "Content-Type: application/json" \ -d '{"query":"카드를 잃어버렸어요"}' ``` --- ## 🎯 요약 ### 데이터 흐름 ``` 엑셀 (3-4열) ↓ excel_to_jsonl.py JSONL ↓ preprocess_qa.py (LLM) 요약된 JSONL ↓ ingest_qa.py (TEI Document 모드) 벡터 + 메타데이터 ↓ build_index_qa.py FAISS 인덱스 ↓ run_service_qa.py 실시간 검색 서비스 ``` ### 검색 흐름 ``` 사용자 질문 ↓ TEI Embed (Query 모드) → Instruct 추가 질문 벡터 ↓ FAISS 검색 30개 후보 ↓ vLLM Reranker → 20개 재랭킹 상위 5개 ↓ LLM 답변 생성 최종 답변 ``` ### 핵심 API | API | 포트 | 기술 | 역할 | | ------------- | ----- | ----------- | ------------------------- | | **LLM** | 16000 | SGLang/vLLM | 요약 + 답변 생성 | | **Embedding** | 16001 | TEI | 벡터 변환 (Query/Document 구분) | | **Reranker** | 16002 | vLLM | 재랭킹 (빠른 추론) | --- **이제 전체 흐름이 명확하시죠?** 🎉 질문이 있으시면 언제든 물어보세요!