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:
Macbook
2026-07-21 22:57:30 +09:00
commit 4b86b2a660
344 changed files with 45787 additions and 0 deletions
@@ -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 | 재랭킹 (빠른 추론) |
---
**이제 전체 흐름이 명확하시죠?** 🎉
질문이 있으시면 언제든 물어보세요!