텍스트 추출 없이 문서 페이지를 이미지로 변환하여 시각적으로 검색하는 RAG(Retrieval-Augmented Generation) 시스템입니다. PPTX/HWP 형식의 경기도 데이터 분석 보고서를 대상으로, ColQwen2.5 멀티벡터 임베딩으로 검색하고 Qwen2.5-VL-7B로 답변을 생성합니다.
대상 하드웨어: GCP L4 GPU (24GB VRAM)
[문서 입력]
PPTX / HWP
│
▼ LibreOffice (headless)
PDF
│
▼ pdf2image (DPI=400)
PNG 이미지 (페이지별)
│
├─ [인덱싱] ──────────────────────────────────────────────────────────┐
│ ColQwen2.5 (GPU, bfloat16) │
│ │ 멀티벡터 임베딩 (128차원, ColBERT MAX_SIM) │
│ ▼ │
│ Qdrant (MultiVector 컬렉션) │
└─────────────────────────────────────────────────────────────────────┘
[쿼리 응답]
사용자 질문
│
▼ ColQwen2.5 (CPU, float32)
MaxSim 검색 → 상위 5개 PNG
│
▼ Qwen2.5-VL-7B-AWQ (vLLM, GPU)
최종 답변
| 단계 | ColQwen2.5 | vLLM (Qwen2.5-VL) | 비고 |
|---|---|---|---|
| 인덱싱 | GPU (~6GB) | 미사용 | 서버 중지 필요 |
| 서빙 | CPU | GPU (~13GB) | 인덱싱 중지 필요 |
중요: 인덱서와 서버는 절대 동시에 실행하지 마세요. VRAM 충돌이 발생합니다.
src/
├── main.py # FastAPI 앱 — /upload, /chat, /health 엔드포인트
├── auth/
│ └── middleware.py # X-API-Key 헤더 인증 (관리자 / 일반 사용자 구분)
├── utils/
│ └── config.py # .env 기반 타입 상수
├── ingestion/
│ ├── converter.py # LibreOffice → PDF → PNG 변환
│ └── indexer.py # ColQwen2.5 GPU → Qdrant 적재 (CLI 진입점)
├── retrieval/
│ └── searcher.py # ColQwen2.5 CPU 싱글턴 + Qdrant MaxSim 검색
├── generation/
│ └── llm_client.py # vLLM 싱글턴 + Qwen2.5-VL 프롬프트 포맷
└── static/
└── index.html # 웹 UI (Vanilla JS, Catppuccin Mocha 테마)
- Qdrant + Nginx: Docker 컨테이너로 실행 (
docker-compose.yml) - FastAPI: 호스트에서 직접 실행 (GPU 접근을 위해 Docker 외부)
- Nginx → FastAPI 통신은
host.docker.internal을 통해 연결
sudo bash setup.sh && sudo rebootnvidia-smi# uv 설치 (없는 경우)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 핵심 패키지 설치
uv sync
# vLLM은 별도 설치 (pyproject.toml 미추적)
uv pip install vllmcp .env.example .env.env 파일을 열어 아래 값을 설정합니다:
| 변수 | 설명 | 예시 |
|---|---|---|
ADMIN_API_KEY |
관리자 API 키 (업로드 + 채팅) | my-secret-admin-key |
USER_API_KEYS |
일반 사용자 키 목록 (쉼표 구분) | key-001,key-002 |
QDRANT_HOST |
Qdrant 호스트 | localhost |
QDRANT_PORT |
Qdrant 포트 | 6333 |
COLLECTION_NAME |
Qdrant 컬렉션 이름 | visual_rag |
RETRIEVAL_MODEL |
검색 모델 | vidore/colqwen2.5-v0.2 |
GENERATION_MODEL |
생성 모델 | Qwen/Qwen2.5-VL-7B-Instruct-AWQ |
TOP_K |
검색 결과 이미지 수 | 5 |
IMAGE_DPI |
이미지 변환 해상도 | 400 |
MAX_CONCURRENT_REQUESTS |
최대 동시 요청 수 | 3 |
REQUEST_TIMEOUT_SECONDS |
요청 타임아웃 (초) | 180 |
docker-compose up -d# data/raw/ 하위 전체 파일 인덱싱
uv run python src/ingestion/indexer.py --input data/raw/
# 특정 연도 디렉토리만 인덱싱
uv run python src/ingestion/indexer.py --input data/raw/2025/
# 단일 파일 인덱싱
uv run python src/ingestion/indexer.py --input data/raw/파일.pptx
# 컬렉션 초기화 후 재인덱싱 (기존 데이터 삭제)
uv run python src/ingestion/indexer.py --input data/raw/ --recreate지원 형식: .pptx, .ppt, .hwp, .odp
# --no-sync: vllm은 pyproject.toml 미추적이므로 필수
uv run --no-sync uvicorn src.main:app --host 0.0.0.0 --port 8000서버가 뜨면 http://localhost:8000 에서 웹 UI에 접근할 수 있습니다.
모든 요청은 X-API-Key 헤더에 인증 키를 포함해야 합니다.
인증 없이 서버 및 Qdrant 연결 상태 확인.
curl http://localhost:8000/health문서 파일을 업로드하고 자동으로 변환 및 인덱싱합니다.
curl -X POST http://localhost:8000/upload \
-H "X-API-Key: $ADMIN_API_KEY" \
-F "file=@data/raw/보고서.pptx"자연어 질문으로 보고서 내용을 검색하고 답변을 받습니다.
curl -X POST http://localhost:8000/chat \
-H "X-API-Key: $USER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "경기도 미세먼지 계절관리제 효과는?"}'응답 형식:
{
"answer": "...",
"sources": [
{"file": "보고서.pptx", "page": 3},
...
]
}bash enable_https.sh your.domain.com서버 실행 뒤 아래 주소로 접속
http://35.184.63.251/
DNS가 서버 IP로 전파된 후 실행하면 Nginx에 SSL 인증서가 자동으로 설정됩니다.
| 항목 | 값 | 이유 |
|---|---|---|
IMAGE_DPI |
400 | 한국어 표/숫자 가독성 보장 |
TOP_K |
5 | vLLM limit_mm_per_prompt 상한 |
| Qwen VL 이미지 토큰 | <|vision_start|><|image_pad|><|vision_end|> |
모델 고정 형식 — 변경 금지 |
| 벡터 차원 | 128 | ColQwen2.5 고정 출력 차원 |
| Qdrant 컬렉션 | MultiVectorConfig(MAX_SIM) |
단일 벡터 컬렉션과 호환 불가 |
| 역할 | 기술 |
|---|---|
| 문서 변환 | LibreOffice (headless), pdf2image |
| 검색 임베딩 | ColQwen2.5 (vidore/colqwen2.5-v0.2) |
| 벡터 DB | Qdrant (MultiVector MAX_SIM) |
| 언어 모델 | Qwen2.5-VL-7B-Instruct-AWQ |
| 추론 엔진 | vLLM |
| 웹 프레임워크 | FastAPI |
| 리버스 프록시 | Nginx (Docker) |
| 패키지 관리 | uv |