LangChainのAgent「zero-shot-react-description」はLLMとどう連携しているのか?調べた
ChatGPTのようなLLMを使ったライブラリやサービスがいくつも最近でてきています。その中でも以前、LangChainというPythonのライブラリを紹介しました。
今回はこの中で紹介した例で出てきた「zero-shot-react-description」というAgentを使って以下の質問をしたときに内部でLLMとどういう連携をしているのか?を調べたのでそのまとめになります。
"Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?"
今回動作検証に用いたコードは以下の通りです。
import langchain
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.llms import OpenAI
langchain.verbose = True
llm = OpenAI(temperature=0, verbose=True)
tools = load_tools(["serpapi", "llm-math"], llm=llm)
agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True)
agent.run("Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?")
目次
zero-shot-react-descriptionとは
zero-shot-react-descriptionはReActベースのLangChainのAgentになります。ReActとは以下で提案されている手法です。
Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K., & Cao, Y. (2022). ReAct: Synergizing Reasoning and Acting in Language Models. ArXiv, abs/2210.03629.
ReActがやっていることを簡単に説明すると以下の二つの行動を繰り返していき、最終的な結果を得るということをしています。
- 次に何をすべきか?を考える
- 考えに基づいてアクションを実行し、アクションの結果を得る
ここでアクションというのはinitialize_agent
で指定したtoolになります。先ほどのコードではserpapi
、 llm-math
です。serpapi
はSerpApiというGoolgeの検索結果をパースして返してくれるサービスを実行するツール、llm-math
はLLMを使ってPythonコードを生成して実行するツールになります。
zero-shot-react-descriptionではこのような動きをLLMにさせるために入力のテキストの前にいろいろ追加で文章を付け加えてLLMに投げています。
この追加でつけている文章がどんなものか気になったので、今回詳しくzero-shot-react-descriptionの動作を調べました。動作チェックには先ほども示した以下の質問を使いました。
"Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?"
動作チェックをするための下準備
頑張ってPythonのデバッガーを使って1行ごとに何が起きているか?を見てもいいのですが、実はLangChainは以下のようにすると、それ以降の実行に関して細かい出力をするようになります。
import langchain
langchain.verbose = True
今回は上の設定によって出てきたログに基づいて動作を説明していきます。
zero-shot-react-descriptionの動作例
先ほど示した質問に対してzero-shot-react-descriptionはまとめると、この記事を書いている時点では以下の手順で問題に対する回答をしようとします。
- 「Leo DiCaprio’s girlfriend」が誰なのか調べる
- 「Leo DiCaprio’s girlfriend」が「Camila Morrone」であることがわかったので、次は「Camila Morrone」の年齢を調べる。
- 25歳であることがわかったので、次は25^0.43を計算する
- 最終的な結果を出力する
ちなみに実行したタイミングによっては1を調べたらついでに年齢もわかってしまって2がスキップされるという挙動をするタイミングもありました。このため、上の例とは違う動作をするケースもありますので注意してください。
それではLangChainで細かい出力をさせてそれぞれのステップでLLMをどのように動作させているか?を細かく見ていきます。
最初の質問時点の入力に関して
まず、そもそも最初の問のときにどのような入力になっているかを見てみます。入力では先ほど示したように以下のものを入れています。
"Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?"
しかし、これだけだとReAct的な動作をしてくれないので、ReAct的な動作をしてくれるように質問の前にいろいろ追加します。実はLLMに投げている文章としては以下のものになっています。
Answer the following questions as best you can. You have access to the following tools: Search: A search engine. Useful for when you need to answer questions about current events. Input should be a search query. Calculator: Useful for when you need to answer questions about math. Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [Search, Calculator] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power? Thought:
「Question: 」のところに入力した質問があって、その前後にいろいろついていることがわかります。
これに関して順番に見ていきます。まず以下の部分です。
Answer the following questions as best you can. You have access to the following tools: Search: A search engine. Useful for when you need to answer questions about current events. Input should be a search query. Calculator: Useful for when you need to answer questions about math.
最初に、質問にできるだけこたえるように頼み、使えるツールとしてどんなものがあるかを説明しています。
それぞれSearchがserpapi
, Calculatorがllm-math
のことを言っています。このツールの説明の部分はinitialize_agent
で指定されたツールのname
とdescription
をから自動生成されて加えられています。このため、もし使用するツールを増やすとこの部分に新しく追加されたツールの説明が付け加わることになります。
次にこの部分に関してです。
Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [Search, Calculator] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question
この部分はこの後、LLMが生成するテキストのフォーマットの指定に関しての説明です。これを加えることで、この後、Agentの中でLLMがこのフォーマットに従って出力してくれるようになります。そのおかげでLLMの出力を簡単にパースすることができ、LLMの出力から次にどのようなアクションをすればよいかを自動で判断し、実行することができます。
そして最後のこの部分です。
Begin! Question: Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power? Thought:
この部分はLLMにここから開始することと、質問、そして最初にLLMに何をするべきか考えてもらうために「Thought:」までテキストで与えます。「Thought:」があることでLLMはちゃんと考えることから初めてくれます。
この入力に加えて「Observation:」という文字列を出力したら出力を止めるようにしてLLMを実行させます。
「Observation:」はアクションの結果を示すところです。このため、「Observation:」より先はLLMでは生成できないので、これを出力したらLLMを止めるようにしています。
「Leo DiCaprio’s girlfriend」が誰なのか調べる
先ほどの文章をLLMに入力するとLLMは次のように出力してきます。
I need to find out who Leo DiCaprio's girlfriend is and then calculate her age raised to the 0.43 power. Action: Search Action Input: "Leo DiCaprio girlfriend"
1行目の「Thought:」の続きなので、LLMが何をするべきか考えたところです。そしてその次は質問に答えるために何をするべきかを出力しています。ここでは「”Leo DiCaprio girlfriend”」という入力で「Search」を実行することを選択しています。
この出力から”Leo DiCaprio girlfriend”を入力としてSerpApiを使います。このSerpApiは以下のURLの説明にあるような形で検索結果を返してきます。
https://serpapi.com/organic-results
このJSONの一部を取り出して「Observation:」のあとに追加します。どこの部分を取り出すかはSerpAPIWrapper
というクラスの_process_response
という関数で決めています。私が実行した際はtopヒットのsnippet
の部分を取り出していました。書かれている内容としては以下の通りです。
Leonardo DiCaprio seemed to prove a long-held theory about his love life right after splitting from girlfriend Camila Morrone just months …
この文章を見ると「Camila Morrone」が「Leo DiCaprio’s girlfriend」だったようですが別れているようですね。ただ、LLMはこの後、「Leo DiCaprio’s girlfriend」が「Camila Morrone」として処理を進めていきます。質問が若干曖昧なので、過去の彼女でも質問の回答としては成り立ちそうなので、OKとしてこのあとも説明していきます。
「Leo DiCaprio’s girlfriend」が「Camila Morrone」であることがわかったので、次は「Camila Morrone」の年齢を調べる。
先ほどの検索結果を合わせてLLMに以下の文章を入力して次にどうするかを考えさせます。
Answer the following questions as best you can. You have access to the following tools: ... Begin! Question: Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power? Thought: I need to find out who Leo DiCaprio's girlfriend is and then calculate her age raised to the 0.43 power. Action: Search Action Input: "Leo DiCaprio girlfriend" Observation: Leonardo DiCaprio seemed to prove a long-held theory about his love life right after splitting from girlfriend Camila Morrone just months … Thought:
前半部分は全く同じで、最後に先ほどの検索結果と「Thought:」をつけています。こうすることで検索結果を受けて次にどうするべきかをLLMに出力させています。
この入力を受けてLLMは以下のような出力をしてきます。
I need to find out Camila Morrone's age Action: Search Action Input: "Camila Morrone age"
最初の行は「Thought:」の出力です。なので、「Camila Morrone」の年齢を調べる必要があると考えています。そして「”Camila Morrone age”」を入力として「Search」を実行することを選択しています。この先は先ほどと同じようにSerpApiを利用して検索して結果を得ます。この部分の結果では以下のような内容を検索結果から抽出してきました。
25 years
これで年齢もわかりました。
25歳であることがわかったので、次は25^0.43を計算する
先ほど、「Camila Morrone」25歳であることがわかったのでLLMに次にどうするか出力させます。これを行うために以下のような入力をいれます。
Answer the following questions as best you can. You have access to the following tools: ... Thought: I need to find out Camila Morrone's age Action: Search Action Input: "Camila Morrone age" Observation: 25 years Thought:
こちらも先ほどと同様にそれまでの文章にさらに検索結果を追加して「Thought:」をつけて次に何をするかLLMに考えさせています。この入力に対してLLMは以下の出力をしてきます。
I need to calculate 25 raised to the 0.43 power Action: Calculator Action Input: 25^0.43
LLMとしては25^0.43を計算するために「Calculator」に対して「25^0.43」という入力を渡して実行するということをします。
Calculatorは先ほど説明した通り、llm-math
を指しています。これはPythonコードを生成させて、生成されたPythonコードを実行して答えを得るということをしています。
具体的には以下のような入力をLLMに渡しています。
Translate a math problem into Python code that can be executed in Python 3 REPL. Use the output of running this code to answer the question. Question: ${Question with math problem.} ```python ${Code that solves the problem and prints the solution} ``` ```output ${Output of running the code} ``` Answer: ${Answer} Begin. Question: What is 37593 * 67? ```python print(37593 * 67) ``` ```output 2518731 ``` Answer: 2518731 Question: 25^0.43
これは何をしているかというとPythonコードを生成するように説明し、その後フォーマットを示しています。そして、そのあとフォーマットに従った簡単なexampleを1つ示してそれにまねてPythonコードを生成させるように指示しています。これはLLMにおけるone-shot learningをしていることになります。この結果、LLMが賢ければちゃんと以下のように出力してきます。
```python import math print(math.pow(25, 0.43)) ``` Answer: 3.991298452658078
ここでAnswerで計算結果も出力していますが、これは使わずにPythonコードの部分を取り出してPythonで実行させ結果を得ています。これでほしかった計算結果を得ることができました。
最終的な結果を出力する
さきほどの計算結果を加えた文章をLLMに入力して文章を生成させます。
LLMへの入力は以下の通りです。
Answer the following questions as best you can. You have access to the following tools: ... Thought: I need to calculate 25 raised to the 0.43 power Action: Calculator Action Input: 25^0.43 Observation: Answer: 3.991298452658078 Thought:
これに対するLLMの出力は以下の通りです。
I now know the final answer Final Answer: Camila Morrone is Leo DiCaprio's girlfriend and her current age raised to the 0.43 power is 3.991298452658078.
ここまでで最終的な結果がわかったので「Final Answer: 」のあとに最終的な結果を出力しています。
終わりに
今回zero-shot-react-descriptionのログを追ってみてだいぶ何をやっているか理解することができました。
感想としては検索結果のチェックが若干甘い感じがしています。この結果、先ほどみたような「Leo DiCaprio’s girlfriend」として別れていそうな「Camila Morrone」をあげてくるということをしています。このあたりより精度の高い結果を得るためには、そもそも最初の質問をより明確にするか事実確認の仕組みをもう少し工夫していれる必要がありそうだなと思っています。
また、llm-math
のLLMの出力したPythonコードをそのまま実行するという仕組みは若干怖いなぁということを思いました。今回のようにどのようなコードを生成されるかわかっているケースはいいのですが、誤って変なコード、例えばhome以下のファイル削除みたいなコードがでてきて実行されたら大変なことになります。このあたりはもうちょっとセキュアにコードを実行する仕組みがほしいところです。
ただ、LLMに入力する文章を工夫することで想像よりもはるかにすごいことができると分かったので、いい勉強になりました。
個人的にはかなり興味がわいてきたので、いろいろLLMの入力を工夫するやり方などを勉強していければと思っています。