Agent 2.0 exdev 서버 배포 스택
- server-dev start/stop/deploy 및 Gitea push 자동 배포 - local-dev 로컬 개발 환경 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
# 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 | 재랭킹 (빠른 추론) |
|
||||
|
||||
|
||||
---
|
||||
|
||||
**이제 전체 흐름이 명확하시죠?** 🎉
|
||||
|
||||
질문이 있으시면 언제든 물어보세요!
|
||||
Reference in New Issue
Block a user