RAG 기반 생성형 AI 웹 애플리케이션
Spring WebFlux 기반 비동기 백엔드 + Next.js 프론트와 함께
vLLM 기반 GPT-OSS 120B 모델을 활용하여 RAG 질의응답을 수행하는 프로젝트입니다.
rag-genAI는 다음을 목표로 합니다:
- Retrieval Augmented Generation (RAG) 파이프라인 구현
- 질의 재작성 및 명확화 → 문서 검색 → 벡터 임베딩 → LLM 질의응답
- GPT-OSS 120B 기반 LLM 연동 (vLLM)
- Spring WebFlux 기반 비동기 / 논블로킹 처리
- Next.js 기반 웹 UI 제공
본 프로젝트의 RAG 파이프라인은
질의 재작성 → 질의 명확화 → 검색 → 리랭킹 → 컨텍스트 구성 → LLM 생성
순으로 구성되어 있습니다.
- 질의 재작성(Query Rewrite) 및 질의 명확화(Query Clarification)
- RAG 기반 질의응답 (검색 + 문맥 증강 + 생성)
- WebFlux 기반 전체 비동기 처리
- LLM 응답 스트리밍 처리 가능
- REST API 기반 프론트 ↔ 백엔드 통신
- 프론트엔드에서 질의 입력 및 결과 확인
| 영역 | 기술 |
|---|---|
| LLM | vLLM + GPT-OSS 120B |
| 언어 | Java21, TypeScript |
| 백앤드 | Spring Boot 2.7 + WebFlux (Reactive) |
| 프론트앤드 | Next.js + Tailwind CSS |
| 검색 | OpenSearch Vector DB |
| 리랭킹 | FastAPI + BAAI/bge-reranker-base |
| 임베딩 | FastAPI + BAAI/bge-base-en-v1.5 |
rag-genAI/
├── backend/ # Spring Boot WebFlux 서버
├── frontend/ # Next.js 기반 UI
├── rag/ # 검색 엔진 및 임베딩/리랭킹 API 도커 환경
├── assets/ # 문서 / 리소스
└── README.md
아래 순서대로 진행하면 로컬 환경에서 바로 실행할 수 있습니다.
git clone https://github.com/tableMinPark/rag-genAI.git
cd rag-genAIcd rag
docker compose up -d컨테이너 기동 여부 확인:
docker container list -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a95fa437581c rag-opensearch "./opensearch-docker…" 29 seconds ago Up 28 seconds 0.0.0.0:9200->9200/tcp, 9300/tcp, 0.0.0.0:9600->9600/tcp, 9650/tcp opensearch
fbbebc623a09 rag-embedding "uvicorn app:app --h…" 29 seconds ago Up 28 seconds 0.0.0.0:8000->8000/tcp embedding
7865318dbc66 rag-reranker "uvicorn app:app --h…" 29 seconds ago Up 28 seconds 0.0.0.0:8001->8000/tcp reranker검색 엔진 인덱스 생성 여부 확인:
curl http://localhost:9200/_cat/indices\?v
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open gen_ai 8miVKqCiRkC_H_hvPJ5dhw 1 1 207 0 4.7mb 4.7mb
yellow open gen_myai VN5sI_PKT-KxpVJV_1laKw 1 1 3 0 87.9kb 87.9kbcd frontend
npm install
npm run dev브라우저 접속:
http://localhost:3000
server:
port: 8080
spring:
application:
name: genai
servlet:
multipart:
max-file-size: 300MB
max-request-size: 300MB
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
username: sa
password:
driverClassName: org.h2.Driver
jpa:
defer-datasource-initialization: true
open-in-view: false
properties:
hibernate:
show_sql: true
format_sql: false
hbm2ddl:
auto: update
engine:
# 인덱스 컬렉션
collection:
connect-time-out: 5000
response-time-out: 300000
read-time-out: 300000
write-time-out: 300000
host: localhost
port: 9200
# 인덱스 검색
search:
connect-time-out: 5000
response-time-out: 300000
read-time-out: 300000
write-time-out: 300000
host: localhost
port: 9200
# 인덱스 색인
indexer:
connect-time-out: 5000
response-time-out: 300000
read-time-out: 300000
write-time-out: 300000
host: localhost
port: 9200
# 임베딩 데이터
embed:
connect-time-out: 5000
response-time-out: 300000
read-time-out: 300000
write-time-out: 300000
host: localhost
port: 8000
path: /embed
# 리랭크
reranker:
connect-time-out: 5000
response-time-out: 300000
read-time-out: 300000
write-time-out: 300000
host: localhost
port: 8001
path: /rerank
file:
# 파일 저장 루트 디렉토리 경로
file-store-path: ${FILE_STORE_PATH}
chunk:
# 기본 청킹 사이즈
token-size: 1400
# 기본 청킹 오버랩 크기
overlap-size: 200engine:
# LLM (멀티 모델 설정)
llm:
# LLM Instance 획득 재시도 설정
retry:
# 획득 시도 타임 아웃 (ms)
timeout-ms: 300000
# 획득 시도 후 대기 시간 (ms)
delay-ms: 500
instances:
# 1) VLLM 설정
# 모델 응답 형식 지정 (vllm)
- platform: vllm
# Instance 별 최대 session count (최대 LLM 동시 요청 수)
session-count: 100
# 사용할 기능 타입 (default) -> ex) 요약 기능만을 위한 모델 설정 시 사용
type: default
# 응답 타임 아웃
response-timeout: 300000
# 리드 타임 아웃
read-timeout: 300000
# 쓰기 타임 아웃
write-timeout: 300000
# 연결 타임 아웃
connect-timeout: 5000
api-key: ${LLM_SERVER_API_KEY}
host: ${LLM_SERVER_HOST}
port: ${LLM_SERVER_PORT}
path: ${LLM_SERVER_PATH}
model-name: openai/gpt-oss-120b
# 최대 제한 토큰 수
model-context-limit: 128000
internal-token-overhead: 120
# 출력 토큰 계산 안전 마진 값
safety-margin: 256
# 최소 출력 토큰
min-output-tokens: 64
# 최대 출력 토큰
max-output-tokens: 4096
# 2) OPENAI 설정
# 모델 응답 형식 지정 (openai)
- platform: openai
# Instance 별 최대 session count (최대 LLM 동시 요청 수)
session-count: 100
# 사용할 기능 타입 (default) -> ex) 요약 기능만을 위한 모델 설정 시 사용
type: default
# 응답 타임 아웃
response-timeout: 300000
# 리드 타임 아웃
read-timeout: 300000
# 쓰기 타임 아웃
write-timeout: 300000
# 연결 타임 아웃
connect-timeout: 5000
api-key: ${LLM_SERVER_API_KEY}
host: https://api.openai.com
port: ${LLM_SERVER_PORT}
path: ${LLM_SERVER_PATH}
model-name: gpt-3.5-turbo
# 최대 제한 토큰 수
model-context-limit: 12800
internal-token-overhead: 120
# 출력 토큰 계산 안전 마진 값
safety-margin: 256
# 최소 출력 토큰
min-output-tokens: 64
# 최대 출력 토큰
max-output-tokens: 2048VLLM Stream 응답 형식:
{
"id": "chatcmpl-id",
"object": "chat.completion",
"created": 1770653409,
"model": "openai/gpt-oss-120b",
"choices": [
{
"index": 0,
"delta": {
"role": "assistant",
"content": "안녕하세요! 무엇을 도와드릴까요?",
"refusal": null,
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": [],
"reasoning": "The user says \"안녕\" which is Korean for \"Hello\". We should respond in Korean, greeting back. Keep it friendly.",
"reasoning_content": "The user says \"안녕\" which is Korean for \"Hello\". We should respond in Korean, greeting back. Keep it friendly."
},
"logprobs": null,
"finish_reason": "stop",
"stop_reason": null,
"token_ids": null
}
],
"service_tier": null,
"system_fingerprint": null,
"usage": {
"prompt_tokens": 71,
"total_tokens": 119,
"completion_tokens": 48,
"prompt_tokens_details": null
},
"prompt_logprobs": null,
"prompt_token_ids": null,
"kv_transfer_params": null
}OpenAI Stream 응답 형식:
{
"id": "chatcmpl-id",
"object": "chat.completion",
"created": 1770822388,
"model": "gpt-3.5-turbo",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "안녕하세요! 무엇을 도와드릴까요?",
"refusal": null,
"annotations": []
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 13,
"completion_tokens": 21,
"total_tokens": 34,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": null
}cd backend
chmod 755 ./gradlew
./gradlew clean bootjar
java -jar build/libs/rag-genai-2.1.0.jar또는 개발 모드:
./gradlew bootRun백엔드 기본 주소:
http://localhost:8080/api
본 프로젝트의 RAG 파이프라인은 사용자 입력 질의를 그대로 검색에 사용하지 않습니다.
LLM을 활용하여 다음 과정을 거칩니다:
-
질의 재작성 (Query Rewrite)
- 사용자의 자연어 질의를 검색 친화적인 형태로 재작성
- 불필요한 수식어 제거
- 핵심 키워드 중심의 질의로 변환
-
질의 명확화 (Query Clarification)
- 모호하거나 포괄적인 질의를 구체적인 검색 질의로 보정
- 의도 파악이 어려운 경우 문맥 중심으로 의미 보강
이를 통해:
- 검색 정확도 향상
- 불필요한 문서 검색 감소
- LLM 응답 품질 개선
- RAG 전체 파이프라인 효율 증가
이라는 효과를 얻을 수 있습니다.
본 프로젝트는 Spring WebFlux를 사용하여 다음과 같은 특징을 가집니다:
- 논블로킹 I/O 기반 처리
- 검색 → RAG → LLM 호출까지 Reactive Stream으로 연결
- 고동시성 환경에서도 안정적인 처리 가능
- LLM 호출과 같은 I/O 중심 작업에 최적화
- GPT-OSS 120B 모델 기준으로 개발
- vLLM을 활용한 고속 추론 환경 전제
- OpenAI API 의존 없이 오픈소스 LLM 서버 연동 구조
- GPU 환경에서 최적의 성능 발휘
검색 엔진(OpenSearch)과 RAG 파이프라인을 결합하여
검색 데이터 기반의 신뢰도 높은 답변을 제공합니다.
- 사용자 질의 → 질의 재작성 / 명확화
- 벡터 검색 기반 관련 문서 조회
- 리랭킹을 통한 컨텍스트 정제
- 정제된 컨텍스트를 기반으로 LLM 응답 생성
단순 LLM 응답이 아닌,
검색 결과에 근거한 답변을 제공하는 것이 핵심 목적입니다.
검색이나 문서 제약 없이 LLM 자체의 추론 능력을 활용한 자유 질의 기능입니다.
- 일반적인 대화형 질의
- 아이디어 브레인스토밍
- 개념 설명, 코드 설명 등
RAG가 필요하지 않은 경우에도
LLM을 유연하게 활용할 수 있는 기본 질의 모드입니다.
LLM의 다양한 옵션을 조절하며 응답 결과를 비교/검증할 수 있는 기능입니다.
- Temperature, Top-P, Max Tokens 등 파라미터 조절
- 동일 질의에 대해 서로 다른 응답 결과 비교
- 프롬프트 설계 및 응답 특성 실험 용도
LLM 응답 특성을 이해하고
최적의 설정을 찾기 위한 실험용 기능입니다.
사용자가 직접 업로드한 문서만을 기반으로
개인화된 RAG 질의응답을 제공합니다.
- PDF / 텍스트 문서 업로드
- 문서 청킹 + 임베딩
- 해당 문서 컬렉션에 대해서만 RAG 질의 수행
- 외부 데이터와 완전히 분리된 RAG 환경
개인 문서, 내부 자료 등
외부로 노출되면 안 되는 데이터 기반 질의에 적합합니다.
문서를 업로드하고 사용자의 요구사항을 입력하면
Markdown 기반 보고서 초안을 자동 생성합니다.
- 문서 분할(Chunking)
- 부분 요약 수행
- 부분 요약 결과를 기반으로 전체 요약
- 전체 요약문을 활용하여 보고서 구조 생성
- Markdown 형식의 보고서 초안 출력
대용량 문서에서도
요약 품질과 구조를 유지하기 위한 단계적 요약 전략을 사용합니다.
특정 문서에 대해 요약 길이를 선택하여 요약문을 생성합니다.
- 짧은 요약 / 중간 요약 / 긴 요약 선택 가능
- 대용량 문서 대응을 위한 부분 요약 → 전체 요약 구조
보고서 생성, 문서 이해, 빠른 내용 파악 등
다양한 시나리오에서 활용할 수 있습니다.
특정 문서에 대해 전체 내용을 번역하는 기능입니다.
- 문서 분할 후 요약 또는 원문 기반 처리
- Spring WebFlux 기반 병렬 처리
- 번역 결과를 순차적으로 병합
- 아호코라식 알고리즘 적용하여 번역 사전 반영한 번역문을 작성하도록 구현
- 번역 작업을 리액티브 스트림으로 병렬 처리
- 결과는 문서 순서를 유지하여 병합
- 대용량 문서에서도 안정적인 처리 가능
질의와 관련된 모든 기능은 다음 정보를 기반으로 동작합니다:
- 이전 대화 히스토리
- 대화 상태 요약(Context Summary)
- 현재 질의의 의도
이를 통해:
- 이전 대화를 고려한 응답
- 문맥이 끊기지 않는 질의 흐름
- 자연스러운 멀티턴 대화 경험
을 제공합니다.
모든 API는 Spring WebFlux 기반으로 구현되어 있습니다.
- Client는 API 응답을 블로킹하지 않음
- LLM 호출, 요약, 번역 등 I/O 작업을 비동기 처리
- 스트리밍 응답을 통한 빠른 사용자 피드백
특히 요약, 번역, 보고서 생성과 같은
시간이 오래 걸리는 작업에 최적화된 구조입니다.
- ✅ 질의 재작성 및 명확화를 포함한 고품질 RAG 파이프라인
- ✅ RAG 기반 생성형 AI 시스템
- ✅ WebFlux 기반 비동기/논블로킹 처리
- ✅ vLLM + GPT-OSS 120B 기준 개발
- ✅ 빠르게 실행 가능한 개발 환경 제공










