1. RAG(검색 증강 생성) 소개 및 필요성 [0]

thumbnail

RAG는 LLM의 출력을 최적화하여 훈련 데이터셋 외부에 있는 신뢰할 수 있는 지식 기반을 참조함으로써, 환각 현상과 최신 정보 부족 문제를 해결하고 비용 효율적으로 LLM을 활용하는 기술이다.

1.1. RAG의 정의 및 특징 [10]

  1. RAG(Retrieval Augmented Generation)는 LLM의 출력을 최적화하는 프로세스이다. [13]
    1. RAG는 응답을 생성하기 전에 LLM의 훈련 데이터셋 외부에 있는 신뢰할 수 있는 지식 기반을 참조한다. [15]
    2. RAG는 LLM을 재훈련할 필요 없이 특정 도메인이나 조직의 내부 지식 기반으로 LLM의 기능을 확장한다. [17]
    3. 이는 LLM 출력을 개선하여 관련성 있고 정확하며 유용하게 만드는 비용 효율적인 접근 방식이다. [18]

1.2. 기존 LLM 애플리케이션의 한계 [22]

  1. 환각(Hallucination) 현상 [29]
    1. LLM은 특정 시점까지의 데이터로 훈련되므로, 훈련 이후의 최신 정보나 특정 이벤트에 대한 질문에는 환각 현상을 보인다. [30]
    2. 환각은 LLM이 지식이 없어도 마치 아는 것처럼 답변을 생성하여 사용자를 오도할 수 있는 현상이다. [37]
  2. 사내 데이터 활용의 어려움 및 높은 비용 [43]
    1. 스타트업이나 기업의 내부 정책, HR, 재무 데이터 등은 공개적으로 접근할 수 없으며, LLM이 이러한 비공개 데이터를 활용하기 어렵다. [44]
    2. LLM에 사내 데이터를 학습시키기 위해 미세 조정(Fine-tuning)하는 방법은 수십억 개의 매개변수를 조정해야 하므로 매우 비싸고 번거로운 과정이다. [48]
    3. 또한, 사내 데이터는 지속적으로 업데이트되므로 매번 미세 조정을 수행하는 것은 비효율적이다. [50]

2. RAG 파이프라인의 구성 및 동작 원리 [53]

thumbnail

RAG는 데이터 주입 파이프라인과 검색 파이프라인으로 구성되며, 외부 지식 기반을 활용하여 LLM의 한계를 극복하고 정확한 답변을 생성한다.

2.1. RAG 파이프라인 개요 [53]

  1. RAG는 LLM의 출력을 최적화하기 위해 훈련 데이터 외부의 신뢰할 수 있는 지식 기반을 참조한다. [53]
  2. RAG 파이프라인은 크게 두 가지 주요 단계로 나뉜다. [67]
    1. 데이터 주입(Data Injection) 파이프라인: 외부 데이터를 벡터 데이터베이스에 저장하는 과정이다. [72]
    2. 검색(Retrieval) 파이프라인: 사용자 쿼리를 기반으로 벡터 데이터베이스에서 관련 정보를 검색하고 LLM에 전달하는 과정이다. [104]
  3. RAG는 LLM과 별도로 외부 벡터 데이터베이스를 활용한다. [71]

2.2. 데이터 주입 파이프라인 상세 [73]

  1. 데이터 수집 및 파싱 [73]
    1. 기업 정책, HR, 재무 등 다양한 형식(PDF, HTML, Excel, SQL 등)의 데이터를 수집한다. [75]
    2. 수집된 데이터를 파싱(Parsing)하여 구조화된 형태로 변환한다. [77]
    3. 파싱 과정에서 데이터를 청크(Chunk) 단위로 나눈다. [80]
  2. 임베딩(Embeddings) 생성 [83]
    1. 청크로 나뉜 텍스트 데이터를 임베딩 모델을 사용하여 벡터(Vector)로 변환한다. [83]
    2. 벡터는 텍스트의 수치적 표현으로, 유사성 검색 알고리즘(예: 코사인 유사도)을 적용할 수 있게 한다. [85]
    3. 다양한 임베딩 모델(Google Gemini, OpenAI, Hugging Face 등)을 사용할 수 있으며, 비용과 성능이 다르다. [89]
  3. 벡터 스토어(Vector Store) 저장 [74]
    1. 생성된 벡터는 벡터 데이터베이스(Vector DB) 또는 벡터 스토어에 저장된다. [74]
    2. 이 과정을 통해 외부 지식 기반이 구축된다. [96]

2.3. 검색 파이프라인 상세 [96]

  1. 사용자 쿼리 처리 [99]
    1. 사용자 쿼리가 들어오면, 이 쿼리도 임베딩 모델을 통해 벡터로 변환된다. [99]
    2. 변환된 쿼리 벡터는 벡터 데이터베이스로 전송된다. [99]
  2. 관련 정보 검색 [99]
    1. 벡터 데이터베이스는 쿼리 벡터와 유사성 검색을 수행하여 가장 관련성이 높은 정보(청크)를 검색한다. [99]
    2. 검색된 정보는 컨텍스트(Context)로 사용된다. [100]
  3. LLM을 통한 답변 생성 [101]
    1. 검색된 컨텍스트는 사용자 쿼리와 함께 프롬프트(Prompt)에 추가되어 LLM에 전달된다. [101]
    2. LLM은 이 컨텍스트를 기반으로 질문에 대한 답변을 생성한다. [102]
    3. 이 과정은 LLM이 훈련 데이터 외부의 최신 정보나 특정 도메인 지식을 활용하여 환각 없이 정확한 답변을 제공하도록 돕는다. [107]

3. RAG 파이프라인 구현을 위한 핵심 구성 요소 [108]

thumbnail

RAG 파이프라인을 구축하기 위해서는 문서 구조 이해, 데이터 로더 활용, 청킹 전략 적용, 임베딩 모델 선택, 그리고 벡터 스토어 구축이 필수적이다.

3.1. 문서(Document) 구조 이해 [158]

  1. LangChain Document 구조 [241]
    1. LangChain의 Document는 텍스트 조각과 관련 메타데이터를 저장하는 데이터 구조이다. [261]
    2. 주요 구성 요소는 page_content와 metadata이다. [241]
    3. page_content: 파일 내의 실제 텍스트 콘텐츠를 담는다. [242]
    4. metadata: 파일 이름, 페이지 수, 타임스탬프, 작성자 등 파일에 대한 추가 정보를 담는다. [244]
  2. 메타데이터의 중요성 [283]
    1. 메타데이터는 벡터 데이터베이스에서 유사성 검색 시 필터링에 활용되어 검색 정확도를 높인다. [283]
    2. 예를 들어, 특정 작성자의 문서만 검색하도록 필터를 적용할 수 있다. [285]
  3. Document 객체 생성 예시 [263]
    1. Document(page_content="메인 텍스트 내용", metadata={"source": "example.txt", "pages": 1, "author": "Krishnaik", "date_created": "2024-01-01"})와 같이 생성한다. [263]

3.2. 데이터 로더(Data Loader) 활용 [159]

  1. 다양한 파일 형식 지원 [162]
    1. RAG 파이프라인은 PDF, HTML, Excel, SQL 데이터베이스, TXT 등 다양한 형식의 파일을 처리할 수 있다. [162]
    2. LangChain은 PDFLoader, CSVLoader, WebBaseLoader, DirectoryLoader 등 다양한 로더를 제공한다. [251]
  2. 로더의 역할 [163]
    1. 로더는 특정 파일 형식의 내용을 읽어 Document 구조로 변환한다. [163]
    2. 예를 들어, TextLoader는 TXT 파일을 읽어 Document 객체를 반환하며, 이때 source와 같은 메타데이터가 자동으로 업데이트된다. [341]
  3. DirectoryLoader를 이용한 여러 파일 로드 [346]
    1. DirectoryLoader는 특정 디렉토리 내의 여러 파일을 한 번에 로드할 수 있다. [346]
    2. loader_class 매개변수를 사용하여 로드할 파일의 유형(예: TextLoader, PyMuPDFLoader)을 지정할 수 있다. [351]
    3. 이를 통해 디렉토리 내의 모든 텍스트 파일이나 PDF 파일을 Document 객체 리스트로 얻을 수 있다. [359]
  4. PyMuPDFLoader를 이용한 PDF 파일 로드 [375]
    1. PyMuPDFLoader는 PDF 파일을 로드하고 파싱하는 데 사용되며, PyPDFLoader보다 더 나은 성능을 제공한다. [382]
    2. PDF 파일 로드 시, creation_date, source_file_path, total_pages, format, author 등 풍부한 메타데이터를 자동으로 추출한다. [405]
    3. 최종적으로 Document 객체 리스트 형태로 반환된다. [409]

3.3. 청킹(Chunking) 전략 [177]

  1. 청킹의 필요성 [182]
    1. LLM 및 임베딩 모델은 고정된 컨텍스트 크기(Context Size) 제한이 있다. [183]
    2. 전체 문서를 한 번에 처리하면 컨텍스트 크기를 초과하여 오류가 발생할 수 있다. [185]
    3. 따라서 데이터를 작은 청크(Chunk)로 나누어 컨텍스트 크기 제한 내에서 처리해야 한다. [190]
  2. RecursiveCharacterTextSplitter 활용 [476]
    1. RecursiveCharacterTextSplitter는 문서를 재귀적으로 분할하여 청크를 생성하는 데 사용된다. [476]
    2. chunk_size: 각 청크의 최대 크기를 정의한다 (예: 1000). [472]
    3. chunk_overlap: 인접한 청크 간에 겹치는 텍스트의 양을 정의한다 (예: 200). 이는 컨텍스트 손실을 방지하는 데 도움이 된다. [473]
    4. separators: 문서를 분할할 때 사용할 구분자를 지정한다 (예: \n\n, \n, ). [480]
  3. 청킹 결과 [495]
    1. 예를 들어, 64개의 원본 문서가 359개의 청크로 분할될 수 있다. [495]
    2. 각 청크는 page_content와 metadata를 포함하는 Document 객체 형태를 유지한다. [488]

3.4. 임베딩(Embeddings) 모델 선택 및 생성 [502]

  1. 임베딩의 역할 [504]
    1. 임베딩은 텍스트 청크를 벡터(Vector)로 변환하는 과정이다. [504]
    2. 이 벡터는 텍스트의 의미를 수치적으로 표현하며, 벡터 데이터베이스에서 유사성 검색에 사용된다. [505]
  2. EmbeddingManager 클래스 구현 [531]
    1. 모델 로드: SentenceTransformer 라이브러리를 사용하여 Hugging Face의 all-MiniLM-L6-v2와 같은 오픈소스 임베딩 모델을 로드한다. [535]
    2. 임베딩 차원: all-MiniLM-L6-v2 모델은 텍스트를 384차원 벡터로 변환한다. [537]
    3. 임베딩 생성: model.encode() 함수를 사용하여 텍스트 리스트를 입력받아 NumPy 배열 형태의 임베딩을 생성한다. [559]
  3. 오픈소스 모델 활용의 이점 [504]
    1. 오픈소스 임베딩 모델을 사용하면 누구나 쉽게 RAG 파이프라인을 구축하고 테스트할 수 있다. [504]

3.5. 벡터 스토어(Vector Store) 구축 [583]

  1. 벡터 스토어의 역할 [586]
    1. 벡터 스토어는 임베딩된 벡터를 저장하고 관리하는 데이터베이스이다. [586]
    2. 저장된 벡터에 대해 유사성 검색을 효율적으로 수행할 수 있도록 한다. [587]
  2. VectorStore 클래스 구현 [590]
    1. ChromaDB 클라이언트 초기화: ChromaDB는 오픈소스 벡터 스토어로, PersistentClient를 사용하여 로컬 디스크에 벡터를 영구적으로 저장할 수 있다. [604]
    2. 컬렉션 생성: 벡터를 저장할 컬렉션(Collection)을 생성한다 (예: PDF_documents). [593]
    3. 문서 추가: add_documents 함수를 사용하여 청크와 해당 임베딩을 벡터 스토어에 추가한다. [618]
      1. 각 문서에는 고유한 UUID(Universally Unique Identifier)가 할당된다. [627]
      2. 문서의 page_content와 metadata 정보가 함께 저장된다. [631]
      3. 임베딩은 리스트 형태로 변환되어 저장된다. [636]
  3. 영구 저장(Persistent Storage) [595]
    1. persistent_directory를 지정하여 벡터 스토어의 데이터를 하드 디스크에 저장할 수 있다. [595]
    2. 이를 통해 애플리케이션을 다시 시작해도 저장된 벡터를 다시 로드하여 사용할 수 있다. [681]

4. RAG 검색 파이프라인 및 LLM 통합 [685]

thumbnail

RAG 검색 파이프라인은 사용자 쿼리를 임베딩하고 벡터 스토어에서 관련 문서를 검색한 후, LLM과 통합하여 최종 답변을 생성한다.

4.1. RAG 검색기(Retriever) 구현 [688]

  1. RAGRetriever 클래스 정의 [698]
    1. RAGRetriever 클래스는 벡터 스토어에서 쿼리 기반 검색을 처리한다. [699]
    2. 생성자에서 VectorStore 객체와 EmbeddingManager 객체를 인자로 받는다. [700]
    3. 검색기는 벡터 스토어 위에 구축된 인터페이스로, 쿼리에 대한 응답을 반환한다. [705]
  2. retrieve 함수 동작 [708]
    1. 쿼리 임베딩: 사용자 쿼리를 EmbeddingManager를 사용하여 벡터로 변환한다. [716]
    2. 벡터 스토어 쿼리: 변환된 쿼리 임베딩을 VectorStore의 query 함수에 전달하여 관련 문서를 검색한다. 이때 top_k 매개변수로 검색할 문서의 수를 지정한다. [720]
    3. 결과 처리: 검색 결과에는 문서 내용, 메타데이터, 거리(distance), ID 등의 정보가 포함된다. [722]
    4. 유사도 점수 계산: 1 - distance를 통해 유사도 점수(similarity score)를 계산한다. [726]
    5. 임계값 필터링: 유사도 점수가 score_threshold보다 높은 문서만 최종 결과로 반환한다. [728]
    6. 컨텍스트 반환: 검색된 문서의 내용과 메타데이터를 포함하는 리스트를 반환한다. [730]
  3. 검색 예시 [745]
    1. "What is attention is all you need?"와 같은 쿼리를 입력하면, RAGRetriever는 관련 PDF 문서에서 컨텍스트를 검색하여 반환한다. [745]

4.2. LLM 통합 및 답변 생성 [774]

  1. LLM 설정 [789]
    1. Groq API 키를 환경 변수에 설정한다. [793]
    2. ChatGroq 클래스를 사용하여 LLM 모델(예: Gemma-2)을 초기화한다. 이때 temperature와 max_tokens 등의 매개변수를 설정할 수 있다. [813]
  2. rag_simple 함수 구현 [818]
    1. 컨텍스트 검색: retriever.retrieve(query)를 호출하여 관련 컨텍스트를 가져온다. [826]
    2. 컨텍스트 결합: 검색된 여러 문서의 내용을 결합하여 하나의 컨텍스트 문자열을 생성한다. [830]
    3. 프롬프트 생성: LLM에 전달할 프롬프트를 정의한다. 프롬프트는 "다음 컨텍스트를 사용하여 질문에 간결하게 답변하세요"와 같은 지시와 함께 검색된 컨텍스트 및 사용자 쿼리를 포함한다. [834]
    4. LLM 호출: llm.invoke(prompt.format(context=context, query=query))를 사용하여 LLM을 호출하고 답변을 생성한다. [848]
    5. 답변 반환: LLM이 생성한 답변 내용을 반환한다. [852]
  3. 통합된 RAG 파이프라인 동작 [853]
    1. 사용자 쿼리가 들어오면 rag_simple 함수가 호출된다. [853]
    2. RAGRetriever가 벡터 스토어에서 관련 컨텍스트를 검색한다. [858]
    3. 검색된 컨텍스트와 쿼리가 LLM 프롬프트에 포함되어 LLM에 전달된다. [859]
    4. LLM은 컨텍스트를 기반으로 정확하고 관련성 있는 답변을 생성한다. [861]

4.3. 고급 RAG 파이프라인 기능 [862]

  1. RAG_advanced 함수 구현 [863]
    1. 추가 정보 반환: 단순 답변 외에 출처(sources), 신뢰도 점수(confidence score), 전체 컨텍스트(full context)를 함께 반환한다. [867]
    2. 출처 정보: 메타데이터에서 source_file과 page_number를 추출하여 출처를 명확히 표시한다. [878]
    3. 컨텍스트 미리보기: 검색된 컨텍스트의 일부(예: 처음 300자)를 미리보기로 제공한다. [881]
    4. 신뢰도 점수: 문서의 유사도 점수를 활용하여 답변의 신뢰도를 제공한다. [883]
  2. 스트리밍, 인용, 기록, 요약 기능 [894]
    1. 더 나아가 스트리밍(Streaming)으로 답변을 실시간으로 제공하고, 인용(Citation)을 통해 답변의 근거를 명확히 하며, 대화 기록(History)을 유지하고, 요약(Summarization) 기능을 추가하는 등 다양한 고급 기능을 RAG 파이프라인에 통합할 수 있다. [894]

5. RAG 파이프라인의 모듈화 및 배포 [922]

thumbnail

RAG 파이프라인을 효율적으로 관리하고 재사용성을 높이기 위해 각 구성 요소를 모듈화하여 파이프라인 형태로 구축한다.

5.1. 모듈화된 RAG 파이프라인 구조 [922]

  1. 모듈화의 필요성: 전체 RAG 파이프라인을 하나의 파일에 구현하는 대신, 각 기능을 별도의 모듈로 분리하여 코드의 재사용성, 유지보수성, 가독성을 높인다. [922]
  2. 주요 구성 요소 모듈: RAG 파이프라인은 다음과 같은 핵심 모듈로 구성된다. [929]
    1. data_loader.py: 문서 로드 및 청킹을 담당한다. [930]
    2. embedding.py: 텍스트를 벡터로 임베딩하는 기능을 담당한다. [937]
    3. vector_store.py: 벡터를 저장하고 관리하는 기능을 담당한다. [932]
    4. search.py: LLM을 통합하여 쿼리 검색 및 답변 생성을 담당한다. [939]
  3. 파이프라인 통합: 각 모듈은 서로 연결되어 데이터 주입부터 답변 생성까지의 전체 RAG 흐름을 구성한다. [941]

5.2. data_loader.py 구현 [942]

  1. load_all_documents 함수: 지정된 디렉토리에서 지원되는 모든 파일(PDF, TXT, CSV 등)을 로드하고 LangChain Document 구조로 변환한다. [949]
    1. PDF 파일 로드: PyPDFLoader를 사용하여 PDF 파일을 읽고 Document 리스트에 추가한다. [961]
    2. 확장성: 텍스트 파일, CSV 파일, SQL 파일 등 다른 형식의 파일 로더를 추가하여 기능을 확장할 수 있다. [968]
  2. 테스트: app.py 파일에서 load_all_documents 함수를 호출하여 PDF 파일이 올바르게 로드되고 Document 구조로 변환되는지 확인한다. [973]

5.3. embedding.py 구현 [989]

  1. EmbeddingPipeline 클래스: 임베딩 모델 초기화, 문서 청킹, 청크 임베딩 기능을 포함한다. [993]
    1. 모델 초기화: all-MiniLM-L6-v2와 같은 SentenceTransformer 모델을 로드한다. [994]
    2. chunk_documents 함수: RecursiveCharacterTextSplitter를 사용하여 문서를 청크 크기(예: 1000)와 오버랩(예: 200)에 따라 분할한다. [997]
    3. embed_chunks 함수: 분할된 청크의 page_content를 임베딩 모델로 인코딩하여 벡터 임베딩을 생성한다. [1004]
  2. 테스트: app.py에서 EmbeddingPipeline을 초기화하고 chunk_documents, embed_chunks를 호출하여 청킹 및 임베딩이 올바르게 수행되는지 확인한다. [1011]

5.4. vector_store.py 구현 [1023]

  1. FaissVectorStore 클래스: FAISS(Facebook AI Similarity Search)를 기반으로 벡터를 저장하고 관리한다. [1032]
    1. 영구 디렉토리: faiss_store와 같은 디렉토리에 index.pickle 파일과 메타데이터를 저장하여 벡터 스토어를 영구적으로 유지한다. [1042]
    2. build_from_documents 함수: EmbeddingPipeline을 사용하여 문서를 청킹하고 임베딩한 후, 이 임베딩을 FAISS 인덱스에 추가하고 저장한다. [1041]
    3. load 함수: 저장된 FAISS 인덱스와 메타데이터를 로드하여 벡터 스토어를 메모리에 올린다. [1049]
    4. query 함수: 사용자 쿼리를 임베딩하고 FAISS 인덱스에서 유사성 검색을 수행하여 관련 문서를 반환한다. [1051]
  2. 테스트: app.py에서 FaissVectorStore를 초기화하고 build_from_documents로 벡터 스토어를 구축한 후, load 및 query 함수를 사용하여 검색 기능을 테스트한다. [1055]

5.5. search.py 구현 및 최종 통합 [1086]

  1. RAGSearch 클래스: LLM을 통합하여 최종 답변을 생성하는 기능을 담당한다. [1090]
    1. LLM 초기화: Groq API 키를 사용하여 ChatGroq LLM을 초기화한다. [1088]
    2. search_and_summarize 함수: VectorStore의 query 함수로 관련 문서를 검색하고, 이 문서들을 컨텍스트로 사용하여 LLM 프롬프트를 구성한 후, LLM을 호출하여 요약된 답변을 생성한다. [1092]
  2. 최종 app.py 통합: data_loader, embedding, vector_store, search 모듈을 모두 임포트하고, 각 구성 요소를 순차적으로 호출하여 전체 RAG 파이프라인을 실행한다. [1094]
    1. load_all_documents로 문서 로드.
    2. FaissVectorStore를 build_from_documents로 구축하거나 load로 로드.
    3. RAGSearch를 사용하여 쿼리를 검색하고 LLM으로 답변을 생성한다.
  3. 결과: 모듈화된 파이프라인을 통해 사용자 쿼리에 대한 정확하고 요약된 답변을 얻을 수 있다. [1100]