Amazon BedrockとStreamlitで簡単チャットアプリを作成する方法

Bedrockの素敵なところは、LangChainを始めとするOSSのエコシステムのおかげて**OpenAIからの置き換えが簡単にできる**ところだと思います。チャットアプリもOpenAI版をちょっと変えるだけで実現できました。

**これまでOpenAIで検証してきた過去の資産を活かしたままBedrock化ができるって素敵**

## Streamlitによるチャット

こちらのサンプルコードをもとにします。

https://python.langchain.com/docs/integrations/memory/streamlit_chat_message_history

まず、OpenAI版のコードがこちらとなります。

```python
from langchain.chains import LLMChain
from langchain.llms import OpenAI
from langchain.memory import ConversationBufferMemory
from langchain.memory.chat_message_histories import StreamlitChatMessageHistory
from langchain.prompts import PromptTemplate

import streamlit as st

# Optionally, specify your own session_state key for storing messages
msgs = StreamlitChatMessageHistory(key="chat_messages")

memory = ConversationBufferMemory(memory_key="history", chat_memory=msgs)
if len(msgs.messages) == 0:
    msgs.add_ai_message("How can I help you?")


template = """You are an AI chatbot having a conversation with a human.

{history}
Human: {human_input}
AI: """
prompt = PromptTemplate(input_variables=["history", "human_input"], template=template)

# Add the memory to an LLMChain as usual
llm_chain = LLMChain(llm=OpenAI(), prompt=prompt, memory=memory)

for msg in msgs.messages:
    st.chat_message(msg.type).write(msg.content)

if prompt := st.chat_input():
    st.chat_message("human").write(prompt)

    # As usual, new messages are added to StreamlitChatMessageHistory when the Chain is called.
    response = llm_chain.run(prompt)
    st.chat_message("ai").write(response)
```

![OpenAI.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/d57c7fa3-f7ee-f73a-ddcf-11c8de56af7c.gif)

**これだけでチャットができる事自体がすごい**ですが、これをBedrock版に変えます。

## Bedrock化

変更点はLLMsを`OpenAI`から`Bedrock`に変更し、プロンプトを少し調整するだけです。

> 💡テンプレートは元のサンプルを活かしつつ、[Amazon Bedrock Workshop](https://github.com/aws-samples/amazon-bedrock-workshop/blob/main/04_Chatbot/00_Chatbot_Claude.ipynb)を参考にしました。

```diff_python
  from langchain.chains import LLMChain
- from langchain.llms import OpenAI
+ from langchain.llms import Bedrock
  from langchain.memory import ConversationBufferMemory
  from langchain.memory.chat_message_histories import StreamlitChatMessageHistory
  from langchain.prompts import PromptTemplate

  import streamlit as st

  # Optionally, specify your own session_state key for storing messages
  msgs = StreamlitChatMessageHistory(key="chat_messages")

  memory = ConversationBufferMemory(memory_key="history", chat_memory=msgs)
  if len(msgs.messages) == 0:
      msgs.add_ai_message("How can I help you?")

- template = """You are an AI chatbot having a conversation with a human.
- 
- {history}
- Human: {human_input}
- AI: """
+ template = """You are an AI chatbot having a conversation with a human.
+ You answer always in japanese.
+
+ Current conversation:
+ <conversation_history>
+ {history}
+ </conversation_history>
+
+ Here is the human's next reply:
+ <human_reply>
+ {human_input}
+ </human_reply>
  """
  prompt = PromptTemplate(input_variables=["history", "human_input"], template=template)

  # Add the memory to an LLMChain as usual
- llm_chain = LLMChain(llm=OpenAI(), prompt=prompt, memory=memory)
+ llm_chain = LLMChain(llm=Bedrock(model_id='anthropic.claude-instant-v1'), prompt=prompt, memory=memory)

  for msg in msgs.messages:
      st.chat_message(msg.type).write(msg.content)

  if prompt := st.chat_input():
      st.chat_message("human").write(prompt)

      # As usual, new messages are added to StreamlitChatMessageHistory when the Chain is called.
      response = llm_chain.run(prompt)
      st.chat_message("ai").write(response)
```

![Bedrock.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/6c67a8bd-da5e-784c-bc28-a14eed008d25.gif)

**超簡単**


## チャット履歴をDynamoDBに保持させる

上記サンプルでは、`StreamlitChatMessageHistory`を使用しており、画面のリロードでチャット履歴が消えてしまいます。

![image.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/fd06d3b4-81a1-18f2-7f4c-b973f79d4ebf.gif)

チャット履歴をDynamoDBに永続化し、画面リロード時にも履歴が残るように対応します。

### DynamoDBにテーブルを作成

パーティションキーを`SessionId`とし、文字列型で定義します。
テーブル名は任意で指定します。（ここでは`langchain-bedrock`）

## プログラムの修正

`StreamlitChatMessageHistory`を`DynamoDBChatMessageHistory`に変更します。

```diff_python
  from langchain.chains import LLMChain
  from langchain.llms import Bedrock
  from langchain.memory import ConversationBufferMemory
- from langchain.memory.chat_message_histories import StreamlitChatMessageHistory
+ from langchain.memory.chat_message_histories import DynamoDBChatMessageHistory
  from langchain.prompts import PromptTemplate

  import streamlit as st

  # Optionally, specify your own session_state key for storing messages
- msgs = StreamlitChatMessageHistory(key="chat_messages")
+ msgs = DynamoDBChatMessageHistory(table_name="langchain-bedrock", session_id="0")

  memory = ConversationBufferMemory(memory_key="history", chat_memory=msgs)
  if len(msgs.messages) == 0:
      msgs.add_ai_message("How can I help you?")


  template = """You are an AI chatbot having a conversation with a human.
  You answer always in japanese.

  Current conversation:
  <conversation_history>
  {history}
  </conversation_history>

  Here is the human's next reply:
  <human_reply>
  {human_input}
  </human_reply>

  """
  prompt = PromptTemplate(input_variables=["history", "human_input"], template=template)

  # Add the memory to an LLMChain as usual
  llm_chain = LLMChain(llm=Bedrock(model_id='anthropic.claude-instant-v1'), prompt=prompt, memory=memory)

  for msg in msgs.messages:
      st.chat_message(msg.type).write(msg.content)

  if prompt := st.chat_input():
      st.chat_message("human").write(prompt)

      # As usual, new messages are added to StreamlitChatMessageHistory when the Chain is called.
      response = llm_chain.run(prompt)
      st.chat_message("ai").write(response)
```

**これだけ？！**

![image.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/7d9c7063-ca1d-73d5-1b73-69e51dc627ca.gif)


履歴のセッションを分ける場合は、`DynamoDBChatMessageHistory`の`session_id`パラメーターを個別に指定するだけでOKです。


## LINEボット化

Lambdaで動くようにカスタマイズし、LINEボットにするのも簡単です。

参考

https://github.com/moritalous/bedrock-line-chat/tree/main

https://qiita.com/moritalous/items/6e0864d724d04d4ecf22

## Slackボット化

チャットではなくて画像生成ですが、こちらと今回のコードサンプルを組み合わせれば作成できると思います。

https://github.com/moritalous/bedrock-slack-sdxl