본 포스팅에 사용된 코드는 (주)인공지능팩토리 대표이사이자 마이크로소프트 지역 디렉터(Microsoft Regional Director)이신 김태영님의 “[랭체인러닝데이] 문서로더부터 벡터스토어까지 (랭체인없이) 쌩 파이썬으로 만들어보기” 유튜브 강의 영상을 바탕으로 작성되었습니다. 해당 코드에 대한 모든 권리는 김태영님께 있음을 밝힙니다.
강의 영상에서는 코드를 직접 공유하지 않으셨기에, 본 포스팅에서는 강의 내용을 토대로 직접 코드를 작성하여 정리해 보았습니다. 코드 작성 과정에서 강의 내용을 최대한 반영하고자 노력하였으나, 일부 변형 또는 오류가 있을 수 있습니다.
이 강의에서 다루는 내용은 랭체인(LangChain)에서 RAG(Retrieval-Augmented Generation)을 위해 사용하는 각 컴포넌트들이 어떻게 구현되었는지를 보여줍니다. 랭체인은 언어 모델과 외부 데이터를 연결하여 더 강력한 자연어 처리 애플리케이션을 만들 수 있게 해주는 프레임워크이며, RAG는 질의에 답할 때 외부 데이터를 활용하여 언어 모델의 성능을 향상시키는 기술입니다. 이 강의는 이러한 최신 기술들의 내부 동작을 이해하는 데 큰 도움이 될 것입니다.
원본 코드와 더 자세한 설명은 김태영님의 유튜브 강의를 참고해 주시기 바랍니다.
필요 라이브러리 설치 (OpenAI, TQDM, ChromaDB, Tiktoken, Sentence Transformers)
fromtqdmimporttqdmclassSimpleCharacterTextSplitter:def__init__(self,chunk_size,chunk_overlap,separator_pattern='\n\n'):self.chunk_size=chunk_sizeself.chunk_overlap=chunk_overlapself.separator_pattern=separator_patterndefsplit_documents(self,documents):# 개행 두 개(\n\n)만큼으로 짜르는데, 이걸 청크라고 부르지 않고, 이걸 다시 청크 사이즈만큼 합친다.
splits=documents.split(self.separator_pattern)chunks=[]current_chunk=splits[0]forsplitintqdm(splits[1:],desc="splitting..."):iflen(current_chunk)+len(split)+len(self.separator_pattern)>self.chunk_size:chunks.append(current_chunk.strip())current_chunk=splitelse:current_chunk+=self.separator_patterncurrent_chunk+=splitifcurrent_chunk:chunks.append(current_chunk.strip())returnchunks
# 청크로 자른 docs와 임베딩벡터로 바꾼 것들을 차곡차곡 쌓아준다
importnumpyasnpfromsklearn.metrics.pairwiseimportcosine_similarityclassSimpleVectorStore:def__init__(self,docs,embedding):self.embedding=embeddingself.documents=[]self.vectors=[]fordocintqdm(docs,desc="embedding..."):self.documents.append(doc)vector=self.embedding.embed_query(doc)self.vectors.append(vector)defsimilarity_search(self,query,k=4):query_vector=self.embedding.embed_query(query)ifnotself.vectors:return[]similarities=cosine_similarity([query_vector],self.vectors)[0]sorted_doc_similarities=sorted(zip(self.documents,similarities),key=lambdax:x[1],reverse=True)returnsorted_doc_similarities[:k]defas_retriever(self,k=4):returnSimpleRetriever(self,k)
raw_documents=SimpleTextLoader('state_of_the_union.txt').load()text_splitter=SimpleCharacterTextSplitter(chunk_size=1000,chunk_overlap=0)documents=text_splitter.split_documents(raw_documents)db=SimpleVectorStore(documents,SimpleOpenAIEmbeddings())query="what did the president say about Ketanji Brown Jackson"docs=db.similarity_search(query)
유사도 검색 기반 검색기(Similarity-search-based retriever)
1
2
3
4
retriever=db.as_retriever()unique_docs=retriever.get_relevant_documents(query="what did the president say about Ketanji Brown Jackson")print(unique_docs)
importopenaisystem_prompt_template=("You are a helpful assistant. ""Based on the following content, ""kindly and comprehensively respond to user questions.""[Content]""{content}""")classSimpleRetrievalQA():def__init__(self,retriever):self.retriever=retrieverdefinvoke(self,query):docs=retriever.get_relevant_documents(query)fori,docinenumerate(docs):print("[#"+str(i)+"]",doc[1])print(doc[0])print("")completion=openai.chat.completions.create(model="gpt-3.5-turbo-1106",messages=[{"role":"system","content":system_prompt_template.format(content=docs)},{"role":"user","content":query}])returncompletion.choices[0].message.content
1
2
3
4
5
chain=SimpleRetrievalQA(retriever)answer=chain.invoke("what did the president say about Ketanji Brown Jackson")print("")print(">>>>",answer)
본 포스팅에서는 RAG 구현에 있어 핵심이라고 생각되는 컴포넌트들의 코드만 정리하였습니다. 더 자세한 내용은 영상을 참고해 주시기 바랍니다.