DM Log

[AI 실습 #4] 다중 PDF RAG 확장하기 본문

개발공부/AI

[AI 실습 #4] 다중 PDF RAG 확장하기

Dev. Dong 2025. 9. 13. 16:17

다중 PDF에서 답변 받기

 

  • 여러 PDF를 동시에 다루는 RAG 구조를 생성, 질문과 관련 잇는 파일을 우선순위로 정하여 답변하는 방법 실

[구현 전략]

1. 여러 PDF를 로딩 → 분할 → 임베딩 → Chroma에 저장

2. 각 문서에 source 메타데이터(파일명)을 붙임

3. 사용자 질문 → 관련 문서 검색 → 파일별 점수 집계

4. 관련성 높은 파일 우선순위 반환

5. 선택된 파일만을 기반으로 QA 실행


다중 PDF RAG 서버 구축

[코드 예시]

1.  여러 PDF 로드 및 벡터 스토어 생성

import os
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

def process_pdf(file_path, persist_directory="chroma_db"):
    loader = PyPDFLoader(file_path)
    pages = loader.load()

    splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
    docs = splitter.split_documents(pages)

    # 파일명 메타데이터 추가
    for doc in docs:
        doc.metadata["source"] = os.path.basename(file_path)

    vectorstore = Chroma.from_documents(
        docs,
        embeddings,
        persist_directory=persist_directory
    )
    return vectorstore

 

2. 관련 문서 검색 & 파일 우선순위 산정

def find_related_files(query, vectorstore, top_k=3):
    results = vectorstore.similarity_search(query, k=top_k)
    file_scores = {}
    for r in results:
        fname = r.metadata["source"]
        file_scores[fname] = file_scores.get(fname, 0) + 1
    return sorted(file_scores.items(), key=lambda x: x[1], reverse=True)

 

3. 선택된 파일 기반 QA 실행

from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o")

def answer_from_file(query, vectorstore, filename):
    retriever = vectorstore.as_retriever(
        search_kwargs={"filter": {"source": filename}}
    )
    qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=retriever)
    return qa_chain.run(query)

 

4. MCP 도구 등록

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("MultiPDF-RAG")

@mcp.tool()
def ask_pdf_multi(query: str) -> dict:
    """여러 PDF 중 관련 있는 파일 우선순위 반환"""
    related_files = find_related_files(query, vectorstore)
    return {"files": [f for f, _ in related_files]}

@mcp.tool()
def answer_pdf(query: str, filename: str) -> str:
    """선택된 파일에서 답변 생성"""
    return answer_from_file(query, vectorstore, filename)

if __name__ == "__main__":
    mcp.run(transport="stdio")

[실행 예시]

1. 여러 PDF를 처리 후 Chroma에 저장

2. 질문: 스마트팜 기술의 장점은?

3. MCP ask_pdf_multi 호출 결과:

{ "files": ["스마트팜.pdf", "농업혁신.pdf"] }

 

4. 사용자가 "스마트팜.pdf"를 선택

5. MCP answer_pdf 호출 결과 출력


(부록) 용어 정리

  • metadata["source"]: 문서 출처(파일명) 저장
  • similarity_search: 질문과 유사한 문서 검색
  • 우선순위 산정: 검색된 문서의 출처 빈도수 기반 점수