일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- tool_binding
- pinecone
- rl
- 강화학습
- subgraph
- add_subgraph
- REACT
- LangChain
- 추천시스템
- conditional_edge
- 강화학습의 수학적 기초와 알고리듬의 이해
- conditional_edges
- rag
- Ai
- 밑바닥부터시작하는딥러닝 #딥러닝 #머신러닝 #신경망
- removemessage
- update_state
- 밑바닥부터 시작하는 딥러닝
- toolnode
- summarize_chat_history
- Python
- langgraph
- humannode
- human-in-the-loop
- 강화학습의 수학적 기초와 알고리듬 이해
- lcel
- RecSys
- chat_history
- tool_calls
- langgrpah
- Today
- Total
타임트리
[LangChain] RAG with Pinecone (LCEL) 본문
이번에는 https://sean-j.tistory.com/38 에서 만든 간단한 rag 파이프라인을 구현된 chain 대신, 직접 프롬프트를 작성하고 LCEL 로 RAG 파이프라인을 구성해보자.
랭체인은 복잡한 체인을 쉽게 구현할 수 있도록 LangChain Expression Language(LCEL)을 제공한다. 기본적으로 Runnable 인터페이스를 따르는 LCEL 객체들을 파이프 연산자(|
)으로 연결하는 형태로 작성할 수 있는데, 이때 앞 객체의 output을 다음 객체의 input으로 전달한다. 그리고 Runnable은 입력을 받아 출력을 만드는 인터페이스라고 생각하면 된다.
이때 Runnable은 공통적으로 invoke, batch, stream 등의 메서드를 갖고 있어야 한다. 또, 모든 LCEL 체인은 그 자체로 하나의 LCEL 객체이므로 마찬가지로 입력을 받아 출력을 다음 객체로 전달할 수 있다.
참고로, 프롬프트도 Runnable이라는 점에 주목하자. 따라서, 아래처럼 invoke 메서드로 input을 전달하면 output으로 place holder에 input을 채워 출력한다.
template = "{input}에 대해 알려줘"
prompt = PromptTemplate.from_template(template)
print(prompt.invoke({"input": "테슬라"}))
테슬라에 대해 알려줘
그럼, 질문과 답변 언어를 LLM에게 넘겨주는 chain을 LCEL로 만들어 보자.
from detenv import load_detenv
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
load_dotenv()
if __name__ == "__main__":
llm = ChatOpenAI()
template = "주어진 질문에 {language}로 대답해. 질문: {question} 대답: "
prompt = PromptTemplate.from_template(template)
qa_chain = prompt | llm | StrOutputParser()
print(qa_chain.invoke({"language": "한국어", "question": "임베딩이 뭐야?"}))
임베딩은 단어나 문장을 벡터 형태로 나타내는 방법이다. 이를 통해 단어나 문장 간의 유사도를 계산하거나 자연어 처리 분야에서 다양한 모델에 활용된다.
이제 앞선 글에서 이미 구성된 chain 대신 직접 LCEL로 RAG 파이프라인을 만들어보자.
Retriever
우선 ChatOpenAI 모델, 임베딩 모델, 그리고 Pinecone Vector Store로부터 사용자 쿼리와 유사한 top-k 개의 문서를 받아올 수 있도록 vectorstore의 as_retriever
메서드로 VectorStoreRetriever
객체를 초기화하자. 이때, top-2개의 관련 문서를 유사도 기준으로 가져오도록 초기화한다.
embeddings = OpenAIEmbeddings()
llm = ChatOpenAI()
# Initialize Pinecone vecter store
vectorstore = PineconeVectorStore(
index_name=os.getenv("INDEX_NAME"), embedding=embeddings
)
# retriever - search_type, search_kwargs 를 지정할 수 있음
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 2})
retriever는 VectorStoreRetriever
객체로 LCEL을 구성할 수 있는 Runnable이다. 따라서, invoke 메서드로 output을 출력, 전달할 수 있다. 확인해보자.
sim_docs = retriever.invoke("키보드")
print(sim_docs)
[Document(page_content='아래처럼 메모장에 입력 후, .reg으로 ...'),
Document(page_content='[메모장에 입력 후, 확장자를 ...')
이렇게 불러온 Document의 page_content를 서로 연결지어서 string으로 반환할 수 있게, 아래처럼 combine_contents 함수를 정의하자. 이 함수는 chain을 구성할 때 LCEL의 요소로 집어넣어 retriever로 불러온 Document List를 입력받고, text를 출력하는 Runnable로 사용한다.
# define combine_contents
def combine_contents(docs):
return "\n".join([d.page_content for d in docs])
Prompt
RAG를 수행하기 위한 프롬프트를 아래와 같이 작성하자. Pinecone vector store로부터 사용자 질문({question}
)과 유사한 2개의 chunk를 받을 수 있도록 {context}
를 플레이스 홀더로 작성하고, 이 안에서만 대답하도록 지시했다. 그리고, 답변 마지막에는 감사합니다!를 출력하게 했다.
# prompt
template = """아래 주어진 context에 기반해서 마지막에 주어진 질문에 답변해.
만약 답변을 모르겠으면, 모르겠다고 대답하고 없는 답변을 생성하지마.
최대 3문장까지만 사용하고, 최대한 간결하게 답변해.
항상 답변 마지막에는 "감사합니다!"라고 말해.
[context]: {context}
질문: {question}
답변:
"""
rag_prompt = PromptTemplate.from_template(template)
Chain
이제 앞선 Runnable을 조합해 다음과 같은 흐름을 갖는 rag chain을 구성하자.
- 프롬프트 구성
- 사용자의 질문과 유사한 2개의 chunk를 Pinecone vector store로부터 가져와
- 가져온 문서들을 text 형태로 combine하고 {context} 자리에 할당
- {question} 자리에 사용자의 질문을 할당
- 사용자의 질문과 유사한 2개의 chunk를 Pinecone vector store로부터 가져와
- LLM에게 prompt를 전달하고 답변을 생성
- 생성한 답변을 String으로 parsing
chain = ({"context": retriever | combine_contents, "question": RunnablePassthrough()}
| rag_prompt
| llm
| StrOutputParser())
print(chain.invoke("내가 원하는 대로 키매핑하려면 무슨 코드를 사용해야 돼?"))
여기서 RunnablePassthrough()
는 앞에서 전달받은 값을 그대로 전달하거나, 다른 key-value를 추가(assign
메서드 사용시)하여 다음 컴포넌트로 전달하는 역할을 한다. 따라서 rag_chain.invoke("hello")를 실행하면, "hello"가 RunnablePassthrough()의 위치에 그대로 들어가게 된다.
Reference
---
[Udemy] LangChain- Develop LLM powered applications with LangChain
[LangChain Advantages of LCEL] https://python.langchain.com/v0.1/docs/use_cases/question_answering/
'LLM > LangChain' 카테고리의 다른 글
[LangChain] RAG with Pinecone (0) | 2024.06.17 |
---|---|
[LangChain] Document Loaders (0) | 2024.06.14 |
[LangChain] AgentExecutor와 ReAct (0) | 2024.06.12 |