03-1-langchain-conversational-memory

2024. 1. 16. 18:15langchain

03-langchain-conversational-memory 오류를 수정한 글입니다.

 

대화 기억

대화 기억은 챗봇이 채팅과 같은 방식으로 우리의 질문에 응답할 수 있는 방법입니다. 이는 일관된 대화를 가능하게 하며, 이것이 없으면 모든 쿼리는 과거 상호 작용을 고려하지 않고 완전히 독립적인 입력으로 처리됩니다.

메모리를 사용하면 "에이전트"가 사용자와의 이전 상호 작용을 기억할 수 있습니다. 기본적으로 에이전트는 상태 비저장입니다. 즉, 들어오는 각 쿼리는 다른 상호 작용과 독립적으로 처리됩니다. 상태 비저장 에이전트에 존재하는 유일한 것은 현재 입력이며 다른 것은 아무것도 없습니다.

In [ ]:
!python3 -m venv langchain
!source langchain/bin/activate
!pip install langchain openai tiktoken
In [ ]:
import inspect

from getpass import getpass
from langchain import OpenAI
from langchain.chains import LLMChain, ConversationChain
from langchain.chains.conversation.memory import (ConversationBufferMemory, 
                                                  ConversationSummaryMemory, 
                                                  ConversationBufferWindowMemory,
                                                  ConversationKGMemory)
from langchain.callbacks import get_openai_callback
import tiktoken

메시지가 표시되면 openai api 키를 입력하기만 하면 됩니다.

In [ ]:
OPENAI_API_KEY = getpass()
In [ ]:
llm = OpenAI(
    temperature=0, 
    openai_api_key=OPENAI_API_KEY,
    model_name='gpt-3.5-turbo'  # 20240104 날짜에 'gpt-3.5-turbo'가 'gpt-3.5-turbo-instruct' 으로 바뀜
)

나중에 count_tokens 유틸리티 함수를 활용해 보겠습니다. 이를 통해 각 호출에 사용되는 토큰 수를 계산할 수 있습니다.

In [ ]:
def count_tokens(chain, query):             
   with get_openai_callback() as cb:
       result = chain.run(query)
       print(f'Spent a total of {cb.total_tokens} tokens')

   return result

이제 대화 기억에 대해 살펴보겠습니다.

기억이란 무엇입니까?

정의: 기억은 사용자와의 이전 상호작용을 기억하는 에이전트의 능력입니다(챗봇을 생각해 보세요)

기억의 공식적인 정의는 다음과 같습니다.

기본적으로 체인과 에이전트는 상태 비저장입니다. 즉, 들어오는 각 쿼리를 독립적으로 처리합니다. 일부 애플리케이션(챗봇이 좋은 예임)에서는 단기적으로나 장기적으로 이전 상호 작용을 기억하는 것이 매우 중요합니다. 바로 이를 위해 '메모리'라는 개념이 존재합니다.
라이브러리가 제공하는 다양한 메모리 모듈을 자세히 살펴보기 전에 이러한 예에 사용할 체인인 ConversationChain.을 소개하겠습니다.

In [ ]:
conversation = ConversationChain(
    llm=llm, 
)
In [ ]:
print(conversation.prompt.template)

이 체인의 메시지는 사용자와 채팅하고 진실된 답변을 제공하도록 지시하는 것입니다.

In [ ]:
print(inspect.getsource(conversation._call), inspect.getsource(conversation.apply))

여기서는 정말 마법 같은 일이 벌어지지 않고 LLM을 통과하는 간단한 과정만 거치면 됩니다. 실제로 이 체인은 수정 없이 LLMChain에서 직접 다음 메소드를 상속합니다.

In [ ]:
print(inspect.getsource(LLMChain._call), inspect.getsource(LLMChain.apply))

따라서 기본적으로 이 체인은 사용자의 입력과 대화 기록을 결합하여 의미 있는(그리고 진실되기를 바라는) 응답을 생성합니다.

이제 우리가 사용할 체인의 기본 사항을 이해했으므로 메모리에 들어갈 수 있습니다.

메모리 유형

메모리 유형 #1: ConversationBufferMemory

ConversationBufferMemory은 이름에서 알 수 있듯이 프롬프트에서 컨텍스트의 일부로 이전 대화에서 발췌한 버퍼를 유지합니다.

주요 기능: ConversationBufferMemory는 이전 대화 부분을 완전히 수정되지 않은 원시 형식으로 유지합니다.

In [ ]:
import dotenv

env_vars = dotenv.dotenv_values()

conversation_buf = ConversationChain(
    llm =llm,
    memory= ConversationBufferMemory()
)

ConversationBufferMemory 사용자 프롬프트를 전달합니다.

In [ ]:
conversation_buf("Good morning AI!")

이 호출은 총 85 토큰을 사용했지만 위에서는 이를 확인할 수 없습니다. 사용되는 토큰의 수를 계산하려면 대화 체인 개체와 앞서 정의한 count_tokens 함수를 통해 입력하려는 메시지를 전달하면 됩니다.

In [ ]:
print(conversation_buf.memory.buffer)
In [ ]:
count_tokens(
    conversation_buf, 
    "My interest here is to explore the potential of integrating Large Language Models with external knowledge"
)
In [ ]:
count_tokens(
    conversation_buf,
    "I just want to analyze the different possibilities. What can you think of?"
)
In [ ]:
count_tokens(
    conversation_buf, 
    "Which data source types could be used to give context to the model?"
)
In [ ]:
count_tokens(
    conversation_buf, 
    "What is my aim again?"
)

저희 LLM ConversationBufferMemory는 대화의 이전 상호작용을 명확하게 기억할 수 있습니다. LLM이 이전 대화를 어떻게 저장하고 있는지 자세히 살펴보겠습니다. 체인에 있는.memory의 buffer 속성에 액세스하여 이를 수행할 수 있습니다.

In [ ]:
print(conversation_buf.memory.buffer)

좋습니다. 대화의 모든 부분이 명시적으로 녹음되어 프롬프트를 통해 LLM으로 전송되었습니다.

Memory type #2: ConversationSummaryMemory

ConversationBufferMemory의 문제는 대화가 진행됨에 따라 컨텍스트 기록의 토큰 수가 합산된다는 것입니다. 처리하기엔 너무 큰 프롬프트를 가진 LLM을 최대한 활용해야 하기 때문에 이는 문제가 됩니다.

입력ConversationSummaryMemory.

다시 말하지만, 무슨 일이 일어나고 있는지 이름에서 유추할 수 있습니다. 이전 대화 내용의 요약을 기록으로 유지하겠습니다. 이를 어떻게 요약할 것인가? LLM이 해결해 드립니다.

주요 기능: 대화 요약 메모리는 이전 대화를 요약된 형식으로 유지하며 LLM이 요약을 수행합니다.

이 경우 요약 기능을 강화하기 위해 llm을 메모리 생성자에 보내야 합니다.

In [ ]:
conversation_sum = ConversationChain(
    llm=llm, 
    memory=ConversationSummaryMemory(llm=llm)
)

LLM이 있으면 항상 프롬프트가 표시됩니다. 대화 요약 메모리 내부에서 무슨 일이 일어나고 있는지 살펴보겠습니다.

In [ ]:
print(conversation_sum.memory.prompt.template)

따라서 각각의 새로운 상호 작용은 요약되어 체인의 메모리로 실행 중인 요약에 추가됩니다. 이것이 실제로 어떻게 작동하는지 살펴보겠습니다!

주요 기능: 대화 요약 메모리는 이전 대화를 요약된 형식으로 유지하며 LLM이 요약을 수행합니다.

이 경우 요약 기능을 강화하기 위해 llm을 메모리 생성자에 보내야 합니다.

In [ ]:
# without count_tokens we'd call `conversation_sum("Good morning AI!")`
# but let's keep track of our tokens:
count_tokens(
    conversation_sum, 
    "Good morning AI!"
)
In [ ]:
count_tokens(
    conversation_sum, 
    "My interest here is to explore the potential of integrating Large Language Models with external knowledge"
)
In [ ]:
count_tokens(
    conversation_sum, 
    "I just want to analyze the different possibilities. What can you think of?"
)
In [ ]:
count_tokens(
    conversation_sum, 
    "Which data source types could be used to give context to the model?"
)
In [ ]:
count_tokens(
    conversation_sum, 
    "What is my aim again?"
)
In [ ]:
print(conversation_sum.memory.buffer)

궁금할 수도 있습니다. 여기의 각 호출에서 집계 토큰 수가 버퍼 예제보다 크다면 왜 이런 유형의 메모리를 사용해야 할까요? 글쎄요, 버퍼를 확인하면 대화의 각 인스턴스에서 더 많은 토큰을 사용하더라도 최종 기록이 더 짧다는 것을 알게 될 것입니다. 이를 통해 프롬프트의 최대 길이에 도달하기 전에 더 많은 상호 작용을 할 수 있게 되어 더 긴 대화에 대해 챗봇이 더욱 강력해집니다.

다음과 같이 tiktoken 토크나이저를 사용하여 OpenAI를 호출하지 않고 사용 중인 토큰 수를 계산할 수 있습니다.

In [ ]:
# initialize tokenizer
tokenizer = tiktoken.encoding_for_model('text-davinci-003')

# show number of tokens for the memory used by each memory type
print(
    f'Buffer memory conversation length: {len(tokenizer.encode(conversation_buf.memory.buffer))}\n'
    f'Summary memory conversation length: {len(tokenizer.encode(conversation_sum.memory.buffer))}'
)

메모리 유형 #3: ConversationBufferWindowMemory

마지막 상호 작용 중 일부를 기억에 유지하지만 의도적으로 가장 오래된 상호 작용을 삭제하는 것입니다.
주요 기능: 대화 버퍼 창 메모리는 대화의 최신 부분을 원시 형식으로 유지합니다

In [ ]:
conversation_bufw = ConversationChain(
    llm=llm, 
    memory=ConversationBufferWindowMemory(k=1)
)
In [ ]:
count_tokens(
    conversation_bufw, 
    "Good morning AI!"
)
In [ ]:
count_tokens(
    conversation_bufw, 
    "My interest here is to explore the potential of integrating Large Language Models with external knowledge"
)
In [ ]:
count_tokens(
    conversation_bufw, 
    "I just want to analyze the different possibilities. What can you think of?"
)
In [ ]:
count_tokens(
    conversation_bufw, 
    "Which data source types could be used to give context to the model?"
)
In [ ]:
count_tokens(
    conversation_bufw, 
    "What is my aim again?"
)

이 메모리 유형에서는 버퍼가 먼저 이 메서드를 통해 전달되어 나중에 llm으로 전송되므로 여기서 특수 메서드에 액세스해야 합니다.

In [ ]:
bufw_history = conversation_bufw.memory.load_memory_variables(
    inputs=[]
)['history']
In [ ]:
print(bufw_history)

플러스 측면에서 버퍼 메모리에 비해 대화 길이가 단축된다는 것입니다.

In [ ]:
print(
    f'Buffer memory conversation length: {len(tokenizer.encode(conversation_buf.memory.buffer))}\n'
    f'Summary memory conversation length: {len(tokenizer.encode(conversation_sum.memory.buffer))}\n'
    f'Buffer window memory conversation length: {len(tokenizer.encode(bufw_history))}'
)

더 많은 메모리 유형!

우리가 이미 메모리를 이해하고 있다는 점을 고려하여 여기서는 몇 가지 메모리 유형을 더 제시할 것이며 간략한 설명만으로도 기본 기능을 이해하는 데 충분할 것입니다.

ConversationSummaryBufferMemory

주요 기능: 대화 요약 메모리는 가장 초기 대화 부분의 요약을 유지하는 동시에 최신 상호작용에 대한 원시 기억을 유지합니다.

onversation knowledge graph memory

이것은 최근 도입된 매우 멋진 메모리 유형입니다. 이는 서로 다른 개체를 인식하고 이를 술어와 쌍으로 연결하여 (주어, 술어, 목적어) 삼중항을 생성하는 지식 그래프 개념을 기반으로 합니다. . 이를 통해 많은 정보를 모델에 컨텍스트로 제공할 수 있는 매우 중요한 조각으로 압축할 수 있습니다.
주요 기능: 대화 지식 그래프 메모리는 의미론적 관계와 함께 상호작용에서 언급된 모든 항목에 대한 지식 그래프를 유지합니다.

In [ ]:
# you may need to install this library
!pip install -qU networkx
In [ ]:
conversation_kg = ConversationChain(
    llm=llm, 
    memory=ConversationKGMemory(llm=llm)
)
In [ ]:
count_tokens(
    conversation_kg, 
    "My name is human and I like mangoes!"
)

기억은 지금까지 배운 모든 것에 대한 지식 그래프를 유지합니다.

In [ ]:
conversation_kg.memory.kg.get_triples()
ConversationEntityMemory

주요 기능: 대화 항목 메모리는 특정 속성과 함께 언급된 주요 항목을 기억합니다.

기억으로 또 무엇을 할 수 있나요?

langchain의 메모리를 사용하여 할 수 있는 몇 가지 멋진 작업이 있습니다. 우리는 다음을 수행할 수 있습니다.

  • 자체 맞춤형 메모리 모듈 구현
  • 동일한 체인에 여러 메모리 모듈 사용
  • 에이전트를 메모리 및 기타 도구와 결합
In [ ]:
출처 : https://github.com/pinecone-io/examples/blob/master/learn/generation/langchain/handbook/03-langchain-conversational-memory.ipynb

'langchain' 카테고리의 다른 글

08-langchain-retrieval-agent  (0) 2024.01.18
07-langchain-tools  (0) 2024.01.17
09-langchain-streaming  (0) 2024.01.15
06-langchain-agents  (1) 2024.01.14
04-langchain-chat  (0) 2024.01.13