paint-brush
LangChain을 사용하여 RAG 애플리케이션을 빌드하는 것에 대한 포괄적인 튜토리얼~에 의해@bexgboost
1,112 판독값
1,112 판독값

LangChain을 사용하여 RAG 애플리케이션을 빌드하는 것에 대한 포괄적인 튜토리얼

~에 의해 Bex19m2024/09/03
Read on Terminal Reader

너무 오래; 읽다

RAG 시스템을 구축하는 데 매우 인기 있는 프레임워크인 LangChain을 사용하는 방법을 알아보세요. 튜토리얼을 마치면 일부 개인 데이터를 통해 RAG를 수행하여 질문에 대한 답변을 제공하는 챗봇(Streamlit 인터페이스 포함)이 생깁니다.
featured image - LangChain을 사용하여 RAG 애플리케이션을 빌드하는 것에 대한 포괄적인 튜토리얼
Bex HackerNoon profile picture

오늘날의 대규모 언어 모델은 점점 더 많은 양의 정보에 접근할 수 있습니다. 그러나 이러한 모델이 활용하지 못하는 방대한 양의 비공개 데이터가 여전히 남아 있습니다. 이것이 기업 환경에서 LLM의 가장 인기 있는 응용 프로그램 중 하나가 검색 증강 생성(RAG)인 이유입니다. 비즈리 , AI 기반 데이터 분석 플랫폼을 통해 효과적인 RAG 시스템을 구축하는 데 귀중한 통찰력을 얻었습니다. 이 튜토리얼에서는 몇 가지 학습 내용을 공유하고 고유한 RAG 시스템을 만드는 방법을 보여드리겠습니다.


RAG 시스템을 구축하는 데 인기 있는 프레임워크인 LangChain을 사용하여 간단한 RAG 시스템을 구축하는 방법을 배우게 됩니다. 튜토리얼을 마치면 일부 개인 데이터를 통해 RAG를 수행하여 질문에 대한 답변을 제공하는 챗봇(Streamlit 인터페이스 포함)이 생깁니다.

RAG란 무엇인가?

RAG가 무엇인지 명확히 알아보기 위해 간단한 예를 살펴보겠습니다.


1학년 대학 학생인 챈들러는 몇 개의 수업을 빼먹는 것을 고려하고 있지만 대학 출석 정책을 위반하지 않는지 확인하고 싶어합니다. 요즘 모든 것과 마찬가지로 그는 ChatGPT 에 질문을 던집니다.


물론 ChatGPT는 답할 수 없습니다. 챗봇이 멍청한 건 아닙니다. 그저 챈들러의 대학 문서에 접근할 수 없을 뿐입니다. 그래서 챈들러는 정책 문서를 직접 찾았고, 그 문서가 자신이 넘기고 싶지 않은 길고 기술적인 내용이라는 것을 알게 됩니다. 대신 그는 전체 문서를 ChatGPT에 주고 다시 질문합니다. 이번에는 답을 얻습니다.


이것은 검색 증강 생성의 개별 사례입니다. 언어 모델의 답변(생성)은 원래 훈련의 일부가 아닌 소스에서 검색된 컨텍스트에 의해 증강(강화)됩니다.


RAG 시스템의 확장 가능한 버전은 대학 문서를 직접 검색하여 관련 문서를 찾고, 답변이 포함되어 있을 가능성이 가장 높은 텍스트 조각을 검색하여 학생의 질문에 답할 수 있습니다.


일반적으로 RAG 시스템에서는 개인 데이터 소스에서 정보를 검색하여 언어 모델에 공급하여 모델이 상황에 맞는 답변을 제공하도록 합니다.

RAG 애플리케이션의 구성 요소

이런 시스템은 간단해 보이지만 움직이는 구성 요소가 많을 것입니다. 스스로 만들기 전에 구성 요소가 무엇이고 어떻게 함께 작동하는지 검토해야 합니다.

서류

첫 번째 구성 요소는 문서 또는 문서 모음입니다. 우리가 구축하는 RAG 시스템의 유형에 따라 문서는 텍스트 파일, PDF, 웹 페이지(RAG over unstructured data) 또는 그래프, SQL 또는 NoSQL 데이터베이스(RAG over structured data)가 될 수 있습니다. 이러한 문서는 다양한 유형의 데이터를 시스템에 수집하는 데 사용됩니다.

문서 로더

LangChain은 PDF, Slack, Notion, Google Drive 등 다양한 문서 소스에서 데이터를 읽기 위해 문서 로더 라는 수백 개의 클래스를 구현합니다.


각 문서 로더 클래스는 고유하지만 모두 동일한 .load() 메서드를 공유합니다. 예를 들어, 다음은 LangChain에서 PDF 문서와 웹페이지를 로드하는 방법입니다.

 from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader # pip install langchain-community pdf_loader = PyPDFLoader("framework_docs.pdf") web_loader = WebBaseLoader( "https://python.langchain.com/v0.2/docs/concepts/#document-loaders" ) pdf_docs = pdf_loader.load() web_docs = web_loader.load()


PyPDFLoader 클래스는 PyPDF2 패키지를 사용하여 PDF 파일을 처리하는 반면, WebBaseLoader는 주어진 웹 페이지 내용을 스크래핑합니다.


pdf_docs 각 페이지당 하나씩 총 4개의 문서 객체가 포함되어 있습니다.


 >>> len(pdf_docs) 4


web_docs 다음 중 하나만 포함됩니다.

 >>> print(web_docs[0].page_content[125:300].strip()) You can view the v0.1 docs here.IntegrationsAPI referenceLatestLegacyMorePeopleContributingCookbooks3rd party tutorialsYouTubearXivv0.2v0.2v0.1🦜️🔗LangSmithLangSmith DocsLangCh


이러한 문서 객체는 나중에 텍스트의 의미를 이해하기 위해 임베딩 모델에 제공됩니다.


다른 유형의 문서 로더에 대한 구체적인 내용은 LangChain에서 제공합니다. 전담 방법 페이지 .

텍스트 분할기

문서를 로드한 후에는 더 작고 관리하기 쉬운 텍스트 청크로 나누는 것이 중요합니다. 주요 이유는 다음과 같습니다.

  1. 많은 임베딩 모델(나중에 자세히 설명)에는 최대 토큰 한도가 있습니다.
  2. 청크가 작을수록 검색이 더 정확해집니다.
  3. 언어 모델에는 정확한 맥락이 제공됩니다.


LangChain은 langchain_text_splitters 패키지로 다양한 유형의 텍스트 분할기를 제공하며, 이러한 분할기는 문서 유형에 따라 달라집니다.

다음은 RecursiveCharacterTextSplitter 사용하여 구분 기호 목록과 청크 크기에 따라 일반 텍스트를 분할하는 방법입니다.

 !pip install langchain_text_splitters from langchain_text_splitters import RecursiveCharacterTextSplitter # Example text text = """ RAG systems combine the power of large language models with external knowledge sources. This allows them to provide up-to-date and context-specific information. The process involves several steps including document loading, text splitting, and embedding. """ # Create a text splitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=50, chunk_overlap=10, length_function=len, separators=["\n\n", "\n", " ", ""], ) # Split the text chunks = text_splitter.split_text(text) # Print the chunks for i, chunk in enumerate(chunks): print(f"Chunk {i + 1}: {chunk}")

산출:

 Chunk 1: RAG systems combine the power of large language Chunk 2: language models with external knowledge sources. Chunk 3: This allows them to provide up-to-date and Chunk 4: and context-specific information. Chunk 5: The process involves several steps including Chunk 6: including document loading, text splitting, and Chunk 7: and embedding.

이 스플리터는 다재다능하며 많은 사용 사례에 적합합니다. 각 청크를 chunk_size 에 최대한 가까운 문자 수로 만듭니다. 문자 수를 유지하기 위해 분할할 구분 기호를 재귀적으로 전환할 수 있습니다.


위의 예에서 분할기는 먼저 줄바꿈 문자를 기준으로 분할하고, 그다음 공백 하나를 기준으로 분할하고, 마지막으로 원하는 청크 크기에 도달하기 위해 모든 문자 사이에서 분할합니다.


langchain_text_splitters 패키지 내부에는 다른 많은 스플리터가 있습니다. 다음은 몇 가지입니다.

  • HTMLSectionSplitter
  • PythonCodeTexSplitter
  • RecursiveJsonSplitter

등등. 일부 스플리터는 후드 아래에 변압기 모델을 사용하여 의미적으로 의미 있는 청크를 생성합니다.


올바른 텍스트 분할기는 RAG 시스템의 성능에 상당한 영향을 미칩니다.


텍스트 분할기를 사용하는 방법에 대한 자세한 내용은 관련 항목을 참조하세요. 여기를 클릭하여 방법 가이드를 확인하세요 .

모델 임베딩

문서가 텍스트로 분할되면 이를 숫자형 표현으로 인코딩해야 하는데, 이는 텍스트 데이터를 사용하는 모든 계산 모델에 필요한 사항입니다.


RAG의 맥락에서 이 인코딩은 임베딩 이라고 하며 임베딩 모델 에 의해 수행됩니다. 임베딩 모델은 의미적 의미를 포착하는 텍스트의 벡터 표현을 만듭니다. 이런 방식으로 텍스트를 제시하면 텍스트에 대해 수학적 연산을 수행할 수 있습니다. 예를 들어, 문서 데이터베이스에서 의미가 가장 유사한 텍스트를 검색하거나 사용자 질의에 대한 답변을 찾을 수 있습니다.


LangChain은 OpenAI, Cohere, HuggingFace 등 모든 주요 임베딩 모델 제공자를 지원합니다. Embedding 클래스로 구현되며 두 가지 메서드를 제공합니다. 하나는 문서를 임베딩하는 것이고 다른 하나는 쿼리(프롬프트)를 임베딩하는 것입니다.


다음은 OpenAI를 사용하여 이전 섹션에서 만든 텍스트 청크를 포함하는 예제 코드입니다.

 from langchain_openai import OpenAIEmbeddings # Initialize the OpenAI embeddings embeddings = OpenAIEmbeddings() # Embed the chunks embedded_chunks = embeddings.embed_documents(chunks) # Print the first embedded chunk to see its structure print(f"Shape of the first embedded chunk: {len(embedded_chunks[0])}") print(f"First few values of the first embedded chunk: {embedded_chunks[0][:5]}")


산출:

 Shape of the first embedded chunk: 1536 First few values of the first embedded chunk: [-0.020282309502363205, -0.0015041005099192262, 0.004193042870610952, 0.00229285703971982, 0.007068077567964792]

위의 출력은 임베딩 모델이 문서의 모든 청크에 대해 1536차원 벡터를 생성하고 있음을 보여줍니다.


단일 쿼리를 포함하려면 embed_query() 메서드를 사용하면 됩니다.

 query = "What is RAG?" query_embedding = embeddings.embed_query(query) print(f"Shape of the query embedding: {len(query_embedding)}") print(f"First few values of the query embedding: {query_embedding[:5]}")


산출:

 Shape of the query embedding: 1536 First few values of the query embedding: [-0.012426204979419708, -0.016619959846138954, 0.007880032062530518, -0.0170428603887558, 0.011404196731746197]

벡터 저장소

기가바이트의 문서가 있는 대규모 RAG 애플리케이션에서는 결국 엄청난 양의 텍스트 청크와 벡터가 생깁니다. 신뢰할 수 있게 저장할 수 없다면 아무 소용이 없습니다.


이것이 벡터 저장소 또는 데이터베이스가 지금 유행하는 이유입니다. 임베딩을 저장하는 것 외에도 벡터 데이터베이스는 벡터 검색을 대신 수행합니다. 이러한 데이터베이스는 쿼리 벡터가 주어졌을 때 가장 유사한 벡터를 빠르게 찾도록 최적화되어 있으며, 이는 RAG 시스템에서 관련 정보를 검색하는 데 필수적입니다.


다음은 웹 페이지의 내용을 내장하고 벡터를 Chroma 벡터 데이터베이스에 저장하는 코드 조각입니다( Chroma는 전적으로 사용자의 컴퓨터에서 실행되는 오픈소스 벡터 데이터베이스 솔루션입니다 ):

 !pip install chromadb langchain_chroma from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # Load the web page loader = WebBaseLoader("https://python.langchain.com/v0.2/docs/tutorials/rag/") docs = loader.load() # Split the documents into chunks text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) chunks = text_splitter.split_documents(docs)


먼저, WebBaseLoader 로 페이지를 로드하고 청크를 만듭니다. 그런 다음 Chromafrom_documents 메서드에 선택한 임베딩 모델과 함께 청크를 직접 전달할 수 있습니다.

 from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma db = Chroma.from_documents(chunks, OpenAIEmbeddings())


LangChain의 모든 벡터 데이터베이스 객체는 쿼리 문자열을 허용하는 similarity_search 메서드를 노출합니다.

 query = "What is indexing in the context of RAG?" docs = db.similarity_search(query) print(docs[1].page_content)


산출:

 If you are interested for RAG over structured data, check out our tutorial on doing question/answering over SQL data.Concepts​A typical RAG application has two main components:Indexing: a pipeline for ingesting data from a source and indexing it. This usually happens offline.Retrieval and generation: the actual RAG chain, which takes the user query at run time and retrieves the relevant data from the index, then passes that to the model.The most common full sequence from raw data to answer looks like:Indexing​Load: First we need to load our data. This is done with Document Loaders.Split: Text splitters break large Documents into smaller chunks. This is useful both for indexing data and for passing it in to a model, since large chunks are harder to search over and won't fit in a model's finite context window.Store: We need somewhere to store and index our splits, so that they can later be searched over. This is often done using a VectorStore and Embeddings model.Retrieval and

similarity_search 의 결과는 우리가 질의에서 묻고 있는 정보를 가장 잘 포함하고 있을 가능성이 높은 문서 목록입니다.


벡터 저장소 사용 방법에 대한 세부 사항은 관련 항목을 참조하세요. 여기를 클릭하여 방법 가이드를 확인하세요 .

리트리버

모든 벡터 저장소가 유사성 검색의 형태로 검색을 지원하지만, LangChain은 구조화되지 않은 쿼리가 주어진 문서를 반환하는 전용 Retriever 인터페이스를 구현합니다. 검색기는 문서를 반환하거나 검색하기만 하면 되고, 저장하지는 않습니다.


LangChain에서 벡터 스토어를 리트리버로 변환하는 방법은 다음과 같습니다.

 # Convert the vector store to a retriever chroma_retriever = db.as_retriever() docs = chroma_retriever.invoke("What is indexing in the context of RAG?") >>> len(docs) 4


search_kwargs 사용하면 관련 문서 수를 상위 k 개로 제한할 수 있습니다.

 chroma_retriever = db.as_retriever(search_kwargs={"k": 1}) docs = chroma_retriever.invoke("What is indexing in the context of RAG?") >>> len(docs) 1

다른 검색 관련 매개변수를 search_kwargs에 전달할 수 있습니다. 검색기 사용에 대해 자세히 알아보세요. 특정 방법 가이드 .

LangChain에서 RAG 앱을 빌드하기 위한 단계별 워크플로

이제 RAG 시스템의 핵심 구성 요소를 다루었으므로 직접 만들어 보겠습니다. 코드 문서화 및 튜토리얼을 위해 특별히 설계된 RAG 챗봇의 단계별 구현을 안내해 드리겠습니다. 오늘날 LLM의 지식 기반에 아직 포함되지 않은 기존 프레임워크의 새로운 프레임워크나 새로운 기능에 대한 AI 코딩 지원이 필요할 때 특히 유용할 것입니다.

0. 프로젝트 구조 생성

먼저, 작업 디렉토리를 다음 프로젝트 구조로 채웁니다.

 rag-chatbot/ ├── .gitignore ├── requirements.txt ├── README.md ├── app.py ├── src/ │ ├── __init__.py │ ├── document_processor.py │ └── rag_chain.py └── .streamlit/ └── config.toml


명령은 다음과 같습니다.

 $ touch .gitignore requirements.txt README.md app.py $ mkdir src .streamlit $ touch src/{.env,__init__.py,document_processor.py,rag_chain.py} $ touch .streamlit/{.env,config.toml}

1. 환경 설정

이 단계에서는 먼저 새 Conda 환경을 만들고 활성화합니다.

 $ conda create -n rag_tutorial python=3.9 -y $ conda activate rag_tutorial


다음으로, requirements.txt 파일을 열고 다음 종속성을 붙여넣습니다.

 langchain==0.2.14 langchain_community==0.2.12 langchain_core==0.2.35 langchain_openai==0.1.22 python-dotenv==1.0.1 streamlit==1.37.1 faiss-cpu pypdf

그리고 설치하세요:

 $ pip install -r requirements.txt


또한, git 인덱싱에서 파일을 숨기려면 .gitignore 파일을 만듭니다.

 # .gitignore venv/ __pycache__/ .env *.pdf *.png *.jpg *.jpeg *.gif *.svg

2. 문서 로더 설정

다음으로, src/document_processor.py 파일을 열고 추가된 코드 조각을 붙여넣습니다.


필요한 수입품:

 import logging from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.text_splitter import Language from langchain_community.document_loaders import PyPDFLoader from langchain_community.document_loaders.parsers.pdf import ( extract_from_images_with_rapidocr, ) from langchain.schema import Document


수입에 대한 설명:

  • RecursiveCharacterTextSplitter : 텍스트를 재귀적으로 더 작은 덩어리로 나눕니다.
  • Language : 텍스트 분할에서 프로그래밍 언어를 지정하기 위한 열거형입니다.
  • PyPDFLoader : PDF 파일에서 텍스트를 로드하고 추출합니다.
  • extract_from_images_with_rapidocr : 이미지에서 텍스트를 추출하는 OCR 함수입니다.
  • Document : 콘텐츠와 메타데이터가 포함된 문서를 나타냅니다.
  • logging : 디버깅과 정보를 위한 로깅 기능을 제공합니다.


그러면 PDF를 처리하는 함수는 다음과 같습니다.

 def process_pdf(source): loader = PyPDFLoader(source) documents = loader.load() # Filter out scanned pages unscanned_documents = [doc for doc in documents if doc.page_content.strip() != ""] scanned_pages = len(documents) - len(unscanned_documents) if scanned_pages > 0: logging.info(f"Omitted {scanned_pages} scanned page(s) from the PDF.") if not unscanned_documents: raise ValueError( "All pages in the PDF appear to be scanned. Please use a PDF with text content." ) return split_documents(unscanned_documents)


작동 원리는 다음과 같습니다.

  1. PyPDFLoader 사용하여 PDF를 로드합니다.
  2. 빈 내용이 있는 문서를 제거하여 스캔된 페이지를 필터링합니다.
  3. 스캔된 페이지 중 누락된 페이지가 있으면 그 수를 기록합니다.
  4. 모든 페이지가 스캔된 경우(즉, 텍스트 콘텐츠 없음) ValueError가 발생합니다.
  5. 마지막으로, split_documents 함수를 사용하여 스캔되지 않은 나머지 문서를 더 작은 청크로 분할합니다.

이 함수는 PDF에 텍스트와 스캔한 페이지가 섞여 있는 경우를 처리하여 텍스트 기반 페이지만 추가로 처리되도록 합니다. 이는 OCR이 없는 스캔한 페이지를 사용할 수 없는 텍스트 분석 작업에 필수적입니다. split_documents 함수는 나중에 정의합니다.


다음으로, 이미지(코드 조각 및/또는 웹 페이지의 스크린샷)에서 정보를 검색하는 함수를 작성합니다.


 def process_image(source): # Extract text from image using OCR with open(source, "rb") as image_file: image_bytes = image_file.read() extracted_text = extract_from_images_with_rapidocr([image_bytes]) documents = [Document(page_content=extracted_text, metadata={"source": source})] return split_documents(documents)


이 함수는 OCR(Optical Character Recognition)을 사용하여 텍스트를 추출하여 이미지 파일을 처리합니다. 이미지 파일을 읽고 바이트로 변환한 다음 RapidOCR 라이브러리를 사용하여 이미지에서 텍스트를 추출합니다. 추출된 텍스트는 소스 파일 경로가 포함된 메타데이터가 있는 Document 객체에 래핑됩니다. 마지막으로, 이 함수는 split_documents 함수를 사용하여 문서를 더 작은 청크로 분할합니다. 이 함수는 다음과 같이 정의합니다.


 def split_documents(documents): # Split documents into smaller chunks for processing text_splitter = RecursiveCharacterTextSplitter.from_language( language=Language.PYTHON, chunk_size=1000, chunk_overlap=200 ) return text_splitter.split_documents(documents)


이 함수는 Python 구문과 함께 RecursiveCharacterTextSplitter 클래스를 사용하여 텍스트를 1000자 단위로 분할하고 200자가 겹치도록 합니다.


마지막 기능은 PDF와 이미지 파서 기능을 하나로 결합한 것입니다.


 def process_document(source): # Determine file type and process accordingly if source.lower().endswith(".pdf"): return process_pdf(source) elif source.lower().endswith((".png", ".jpg", ".jpeg")): return process_image(source) else: raise ValueError(f"Unsupported file type: {source}")


이 마지막 기능은 Streamlit UI에서 제공된 문서의 청크를 생성, 내장, 저장하고 이를 시스템의 RAG 구성 요소로 전달하는 데 사용됩니다.

3. RAG 설정

이제 src/rag_chain.py 파일을 열고 추가된 코드 조각을 붙여넣습니다.


먼저, 필요한 모듈을 가져옵니다.


 import os from dotenv import load_dotenv from langchain.prompts import PromptTemplate from langchain_community.vectorstores import FAISS from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from langchain_openai import ChatOpenAI, OpenAIEmbeddings # Load the API key from env variables load_dotenv() api_key = os.getenv("OPENAI_API_KEY")


수입에 대한 설명은 다음과 같습니다.

os : 운영 체제 상호작용 • dotenv : 환경 변수 로드 • langchain 구성 요소:

  • PromptTemplate : 사용자 정의 프롬프트 생성
  • FAISS : 문서를 위한 가벼운 벡터 저장소
  • StrOutputParser : LLM 메시지 객체를 문자열 출력으로 변환
  • RunnablePassthrough : 구성 가능한 체인 생성
  • ChatOpenAI , OpenAIEmbeddings : OpenAI 모델 상호 작용


다음으로, RAG 시스템에 대한 프롬프트를 생성합니다.


 RAG_PROMPT_TEMPLATE = """ You are a helpful coding assistant that can answer questions about the provided context. The context is usually a PDF document or an image (screenshot) of a code file. Augment your answers with code snippets from the context if necessary. If you don't know the answer, say you don't know. Context: {context} Question: {question} """ PROMPT = PromptTemplate.from_template(RAG_PROMPT_TEMPLATE)


RAG 시스템 프롬프트는 성공의 중요한 요소 중 하나입니다. 저희 버전은 간단하지만 대부분의 경우 작업을 완료할 수 있습니다. 실제로 프롬프트를 반복하고 개선하는 데 많은 시간을 할애해야 합니다.


알아차리셨겠지만, 우리는 프롬프트를 구성하기 위해 PromptTemplate 클래스를 사용하고 있습니다. 이 구성을 사용하면 문서에서 검색한 컨텍스트와 사용자의 쿼리를 최종 프롬프트에 동적으로 수집할 수 있습니다.


문서에 관해 말하자면, 시스템 프롬프트에 컨텍스트로 전달되기 전에 문서를 포맷할 함수가 필요합니다.


 def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs)


검색된 문서의 페이지 내용을 연결하는 간단한 기능입니다.


마지막으로 RAG 체인을 개발할 함수를 생성합니다.


 def create_rag_chain(chunks): embeddings = OpenAIEmbeddings(api_key=api_key) doc_search = FAISS.from_documents(chunks, embeddings) retriever = doc_search.as_retriever( search_type="similarity", search_kwargs={"k": 5} ) llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | PROMPT | llm | StrOutputParser() ) return rag_chain


이 함수는 document_processor.py 스크립트 내의 process_document 함수에 의해 제공되는 문서 청크를 허용합니다.

이 기능은 임베딩 모델을 정의하고 문서를 FAISS 벡터 스토어에 저장하는 것으로 시작합니다. 그런 다음, 사용자의 쿼리와 일치하는 상위 5개 문서를 반환하는 유사성 검색을 통해 검색기 인터페이스로 변환됩니다.


언어 모델의 경우 gpt-4o-mini 사용하지만 예산과 필요에 따라 GPT-4o 등 다른 모델을 사용할 수 있습니다.

그런 다음, LangChain Expression Language(LCEL)를 사용하여 이러한 모든 구성 요소를 함께 배치합니다. 체인의 첫 번째 구성 요소는 contextquestion 사전입니다. 이러한 키의 값은 각각 포맷팅 함수와 RunnablePassthrough() 로 포맷된 검색기에 의해 제공됩니다. 후자의 클래스는 사용자 쿼리의 플레이스홀더 역할을 합니다.


그런 다음 사전은 시스템 프롬프트로 전달됩니다. 프롬프트는 LLM에 공급되고, LLM은 출력 메시지 클래스를 생성합니다. 메시지 클래스는 일반 텍스트 응답을 반환하는 문자열 출력 파서에 제공됩니다.

4. Streamlit UI 만들기

이 섹션에서는 앱의 아래 UI를 빌드합니다.


Streamlit UI의 스크린샷입니다.

두 개의 입력 필드가 있는 깔끔하고 최소한의 인터페이스입니다. 하나는 문서용이고 다른 하나는 문서에 대한 질문을 위한 것입니다. 왼쪽 사이드바에서 사용자는 API 키를 입력하라는 요청을 받습니다.


인터페이스를 빌드하려면 작업 디렉토리의 최상위에 있는 app.py 스크립트를 열고 다음 코드를 붙여넣습니다.


 import streamlit as st import os from dotenv import load_dotenv from src.document_processor import process_document from src.rag_chain import create_rag_chain # Load environment variables load_dotenv() st.set_page_config(page_title="RAG Chatbot", page_icon="🤖") st.title("RAG Chatbot") # Initialize session state if "rag_chain" not in st.session_state: st.session_state.rag_chain = None # Sidebar for API key input with st.sidebar: api_key = st.text_input("Enter your OpenAI API Key", type="password") if api_key: os.environ["OPENAI_API_KEY"] = api_key # File uploader uploaded_file = st.file_uploader("Choose a file", type=["pdf", "png", "jpg", "jpeg"]) if uploaded_file is not None: if st.button("Process File"): if api_key: with st.spinner("Processing file..."): # Save the uploaded file temporarily with open(uploaded_file.name, "wb") as f: f.write(uploaded_file.getbuffer()) try: # Process the document chunks = process_document(uploaded_file.name) # Create RAG chain st.session_state.rag_chain = create_rag_chain(chunks) st.success("File processed successfully!") except ValueError as e: st.error(str(e)) finally: # Remove the temporary file os.remove(uploaded_file.name) else: st.error("Please provide your OpenAI API key.") # Query input query = st.text_input("Ask a question about the uploaded document") if st.button("Ask"): if st.session_state.rag_chain and query: with st.spinner("Generating answer..."): result = st.session_state.rag_chain.invoke(query) st.subheader("Answer:") st.write(result) elif not st.session_state.rag_chain: st.error("Please upload and process a file first.") else: st.error("Please enter a question.")


단 65줄 길이임에도 불구하고 다음과 같은 기능을 구현하고 있습니다.

  1. API 키 입력: 사용자가 OpenAI API 키를 안전하게 입력할 수 있습니다.
  2. 파일 업로드: PDF, PNG, JPG, JPEG 파일 업로드를 지원합니다.
  3. 문서 처리: 업로드된 파일을 처리하고 텍스트 청크를 생성합니다.
  4. RAG 체인 생성: 처리된 문서 청크를 사용하여 검색 증강 생성 체인을 구축합니다.
  5. 쿼리 처리: 업로드된 문서에 대한 사용자 질문을 수락합니다.
  6. 답변 생성: RAG 체인을 사용하여 업로드된 문서와 사용자 쿼리를 기반으로 답변을 생성합니다.
  7. 오류 처리: 누락된 API 키, 처리되지 않은 파일, 비어 있는 쿼리에 대한 적절한 오류 메시지를 제공합니다.
  8. 사용자 피드백: 처리 중에는 스피너를 표시하고 성공/오류 메시지를 표시하여 사용자에게 정보를 제공합니다.
  9. 상태 관리: Streamlit의 세션 상태를 활용하여 상호작용 전반에서 RAG 체인을 유지합니다.

5. Streamlit 챗봇으로 배포

이제 한 단계만 남았습니다. Streamlit 앱을 배포하는 것입니다. 여기에는 많은 옵션이 있지만 가장 쉬운 방법은 무료이며 설정이 쉬운 Streamlit Cloud를 사용하는 것입니다.


먼저 .streamlit/config.toml 스크립트를 열고 다음 구성을 붙여넣습니다.


 [theme] primaryColor = "#F63366" backgroundColor = "#FFFFFF" secondaryBackgroundColor = "#F0F2F6" textColor = "#262730" font = "sans serif"


이것들은 개인적 선호도에서 나온 몇 가지 테마 조정입니다. 그런 다음 README.md 파일을 작성합니다( GitHub에서 호스팅된 이 파일 에서 내용을 복사할 수 있습니다).


마지막으로 GitHub.com 으로 가서 새 저장소를 만듭니다. 링크를 복사하고 작업 디렉토리로 돌아갑니다.


 $ git init $ git add . $ git commit -m "Initial commit" $ git remote add origin https://github.com/YourUsername/YourRepo.git $ git push --set-upstream origin master


위 명령어는 Git을 초기화하고, 초기 커밋을 생성하고 모든 내용을 저장소에 푸시합니다(저장소 링크를 자신의 링크로 바꾸는 것을 잊지 마세요).


이제 Streamlit Cloud 에서 무료 계정에 가입해야 합니다. GitHub 계정을 연결하고 앱이 포함된 리포지토리를 선택합니다.


그런 다음 앱 설정을 구성하세요.

  • Python 버전을 설정합니다(예: 3.9)
  • 메인 파일 경로를 app.py 로 설정합니다.
  • 앱 설정에 필요한 비밀(예: OPENAI_API_KEY )을 추가합니다.


마지막으로 "배포"를 클릭하세요!


앱은 몇 분 안에 작동해야 합니다. 이 튜토리얼을 위해 제가 만든 앱은 이 링크 에서 찾을 수 있습니다. 시도해 보세요!

RAG 시스템의 작동

결론

이 튜토리얼은 문서 기반의 대화형 질문-답변 시스템을 형성하는 검색 증강 생성(RAG)과 Streamlit의 강력한 조합을 살펴봅니다. 환경 설정과 문서 처리부터 RAG 체인 구축과 친절한 웹 앱 배포까지 전체 프로세스를 독자에게 안내합니다.


중요한 점은 다음과 같습니다.

  • 외부 지식의 관점에서 보다 스마트한 언어 모델을 위한 RAG
  • RAG 체인은 LangChain, OpenAI의 모델, 타사 커뮤니티 통합을 사용하여 구축할 수 있습니다.
  • Streamlit을 사용하면 앱의 상호작용이 가능하며, 대중에게 공개될 수도 있습니다.


이 프로젝트는 더욱 진보된 애플리케이션의 기반을 형성합니다. 여러 문서 유형의 통합, 향상된 검색 정확도, 문서 요약과 같은 기능과 같이 상당한 방식으로 확장될 수 있습니다. 그러나 실제로 이 프로젝트가 제공하는 것은 이러한 기술의 잠재적인 힘을 개별적으로 그리고 결합하여 보여주는 것입니다.