# Link Method - Model에 Memory을 연결할 Chain은 2가지 방법으로 만들 수 있다. - `off-the-shelf` Chain : LLM에서 제공하는 보편화된 방법 - Manual Chain : [[LCEL(LangChain Expression Language)]]으로 수동으로 연결하는 방법. - [[Memory Modules]]은 **String 형태**로 저장하거나 **Message형태**로 저장하기 때문에 어떠한 형태로 저장하는지에 따라 연결하는 방법이 달라진다. # Off-The-Shelf Chain - Memory에 `Load`와 `Save` function을 따로 만들고 연결할 필요가 없기 때문에 간편하게 만들 수 있다. - 특정 목적을 위해 특수한 Chain을 만들고 싶다면 이 방법을 사용하긴 어렵다. - `from langchain.chains import LLMChain`을 통해 LLMChain(`off-the-shelf Chain`) Module을 불러올 수 있다. - LLMChain에서 알아야 할 Parameter는 총 4개이다. - `llm-llm` : llm model을 연결한다. - `memory=memory` : memory를 연결한다. - `prompt=PromptTemplate.from_template(template)` : prompt를 연결한다. - `verbose=True` : prompt formatting이나 result 값은 결과를 보기 쉽게 나태내어준다. **(디버깅 역할)** ### Memory를 String 형태로 Prompt에게 전달하는 방법 - [[Prompt]]을 만들 때, Memory가 String 형태로 저장되기 때문에 [[Prompt#PromptTemplate|PromptTemplate]]으로 만들어야 한다. - 만들어진 Prompt와 memory를 연결하기 위해 `memory_key`와 template의 `{value}`를 서로 같게 하여야 한다. ```python from langchain.prompts import PromptTemplate from langchain.chains import LLMChain memory = ConversationSummaryBufferMemory(     llm=llm,     max_token_limit=80,     memory_key="chat_history", ) template = """     You are a helpful AI talking to a human.         {chat_history}         Human:{question}     You: """ chain = LLMChain(     llm=llm,     memory=memory,     prompt=PromptTemplate.from_template(template),     verbose=True, ) chain.predict(question="My name is KKY") ``` ### Memory를 Message 형태로 Prompt에게 전달하는 방법 - Memory를 Message형태로 Prompt에게 전달하기 위해서는 [[Prompt]] 자체도 [[Prompt#ChatPromptTemplate|ChatPromptTemplate]]로 만들어야 한다. - Memory 또한 Message 형태로 저장되게 하기 위해서 `return_messages=True`을 해야 한다. - Prompt를 만들 때 Memory가 만들어낸 message가 누구(system, ai, human)인지, 얼마만큼 인지를 모르기 때문에 `from langchain.prompts import MessagesPlaceholder`을 사용해야 한다, - `MessagesPlaceholder` 는 누가 보냈는지 **알 수 없는, 예측하기 어려운** 메세지의 양과 **제한 없는 양**의 메시지를 **Template에 넣기 위해 만들어진 Module**이다. - `MessagesPlaceholder` Parameter에 `variable_name="{memory_key}"`로 설정하여 Prompt와 Memory 값을 연결 시켜 줄 수 있다. ```python from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder memory = ConversationSummaryBufferMemory(     llm=llm,     max_token_limit=80,     memory_key="chat_history",     return_messages=True, ) prompt = ChatPromptTemplate.from_messages(     [         ("system", "You are a helpful AI talking to a human"),         MessagesPlaceholder(variable_name="chat_history"),         ("human", "{question}"),     ] ) chain = LLMChain(     llm=llm,     memory=memory,     prompt=prompt,     verbose=True, ) chain.predict(question="My name is KKY") ``` # Manual Chain - 수동으로 Chain을 만들 경우에는 [[Memory Modules]]의 `load`와 `save` functoin과 chain을 연결해주어야 한다. - `off-the-shelf` Chain보다 복잡하지만, 원하는대로 수정이 용이하다는 장점이 있다. ### Connect Load Function to Chain - `load_memory_variables({})["value"]`의 value값과 `memory_key`이 값이 같아야 한다. - `memory_key` 값의 Default가 **history**이기 때문에 따로 설정하지 않을 시, `load_memory_variables` 값을 `history`로 설정하여야 한다. - `invoke`과정에서 memory의 정보를 prompt에게 넘겨준다. - `(과정 요약 : memory.load가 chat_history value가 되고 이것이 prompt에게 전달되어MessagesPlaceholder에 들어감)` ```python memory = ConversationSummaryBufferMemory(     llm=llm,     max_token_limit=80,     memory_key="chat_history",     return_messages=True, ) prompt = ChatPromptTemplate.from_messages(     [         ("system", "You are a helpful AI talking to a human"),         MessagesPlaceholder(variable_name="chat_history"),         ("human", "{question}"),     ] ) chain = prompt | llm chain.invoke({     "chat_history": memory.load_memory_variables({})["chat_history"],     "question": "My name is KKY" }) ``` - 하지만 `invoke`에 `memory load` function를 넣게 되면 사용자가 질문할 때마다 memory 또한 load 해야 하는 상황이 발생한다. - 이를 `from langchain.schema.runnable import RunnablePassthrough`를 사용하여 해결한다. - `RunnablePassthrough`을 통해 **항상** Prompt가 format되기 전에 특정 함수를 실행시켜준다. - 따라서 `RunnablePassthrough.assign()`을 사용해 `memory.load_memory_variables({})["chat_history"]`이 부분을 함수(`def load_memory(_):`)로 만들어 Chain과 연결시켜주면 된다. - 단 여기서 주의해야 할 것이 **Chain과 연결된 모든 component는 input과 output이 존재해야 한다.** 따라서 `RunnablePassthrough`을 통해 실행되는 함수 또한 input 하나를 받을 공간을 만들어야 한다. ```python from langchain.schema.runnable import RunnablePassthrough def load_memory(_):     return memory.load_memory_variables({})["history"]     chain = RunnablePassthrough.assign(history=load_memory) | prompt | llm chain.invoke(     {         "question": "My name is KKY",     } ) ``` ### Connect Save Function to Chain - `invoke`를 통해 만들어진 결과 memory save function에 넣어주면 된다. - 단 이러한 저장 과정은 `invoke`를 할 때마다 **필수적으로** 행해지기 때문에 `invoke` 동시에 memory에 저장하는 함수(`def invoke_chain(question)`)를 만들어 호출하는게 편리하다. ```python def invoke_chain(question):     result = chain.invoke(         {             "question": question,         }     )     memory.save_context({"input": question}, {"output": result.content})     print(result) invoke_chain("Hi My name is KKY") invoke_chain("What is my name?") ```