5.6. 전형적인 RAG (랭체인 버전)¶
랭체인 버전은 다음 튜토리얼에서 진행하겠습니다.
많은 응원 부탁드립니다. 😉
5.6.1. 지식 Load 및 Split¶
pip install -U langchain langchain-community langchain-text-splitters
1from pprint import pprint
2
3from langchain_community.document_loaders import TextLoader
4from langchain_text_splitters import RecursiveCharacterTextSplitter
5
6# Load 단계
7doc_list = TextLoader(file_path="./빽다방.txt").load()
8print(f"loaded {len(doc_list)} documents") # 1
9
10# Split 단계
11text_splitter = RecursiveCharacterTextSplitter(
12 chunk_size=140, # 문서를 나눌 최소 글자 수 (디폴트: 4000)
13 chunk_overlap=0, # 문서를 나눌 때 겹치는 글자 수 (디폴트: 200)
14)
15doc_list = text_splitter.split_documents(doc_list)
16print(f"split into {len(doc_list)} documents") # 9
17
18pprint(doc_list)
RecursiveCharacterTextSplitter
에서는 구분자 (디폴트 ["\n\n", "\n", " ", ""]
)로 나누고
chunk_size
크기 만큼 문서를 모으고, 이어지는 문서는 chunk_overlap
만큼 겹쳐서 문서를 생성합니다.
“빽다방.txt” 파일에서 각 메뉴들이 구분자 \n\n
로 나눠지니까 10개 문서로 나눠지지만,
각 문서의 길이가 [98, 68, 52, 68, 114, 126, 81, 105, 65, 83]
이고,
두번째 문서는 68자, 세번째 문서는 52자로서 chunk_size=140
보다 작은 값이라 한 문서로 묶여 처리되었습니다.
그래서 10개의 문서가 아니라, 9개의 문서로만 나눠진 상황입니다. 2번 메뉴와 3번 메뉴가 묶여있네요. 🤔
loaded 1 documents
split into 9 documents
[Document(metadata={'source': './빽다방.txt'}, page_content='1. 아이스티샷추가(아.샷.추)\n - SNS에서 더 유명한 꿀팁 조합 음료 :) 상콤달콤한 복숭아맛 아이스티에 진한 에스프레소 샷이 어우러져 환상조합\n - 가격: 3800원'),
Document(metadata={'source': './빽다방.txt'}, page_content='2. 바닐라라떼(ICED)\n - 부드러운 우유와 달콤하고 은은한 바닐라가 조화를 이루는 음료\n - 가격: 4200원\n\n3. 사라다빵\n - 빽다방의 대표메뉴 :) 추억의 감자 사라다빵\n - 가격: 3900원'),
Document(metadata={'source': './빽다방.txt'}, page_content='4. 빽사이즈 아메리카노(ICED)\n - 에스프레소 4샷이 들어가 깊고 진한 맛의 아메리카노\n - 가격: 3500원'),
Document(metadata={'source': './빽다방.txt'}, page_content='5. 빽사이즈 원조커피(ICED)\n - 빽다방의 BEST메뉴를 더 크게 즐겨보세요 :) [주의. 564mg 고카페인으로 카페인에 민감한 어린이, 임산부는 섭취에 주의바랍니다]\n - 가격: 4000원'),
Document(metadata={'source': './빽다방.txt'}, page_content='6. 빽사이즈 원조커피 제로슈거(ICED)\n - 빽다방의 BEST메뉴를 더 크게, 제로슈거로 즐겨보세요 :) [주의. 686mg 고카페인으로 카페인에 민감한 어린이, 임산부는 섭취에 주의바랍니다]\n - 가격: 4000원'),
Document(metadata={'source': './빽다방.txt'}, page_content='7. 빽사이즈 달콤아이스티(ICED)\n - 빽다방의 BEST메뉴를 더 크게 즐겨보세요 :) 시원한 복숭아맛 아이스티\n - 가격: 4300원'),
Document(metadata={'source': './빽다방.txt'}, page_content='8. 빽사이즈 아이스티샷추가(ICED)\n - SNS에서 더 유명한 꿀팁 조합 음료 :) 상콤달콤한 복숭아맛 아이스티에 진한 에스프레소 2샷이 어우러져 환상조합\n - 가격: 4800원'),
Document(metadata={'source': './빽다방.txt'}, page_content='9. 빽사이즈 아이스티 망고추가+노란빨대\n - SNS핫메뉴 아이스티에 망고를 한가득:)\n - 가격: 6300원'),
Document(metadata={'source': './빽다방.txt'}, page_content='10. 빽사이즈 초코라떼(ICED)\n - 빽다방의 BEST메뉴를 더 크게 즐겨보세요 :) 진짜~완~전 진한 초코라떼\n - 가격 : 5500원')]
5.6.2. 전체 코드¶
VectorStore는 FAISS를 사용하겠습니다. Facebook AI Research에서 개발한 효율적인 유사도 검색 라이브러리입니다. 대규모 데이터셋에서도 빠른 유사도 검색이 가능하며, 메모리에 데이터를 저장하고 디스크에 저장/로드할 수 있습니다.
pip install -U langchain langchain-community langchain-openai langchain-text-splitters faiss-cpu tiktoken
1import os.path
2from pprint import pprint
3from typing import List
4from uuid import uuid4
5
6import faiss
7from langchain.chains.llm import LLMChain
8from langchain.chains.retrieval_qa.base import RetrievalQA
9from langchain_community.docstore import InMemoryDocstore
10from langchain_community.document_loaders import TextLoader
11from langchain_core.messages import AIMessage
12from langchain_core.prompts import PromptTemplate
13from langchain_core.runnables import RunnableLambda
14from langchain_core.vectorstores import VectorStore
15from langchain_openai import ChatOpenAI
16from langchain_openai.embeddings import OpenAIEmbeddings
17from langchain_community.vectorstores import FAISS
18from langchain_text_splitters import RecursiveCharacterTextSplitter
19
20faiss_folder_path = "faiss_index"
21
22embedding = OpenAIEmbeddings(model="text-embedding-3-small")
23
24
25def get_vector_store() -> VectorStore:
26 if not os.path.exists(faiss_folder_path):
27 doc_list = TextLoader(file_path="./빽다방.txt").load()
28 print(f"loaded {len(doc_list)} documents") # 1
29
30 text_splitter = RecursiveCharacterTextSplitter(
31 chunk_size=140,
32 chunk_overlap=0,
33 length_function=len,
34 is_separator_regex=True,
35 )
36 doc_list = text_splitter.split_documents(doc_list)
37 print(f"split into {len(doc_list)} documents") # 9
38
39 차원수 = len(embedding.embed_query("hello")) # 1536
40 # 차원수 = 1536
41
42 index = faiss.IndexFlatL2(차원수)
43
44 vector_store = FAISS(
45 embedding_function=embedding,
46 index=index,
47 docstore=InMemoryDocstore(),
48 index_to_docstore_id={},
49 )
50
51 uuids = [str(uuid4()) for _ in range(len(doc_list))]
52 vector_store.add_documents(documents=doc_list, ids=uuids)
53
54 vector_store.save_local("faiss_index")
55 else:
56 vector_store = FAISS.load_local(
57 faiss_folder_path,
58 embedding,
59 allow_dangerous_deserialization=True,
60 )
61
62 return vector_store
63
64
65def main():
66 vector_store = get_vector_store()
67
68 question = "빽다방 카페인이 높은 음료와 가격은?"
69
70 # 직접 similarity_search 메서드 호출을 통한 유사 문서 검색
71 # search_doc_list = vector_store.similarity_search(question)
72 # pprint(search_doc_list)
73
74 # retriever 인터페이스를 통한 유사 문서 검색
75 # retriever = vector_store.as_retriever()
76 # search_doc_list = retriever.invoke(question)
77 # pprint(search_doc_list)
78
79 # Chain을 통한 retriever 자동 호출
80 # llm = ChatOpenAI(model_name="gpt-4o-mini")
81 # retriever = vector_store.as_retriever()
82 # qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=retriever)
83 # ai_message = qa_chain.invoke(question)
84 # print("[AI]", ai_message["result"]) # keys: "query", "result"
85
86 llm = ChatOpenAI(model_name="gpt-4o-mini")
87 retriever = vector_store.as_retriever()
88 prompt_template = PromptTemplate(
89 template="Context: {context}\n\nQuestion: {question}\n\nAnswer:",
90 input_variables=["context", "question"],
91 )
92
93 rag_pipeline = (
94 RunnableLambda(
95 # 아래 invoke를 통해 전달되는 값이 인자로 전달됩니다.
96 lambda x: {
97 "context": retriever.invoke(x),
98 "question": x,
99 }
100 )
101 | prompt_template
102 | llm
103 )
104 ai_message: AIMessage = rag_pipeline.invoke(question)
105 print("[AI]", ai_message.content) # AIMessage 타입
106 print(ai_message.usage_metadata)
107
108
109if __name__ == "__main__":
110 main()
5.6.3. 실행 결과¶
[AI] 빽다방에서 카페인이 높은 음료와 그 가격은 다음과 같습니다:
1. **빽사이즈 원조커피(ICED)**
- 카페인: 564mg
- 가격: 4000원
2. **빽사이즈 원조커피 제로슈거(ICED)**
- 카페인: 686mg
- 가격: 4000원
이 두 음료는 카페인 함량이 높으므로, 카페인에 민감한 어린이와 임산부는 섭취에 주의해야 합니다.
{'input_tokens': 499, 'output_tokens': 132, 'total_tokens': 631, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
랭체인/랭그래프 버전도 기대해주세요. 🥳