rag
개요
전처리
서비스
두개의 단계로 나눠진다.
전처리
전처리를 해서 가지고잇는 모든 문서를 embed해서 디비에 넣어둔다.

서비스

정리

Document Loader
https://python.langchain.com/v0.2/docs/integrations/document_loaders/
많은것중에 알아서 골라쓰면 된다.
https://python.langchain.com/v0.2/docs/integrations/document_loaders/unstructured_file/
일단 난 이걸로 선택
%pip install --user -Uq unstructured
from dotenv import load_dotenv
load_dotenv()
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader("files/kubernetes_hardening_guidance.md")
docs = loader.load()
docs
len(docs)
Document Splitter
도큐먼트 1개로 로드가 된다. 이 문서를 다 llm으로 보내도 되나 비용문제가 발생한다.
그리고 llm에서 최대 크기가 잇어서 전부다 한꺼번에 보내지 못한다.
필요없는부분까지 보내서 비용을 내는것보다 짤라서 필요한 부분만 보내면 더 좋을거같다.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter()
docs = loader.load_and_split(text_splitter=splitter)
docs
len(docs)
14개로 짤라진걸 볼수 있다.
더 많이 잘라보자.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=200
)
docs = loader.load_and_split(text_splitter=splitter)
docs
len(docs)
14개가 2482로 늘어났다. 근데 문장이 짤림.. llm이 이해를 못함.
overlap을 사용하자.
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=50,
separators=["\n", "\r\n"],
length_function=len,
)
docs = loader.load_and_split(text_splitter=splitter)
docs
len(docs)
openai는 length를 사용하지 않고 token을 사용한다.
https://platform.openai.com/tokenizer
토큰과 character가 다른걸 알수있다.
우리는 문서를 token단위로 이해해야할 필요가 있다.
링크에서 tikoken 패키지를 추천한다.
https://github.com/openai/tiktoken
사용하자.
%pip install --user -Uq tiktoken
from langchain.text_splitter import CharacterTextSplitter
splitter = CharacterTextSplitter.from_tiktoken_encoder(
chunk_size=200,
chunk_overlap=50,
separator="\n",
)
docs = loader.load_and_split(text_splitter=splitter)
len(docs)
문서가 잘 나눠졌다. 이제 임베딩을 해서 디비에 넣어보자.
Embedding 하기
문서를 숫자로 바꿔서 디비에 넣을준비를 하는것임 이 숫자를 기준으로 유사도를 검색한다.
주의사항은 임베딩 방식을 지금 결정하면 나중에 query를 할때도 같은 방식으로 해야한다.
openai embeding을 이용합니다.다른것들도 있으니 찾아서 보시요
https://github.com/turbomaze/word2vecjson 을 참고하시요
난 일단 몰라도 되는거같아서 바로 코드로 진행
https://python.langchain.com/v0.2/docs/integrations/text_embedding/openai/
OpenAI Embedding을 사용하자.
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
text = "This is a test document."
vector = embeddings.embed_query(text)
vector
len(vector)
doc_result = embeddings.embed_documents([text])
doc_result[0][:5]
print(len(doc_result))
len(doc_result[0])
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large", dimensions=1024)
len(embeddings.embed_documents([text])[0])
Embedding을 디비(FAISS) 에 넣기
임베딩을 해서 FAISS 에 입력하기
%pip install --user -Uq faiss-cpu
# %pip install --user -Uq faiss-gpu
from langchain_openai import OpenAIEmbeddings
from langchain.document_loaders import UnstructuredFileLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.storage import LocalFileStore
from langchain_community.document_loaders import UnstructuredMarkdownLoader
splitter = CharacterTextSplitter.from_tiktoken_encoder(
separator="\n",
chunk_size=600,
chunk_overlap=100,
)
loader = UnstructuredMarkdownLoader("files/kubernetes_hardening_guidance.md")
docs = loader.load_and_split(text_splitter=splitter)
from langchain.vectorstores import FAISS
embeddings = OpenAIEmbeddings()
# 임베딩을 해서 디비에 넣는다.
vectorstore = FAISS.from_documents(docs, embeddings)
results = vectorstore.similarity_search("누구에게 이 가이드를 추천하는가?")
results
유사도 검사를 해서 문장을 잘 찾아온다. 이걸 프롬프트로 llm에 보내면 된다.
보통 3개나 5개가 좋다고한다.
Embedding cache
할때마다 비용이 발생한다.
디비에 입력한 것은 로컬 드라이브에 cache를 하자. 그러면 openai에 다시 요청하지 않아서 요금이 줄어든다.
# caching
from langchain.embeddings import CacheBackedEmbeddings
embeddings = OpenAIEmbeddings()
cache_dir = LocalFileStore("./cache/")
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(embeddings, cache_dir)
# vectorstore = FAISS.from_documents(docs, embeddings)
vectorstore = FAISS.from_documents(docs, cached_embeddings)
# 검색
results = vectorstore.similarity_search("누구에게 이 가이드를 추천하는가?")
results
이제 이 결과를 llm에 보낼가?
검색된 결과를 prompt에 넣고 llm에 보내기
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
retriver = vectorstore.as_retriever()
prompt = ChatPromptTemplate.from_messages(
[
("system",
"""
You are a helpful AI talking to a human, Answer questions using only the following context.
If you don't know the answer just say you don't know, don't make it up:
{context}
"""),
("human", "{question}"),
]
)
llm = ChatOpenAI(temperature=0.1)
from langchain.schema.runnable import RunnablePassthrough
chain = ({
"context": retriver,
"question": RunnablePassthrough(),
}
| prompt | llm
)
chain.invoke("누구에게 이 가이드를 추천하는가?")
DocumentsChain (문서를 vector에서 가져와서 처리하는 방식)
stuff - vector 검색에 걸린 모든 문서를 prompt에 넣어서 보낸다.
map reduce
refine
그림으로 설명
stuff

map reduce

refine

stuff 검색 결과 문서를 prompt에 넣어서 보내자.
stuff라고 부르던데 .
이건 위 샘플에서 처리했다. https://python.langchain.com/v0.2/docs/tutorials/summarization/#stuff
모든 문서를 단일 프롬프트에 "채우기"만 하면 됩니다. 이것이 가장 간단한 접근 방식입니다
We can use chain_type="stuff", especially if using larger context window models such as:
128k token OpenAI gpt-4-turbo-2024-04-09 200k token Anthropic claude-3-sonnet-20240229
beautifulsoup4 패키지로 웹을 크롤링 해오고 그 데이터를 llm에 같이 넣어서 요약을 받자.
파일이 크니까. 큰모델로 하자.
map reduce
"맵" 단계에서 각 문서를 자체적으로 요약한 다음 요약을 최종 요약으로 "축소"합니다
https://python.langchain.com/v0.2/docs/tutorials/summarization/#map-reduce
from langchain_openai import OpenAIEmbeddings
from langchain.embeddings import CacheBackedEmbeddings
from langchain.vectorstores import FAISS
embeddings = OpenAIEmbeddings()
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
embeddings, cache_dir)
vectorstore = FAISS.from_documents(docs, cached_embeddings)
retriever = vectorstore.as_retriever()
vector store에서 검색을 하도록 체인을 구성
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough, RunnableLambda
map_doc_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
Use the following portion of a long document to see if any of the text is relevant to answer the question. Return any relevant text verbatim. If there is no relevant text, return : ''
-------
{context}
""",
),
("human", "{question}"),
]
)
map_doc_chain = map_doc_prompt | llm
def map_docs(inputs):
documents = inputs["documents"]
question = inputs["question"]
return "\n\n".join(
map_doc_chain.invoke(
{"context": doc.page_content, "question": question}
).content
for doc in documents
)
map_chain = {
"documents": retriever,
"question": RunnablePassthrough(),
} | RunnableLambda(map_docs)
결과 나온 내용을 다시 마지막 prompt에 넣어서 최종 쿼리 llm에 보내기
final_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
Given the following extracted parts of a long document and a question, create a final answer.
If you don't know the answer, just say that you don't know. Don't try to make up an answer.
------
{context}
""",
),
("human", "{question}"),
]
)
chain = {"context": map_chain, "question": RunnablePassthrough()
} | final_prompt | llm
요청해보기
chain.invoke("요약해줘")
Refine
이건 직접 해보시요
문서를 순차적으로 요약하는데 기존에 요약된 것과 함께 다시 요약을 하는 방식 으로 업데이트합니다.
Last updated
Was this helpful?