spring airag 1일차 / Rag, springairag1

2026. 5. 21. 19:55대우개발원 수업 내용/spring기반 ai

반응형


RAG 검색 증강 생성의 핵심, 임베딩 원리와 활용

더보기
더보기

1. 임베딩의 개념과 필요성

개념: 자연어 단어나 문장 등을 컴퓨터가 이해하고 계산할 수 있도록 고유한 실수 벡터 

즉, 숫자 배열 형태로 변환하는 기술입니다.
필요성: AI 모델은 숫자만 처리하는 함수이기 때문에 문장 간의 의미적 유사성을 판단하려면 

텍스트를 벡터 공간의 GPS 좌표처럼 수치화해야 합니다.
전처리: 텍스트를 정해진 단위로 쪼개는 토크나이즈 과정을 거친 후, 의미를 담아 숫자로 바꾸는 임베딩 단계로 

파이프라인이 진행됩니다.

2. 임베딩 기법의 발전: 과거와 현재

과거 원핫 인코딩 방식: 단어집을 만들어 해당 단어 위치에만 1을 넣고, 나머지는 0을 채우는 방식입니다. 

단어 수가 늘어날수록 벡터 차원이 커지고 대부분이 0이 되는 희소 벡터가 되어 계산이 비효율적이며, 

단어 간 의미 관계를 전혀 표현하지 못하는 단점이 있습니다.
현재 러닝 임베딩 방식: 대규모 데이터셋으로 신경망 모델을 학습시켜 얻는 방식입니다. 

벡터 차원이 낮고 밀집된 형태라 효율적이며, 문맥을 반영하므로 의미가 유사한 단어들은 

벡터 공간상에서 가까운 거리에 위치하게 되는 장점이 있습니다.

3. 임베딩의 핵심 활용 사례

첫 번째 사례는 의미 기반 검색입니다. 전체 문서들의 임베딩을 미리 계산하여 저장소에 보관한 뒤, 사용자의 질문이 들어오면 질문을 임베딩합니다.

 그 후 문서 임베딩과 질문 임베딩 간의 코사인 유사도를 계산하여 내림차순 정렬하고, 

가장 연관성이 높은 상위 문서들을 사용자에게 반환합니다.

두 번째 사례는 LLM 부가 정보 주입 즉, RAG 기반 질의응답입니다. 

대형 언어 모델은 비공개 내부 정보를 모르며 프롬프트 입력 길이에 제한이 있다는 배경이 있습니다. 

이를 해결하기 위해 전체 문서를 작은 덩어리인 청크로 쪼개어 각각 임베딩한 후 벡터 데이터베이스에 저장합니다. 

사용자가 질문하면 질문과 가장 유사도가 높은 핵심 덩어리만 골라 프롬프트에 결합한 뒤, 

LLM이 이를 바탕으로 정확한 답변을 생성하도록 만듭니다.

4. 벡터 데이터베이스 및 한국어 임베딩 모델 가이드

벡터 데이터베이스는 고차원 벡터 데이터를 관리하고 빠른 유사도 검색을 지원하는 전용 DB입니다. 

대표적으로 밀부스, 페이스, 크로마, 큐드런트, 파인콘 등이 있습니다.
한국어 임베딩을 최적화할 때 오픈소스 모델은 주로 영어에 특화되어 있어서 오픈에이아이나 클로드 같은 유료 API 성능이 가장 좋습니다. 만약 오픈소스를 써야 하는 상황이라면 아래 다국어 및 한국어 전용 모델을 고려해야 합니다.

multilingual-e5-large 모델은 사이즈 560M이며 다국어를 지원하지만 최대 512 토큰 제한이 있습니다.
bge-multilingual-gemma2 모델은 사이즈 9.24B로 높은 성능을 내지만 모델 사이즈가 큽니다.
gte-multilingual-base 모델은 사이즈 305M이며 768 차원에 최대 8192 토큰을 지원합니다.
jina-embeddings-v3 모델은 사이즈 572M이며 최대 8192 토큰과 유연한 벡터 차원 조절을 지원합니다.
klue/bert-base 및 roberta-base 모델은 사이즈 111M으로 한국어 전용 표준 모델이며 가볍고 빠른 처리가 가능합니다.
KoE5 모델은 사이즈 560M으로 한국어에 최적화되어 있으나 최대 512 토큰 제한이 있습니다.

요약하자면 다음과 같습니다.
첫째, 컴퓨터가 자연어의 의미를 파악할 수 있도록 밀집 형태의 실수 벡터로 변환하는 기술입니다.
둘째, 과거 방식의 한계를 극복하고 문맥과 단어 간 거리를 반영하는 최신 임베딩 기술이 도입되었습니다.
셋째, 코사인 유사도 기반의 의미 검색과 텍스트 분할 청킹을 통해 RAG를 구현합니다.
넷째, 대용량 벡터 매핑 및 고속 탐색을 전담하는 벡터 데이터베이스가 필수적입니다.
다섯째, 한국어 환경에 최적화된 상용 API와 다국어 또는 국산 오픈소스 임베딩 모델 라인업을 선택하여 활용합니다.

참고로 수집된 가이드 문서 및 DB 스키마 예시는 아래와 같습니다.
테스트 케이스 예시로 PDF나 TEXT 파일을 수집하여 책임 있는 얼굴인식 기술 사용을 위한 권고사항 같은 질문에 대한 답변을 도출할 수 있습니다.
벡터 데이터 저장 테이블 생성 SQL 문법 예시는 다음과 같습니다.

CREATE TABLE hotel_vector (
    id VARCHAR(255) PRIMARY KEY,
    content TEXT,
    metadata JSON,
    embedding VECTOR(1536)
);


여기서 1536은 오픈에이아이 임베딩 기본 차원 규격에 대응하는 크기입니다.


springairag1 프로젝트 생성


application.yaml

코드 수정

더보기
더보기
spring:
  application:
    name: springairag1

  datasource:
    utl: jdbc:mariadb://localhost:3306/aidb
    username: root
    password: 1234
    driver-class-name: org.mariadb.jdbc.Driver

  ai:
    openai:
      api-key: [key넣기]


    vectorstore:
      mariadb:
        # Spring AI가 자동으로 백터 테이블을 생성
        initialize-schema: true

db 생성


resources 폴더에 pdf 넣기


config 패키지를 생성하고 DataLoader 클래스를 생성

더보기
더보기
package com.example.springairag1.config;

import jakarta.annotation.PostConstruct;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.ExtractedTextFormatter;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.simple.JdbcClient;
import java.util.List;


@Configuration
public class DataLoader {
    private final VectorStore vectorStore;
    private final JdbcClient jdbcClient;
    // # 0.PDF 경로 (resources 아래)
    @Value("classpath:/SPRi AI Brief_11월호_산업동향_F.pdf")
    private Resource pdfResource;
    public DataLoader(VectorStore vectorStore, JdbcClient jdbcClient) {
        this.vectorStore = vectorStore;
        this.jdbcClient = jdbcClient;
    }

    // 애플리케이션 시작 후 자동 실행되는 메서드
    // 빈(Bean) 생성이 완료되면 @PostConstruct가 실행됨
    @PostConstruct
    public void init() {
        // vector_store 테이블에 저장된 데이터 개수 조회
        Integer count = jdbcClient.sql("select count(*) from vector_store")
                .query(Integer.class)
                .single();
        System.out.println("No of Records in the MariaDB Vactor Store="+count);

        // 백터 데이터가 없는 경우에만 PDF로딩
        if (count==0){
            System.out.println("Loading......");
            // PDF Reader
            PdfDocumentReaderConfig config = PdfDocumentReaderConfig.builder()
                    // PDF 상단 여백 제거 설정
                    .withPageTopMargin(0)
                    // 텍스트 추출 옵션 설정
                    .withPageExtractedTextFormatter(
                            ExtractedTextFormatter.builder()
                                    // 페이지 상단의 제거할 줄 수
                                    .withNumberOfTopTextLinesToDelete(0)
                                    .build()
                    )
                    // 한 페이지씩 Document 생성
                    .withPagesPerDocument(1)
                    .build();
            // # 1. 문서 로드(Load Document 생성)
            PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(pdfResource,config);
            // PDF 내용을 Document 객체 목록으로 변환
            List<Document> documents=pdfReader.get();
            System.out.println(documents.toString());
            // # 2. 문서분할(Split Documents)
            // 긴 문서를 작은 토큰 단위로 자르기 위한 객체
            TokenTextSplitter splitter = new TokenTextSplitter();
            // 문서를 토큰 기준으로 분할
            List<Document> splitDocuments = splitter.apply(documents);
            System.out.println(splitDocuments.size());
            // 첫 번째 분할 결과 출력
            System.out.println(splitDocuments.get(0));
            // # 3. 임베딩(Embedding) -> 4. DB에 저장(벡터스토어 생성)
            // 문서를 임베딩 후 Vector Store에 저장
            // 내부적으로:
            // Document
            // -> Enbedding 생성(OpenAI)
            // -> 벡터 변환
            // -> MairaDB vector_store 저장
            vectorStore.accept(splitDocuments);
            System.out.println("Apllication is ready to Server the Reqiests");
        }
    }

}

로직은

애플리케이션 시작

 

실행하면

읽는 것을 확인 할 수 있다.


controller 패키지 추가

PDFController 클래스 생성

더보기
더보기
package com.example.springairag1.controller;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@RestController
public class PDFController {
    private final ChatModel chatModel;
    private final VectorStore vectorStore;
    // # 6. 프롬프트 생성(Create Prompt)
    private String prompt = """
            You are an assistant for question-answering tasks.
            Use the following pieces of retrieved context to answer the question.
            If you don't know the answer, just say that you don't know.
            Answer in Korean.
            
            #Questions:
            {input}
            
            #Context:
            {documents}
            
            #Answer:
            
            """;



    public PDFController(ChatModel chatModel, VectorStore vectorStore) {
        this.chatModel = chatModel;
        this.vectorStore = vectorStore;
    }
    @GetMapping("/api/answer")
    public String simplify(String question) {
        PromptTemplate template
                = new PromptTemplate(prompt);
        Map<String, Object> promptsParameters = new HashMap<>();
        promptsParameters.put("input", question);
        promptsParameters.put("documents",findSimilarData(question));
        return chatModel
                .call(template.create(promptsParameters))
                .getResult()
                .getOutput()
                .getText();
    }

    // # 5. 검색기(Retriever) 생성 --- |(Question)<----유사도 검색(similarity)
    // 문서에 포함되어 있는 정보를 검색하고 생성
    // 사용자 질문과 가장 유사한 문서를 Vector DB에서 검색하는 메서드
    private String findSimilarData(String question) {
        // Vector Store에서 질문과 유사한 문서 검색
        List<Document> documents = vectorStore.similaritySearch(
                SearchRequest.builder()
                        // 사용자 질문 전달
                        .query(question)
                        // 상위 유사 문서 2개 반환
                        .topK(2)
                        .build()
        );

    // 검색된 문서 출력
    System.out.println("[ 유사 내용 담고 있는 도큐먼트 ] " + documents.toString());
    return documents
            //List<Document> -> Stream<Document>
            .stream()
            //Document 내부 텍스트 추출
            .map(document ->
                    document.getFormattedContent().toString())
            //여러 문서를 하나의 문자열로 합침
            .collect(Collectors.joining());
    }
}

 


rag.html 추가


실행해서

 

http://localhost:8080/pdfRag 주소에 접속하면

내용이 나옴