📚 STUDY/AI

[LLM/LangChain] RAG - LangChain 과 ChromaDB 사용하기

삶감 2024. 10. 21. 11:23

# 1. LangChain과 ChromaDB란 무엇인가?

## 1-1. LangChain

LangChain은 자연어 처리(NLP) 및 언어 모델을 활용하여 다양한 애플리케이션을 개발할 수 있도록 도와주는 프레임워크입니다. 이 프레임워크는 주로 다음과 같은 기능을 제공합니다:

1. 모델 통합: 다양한 언어 모델(API)을 통합하여 사용자가 원하는 방식으로 쉽게 활용할 수 있습니다. OpenAI, Hugging Face 등 다양한 모델과의 호환성을 지원합니다.

2. 체인(Chain) 구성: LangChain의 핵심 개념은 '체인'입니다. 사용자는 여러 개의 작업을 연결하여 복잡한 작업 흐름을 만들 수 있습니다. 예를 들어, 데이터 수집, 처리, 모델 예측 등의 과정을 연속적으로 구성할 수 있습니다.

3. 데이터 소스 연결: LangChain은 외부 데이터 소스(예: 데이터베이스, API)와의 연결을 지원하여, 모델이 더 많은 정보를 바탕으로 작업을 수행할 수 있게 합니다.

4. 사용자 정의: 사용자에게 맞춤형 작업 흐름을 구성할 수 있는 유연성을 제공하여, 특정 요구사항에 맞는 솔루션을 만들 수 있습니다.

LangChain은 AI 및 언어 모델을 활용하여 비즈니스 애플리케이션, 챗봇, 데이터 분석 도구 등 다양한 분야에 응용할 수 있는 매우 강력한 도구입니다. 

Response by 'ChatGPT 4o mini'

 

## 1-2. ChromaDB

ChromaDB는 대규모 데이터셋을 효율적으로 저장하고 검색하기 위한 오픈 소스 벡터 데이터베이스입니다. 주로 다음과 같은 기능과 특성을 갖추고 있습니다:

1. 벡터 저장: ChromaDB는 고차원 벡터(예: 텍스트, 이미지, 오디오 등)의 저장과 검색을 최적화하여, 머신러닝 모델의 출력 결과를 효과적으로 관리할 수 있습니다.

2. 빠른 검색: 효율적인 검색 알고리즘을 통해 대량의 벡터 데이터에서 유사한 항목을 신속하게 찾을 수 있습니다. 이는 특히 추천 시스템이나 유사도 검색에 유용합니다.

3. 사용자 친화성: 간편한 API를 제공하여, 개발자들이 쉽게 데이터베이스를 구축하고 사용할 수 있도록 지원합니다. 이는 코드 몇 줄로 기본적인 CRUD(Create, Read, Update, Delete) 작업을 수행할 수 있게 합니다.

4. 확장성: ChromaDB는 클라우드 환경에서의 확장을 지원하여, 데이터의 양이 많아지더라도 성능을 유지할 수 있도록 설계되었습니다.

5. 다양한 데이터 형식 지원: 텍스트, 이미지, 오디오 등 다양한 데이터 유형을 처리할 수 있어, 다양한 분야에 활용할 수 있습니다.

ChromaDB는 AI 및 데이터 과학 분야에서 데이터 관리를 용이하게 하여, 다양한 머신러닝 및 딥러닝 애플리케이션에 적합한 도구입니다.

Response by 'ChatGPT 4o mini'

 

 

# 2. LangChain과 ChromaDB를 사용하여 RAG 구현하기

위에서 설명했듯이 ChromaDB는 문서 검색을 관리하는 저장소, LangChain은 LLM을 효과적으로 사용할 수 있는 프레임워크의 개념이다. LangChain 프레임워크에서 ChromaDB와 연동하는 라이브러리를 제공해서 더 쉽게 Retrieval Augmentated Generation(RAG)를 구현할 수 있다.

 

 

🔽 Requirements

더보기
pip install -qU langchain langchain-chroma langchain-community langchain-core langchain-huggingface langchain-text-splitters langdetect langsmith pydantic==2.9.2

 

## 2-1. ChromaDB 초기화

from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings

ENCODER = # 임베딩 모델. (Huggingface model id)
COLLECTION_NAME = # Chroma collection 이름
CHROMA_PATH = # Chroma vector store 저장할 경로


# Init chromadb
embedding_func = HuggingFaceEmbeddings(model_name=ENCODER, encode_kwargs={'normalize_embeddings':True},)
vectorstore = Chroma(
    collection_name=COLLECTION_NAME,
    embedding_function=embedding_func,
    persist_directory=CHROMA_PATH,
)
  1. 'collection_name': collection에 접근하기 위한 키... 정도로 생각하면 될 듯
  2. 'embedding_function': Chroma가 문서를 벡터화 할 때 사용하는 인코딩 방식이다. Huggingface에 다양한 encoding 모델들이 있으니 찾아보고 사용에 맞게 적절한 모델을 선택하면 된다.
  3. 'persist_directory': Chroma가 문서를 저장할 실제적인 경로

 

## 2-2. 문서 임베딩

1) 파일 읽어오기

from langchain_community.document_loaders import PyPDFLoader

file_name = # 읽어오려는 파일 경로

loader = PyPDFLoader(file_name)
pages = loader.load()
text = ""
for page in pages:
    sub = page.page_content
    text += sub

 

위의 예시는 '.pdf' 파일을 읽어오는 코드인데, 'langchain_community.document_loaders' 에서 다른 형식의 파일들을 파싱해오는 코드들이 많으니 다른 사이트들을 참고해서 필요한 모듈을 쓰면 된다.

 

 

2) 문서 chunking 하고 vectorstore에 저장하기

from transformers import AutoTokenizer
from langchain_text_splitters import CharacterTextSplitter
from langchain_core.documents import Document

# token size 기준으로 contents split
tokenizer = AutoTokenizer.from_pretrained(ENCODER)
text_splitter = CharacterTextSplitter.from_huggingface_tokenizer(
            tokenizer, 
            chunk_size=CHUNK_SIZE, 
            chunk_overlap=CHUNK_OVERLAP, 
            separator="\n" # default: "\n\n"
        ) 


documents=[] # split 한 문서들을 담기 위한 array

split_conts = text_splitter.split_text(text)
for chunk_idx, split_cont in enumerate(split_conts):
    documents.append(Document(
        page_content=split_cont,
        metadata={
            "file_name": file_name,
        },
        id=chunk_idx,
    ))
    idx+=1

vectorstore.add_documents(documents)

 

나는 tokenizer 기준으로 청킹을 수행했다. Tokenizer는 문서를 embedding 할 때 사용했던 encoder와 동일한 모델을 선택했다.

'langchain_text_splitter'에 다양한 splitter들이 있으니 상황에 맞게 선택해 쓰면 된다.

 

Splitter는 seperator를 기준으로 자르는데, chunk_size와 chunk_overlap이 어떻게 작동하는지는 더 살펴봐야 할 것 같음.

 

 

## 2-3. Retriever 설정하기

SEARCH_TYPE="similarity" # cosine similarity
TOPK = 3

# Init Retriver
retriever = vectorstore.as_retriever(
    search_type=SEARCH_TYPE,
    search_kwargs={
        'k': TOPK,
        } 
    )

 

'search_type="mrr"'로 설정하면 MRR(Maximal Marginal Relevance) 검색이 가능하다

 

 

## 2-4 Chain 만들기

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_huggingface.llms import HuggingFacePipeline


INSTRUCT= # llm 으로 사용할 모델 id

# Create Template
template='''당신은 도움이 되는 한국어 어시스턴트입니다. 검색된 context를 이용해 question에 답변하십시오. 만약 정답을 모른다면, 모르겠다고 답변하세요. 답변은 한국어로 간결하게 온전한 문장으로 유지하세요.
Context: {context}
Question: {question}
Answer: '''
prompt = PromptTemplate.from_template(template)


# Set LLM
llm = HuggingFacePipeline.from_model_id(
    model_id=INSTRUCT,
    task="text-generation",
    pipeline_kwargs={
        "max_new_tokens": 512,
    },
    device=0,
)

# 검색 결과로 나온 문서를 원하는 포멧으로 만들어주기 위한 함수 (없어도 됨)
def format_docs(docs):
    context = ""
    for doc in docs:
        context+=doc.metadata["file_name"]+"\n"
        context+=doc.page_content
        context+="\n\n"
    return context

# Create chain
chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

 

chain을 생성해서 retriever(vector db)와 프롬프트, LLM을 간단하게 연결할 수 있다.

 

 

## 2-5. Retrieval 하여 LLM에서 질의응답 받기

query=input("질문: ")
res = chain.invoke(query)
print(f"response: \n{res}")

 

 

 

 

Reference

 

 

 

728x90
728x90