안녕하세요! 오늘은 전자정부 표준프레임워크(eGovFrame) 환경에서 Spring AI와 Redis Stack을 결합하여, 나만의 문서를 학습한 로컬 AI 시스템을 구축하는 과정을 상세히 정리해 보겠습니다.
단순히 실행하는 것을 넘어, 올린 PDF나 Markdown 파일이 어떤 단계를 통해 지식화되어 저장이 되는지 그 내부 파이프라인까지 같이 이해해보겠습니다.
1. 프로젝트 준비
이번 실습의 핵심은 "모든 것을 로컬에서 제어한다"는 점입니다.
1) 개발환경
- JDK(Java Development Kit) 17 이상
- Apache Maven 3.8.4 이상
- Docker : 최신 버전 권장
- Docker compose : 최신 버전 권장
- AI 엔진: Ollama (qwen3.5:2b, bge-m3:567m)
- 환경 관리: uv (Python 3.12 기반)
2) uv를 이용한 가상환경 구축 프로세스
자바 프로젝트임에도 파이썬 환경을 구축하는 이유는 AI 모델의 전처리나 보조 스크립트가 파이썬 환경에서 동작하기 때문입니다. 이때 가상환경(Virtual Environment)은 내 시스템의 기본 파이썬과 프로젝트용 파이썬을 분리하여 라이브러리 충돌을 막아주는 필수적인 역할을 합니다.
uv init --python 3.12
- 현재 디렉토리를 파이썬 프로젝트로 초기화하며, 사용할 파이썬 버전을 3.12로 명시합니다.
- 프로젝트의 뼈대가 되는 설정 파일들을 생성하고 시스템에 해당 버전의 파이썬이 없다면 자동으로 관리해 줍니다.

uv venv --python 3.12
- 프로젝트 폴더 내에 독립적인 가상환경 공간(.venv)을 생성합니다.
- 이 프로젝트만을 위한 '깨끗한 공간'을 만드는 과정입니다. 여기서 설치하는 라이브러리들은 내 컴퓨터의 다른 설정에 영향을 주지 않습니다.
source .venv/bin/activate
- 생성한 가상환경을 현재 터미널 세션에 적용(활성화)합니다.
- "이제부터 내가 입력하는 파이썬 명령어는 방금 만든 독립된 공간에서 실행해!"라고 컴퓨터에게 명령하는 단계입니다. 실행 후 터미널 앞에 (.venv) 표시가 나타나며 활성화를 확인할 수 있습니다.

3) 프로젝트 소스
이번 실습에서 사용할 프로젝트를 클론합니다.
https://github.com/eGovFramework/egovframe-ai-rag
GitHub - eGovFramework/egovframe-ai-rag: Spring AI및 Langchain4j를 사용한 RAG(Retrieval-Augmented Generation) 샘플
Spring AI및 Langchain4j를 사용한 RAG(Retrieval-Augmented Generation) 샘플 - eGovFramework/egovframe-ai-rag
github.com
저는 개인 개발 로그와 교육용 프로젝트를 관리하는 폴더 내에 환경을 구성했습니다.
# 프로젝트 클론
git clone https://github.com/eGovFramework/egovframe-ai-rag.git
2. 인프라 구성: Docker & Ollama
1) Redis Stack 컨테이너 실행
docker-compose를 통해 벡터 데이터를 저장할 Redis Stack을 실행합니다. Redis Stack은 일반 Redis와 달리 벡터 검색 엔진을 포함하고 있어 RAG의 핵심 저장소 역할을 합니다.
# 프로젝트 디렉토리(langchain4j-ai-rag-postgre, spring-ai-rag-redis-stack) 이동 후 실행
docker-compose up -d

docker desktop에서 각 db들이 실행된 것을 확인할 수 있습니다.

2) 로컬 모델 준비 (Ollama)
RAG의 두뇌 역할을 할될 추론 모델과 문서를 숫자로 바꿔줄 임베딩 모델을 각각 다운로드합니다.
- 추론용: qwen3.5:2b (가볍지만 강력한 성능) https://ollama.com/library/qwen3.5:2b
- 임베딩용: bge-m3:567m (다국어 및 한국어 성능 탁월) https://ollama.com/library/bge-m3:567m
# 추론용 다운로드
ollama pull qwen3.5:2b
# 임베딩용 다운로드
ollama pull bge-m3:567m
# 다운로드 확인
ollama list
3. RAG 시스템의 핵심: 데이터 파이프라인 프로세스
프로젝트에 올린 PDF나 Markdown 파일이 어떻게 AI의 지식이 되어 답변으로 사용 될까요?
메이븐(Maven) 라이브러리를 통해 관리되는 이 프로세스는 다음과 같은 순서로 진행됩니다.
- 문서 로딩 (Document Loading)
- application.yml에 설정된 경로에서 파일을 스캔합니다.
- PDF : 메이븐 라이브러리(Apache Tika 등)가 텍스트를 추출
- Markdown : 텍스트 소스로 직접 로딩
- 문서 정규화 (Normalization)
- normalization.enabled: true 설정(application.yml)에 따라 불필요한 HTML 태그, 공백, 줄바꿈을 제거하여 깨끗한 텍스트 상태로 만듭니다.
- 청킹 (Chunking - 문서 쪼개기)
- 추출된 긴 텍스트를 AI가 한 번에 읽기 좋은 크기(chunk-size: 4000)로 잘게 쪼갭니다.
- 이때 단순하게 자르는 것이 아니라, 문맥이 끊기지 않도록 정규화(공백 제거 등) 과정을 거칩니다.
- 또한, 너무 짧은 데이터가 들어가지 않도록 min-chunk-length-to-embed: 50과 같은 제약 조건을 두어 품질을 유지합니다.
- 임베딩 (Embedding - 벡터화) 핵심 단계!
- 청킹이 끝난 텍스트 조각들을 Ollama의 임베딩 모델로 보냅니다.
- 이 모델은 텍스트를 컴퓨터가 이해할 수 있는 수만 개의 좌표를 가진 '벡터' 데이터로 변환합니다.
- Redis 적재 (Vector Store Storage)
- 임베딩이 끝난 데이터만 비로소 Redis Stack의 벡터 저장소(document-index)에 저장됩니다. 이제 검색 준비가 끝난 상태가 됩니다.

4. application.yml 수정
spring-ai-rag-redis-stack/src/main/resources/application.yml
저는 임베딩 처리 방식을 GitHub의 소스와 다르게 수정하였습니다.
- 기존 (Transformers & ONNX): embedding: transformers 설정 사용
- 애플리케이션 서버(Spring 부트)가 직접 로컬 리소스에 있는 model.onnx 파일과 tokenizer.json을 읽어와서 임베딩 계산을 직접 수행하는 방식입니다.
- 별도의 외부 서버 없이 앱 하나만 띄우면 돌아가는 '올인원' 구성이 가능하지만 서버 리소스를 직접 사용하며, 설정이 복잡하다고 단점이 있습니다.
- 수정 (Ollama): embedding: ollama로 변경
- 애플리케이션은 임베딩 계산을 직접 하지 않고, 외부 서비스인 Ollama API에 텍스트를 던져서 결과값(벡터)만 받아옵니다.
- 설정을 따로 하지 않아도 되서 관리가 편해지고, 임베딩 전용 고성능 모델(bge-m3)을 Ollama를 통해 유연하게 교체하며 사용할 수 있게 되었습니다.
spring:
application:
name: spring-ai-rag-redis
# Ollama 설정
ai:
ollama:
base-url: http://localhost:11434
chat:
model: qwen3.5:2b
options:
temperature: 0.4
embedding:
options:
model: bge-m3:567m
num-ctx: 512
# ollama 모델 사용 설정
model:
embedding: ollama
# Redis 벡터 저장소 설정
# 처음 실행 시에만 true로 설정하고, 이후에는 false로 변경하여 중복 데이터 방지
vectorstore:
redis:
initialize-schema: true
index-name: document-index
# 문서 경로 설정
document:
path: file:/Users/.../egovframe-ai-rag/data/**/*.md
pdf-path: file:/Users/.../egovframe-ai-rag/data/**/*.pdf
# PDF 리더 설정
pdf:
page-top-margin: 0
pages-per-document: 1
# 요약 생성 설정 여부
enable-summary: false
# 요약을 생성할 최소 청크 수 (기본값: 5)
summary-min-chunks: 2
# 키워드 추출 설정 여부
enable-keywords: false
keyword-count: 5
# 청크 크기 설정 (토큰 단위)
chunk-size: 4000
# 최소 청크 크기 (문자 단위)
min-chunk-size-chars: 350
# 최대 청크 수
max-num-chunks: 500
# 임베딩할 최소 청크 길이
min-chunk-length-to-embed: 50
# 문서 정규화 설정
normalization:
# 정규화 기능 활성화 여부
enabled: true
# HTML 태그 제거 여부
remove-html-tags: true
# 마크다운 코드 블록 제거 여부
remove-code-blocks: false
# 공백 정규화 여부
normalize-whitespace: true
# 줄바꿈 정규화 여부
normalize-newlines: true
# 특수문자 정리 여부
clean-special-chars: true
# Redis 연결 설정
data:
redis:
host: localhost
port: 6379
# 빈 오버라이딩 허용 (임베딩 모델 빈 충돌 해결)
main:
allow-bean-definition-overriding: true
# 로깅 설정
logging:
level:
com.example.chat: DEBUG
org.springframework.ai: INFO
# DJL 관련 로그 레벨 조정 (성능 최적화 경고 숨김)
ai.djl.pytorch.engine.PtEngine: WARN
ai.djl: WARN
# RAG 질문 압축 설정
# 대화 히스토리를 기반으로 후속 질문을 독립적인 질문으로 압축
# true: 압축 활성화 (검색 정확도 향상, 응답 시간 증가)
# false: 압축 비활성화 (빠른 응답, 원본 질문으로 검색)
rag:
enable-query-compression: true
# RAG 유사도 임계값 설정 (0.0 ~ 1.0)
# 실제 검색 유사도: 0.22~0.26 범위 → 임베딩 품질 제한
# 임계값을 조정: 0.20 (너무 높으면 검색 결과 없음)
similarity:
threshold: 0.70
# RAG 검색 결과 개수 (Top K)
top-k: 3
# 채팅 메모리 설정 (기본값: 20)
chat:
memory:
max-messages: 20
5. 실제 활용: 두 가지 채팅 모드
프로젝트를 실행하면 웹 인터페이스를 통해 두 가지 방식의 대화가 가능합니다.
- RAG 채팅 (나의 문서 기반):
- 사용자의 질문 → Redis에서 관련 문서 검색 → 문서 내용 + 질문을 합쳐서 모델에게 전달 → 내 문서에 근거한 답변 생성.
- 사내 매뉴얼, 개인 개발 로그(dev-log) 등을 물어볼 때 최적입니다.
- (similarity.threshold: 0.70 : 연관성이 매우 높은 문서만 참조하도록 설정)


- 일반 채팅 (Ollama 직접 대화):
- 문서 검색 없이 바로 LLM(qwen3.5:2b)과 대화합니다.
- 모델이 기본적으로 알고 있는 일반 상식, 코딩 지식 등을 물어볼 때 사용합니다.


전자정부 표준프레임워크 기반의 RAG 프로젝트를 구동해 보니, Spring AI의 추상화 덕분에 복잡한 벡터 연산 로직 없이도 강력한 AI 서비스를 만들 수 있었습니다.
특히 uv를 통한 파이썬 환경 관리와 Docker를 이용한 Redis 구축, 그리고 Ollama를 통한 로컬 모델 실행까지 결합되어 보안 걱정 없는 완벽한 폐쇄형 AI 환경을 구축할 수 있다는 점이 가장 큰 매력이었습니다.
참조
'Backend > Spring' 카테고리의 다른 글
| [Spring] 전자정부프레임워크 crpto DB정보 globals.properties 암호화 (0) | 2026.01.29 |
|---|---|
| [Spring] WebFlux 초기 설정 (0) | 2024.11.18 |
| [Spring] Spring Boot를 통해 간단한 REST API와 Redis를 이용한 캐시 기능을 구현 (0) | 2024.11.03 |
| [Spring] MyBatis 댓글 목록 들여쓰기 구현 (0) | 2023.03.03 |
| [Spring] JdbcTemplate (0) | 2023.02.17 |
