LangChainのモジュール「Indexes」

大嶋勇樹氏:次はIndexesとMemoryを見ていきます。質問をもらっていますが、続きを見てから回答できればと思います。

Indexesですが、まずモチベーションとして、GPT-4やGPT-3.5は、2021年9月までの公になっているパブリックな情報しか持っていません。

よくある例として、もっと新しい情報を知りたい。例えばLangChainの情報もそうだし、プライベートな情報を使わせたいということは多いと思います。

そのために、プロンプトに文脈、contextを入れる方法が考えられます。最初のほうにプロンプトエンジニアリングの例でも出しましたが、「文脈を踏まえて回答してください。文脈はこれです」と言ってLangChainのドキュメントのページの内容を入れて、質問は「LangChainとは?」みたいなことをするという手が考えられます。

ただ、GPTのAPIはトークン数の最大値という制限があったり、トークン数が多いほど料金が高くなる性質があるので、なんでもかんでも文脈に入れることはできません。

そこで、Vector Storeを活用する手法があります。文書をあらかじめベクトル化してVector Storeに保存しておきます。その上で入力に関係しそうな文章をVector Storeから検索して、contextに含めて質問します。このプロンプトのテンプレートは、LangChainのソースコードにもあるようなものです。

「Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer」、「続きの内容を踏まえて最後にある質問に回答にしてください、わからない時はちゃんとわからないと言ってね」という意味ですね。

そしてcontextと質問を埋め込んで、「Helpful Answer:」、アンサーを出してもらいます。「Answer:」、「続きを出してね」という意味のプロンプトですね。こういうことをします。

このベクトル化にOpenAIのEmbeddingsというAPIが使われる感じです。ベクトル化するほうは、別になんでもいいと言えばなんでもいいんですが、例えばOpenAIのAPIのEmbeddingsがあって、それで文章をベクトル化して使えます。

ベクトル化したりするので実行時間がかかったりするので、実際の実行はしないでおきます。ソースコードを見たほうが色もついていて見やすいかもしれないので、ソースコードを出します。

(画面を示して)LangChainのドキュメントを最低限読んで、「LangChainの概要を1文で説明してください」と言うぐらいであれば、このぐらいのコード量になります。

DirectoryLoaderというディレクトリ以下のドキュメントを読むLangChainの機能を使って、「ディレクトリにあるものを読んでください」と(指示します)。それをインデックスします。Vector Storeに入れて検索できるようにします。

そして「LangChainの概要を1文で説明してください」と言うと、内部ではプロンプトが使われて、{context}の箇所にVector Storeから検索した、今回の質問の内容に関係しそうな文章が入ってきて、{question}の箇所に質問が入って、プロンプトが投げられて応答が返ってきます。

毎回同じ(回答)じゃないかもしれませんが、実際にこれをやると、「LangChainは、言語モデルを利用したアプリケーション開発のためのフレームワークです」と返ってきたりします。

これと同じようなこと、似たことができるライブラリとして、LlamaIndex、旧GPT Indexがすごく有名かなと思います。

検索したい需要はいろいろあると思います。例えばここ(の例)ではDirectoryLoaderといって、ローカルにあるディレクトリから読み込んでインデックスするものを使いました。

ほかにも、サイトマップに従ってあるサイトのデータを読み込むようなこともできれば、いろいろなやり方でVector Storeにデータを入れること自体はできるようになっています。

LangChainのモジュール「Memory」

そして次のモジュールはMemoryです。まず前提として、OpenAIのAPIは前回のやり取りを覚えておいたりはしてくれません。

例えば、「こんにちは。私は大嶋といいます」と言うと、「こんにちは、大嶋さん。私は田中です。どうぞよろしくお願いします」と返ってきます。実際にこう返ってきたんですが、「あなたは田中じゃないですよね」と思いますが、「私の名前がわかりますか?」と聞くと「いいえ、わかりません」と言ってくるんですね。

ただチャットボットを実装したい時は、過去の会話を踏まえて応答してほしい。つまり、記憶を持たせたいということがきっとあると思います。

そこでMemoryという機能が使えるんですが、文脈、contextを持たせたようにプロンプトに履歴を入れる、historyを入れることによって、過去の会話を踏まえた応答ができるよ。そんなプロンプトの手法があります。

例えば「Human:こんにちは。私は大嶋といいます。 AI:」、こんなようにするとAI側の回答を埋めてくれるんですが、「こんにちは、大嶋さん。私はAIです。私の名前はジェイソンです。どうぞよろしくお願いします」と(回答が返ってきます)。「ジェイソンです」とまた嘘をついていますが。

それに対して次にプロンプトを投げる時に、過去のやり取りをプロンプトに含めて、続きに「Human:私の名前がわかりますか?」と投げます。すると、「はい、大嶋さんという名前を知っています。あなたのお話を聞かせてください」と回答してくれたりするんですね。

これをLangChainのMemory機能を使って簡単にできるようになっています。

ちょっとやってみようと思います。(画面を示して)実装はこんな感じですね。poetry run python...、memoryのサンプルコードを実行してみます。

内容は、モデルを用意して、Conversation、会話のConversationChainみたいなものを用意します。LLMとメモリの仕方、これはLangChainが用意しているConversationBufferMemoryというやつですね。そして、その後ループで標準入力の入力を読み取っては実行して、AIの内容を出力するだけです。

実際に内容を入力してみようと思います。「こんにちは、私は大嶋といいます」と言うと、APIを投げるわけですね。ここでLangChainが内部で用意しているテンプレートに穴埋めして、「Human:」「AI:」と来ます。

(画面を示して)この緑の部分が実際にテンプレートを穴埋めした後のものですね。LangChainが内部で用意しているPrompt Templatesに対して私が今入力した内容が、「こんにちは、私は大嶋といいます。 AI:」こんなふうに穴埋めされて投げられました。

それに対してGPTのAPIは、「こんにちは、大嶋さん。私はAIです。私の名前はジェイソンです。よろしくお願いします」と。たぶん英語だからジェイソンという名前を使ってくるんだと思うんですが、こんなふうに返してきました。

続いて「私の名前はわかりますか?」と聞くと、今度は前回の会話の内容も含めたプロンプトを作っています。

これはConversationBufferMemoryとかがやってくれていることなんですが、それを含めてAPIに投げることによって、「はい、大嶋さんという名前を知っています。あなたはどこから来ましたか?」みたいに名前を答えたり、記憶を持って回答してもらうことができるようになっています。これがMemoryという機能ですね。

Memoryという機能はこれだけだと単純ですが、例えば記憶をずっと持っているとトークン数がとんでもないことになるので、トークン数を削減するために古い会話を要約するような高度な機能もあります。

ということで、ここでもう1回整理しておこうと思います。ここまででLangChainのModels、Prompts、Chains、Indexes、Memoryを見てきました。IndexesはVector Storeとかに保存した文章を利用するもので、Memoryは過去の対話を保持するものですね。LangChainを理解するにはプロンプトエンジニアリングから(理解する)というのが、伝わってきたんじゃないかと思います。

(次回につづく)