OpenAI 임베딩을 활용한 RAG 기반 LLM 시스템 만들기 (1)

2026. 4. 9. 19:00·Spring Boot/LLM

 

 

 

이 게시물은 PGVector 설치를 마치고 RAG에 관한 내용입니다.

https://kimfishes.tistory.com/18

 

PGvector Window 설치 방법

https://kimfishes.tistory.com/17 LLM (Ollama) RAG 구현 전 pgvector VS Qdrant 중 무엇이 좋을까https://kimfishes.tistory.com/3 SpringAI 사용해서 LLM (Ollama) 연결 + Resilience4j 도입 (Spring Boot)고객센터에 사용자가 문의 게시

kimfishes.tistory.com

 

 

 

구현하려는 상황

  • 저는 현재 날씨, 수온, 풍속, 물때, 파도와 같은 환경 요소와 사용자 위치, 어종별 특성을 기반으로 물고기가 잡힐 확률을 예측하는 서비스를 제작하고 있으며,
  • 각 어종별 낚시 성공 확률을 수치로 제공하고, 왜 이러한 확률이 도출되었는지를 GPT 또는 Gemini를 활용하여 자연어로 설명하는 기능을 구현하고자 합니다.
  • 로컬 LLM 환경인 Ollama를 직접 운영하는 대신 외부 API를 활용하여 모델 관리 부담을 줄이고 안정적인 서비스를 목표로 합니다.
  • 사용자 요청마다 AI를 호출하는 방식이 아니라 서버에서 주기적으로 데이터를 기반으로 AI 요청을 수행하고, 그 결과를 캐싱하여 제공하는 구조로 설계하여 응답 속도를 개선하고 API 호출 비용을 절감하고자 합니다.
  • 해당 서비스는 배낚시가 아닌 갯바위나 해변에서 도보로 이동하며 진행하는 워킹 낚시 환경을 대상으로 합니다.

 

 

 

1차 구현 MVP 목표

  • PDF 업로드
  • 텍스트 추출
  • 청크 생성
  • OpenAI 임베딩 생성
  • pgvector 저장
  • 질문 임베딩
  • cosine 유사도 검색
  • GPT로 답변 생성

임베딩 모델 : OpenAI,  KoBERT 등

생성 모델 : GPT, Gemini 등

DB : PostgreSQL + PGVector

백엔드 : Spring Boot

 

 

 

 

 

RAG란 (Retrieval-Augmented Generation)

  • RAG의 경우 임베딩 모델이 문서를 잘게 나눈 뒤 벡터로 변환해서 DB에 저장해 두고, 질문이 오면 관련 문서를 찾아 생성형 모델에게 자료를 줌
  • PDF, txt, md, 사내 문서, 매뉴얼 등을 넣고 "이 문서 기준으로 질문에 답해줘”라고 하는 방식
  • 회사 정책 문서, 서비스 설명서, API 문서, FAQ

RAG 학습에 활용될 때 좋은 문서 형태

  • 문장 단위가 깔끔하고, 주제가 섞이지 않아야 함
  • 제목/소제목 구조가 명확해야 함
  • 최신 정보 기준이 분명해야 한다

 

 

1. RAG에서는 문서를 그대로 넣지 말고 청크(chunk) 단위로 쪼개 사용한다.

  • 문서를 통째로 벡터화하면 검색 품질이 떨어짐 (유사도 검색 저하)
  • 보통 문단 단위, 섹션 단위, 500~1000자 또는 토큰 기준 일정 크기로 나눈다.
  • 질문이 들어왔을 때 관련 부분만 정확히 찾을 수 있게 하기 위해 나눔

원본 문서:

  • 사용자 인증
  • 로그인
  • 토큰 재발급
  • 로그아웃
  • 예외 처리

이걸 이렇게 나눈다.

  • chunk 1: 사용자 인증 개요
  • chunk 2: 로그인 요청/응답
  • chunk 3: access token / refresh token 정책
  • chunk 4: 로그아웃 처리
  • chunk 5: 예외 코드

 

 

2. 청크로 나눠진 데이터를 임베딩으로 생성해서 pgvector에 저장한다.

 

refresh token은 언제 재발급되는가?

 

👇

임베딩으로 변환

[0.123, -0.812, 0.442, ...] ← 벡터  

 

👇

pgvector에 저장

// 텍스트 + 벡터 같이 저장

INSERT INTO document_chunks (content, embedding)
VALUES (
  'refresh token은 만료 1일 이하일 때 재발급된다',
  '[0.123, -0.812, 0.442, ...]'
);

 

 

 

3. 구현이 완료된 후 질문이 들어오면 답하는 순서

질문 : 우리 부서가 개발한 refresh token은 언제 다시 발급돼?

 

1) 질문도 임베딩으로 변환

  • 질문 텍스트를 임베딩 모델로 벡터화

2) PGVector를 활용해 DB에서 비슷한 chunk 검색 (Dense Retrieval)

  • PGVector는 여러 거리 연산을 지원하며 보통은 Dense Retrieval 방식 중 cosine distance 기반 검색을 많이 쓴다
    • cosine distance란 두 벡터(숫자 배열)가 얼마나 비슷한 방향을 가지는지를 기준으로 가장 유사한 데이터를 찾는 방법
    • 단어가 달라도 의미가 같으면 찾음 (예시 : “강아지”라고 입력해도 → “반려견”이라 찾아낼 수 있음)

3) DB에서 찾은 답변을 생성 모델이 응답 생성 후 반환

 

 

 

 

 

 

 

문서 임베딩 흐름 및 규칙

  1. PDF 업로드
  2. PDF에서 텍스트 추출
  3. 추출한 텍스트를 정제
  4. 청크로 분할
  5. 각 청크에 메타데이터 부여
  6. 임베딩 생성
  7. pgvector에 저장

 

이때 PDF 파일 자체를 바로 벡터 DB에 던지는 게 아닌 문서를 표준화 규칙에 따라 작성 후 넣어야 함

( 원본 문서에 정보를 가지고 메타 데이터로 만들기 때문 )

  • PDF 파일명
  • 문서 안 첫 페이지 제목
  • 문서 내 대제목/소제목
  • 문서 작성일

 

위에 문서 정보에 맞춰 업로드 시점에 시스템이 메타 데이터를 부여

  • document_id ( 문서를 식별하는 유일한 키 (Primary Key)로 chunk들이 어떤 문서에 속하는지 연결을 위해 )
  • source ( 이 문서가 어디서 왔는지로 pdf_upload, notion, api_import 등 )
  • uploaded_at ( 문서가 시스템에 들어온 시점 )
  • version ( 같은 문서의 변경 이력 관리로 최신 문서만 검색되게 하기 위해 )
  • permission_scope ( 이 문서를 누가 볼 수 있는지 권한 처리 ( public, private, admin_only, team_a, team_b ))

 

또는 운영자가 직접 지정

  • category
  • 문서 유형
  • 권한 범위
  • 이 문서가 최신본인지 여부

 

 

 

 

 

문서 작성 시 최소 권장 규칙

  • 문서 제목이 있어야 함
  • 소제목(섹션 제목)이 있어야 함
  • 한 섹션에는 한 주제만 담기
  • 표를 넣는다면 표만 넣지 말고 설명 문장도 함께 넣기
  • 최신 수정일이나 버전이 있으면 좋음
  • 의미 없는 반복 문구(회사명, 바닥글, 페이지번호)는 최소화

 

 

 

만약 같은 문서를 연속으로 학습 시킨다면?

  • 아무런 처리를 하지 않고 PDF를 중복 업로드 시킨다면, chunk도 똑같은 내용으로 2세트 생김
  • RAG는 본질적으로 여러 근거를 통해 판단하지만 중복된 데이터가 많다면 실제론 근거가 부족해도 중복 개수로 인해 맞다고 판단 가능 ( 유사도 상위 결과를 독점하게 되어 검색의 다양성이 완전히 깨짐 )
  • 만약 같은 문서 제목이지만 정책이 바뀌어 내용이 다른 경우 무엇이 정답인지 판단 불가

해결법 1

- 파일 해시 계산 방법으로 같은 파일이면 같은 해시가 나오는 점을 이용하여 중복 저장을 막음 

- 문제점으론 파일이 달라도 내용이 같은 파일이 있을 수 있어 문제 발생

 

해결법 2

- 텍스트 추출 후 정규화한 내용을 해시로 사용

- 그러나 만약 버전 1 문서와 버전 2 문서의 내용은 같지만 문장만 달라진 경우 중복 여부를 판별할 수 없다.

 

해결법 3

- 운영 정책 설정

  • 제목이 같고 본문 유사도가 95% 이상이면 새 버전으로 본다
  • 같은 source + 같은 title이면 version up 처리
  • 사용자가 “덮어쓰기”를 선택하면 기존 문서를 비활성화한다
file_hash 같으면 완전 중복 → 저장 안 함
content_hash 같으면 내용 중복 → 저장 안 하거나 기존 문서 재사용
제목은 같지만 내용이 다르면 → 새 버전으로 저장

 

 

 

 

 

 

청킹 전략

  • 문서를 “어디서 어떻게 잘라서” 벡터로 만들지에 대한 규칙
환불 정책
1. 일반 환불 규정 결제 후 7일 이내 미사용 시 전액 환불 가능
2. 예외 사항 이벤트 상품은 환불 불가

 

잘못된 청킹 결과 ( 문장이 끊김 → 의미 깨짐 → 검색 망함 )

chunk1: 환불 정책 1. 일반 환불 규정 결제 후
chunk2: 7일 이내 미사용 시 전액 환불 가능 2. 예외 사항
chunk3: 이벤트 상품은 환불 불가

 

잘된 청킹 결과

chunk1:
환불 정책 / 일반 환불 규정 / 결제 후 7일 이내 미사용 시 전액 환불 가능

chunk2:
환불 정책 / 예외 사항 / 이벤트 상품은 환불 불가

 

 

 

청킹 전략의 핵심 요소 4가지

1) 어디서 자를 것인가 (Boundary)

  • 문장 기준
  • 문단 기준 (추천)
  • 섹션 기준 (제일 좋음)
  • 페이지 기준 (비추천)

 

2) 얼마나 자를 것인가 (Chunk Size)

  • 너무 작으면: 정보 부족
  • 너무 크면: 내용이 섞임

 

3) 겹칠 것인가 (Overlap)

  • chunk 내용을 겹치게 구현할지
    • chunk1: A B C D
    • chunk2: C D E F

 

4) 문맥을 포함할 것인가

  • 제목 + 섹션을 같이 넣는지 여부로 이걸 사용하지 않으면 검색 품질이 떨어진다
  • 환불 정책에 여러 섹션 중 예외 상황을 같이 합한 상태 
    • [환불 정책 / 예외 사항]
      이벤트 상품은 환불 불가

 

 

 

위 방식들을 사용한 Hybrid 청킹 전략 필요

Step 1. 텍스트 추출

Step 2. 섹션 탐지

Step 3. 섹션 단위로 chunk 생성 

Step 4. 너무 길면 분할

Step 5. overlap 적용 (선택)

Step 6. context 추가

1. 섹션 기준으로 자름
2. 너무 길면 문단으로 추가 분할
3. overlap 적용

 

 

 

기본 청킹 전략 (일반적인 문서 기준)

- 블로그, 기사, 설명문 등 긴 자연어 문서에 최적화된 설정

  • Chunk Size: 300 ~ 500 토큰
  • Overlap: 50 ~ 100 토큰
  • Boundary: 문단 기준
  • Context: 선택적 포함

 

그러나 현재 사용하려는 데이터의 특징

  • 날씨, 수온, 풍속, 물때 등 구조화된 정보
  • 정보 단위가 짧고 명확함
  • 조건 기반 검색 (필터링 중심)

 

 

사용한 청킹 전략

  • Chunk Size: 200 ~ 400 토큰
  • Overlap: 30 ~ 80 토큰
  • Boundary: 섹션 + 문단 혼합
  • Context: 필수 포함

왜 기본값보다 더 작게 설정했는가?

  1. 정보 단위가 이미 작기 때문 ( 긴 문장이 아니라 “짧은 정보 묶음”인 상태 )
  2. 검색 정확도 향상을 위해 ( 청크 단위가 커질수록 관련 없는 정보까지 같이 검색됨 )
  3. 지역, 날씨, 시간 등 조건 필터 존재 ( 작은 청크는 필터 + 벡터 검색 조합 최적 )

 

 

 

 

 

임베딩 모델과 생성 모델

LLM에서 사용자에게 답변을 하기 위해 임베딩 모델과 생성 모델이 필요하다.

  • 임베딩 모델: 문서를 벡터화하는 모델
  • 생성 모델: 질문에 답하는 모델 
  • 이때 임베딩 모델의 경우 문서 임베딩과 질문 임베딩은 서로 같아야 한다.  ( OpenAI,KoBERT, KoGPT )
  • 생성형 모델은 자유롭게 바뀌어도 상관이 없다. ( Ollama, GPT, Gemini, Claude )

예시

  • 문서 → OpenAI embedding → 저장
  • 질문 → OpenAI embedding → 검색
  • 답변 → Ollama

 

임베딩 모델

  • 외부 API 호출의 경우 비용 문제가 발생하기 때문에 상대적으로 저렴한 OpenAI text-embedding-3-small 선택
  • 또한, 여러 임베딩 모델 중 SK Telecom의 KoBERT 같은 임베딩 시스템의 경우 직접 서버 띄워야 하므로 선택지에서 제외
  • 임베딩을 활용 시 주의 사항이 존재한다. 바로 차원 수로 인한 DB 비용과 연산 시간 증가

차원 수(dimension)

  • 차원이란 : “하나의 청크를 숫자로 표현했을 때 그 숫자의 개수” ( chunk1 → [0.12, -0.33, 0.88, ..., 총 1536개] ) 
    • 차원은 벡터의 길이를 말한다.
  • 임베딩 작업이 끝나면 [0.123, -0.532, 0.884, ...] 같은 숫자 형태로 저장이 되는데 이 숫자의 개수가 바로 차원 수
    • 임베딩의 숫자들은 “문장의 의미를 좌표(위치)”로 표현한 값이며 이 좌표 개수 숫자가 차원의 수
  • 차원의 수가 커질수록 = 표현력 ↑ / 비용 ↑ / 연산 시간 ↑
  • 차원 수가 낮을수록 단순하지만 가벼움
  • 차원의 수가 커질수록 pgvector 저장 용량 증가, 인덱스 크기 증가, 검색 비용 증가, 네트워크 비용 증가 문제 발생
  • OpenAI 임베딩은 문장의 길이만 비용에 영향을 끼치고 차원의 수는 비용과 관련이 없지만 저장 비용이 증가 (DB)

OpenAI의 임베딩 모델의 경우 이렇게 차원 수가 정해져 있음 ( 모델이 결정 )

  • text-embedding-3-small → 약 1536차원
  • text-embedding-3-large → 최대 3072차원

 

 

생성 모델

  • 검색된 정보를 바탕으로 최종 답변을 만들어내는 역할
  • 여러 문서를 종합하여 자연스럽게 정리 후 사용자 질문에 맞게 문장 형태로 재구성
  • 부족한 문맥을 보완하여 이해 가능한 답변 생성

모델 선택 전략

외부 생성 모델 사용

  • 안정성 높음
  • 최신 모델 사용 가능
  • 운영 부담 없음
  • 예시 : GPT, Gemini, Claude

로컬 모델 사용

  • 비용 절감 가능
  • 커스터마이징 가능
  • 단점으론 성능 제한과 서버 관리 필요
  • 예시 : Ollama, Llama 3, Mistral

 

 

 

 

 

지금까지 RAG의 전체 흐름과 청킹 전략, 임베딩 설계까지 이론적인 부분을 정리해 보았습니다.

다음 글에서는 실제로 PostgreSQL + pgvector 환경에서 테이블 설계부터 임베딩 저장, 유사도 검색, 그리고 GPT를 활용한 응답 생성까지 전체 구현 과정을 코드 중심으로 다뤄보겠습니다.

 

 

 

'Spring Boot > LLM' 카테고리의 다른 글

PGVector Window 설치 방법  (0) 2026.04.07
RAG 구현 전 Vector DB 선택 (PGVector VS Qdrant 중 무엇이 좋을까?)  (0) 2026.04.07
LLM (Ollama) + Resilience4j 재시도 처리 (Spring Boot)  (0) 2025.11.16
SpringAI 사용해서 LLM (Ollama) 연결 + Resilience4j 도입 (Spring Boot)  (0) 2025.10.20
'Spring Boot/LLM' 카테고리의 다른 글
  • PGVector Window 설치 방법
  • RAG 구현 전 Vector DB 선택 (PGVector VS Qdrant 중 무엇이 좋을까?)
  • LLM (Ollama) + Resilience4j 재시도 처리 (Spring Boot)
  • SpringAI 사용해서 LLM (Ollama) 연결 + Resilience4j 도입 (Spring Boot)
kimfishes
kimfishes
kimfishes 님의 블로그 입니다.
  • kimfishes
    kimfishes 님의 블로그
    kimfishes
  • 전체
    오늘
    어제
    • 전체 (19) N
      • Infra (5)
        • AWS (0)
        • LogBack (4)
      • Spring Boot (14) N
        • LLM (5) N
      • 일상 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    캐시 스탬피드
    Redis
    스프링 알림 시스템
    실시간 알림 시스템
    ELK
    ollama
    cache stampede
    spring ai
    Discord 알림 연동
    LLM
    loging
    retrieval-augmented generation
    Qdrant
    Pre-Signed URL
    로깅
    rag
    pgvector
    UUID v7
    traceId
    Spring boot
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
kimfishes
OpenAI 임베딩을 활용한 RAG 기반 LLM 시스템 만들기 (1)
상단으로

티스토리툴바