Retrieval Augmented Generation (RAG) 방법은 LLM에게 질문과 관련 정보를 함께 줘서 해당 정보를 바탕으로 응답하게 하는 방법입니다. LLM의 환각 현상을 줄일 수 있는 방법이자, 최신 정보나 특정 분야의 지식을 전달해줄 수 있는 방법입니다. LangChain에서 RAG를 구현하는 과정은 아래 그림과 같습니다.
LangChain RAG 구현 과정
- Source: 문서 파일을 준비합니다.
- Load: document_loaders 모듈을 이용해 문서의 내용을 읽어들입니다.
- Transform: 문서가 길면 text_splitter 모듈을 이용해 나눕니다.
- Embed: embeddings 모듈의 임베딩 모델을 이용해 텍스트를 벡터로 변환합니다.
- Store: vectorstores 모듈의 벡터 데이터베이스에 임베딩 벡터를 저장합니다. 벡터 데이터베이스에서는 유사도 기반 벡터 검색 기능도 제공합니다.
- Retrieve: 질문과 가장 가까운 문서를 반환하는 인터페이스로, index나 vectorstore를 이용해 문서를 검색합니다.
LangChain에서 index는 문서를 저장하고 검색하기 위한 데이터 구조로, 벡터 데이터베이스는 index의 일종이라고 할 수 있습니다. Index는 벡터 데이터베이스보다 더 큰 개념으로, 저장과 유사도 기반 검색 뿐 아니라 문서 관리(중복 제거, 업데이트, 삭제) 기능도 포함합니다. 위 과정을 코드로 살펴보겠습니다.
Retrieval Augmented Generation 예제
이 예제는 DeepLearning.AI에서 가져온 예제입니다. 예제에서 사용하는 CSV 파일은 1000 종류의 옷에 관한 정보를 포함한 파일입니다.
Source, Load (Transform은 생략)
from langchain.document_loaders import CSVLoader
loader = CSVLoader(file_path="09_OutdoorClothingCatalog_1000.csv")
docs = loader.load()
docs
는 1000개의 Document 객체를 포함한 리스트입니다. CSV 파일의 한 행이 하나의 문서가 되었습니다. Document 길이가 길지 않기 때문에 text_splitter
는 사용하지 않았습니다.
Embed, Store
! pip install docarray
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import DocArrayInMemorySearch
embeddings = OpenAIEmbeddings()
db = DocArrayInMemorySearch.from_documents(
docs,
embeddings
)
다양한 vectorstores
들 중 DocArrayInMemorySearch
를 사용했습니다. 다른 벡터 데이터베이스를 사용하는 것도 가능합니다.
Store (Vector Search)
query = "한여름 산행에 좋은 여성 옷을 추천해줘. 추천 이유를 한국어로 간략하게 설명해줘.\nOutput format: 'name: reason'"
docs = db.similarity_search(query)
docs
는 네 개의 Document를 포함한 리스트입니다. 포함된 문서는 질문과 의미가 가장 가까운 문서들입니다.
Retrieve
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(temperature = 0.0)
qdocs = "".join([docs[i].page_content for i in range(len(docs))])
rag_prompt = f"{qdocs} Question: {query}"
response = llm.predict(rag_prompt)
## 출력
"name: Women's Camo Mountain Fleece Vest\nreason: 한여름 산행에 좋은 여성 옷으로 추천합니다. 이 베스트는 두꺼운 300무게의 셔파 플리스로 제작되어 견고하고 따뜻한 착용감을 제공합니다. 여름에도 산행 시에는 밤에 추워질 수 있으므로 이 베스트는 완벽한 보온층이 될 것입니다. 또한, 팔 구멍과 밑단에 탄력 있는 실이 있어 편안하게 착용할 수 있으며 바람을 막아줍니다."
앞에서 검색한 네 개의 Document 내용을 프롬프트에 넣어 LLM에 질문과 함께 전달했고, 옷을 하나 추천 받았습니다. 위의 Retrieve 과정은 RetrievalQA Chain을 이용해 구현할 수도 있습니다.
Retrieve: RetrievalQA Chain을 이용하는 방법
from langchain.chains import RetrievalQA
retriever = db.as_retriever()
qa_stuff = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
verbose=True
)
response = qa_stuff.run(query)
문서 내용이 길지 않기 때문에 옵션에서 chain_type
을 stuff로 했습니다.네 개의 Document와 질문을 함께 전달하기 때문에 실행할 때마다 추천하는 옷이 달라질 수도 있습니다. 이러한 Retrieval Augmented Generation은 자주 사용하는 방법이기 때문에 index를 이용해 좀 더 간단하게 구현하는 방법도 있습니다.
Vectorstore Index를 이용하는 방법
RAG 과정 중 Source와 Load (loader 선언까지) 과정은 앞과 동일합니다. 이후 Embed, Store, Retrieve는 다음과 같이 구현할 수 있습니다.
from langchain.indexes import VectorstoreIndexCreator
index = VectorstoreIndexCreator(
vectorstore_cls=DocArrayInMemorySearch,
embedding=embeddings
).from_loaders([loader])
response = index.query(query)
앞의 코드에 비해 많이 짧아졌죠? 중간 과정에서 별도의 처리가 필요하다면 앞의 코드를 사용해야겠지만, 그렇지 않다면 index를 사용하는 것이 편리하겠습니다.