オープンソース【LangChain/OpenAI/Gradio】を使ったChatGPTライクな会話チャットボットの作成

# 概要
OpenAIのGPT3を使い、会話をするためのチャットを構築します。
最近話題のChatGPTのようなものを自分で作ってみたい人向けの記事となります。

最終制作物のイメージはこんな感じです。

![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687015/b0b3b186-c3f4-568b-6039-3f2894401240.png)

[コード全文はGithubにアップロード](https://github.com/KENTAROSZK/ChatBotByLangChain/blob/main/MyOriginalChatBotForGitHub.ipynb)しているので、そちらを見てください。

ChatGPTはナチュラルな会話ができるすごいAIですが、**弱点が2つ**あると筆者は認識しています。

①最近の出来事について対応できていない。ChatGPTの学習元データは2021年までのデータなので、2022年以降の出来事（例えば、ワールドカップなど）について聞いてもでたらめな回答をします。

②間違っていても、それっぽい回答をしてくるため、でたらめな情報なのかどうかの判断がつきづらい。

これらの問題を克服する方法の一つとして、**AIがわからない情報については、随時検索してもらう**、という方法があると思います。

今回実装したのは、その**必要とあらば随時検索を自動でやってくれるチャットボットを作成しました。**

今回は**とりあえず動かすことを目的に作成しているので、お遊びでいじれるおもちゃを作りたい方向けの記事**です。ChatGPTやLangChain、OpenAIの動作方法を正確に知りたい方向けではありません。


## 利用するライブラリ

使っているライブラリは下の3つです。
```
langchain
openai
gradio
```

### ライブラリの入れ方
全て``` pip ```で入れることができます。

``` 
pip install langchain
pip install openai
pip install gradio
```

## コードの中身

### ライブラリのインポート

``` python
# 必要ライブラリのimport
import os
# gradioのインポート
import gradio as gr

# langchain関連のパッケージインポート

# エージェント系のライブラリインポート
from langchain.agents import load_tools
from langchain.agents import initialize_agent

# OpenAI
from langchain.llms import OpenAI

# 会話用のメモリ
from langchain import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferMemory # 要約しながら会話をするとき
from langchain.chains.conversation.memory import ConversationalBufferWindowMemory # ある時点までの過去の会話を記憶しながら会話をするとき


# ChatGPTっぽく使うためにはプロンプトが必要なので
from langchain.agents  import ZeroShotAgent


from langchain.agents  import AgentExecutor


from langchain.chains  import LLMChain
```

### APIの設定


それぞれのAPIの取得の方法についてはここでは解説しませんが、参考ページを掲載しておきます。

- OpenAIのAPIキー：https://auto-worker.com/blog/?p=6988
- Googleカスタム検索の検索エンジンID：Googleカスタム検索の検索エンジンID
- Googleカスタム検索のAPIキー：https://qiita.com/zak_y/items/42ca0f1ea14f7046108c


``` python
os.environ["OPENAI_API_KEY"]  = "OpenAIのAPIキー"
os.environ["GOOGLE_CSE_ID"]   = "Googleカスタム検索の検索エンジンID" 
os.environ["GOOGLE_API_KEY"]  = "Googleカスタム検索のAPIキー"
```

### LLMのインスタンス化
LLM（LargeLanguageModelの略）をインスタンス化します。ここでOpenAIのGPT3の設定をしています。

``` python
llm = OpenAI(temperature=0)
```
パラメータとして設定している```temperature```ですが、0から2の範囲を取り、出力する文章のランダム性を指定します。

0であれば完全に確定的な文章を出力するので、毎回同じ文章を生成します。

2であれば完全にランダムに次の単語、次の単語と選ぶので、意味の通らない文章になります。

筆者の間隔では、0～0.5くらいでお好みに調整するのがちょうどよいかなと思います。

### 利用するツールの設定

今回使用しているLangChainというPythonパッケージには、Toolという概念があります。

Toolという概念については簡単に述べると、文字通り、LangChainという賢いパッケージが利用する道具といったイメージです。

正確に把握したい方は、[公式のページ](https://langchain.readthedocs.io/en/latest/modules/agents/key_concepts.html)か、[こちらの方の解説](https://note.com/npaka/n/n6b7a07e492f1)が非常にわかりやすくまとめてくれていますので、そちらを参照してください。


``` python
# 利用するツールの定義
# ※llm-mathは必須だから加えておく

tools = load_tools(["google-search", "llm-math"],
                   llm = llm)
```

```"google-search"```は今回、GPTが困ったらGoogle検索をかけてほしいので、設定しています。
```"llm-math"```は、必須なツールとのことなので設定しています。（必須なことの理由の推測ですが、会話の中で「〇〇を計算して」といった計算問題に対する回答をさせるのはGPTの機能やGoogle検索には限界があるので、計算専用のツールを事前に入れておくのだと思います。）

### プロンプトの生成&定義

AIと会話する際は、見た目上では、聞きたい内容を文字列として入力していますが、裏ではプロンプトという形に直したうえでAIに入力しています。ここでは、そのプロンプトを設定しています。

``` python
# プロンプトの生成&定義
prefix = """ Have a conversation with a human, answering the following questions as best you can. You have access to the following tools: """

suffix = """Begin!"
            {chat_history}
            Question: {input}
            {agent_scratchpad}
         """

prompt = ZeroShotAgent.create_prompt(tools,
                                     prefix = prefix,
                                     suffix = suffix,
                                     input_variables = ["input", "chat_history", "agent_scratchpad"])
```

注目していただきたいのが、```prefix```側で設定している最後の文章『You have access to the following tools:』です。ここの意味は「GPT3さん、あなたが使っても良いツールはこれこれですよ」ということをAIに指示している個所です。ここでのツールというのが先ほど設定したTool達です（上ではgoogle-searchとllm-mathというのを設定しましたね）。

suffixでは、```{chat_history}```、```Question: {input}```、```{agent_scratchpad}```という文字列がありますが、ここがプロンプトの要のような箇所です。

"chat_history"という文字列は実は変数で、会話ボットを動かしているときはここに、過去の会話履歴が格納されます。
"Question:{input}"の「input」の箇所に人間がうち込んだ文章が入ります。要するに、「ここが人間から受け取った質問/会話内容ですよ」と明示してあげてます。

"agent_scratchpad"はこのAIが必須で持っておくべき変数のことで、あまり気にしなくても良いパートです。お作法的に必要なものだという程度で認識する形で問題ないです。（正確なところを言えば、これがLangChainのエージェントが処理内容を内部で保持するための中間生成物だったりします。）

### チェーンの定義

LangChainではチェーンという概念で、LLM（LargeLanguageModel。ここではGPT3のこと）とプロンプトを紐づけて処理をする構造になっています。
チェーンについても詳細を知りたい方は[公式サイト](https://langchain.readthedocs.io/en/latest/modules/chains.html)または、[こちらの方の解説](https://note.com/npaka/n/n61ad59380a43)を参考にしてください。

``` python
# LLM Chainの定義
llm_chain = LLMChain(llm    = llm,
                     prompt = prompt)
```

### エージェントのインスタンス化

ここではエージェントを定義します。エージェントとは代理人という意味の言葉ですが、LangChainでは、この代理人が会話の時の動作を制御しています。例えば、GPT3では困るような内容であれば、エージェントがGoogle検索をするようにAIに指示を出すのです。

こちらも詳細を知りたい方は[公式サイト](https://langchain.readthedocs.io/en/latest/modules/agents.html)または[こちらの解説記事](https://note.com/npaka/n/n6b7a07e492f1)を参考にしていただければと思います。

``` python
# エージェントのインスタンス化

agent = ZeroShotAgent(llm_chain = llm_chain,
                      tools     = tools,
                      verbose   = True)
```

### メモリを定義

メモリは過去の会話の履歴を保持していく際に必要なものです。過去の文脈を覚えて回答してほしい場合はメモリを設定するようにしましょう。

``` python
# メモリの定義

memory = ConversationBufferMemory(memory_key="chat_history")
```

LangChainではメモリには2種類あります。

1. 直近の会話履歴だけ記憶しておく：```ConversationalBufferWindowMemory```
2. 過去の会話を要約して要約した内容だけ記憶しておく：```ConversationBufferMemory```

どちらを使うでも良いと思いますが、今回は"2"のメモリを採用しています。特に理由は無いのですが、人間の会話は1と2で言えば2に近い方式になっているので、"2"の方がより自然な会話になるかなという期待を込めている、という感じです（実際はどちらでも差は感じられないと思います。あくまで気持ちです）。

### すべてを結合

これまでに定義してきた各パーツをここで一つにまとめ上げてエージェントチェーンという形で定義づけしてあげます。

``` python
# 定義したエージェントやツール、メモリを使って、エージェントのチェーンを生成

agent_chain = AgentExecutor.from_agent_and_tools(agent   = agent,
                                                 tools   = tools,
                                                 verbose = True,
                                                 memory  = memory
                                                 )
```

### フロントエンド側の設定

フロントエンド側はすべて```gradio```というライブラリを使います。これを使うとチャットっぽい見た目を簡単に作れます。
チャットの会話の内容も、人間が入力したものに対して、ここまでで定義したエージェントを使って思考&検索した結果を答えてくれます。

``` python
def chat(message, history):
  history  = history or []
  response = agent_chain.run(input = message) # 人間が入力したテキストをmessageとして受け取って、responseを返す
  
  history.append((message, response))
  
  return history, history

# 見た目の設定
chatbot = gr.Chatbot().style(color_map=('green', 'pink'))

demo = gr.Interface(
    chat,
    ['text',  'state'],
    [chatbot, 'state'],
    allow_flagging = 'never',
)
```


### チャットボットの実行

この一行で実行です。
しばらくすると、自動でチャットボット画面が立ち上がってくると思いますので、いろいろ入力して遊んでみてください。

``` python
demo.launch()
```


## まとめと感想

- 今回はLangChainというPythonパッケージを使い、OpenAIのGPT3を会話エンジンとしてChatGPTもどきを作成しました。
- また、最近の話題については必要に応じてGoogle検索をかけるようにエージェントを設定することで、最新情報についても会話できるようになっており、その点はChatGPTを上回ったと言っても良いのではないでしょうか？
- こんな簡単に賢いチャットボットが作れてしまうのですごい時代になったものだと改めて思いました。