LangChainでのOpenAIのChat APIの使い方の基本

大嶋勇樹氏:ということで、ざっとOpenAIのChat APIについて復習してきましたが、ここからはLangChainでのOpenAIのChat APIの使い方の基本を見ていきます。

まずそもそものLangChainについて簡単に紹介すると、LangChainは大規模言語モデルを使ったアプリケーション開発のフレームワークです。実装としてはPythonとJavaScript、TypeScriptの2つがあります。機械学習関連分野によくあることですが、Pythonのほうがやはり開発は活発です。

今日もPythonを使って、3日前(登壇時点)のリリースのバージョンを使っていきます。LangChainはほぼ毎日新しいバージョンがリリースされているので、今はたぶん(バージョン)218とかまで出ているんじゃないかと思いますが、3日前のバージョンを使わせてもらいます。

LangChainは要約やチャットボット、ドキュメントへのQ&Aなど、LLMを使ったさまざまなアプリケーションで活用できるフレームワークとなっています。

そんなLangChainですが、モジュールがいろいろ整理されています。現在だとLanguage modelsやPrompt templates、Chain、Data connection、Memory、Agentsと、大きなモジュールの整理があって、その中にたくさんクラスが存在するように実装されています。

そして、このLangChainで先ほど紹介したOpenAIのChat APIを使う時は、ChatOpenAIというクラスを使います。

LangChainでgpt-3.5-turboを呼び出す方法

このあたりはドキュメントを見ていくともちろん載っていることではありますが、LangChainでChatOpenAIを使う時は、実装方法が2つあります。

スライドだと見にくいかもしれないので、コードを実際に見てみようと思います。(画面を示して)これがLangChainでChatOpenAIというクラスを使ってgpt-3.5-turboのモデルを呼び出す、使う例です。

下の部分はいったんコメントアウトします。

messagesとして、リストの形式でHumanMessage、つまり人間側のメッセージとして「content="Hello!"」「Hello!」と言います。それをchat(messages)のように、APIを呼び出して結果を表示しています。これを実行してみようと思います。

このコードを実行すると……。実行結果は毎回多少違う可能性はありますが、gpt-3.5-turboからの応答が返ってきますね。「Hello!」に対して、「Hello! How can I assist you today?」ということで、こんな応答が返ってきます。これがLangChainでgpt-3.5-turboを呼び出す、すごくシンプルなコードです。

chat(messages)と呼ぶ代わりにchat.predictのように呼び出す

実は呼び出し方はほかにもあります。このほかにも、下に書いてあるように、「chat = ChatOpenAI」に対してchat(messages)と呼ぶ代わりに、chat.predictのように呼び出すこともできます。上側をコメントアウトして、呼び出せることをもう一度実行して確認してみようと思います。

たまたまtemperatureが0というのもあって同じような応答が返ってきましたが、この「.predict」という呼びだし方もできるんですね。chat(messages)でもchat.predictでも、このChatOpenAIは呼び出せます。

chat(messages)とchat.predictが同じ挙動であるか確認する

実はこれが同じような挙動になっているということを、LangChainのソースコードリーディングのまあまあ簡単ではありますが、1つの例として見ていこうと思います。

(画面を示して)まず、コードが2種類使えるということを見て思うことは、上の「HumanMessage(content="Hello!")」については、おそらく内部で「"role": "user", "content": "Hello!"」みたいに組み立てられて実行されています。

一方で下のpredictについては、HumanMessageのような……。このメッセージが人間のメッセージなのか……。HumanMessage以外にAIMessageでも使えるんですが、そういうのを指定せずに呼んでいます。なので、たぶん内部で勝手にHumanMessageと解釈されて、上と同じような呼び出しになっているんじゃないかと想像できるかと思います。

本当にそうなっているかを、ソースコード上で見ていこうと思います。比較的簡単なテーマではありますが、ソースコードを読むにあたってChat APIの形式がわかっていると、なんとなくこの呼び出し方は疑問に思います。もしもどういうことをやろうとしているかイメージがつかないなどあれば、Q&Aなどに質問をもらえれば、少し補足します。

いったんこのテーマでLangChainのソースコードを読んでみようと思います。 ここからは資料はなく、実際に画面にLangChainのソースコードを映しながらやっていきます。

(画面を示して)ソースコードを読む時に、やり方はいろいろありますが、まずはGoolge検索で「LangChain GitHub」と検索してみることにします。公式のLangChain……。公式というか、LangChainの「GitHub」のリポジトリが見つかりますね。

ソースコードを読む時にローカルで読むか、GitHubで読むか。人によってやり方はいろいろありますが、GitHub上で見ていくのも楽なので、まずはGitHub上で見ていこうと思います。

今質問をもらっていますが、急ぎじゃなくてもよさそうな内容だと思うので、いったん進めて、いいタイミングで回答できればと思います。

まずGitHub上でこのChatOpenAIクラスの呼び出し方を見ていこうと思いますが、この時どうするか。最初の一歩をどうするかは、いろいろなやり方があると思います。例えばGitHubの検索欄で検索するとか、ソースコードのツリーを見ていくとか、いろいろあると思いますが、特にLangChainのようなアップデートの激しいものを扱う上で僕がお勧めしたいのは、まずはバージョンを合わせることですね。

まずバージョンを合わせるのが一番ポイントだと思います。(今回は)0.0.219ですね。4時間前に最新版がリリースされて、今(画面に)表示されているのは0.0.219か、さらに新しいバージョンです。0.0.219のリリースのところが表示されているので、まずはGitHubのReleasesから、少し過去のバージョンをたどっていきます。

先ほどのサンプルコードで使っているのは0.0.215なので、0.0.215のタグマークをクリックすると、GitHubのトップページ的な、ソースツリーのトップになります。

ここがv0.0.215、URLもv0.0.215となって、その時点のソースコードのツリーの表示になっています。この状態でChatOpenAIクラスの呼び出し方を見ていきます。いったん確認してみると、このChatOpenAIクラスは「from langchain.chat_models」とimportしています。

importしているパスにもヒントになることがけっこうあったりしますね。実際にLangChainのソースツリーのトップを見ると、重要なソースコードは、おそらくlangchain以下にありそうだなと(わかります)。ここを見ていきます。

するといろいろなモジュール、ディレクトリに括られているわけですが、chat_modelsというディレクトリが見つかるので、これを見てみます。

エディタなどのように左側にも出ていますね。langchain以下のchat_models以下を開いてみました。

この中に、ChatOpenAIクラスがあるんじゃないかと当たりをつけて見ていこうと思います。(画面を示して)これを見ると、openai.pyにあるんじゃないかとなんとなく思えると思います。もっと確実に検索したりして、ちゃんと探す方法も後で説明しようと思いますが、僕は「なんとなくここにありそうだな」という当たりをつけて見ていくこともよくあります。

openai.pyを開くと、右側にどんなクラスがあるかとか……。最近のGitHubはこのあたりをわかりやすく出してくれるんですが、ChatOpenAIクラスがあることが出ていますね。

実際に下側を見ていくか右をクリックすると、openai.pyにChatOpenAIというクラスがあることがわかりました。(画面を示して)こんなふうに、ChatOpenAIクラスを見つけられました。

今の目標は、このchat(messages)とchat.predictがどんな対応にあるかを見ていきたいわけですが、まずchat(messages)から見ていこうと思います。

このchat(messages)は、文法的にPythonの何なのかが1個の大事な要素です。これはPythonの実践入門みたいな本とかを読んだことがあれば知っていることだと思うんですが、chat.__call__というものと同じですね。

このchat(messages)は、call、特殊メソッドといったりしますが、これを呼び出しているわけです。これが省略されているようなものです。(画面を示して)実際にこうやって書いてあげても動くはずです。同じような応答が返ってきますね。

このChatOpenAIのインスタンスに対するchat(messages)という呼び出しは、実は__call__というメソッドの呼び出しでした。なので、ChatOpenAIの__call__というメソッドを探していきたいわけです。そうすれば、実装が見つかるはずです。

GitHubの設定次第だったかもしれませんが、GitHubを使っていると、シンボルの一覧みたいなものが右側に出て、ChatOpenAIクラスに定義されているメソッド、defで定義されている関数を見ることができますが、__call__は見当たりません。

これはなぜかというと、ChatOpenAIはBaseChatModelを継承しているからです。BaseChatModelを継承、拡張して、ChatOpenAIになっているからですね。

なので、ここではBaseChatModelという親のクラスを見にいきます。(画面を示して)最近のGitHubは、選択すると右側の定義に移動するようなリンクが表示されるので、これでBaseChatModelを見ていきます。

隣のタブで開きました。(画面を示して)こんなふうに「class BaseChatModel」が「langchain/chat_models/base.py」というファイルにあることがわかりました。

このBaseChatModelに探していた__call__というメソッドがあるかを見ると……。ありますね。定義されています。

List[BaseMessage]のBaseMessageは、HumanMessageやAIMessageなどが含まれるものですが、__call__はmessagesや、(その他にも)必要に応じていろいろ追加で受け取って、いろいろ実行されていきます。

その中ではgenerateという別のメソッドを呼び出したりしていて、さらにgenerateを深掘りしていくと、「このgenerateの中で実際の文書の生成のAPIを呼んでいそうだな」ということがなんとなく推測できます。

ということで、chat(messages)は__call__という特殊メソッドの呼び出しで、親クラスに(あることを)見つけられました。今度は、これと比較したい、もう1つの呼び出し方のpredictを見ていきます。

predictについても、まずはChatOpenAIクラスの中にpredictというメソッドがあるか見ようとすると、やはりこれもないんですね。

predictについても実は親クラスのほうにあるんじゃないかなと思って、ChatOpenAIの親クラスのBaseChatModelをもう一度見にいきます。BaseChatModelのメソッドを探してみると、predictがありますね。

このpredictが、textという引数を文字列型で受け取るように、こんなふうにタイプヒントが書かれていますね。

そしてその中で処理しています。「self([HumanMessage(content=text)], stop=_stop, **kwargs)」となっていますね。このself()はself.__call__と同じですね。これは__call__という特殊メソッドの呼び出しです。

つまり、chat.predictは、中でchat(messages)をやっていることが、ソースコードで読み取れました。

確かに受け取った文字列をHumanMessageに詰め込んで、__call__のほうの特殊メソッドを呼んでいる挙動になっていますね。

ということで、最初の一歩として比較的シンプルな例ですけれども、ChatOpenAIクラスの2つの呼び出し方がちゃんと中の実装で互換性があるというか、もう一方の呼び出しに対応しているのを見てきました。

(次回につづく)