# 08. Add memory to LCEL Chain

## LCEL (Remember Conversation): Menambahkan memory

Menunjukkan cara menambahkan memori ke chain sembarang. Anda dapat menggunakan kelas memory saat ini, tetapi Anda harus menambahkannya secara manual

```python
from dotenv import load_dotenv

load_dotenv()
```

```python
from operator import itemgetter
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_openai import ChatOpenAI

# Menginisialisasi model ChatOpenAI.
model = ChatOpenAI()

# Membuat prompt percakapan. Prompt ini mencakup pesan sistem, riwayat percakapan sebelumnya, dan input pengguna.
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "Anda adalah chatbot yang membantu"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{input}"),
    ]
)
```

Buat `ConversationBufferMemory`, memori untuk menyimpan dialog, dan setel parameter `return_messages` ke `True`, sehingga instance yang dibuat akan mengembalikan pesan.

Atur `memory_key`: kunci yang nantinya akan ditetapkan ke `prompt` dalam chain. Anda dapat mengubahnya dan menggunakannya

```python
# Buat conversation buffer memory, dan aktifkan fitur pengembalian pesan.
memory = ConversationBufferMemory(return_messages=True, memory_key="chat_history")
```

Periksa riwayat obrolan yang disimpan. Karena Anda belum menyimpannya, riwayat obrolan masih kosong.

```python
# Menginisialisasi variabel-variabel memori ke dalam kamus kosong.
memory.load_memory_variables({})
```

```
{'chat_history': []}
```

Gunakan `RunnablePasssthrough.assign` untuk menetapkan hasil fungsi `memory.load_memory_variables` ke variabel `chat_history`, dan mengekstrak nilai yang sesuai dengan kunci `chat_history` dari hasil ini.

```python
runnable = RunnablePassthrough.assign(
    chat_history = RunnableLambda(memory.load_memory_variables)
    | itemgetter("chat_history") # Masukkan yang sama dengan memory_key.
)
```

```python
runnable.invoke({"input": "hi"})
```

```
{'input': 'hi', 'chat_history': []}
```

```python
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "Anda adalah chatbot yang membantu"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{input}"),
    ]
)
```

`runnable` untuk memulai dialog pertama

* `input`: Dialog masukan pengguna dimasukkan.
* `chat_history`: Riwayat obrolan diteruskan.

```python
runnable.invoke({"input": "hi!"})
```

```
{'input': 'hi', 'chat_history': []}
```

```python
chain = runnable | prompt | model
```

Lakukan percakapan pertama Anda.

```python
# Menggunakan metode invoke dari objek chain untuk menghasilkan respons terhadap input.
response = chain.invoke({"input": "Senang bertemu dengan Anda. Nama saya Teddy."})
print(response.content)  # Mencetak respons yang dihasilkan.
```

```
Senang bertemu dengan Anda juga, Teddy! Ada yang bisa saya bantu hari ini?
```

```python
memory.load_memory_variables({})
```

```
{'chat_history': []}
```

Fungsi `memory.save_context` bertanggung jawab untuk menyimpan data input (`input`) dan konten respons (`response.content`) di dalam memori. Hal ini dapat digunakan untuk merekam kondisi saat ini selama proses pelatihan model AI, atau untuk melacak permintaan pengguna dan respons sistem.

```python
# Menyimpan data input dan konten respons ke dalam memori.
memory.save_context(
    {"human": "Senang bertemu dengan Anda. Nama saya Teddy."}, {"ai": response.content}
)

# Mencetak riwayat percakapan yang disimpan.
memory.load_memory_variables({})
```

{% code overflow="wrap" %}
```
{'chat_history': [HumanMessage(content='Senang bertemu dengan Anda. Nama saya Teddy.'), AIMessage(content='Senang bertemu dengan Anda juga, Teddy! Ada yang bisa saya bantu hari ini?')]}
```
{% endcode %}

Tanyakan lebih lanjut apakah masih mengingat nama yang diberikan

```python
 # Menanyakan apakah nama masih diingat.
  response = chain.invoke({"input": "Apakah Anda ingat apa nama saya?"})
  # Mencetak jawaban.
  print(response.content)
```

```
Ya, tentu saja! Nama Anda adalah Teddy. Apa yang bisa saya bantu untuk Anda hari ini?
```

## Contoh penerapan CoversationChain khusus

```python
from operator import itemgetter
from langchain.memory import ConversationBufferMemory, ConversationSummaryMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableLambda, RunnablePassthrough, Runnable
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

# Menginisialisasi model ChatOpenAI.
llm = ChatOpenAI(model_name="gpt-4o", temperature=0)

# Membuat prompt percakapan. Prompt ini mencakup pesan sistem, riwayat percakapan sebelumnya, dan input pengguna.
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "Anda adalah chatbot yang membantu"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{input}"),
    ]
)

# Membuat memori buffer percakapan, dan mengaktifkan fitur pengembalian pesan.
memory = ConversationBufferMemory(return_messages=True, memory_key="chat_history")
```

```python
class MyConversationChain(Runnable):

    def __init__(self, llm, prompt, memory, input_key="input"):

        self.prompt = prompt
        self.memory = memory
        self.input_key = input_key

        self.chain = (
            RunnablePassthrough.assign(
                chat_history=RunnableLambda(self.memory.load_memory_variables)
                | itemgetter(memory.memory_key)  # Sesuaikan dengan memory_key.
            )
            | prompt
            | llm
            | StrOutputParser()
        )

    def invoke(self, query, configs=None, **kwargs):
        answer = self.chain.invoke({self.input_key: query})
        self.memory.save_context(inputs={"human": query}, outputs={"ai": answer})
        return answer
```

```python
# Menginisialisasi model ChatOpenAI.
llm = ChatOpenAI(model_name="gpt-4o", temperature=0)

# Membuat prompt percakapan. Prompt ini mencakup pesan sistem, riwayat percakapan sebelumnya, dan input pengguna.
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "Anda adalah chatbot yang membantu"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{input}"),
    ]
)

# Membuat memori buffer percakapan, dan mengaktifkan fitur pengembalian pesan.
memory = ConversationBufferMemory(return_messages=True, memory_key="chat_history")

# Jika ingin mengganti dengan memori ringkasan
# memory = ConversationSummaryMemory(
#     llm=llm, return_messages=True, memory_key="chat_history"
# )

conversation_chain = MyConversationChain(llm, prompt, memory)
```

```python
conversation_chain.invoke("Halo? Senang bertemu dengan Anda. Nama saya Teddy.")
```

```
Halo, Teddy! Senang bertemu dengan Anda juga. Ada yang bisa saya bantu hari ini?
```

```python
conversation_chain.invoke("Apa nama saya tadi?")
```

```
Nama Anda Teddy. Ada yang bisa saya bantu, Teddy?
```

```python
conversation_chain.invoke("Tolong jawab hanya dalam bahasa Inggris mulai sekarang, oke?")
```

```
Sure, I can do that. How can I assist you today?
```

```python
conversation_chain.invoke("Bisakah Anda sebutkan nama saya sekali lagi?")
```

```
Your name is Teddy.
```

```python
conversation_chain.memory.load_memory_variables({})["chat_history"]
```

{% code overflow="wrap" %}
```
[HumanMessage(content='Halo? Senang bertemu dengan Anda. Nama saya Teddy.'), AIMessage(content='Halo, Teddy! Senang bertemu dengan Anda juga. Ada yang bisa saya bantu hari ini?'), HumanMessage(content='Apa nama saya tadi?'), AIMessage(content='Nama Anda Teddy. Ada yang bisa saya bantu, Teddy?'), HumanMessage(content='Tolong jawab hanya dalam bahasa Inggris mulai sekarang, oke?'), AIMessage(content='Sure, I can do that. How can I assist you today?'), HumanMessage(content='Bisakah Anda sebutkan nama saya sekali lagi?'), AIMessage(content='Your name is Teddy.')]
```
{% endcode %}
