LLMの回答精度を高める、ReActの論文を読んでみた

# はじめに

大規模言語モデル(以下、LLM)が質問応答をする際に、嘘の回答を生成するのを防ぐための技術がいくつかあります。
そのひとつに、回答に必要な情報をネット検索するなどの行動をLLM自身に判断・実行させ、その結果を使って回答生成するものがあります。具体的な実装例には、LangChainのAgentやBingのChat AIがあります。

このような仕組みはどのようにして成り立っているのでしょうか。
本記事では、ベースとなっている手法の原著論文を読み解くことで、その仕組みの理解に迫ろうと思います。
加えてLangChainのAgentのやり取りを可視化し、論文の手法が具体的にどのように実装されているかを解説します。

# ReAct

LangChainのAgentの実装が下敷きにしているのは、[ReAct(Yaoら 2022)](https://arxiv.org/abs/2210.03629)という論文です。
実際、先日行われたLangChainのウェビナーには筆頭著者のYao氏が招待されていました。

https://twitter.com/hwchase17/status/1644742068194144256

## ReAct概要

ReActは大まかには以下の処理シーケンスから成り立っています。

1. 入力された文章に答えるには、どういう**理由**(**Reason**)でどういう**行動**(**Action**)を取る必要があるかを**思考**(**Thought**)する
2. 行動した結果を**観察**(**Observation**)した上で再び、どういう**理由**(**Reason**)でどういう**行動**(**Action**)を取る必要があるかを**思考**(**Thought**)する
3. **思考の結果、ファイナルアンサーが生成できたら終了**。さもなくば、生成できるまで2を繰り返す。

ちなみにReActという名前の由来は、上の説明に出てきたReasonとActionです。

## Reactの処理プロセスの具体例

ここからは、ReAct論文のAppendixに掲載されている質問応答プロセスの具体例をいくつか紹介します。
具体例は以下から成っています。
1. LLMへの入力文
1. 入力文やこれまでの行動結果をもとにLLMが出力した、思考、行動、およびその結果観察の内容

ちなみにReAct論文では、そのページ数の半分ほどがこのような具体例で占められています。

### ReActの具体例１(コロラド造山帯の東部の標高差)

下記の例は、クイズ問題的な質問応答をReActで試みているものです。

ここではReActが可能な行動として、search(Web or WikiPediaの検索)、lookup(searchで見つかったテキスト内の検索)、finish(ファイナルアンサーを出力して終了)の3点を定義しています。

```text
- 質問文：コロラド造山帯の東部が広がるエリアの標高差はどのくらいか？

- 思考１：まずコロラド造山帯を検索し、その東部が広がるエリアを探す必要がある。
  それからそのエリアの標高差を探す必要がある。
- 行動１：「コロラド造山帯」で検索する(search)。
- 観察１：検索で見つかったテキストの中に、「コロラド造山帯とは、コロラド州と
  その周辺で起こった造山の現象である」と書かれた箇所がある。

- 思考２：そこには東部エリアのことが言及されていない。よって、
  見つかったテキストから東部エリアに関する内容を洗い出す必要がある。
- 行動２：検索で見つかったテキストの中から、「東部エリア」に関する内容を
  検索する(lookup)。
- 観察２：1件ヒットがあり、「東側エリアはハイプレーンズに広がり、
  セントラルプレーンズ造山と呼ばれている」と書かれている箇所が見つかった。

- 思考３：コロラド造山帯の東側はハイプレーンズまで伸びている。
  そこでハイプレーンズを検索して、その標高差を見つける必要がある。
- 行動３：「ハイプレーンズ」で検索する(search)。
- 観察３：「ハイプレーンズ」と呼ばれる地域が、異なる2箇所にあることがわかった。

- 思考４：アメリカ合衆国にあるハイプレーンズを検索する必要がある。
- 行動４：「ハイプレインズ (アメリカ合衆国)」で検索する(search)。
- 観察４：検索で見つかったテキストの中に、「ハイプレーンズは、
  グレートプレーンズの一地域である。東から西へかけて、
  ハイプレーンズは約1,800フィートから7,000フィート（550m～2,130m）まで
  標高が高くなっていく」と書かれた箇所がある。

- 思考５：ハイプレーンズの標高は約1,800～7,000フィートなので、
  答えは1,800～7,000フィートとなる。
- 行動５：「1,800～7,000フィート」と回答して終了する(finish)。
```

### ReActの具体例２(もののけ姫)

質問文の代わりに命題を与え、その真偽を回答するタスクにもReActを使うことができます。
下記は我々日本人からすると常識ですが、ここでも思考、行動、観察のステップを経由して、LLMが丁寧に回答を提示していることが見て取れます。

```text
- 命題　：もののけ姫は映画か。

- 思考１：もののけ姫が映画が否か検索する必要がある。
- 行動１：「もののけ姫」で検索する(search)。
- 観察１：「もののけ姫とは～(後略)」という解説文が見つかった。

- 思考２：解説文に「もののけ姫は映画である」ということが書かれていた。
- 行動２：「命題は正しい」と回答して終了する(finish)。
```

## ReActの評価方法

例を見る限り、ReActを使うと愚直に丁寧にLLMが質問に対処し、正しい回答を返していることが見て取れます。
しかし、他の手法と比べて本当に正しい回答をコンスタントに返せるのでしょうか。

ReAct論文には当然、そのあたりの評価結果が載っています。が、少し意外な結果になっています。
ともあれ、まずは評価手法を詳しく見ていきましょう。

### ReActと比較評価している手法

ReActと比較している3点の手法を下記に示します。
いずれのものも、「幻覚」(hallucination)と呼ばれる嘘の情報を提示するリスクが排除しきれないことが知られています。

- １．**Starndard**(Original)
  - 何の工夫もせず、直接LLMに回答させる。
- ２．**Reasoning only** (論拠づけのみ)
  - LLMに単に答えを言わせるだけではなくて、なぜその答えになるかの論拠とセットで答えを提示させる。
  - 論文では、具体的なReasoningの手法として[CoT(Chain of Thought)](https://arxiv.org/abs/2201.11903)を使用。
    - 「step by stepで考えてくれ」と質問文で指示すると回答の精度が上がるという、例の手法。
- ３．**Action only**(行動のみ)
  - 論拠づけはしないで、検索などの行動を経て得た情報のみを踏まえて回答を提示する手法。
  - 論文では、既存の行動を真似するよう学習させたり、強化学習をすることでActionのみを行うエージェントを作り、ReActと比較。

なお上記１～３およびReActの評価に用いるLLMは、GoogleのPaLM-540Bです。

### 評価に使ったベンチマーク

論文では、下記4種の既存のベンチマークを使用して精度評価をしています。

- **HotPotQA**: 質問文に対して正しい回答分を生成するタスク
  - 先の「コロラド造山帯の東部の標高差」を問うタスクは、本ベンチマークに由来。
- **Fever**: 事実の真偽の検証を行うタスク
  - 同じく「もののけ姫が映画が否か」を問うタスクは、本ベンチマークに由来。
- **ALFWorld**: テキストベースのゲームのタスク
- **WebShop**: ECサイトをページ遷移しながらお遣いをこなすタスク

このうちHotPotQAとFeverは質問応答系のタスクであり、ALFWorldとWebShopは意思決定を必要とする強化学習系のタスクです。
HotPotQAとFeverの検証では、LLMが使える行動としてWikipedia検索のみを用意しています。
また、ALFWorldとWebShopではReActとAction onlyのみを比較評価しています。

#### ALFWorldのタスクの例

ここからは、まだ紹介していなかったALFWorldとWebShopのタスクの具体例を見ていきます。

ALFWorldでは下記のように、置かれているシチュエーションと実施するべきタスクが、ゲームマスター的なプログラムから文章で説明されます。ちなみにアイテムの右の数値は個数ではなくてIDです。(マドハンドA、マドハンドBみたいなもの)

```text
あなたは部屋の真ん中にいます。
周囲を見渡すと以下のものが見つかりました。

キャビネット13、キャビネット12、キャビネット11、キャビネット10、
キャビネット9、キャビネット8、キャビネット7、キャビネット6、
キャビネット5、キャビネット4、キャビネット3、キャビネット2、キャビネット1、
コーヒーメーカー1、調理台1、ダイニングテーブル1、引き出し1、冷蔵庫1、
ゴミ箱1、電子レンジ1、棚3、棚2、棚1、流し台1、
コンロ4、コンロ3、コンロ2、コンロ1、トースター1です。

あなたのタスクは、「ダイニングテーブルに、洗ったレタスを並べること」です。
```

これに対して正しい行動を導くことがLLMには求められます。

例えば上記の例なら、「冷蔵庫などを探してレタスを見つける」、「流し台でレタスを洗う」、「レタスをダイニングテーブルに置く」という行動を順次実行すると回答できれば良いことになります。

論文では、ReActを使った場合のLLMの回答例が示されています。LLMが思考や行動をするたびに、ゲームマスター的なプログラムから都度適切な応答が返ってきます。

```text
LLMの思考：まずレタスを探す必要がある。レタスは冷蔵庫1、ダイニングテーブル1、
流し台1、コンロ(1～3)、キャビネット(1～13)にありそうだ。冷蔵庫1から一つ一つ見ていく。
→OK。

LLMの行動：冷蔵庫1に行く
→冷蔵庫1は閉まっている。

LLMの行動：冷蔵庫1を開ける
→あなたは冷蔵庫1を開けた。冷蔵庫1は開いている。中にはカップ3、卵2、ジャガイモ3とジャガイモ2がある。

LLMの行動：ダイニングテーブル1に行く
→ダイニングテーブルには、リンゴ1、食パン1、バターナイフ2、カップ2、フォーク2、ナイフ2、ナイフ1、
おたま1、レタス1、マグカップ2、マグカップ1、フライパン2、
コショウ入れ1(注：ヌートバーのでなく、S&Bのテーブルコショーみたいなもの)、
ヘラ3、トマト2、トマト1がある。

LLMの思考：レタス1が見つかったので手に取る
→OK。

LLMの行動：レタス1をダイニングテーブル1から取る
→あなたはダイニングテーブル1からレタス1を取った。

(後略)
```

ちなみにこのタスクと同様、取りたい行動をフリーテキストで指示して進めていくゲーム"AI Tech Preview: THE PORTOPIA SERIAL MURDER CASE"がスクウェア・エニックスからリリースされています[^1]。
いわゆる「[ポートピア連続殺人事件](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%BC%E3%83%88%E3%83%94%E3%82%A2%E9%80%A3%E7%B6%9A%E6%AE%BA%E4%BA%BA%E4%BA%8B%E4%BB%B6)」のUIがチャット形式になったものです。

https://www.youtube.com/watch?v=_dF7rZdb3NU&t=30s

[^1]: わずかでも定型的な文から外れたことを言うと、部下の刑事が「ちょっとよくわからないです」をサンドウィッチマン並に連発するのが頭にくる

#### WebShopのタスクの例

WebShopは、頼まれたおつかいをECサイトで行うシチュエーションのタスクです。

説明文を見ながら、所望の商品があるリンクに遷移して、最後に購入ボタンをポチれればクリアです。
![webshop_task.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/627a229b-f3d9-827a-7cbd-5b42df4ac061.png)

### 質問応答系タスクでの結果

一連のタスクに対し、ReActでどこまでの精度が出せたのでしょうか。
まず質問応答系タスクであるHotPotQAとFeverにおける、手法の精度比較を見ていきましょう。

HotPotQAでは精度指標にEMというものを使っています。これは"exact match"のこと[^2]です。つまり完答率ですね。
他方、命題の真偽を問うFeverでは単純に正答率(Acc)を評価指標にしています。
従って、どちらも値が大きいほど精度が高いことを意味します。
![hotpotqa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/b25c6312-859c-f09e-eaf6-81359d15c51a.png)

[^2]: EMが"exact match"であることは、[https://paperswithcode.com/dataset/hotpotqa](https://paperswithcode.com/dataset/hotpotqa)に記載があります。教えてくださったMさん、ありがとうございました。

**この表を見てわかる通り、実はReActを単体で使った場合の精度は比較手法中のベストではありません**。

これを踏まえ、原著論文はおおよそ以下の事柄を主張しています。詳しく見ていきましょう。

#### 主張１．HotPotQAとFeverでは、ReActはActより常に勝る

外部ツールを用いたアクション(Act)が可能ならば、加えてLLMに理由付けもさせる(ReAct)ほうが正答しやすいと主張しているわけです。

#### 主張２．**対CoTでは、ReActはFeverで勝てた一方、HotPotQAでは負けた**

先の表のCoTやCoT-SCといった手法は、いずれもHotPotQAではReActを上回る結果となっています。

なおCoT-SCとは、乱数を変えて複数回CoTをやって得た回答の多数決を取るやり方です。いわばCoTのアンサンブル版といえます。

#### 主張３．ReActとCoT-SCを状況に応じて切り替えることが最も望ましい

切り替え方には下記の2通りがあります。

- **【切り替え方１】ReActがうまくいかないときにCoT-SCに切り替え(ReAct → CoT-SC)**
  - ReActで思考→行動→観察→思考→…のサイクルを何回か繰り返しても、ファイナルアンサーを提示できない場合にCoT-SCを試みる。
- **【切り替え方２】CoT-SCがうまくいかないときにReActに切り替え(CoT-SC → ReAct)**
  - CoT-SCをやって複数のファイナルアンサーを得たときに、その過半数を占める共通の答えが得られなければReActを試みる。
    - ReActを試みる意義は、CoT-SCで答えが分かれる原因が情報不足にあると考えられること。

### 強化学習系タスクでの結果

次に、意思決定を必要とする強化学習タスクであるALFWorldでの精度比較を示します。
こちらでも、ActよりもReActのほうが優れていることが示されています。
![alfworld.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/9e084b66-3d51-e291-9b02-78de5eb2adcc.png)

なお表のReAct-IMとは、自然言語による指示をロボットアームに処理させる際に使われる"Inner Monologue"[^3]という手法とReActを組み合わせたもの[^4]です。"Inner Monologue"とは、カメラやセンサで捉えた外界の情報を言語化することにより、状況に応じた行動をロボットが取りやすくできるという手法ですが、今回のタスクにはやや不向きな傾向が見られました。

表のBUTLERはALFWorldに特化したプログラムです。1点のタスクを除いて、汎用的な手法であるはずのReActがBUTLERにも勝ってしまっていることがわかります。

[^3]: "Inner Monologue"の中身については、[東大松尾研の解説記事](https://trail.t.u-tokyo.ac.jp/ja/blog/22-12-12-inner-monologue/)が詳しいです。
[^4]: 実は、ReAct-IMの詳しい説明は原著論文には書かれていません。

もうひとつの強化学習系タスクであるWebShopについても、ReActがAIの中では最善であったことが示されています。が、経験豊富な人間には負けています。お遣いまでもAIに完全に奪われることは、辛うじて逃れられたというところでしょうか。
![webshop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/f044b5e1-1bb6-57d7-a321-f79c62ef7f83.png)

# LangChainにおけるReActの実装

ここまでReActでLLMが行う思考や回答を見てきましたが、次に疑問に思うのは「**では、そのような思考や回答をどのようなプロンプトで引き出すのだろうか？**」ということです。

実はReAct論文自体には具体的なプロンプトの内容も設計方法も記載がないように見えます。
そのため、ReActに則って実装されたプログラムの挙動から、具体的なプロンプトが何かを観察する必要があります。

ReActの考え方を実装したライブラリにLangChainがあります。LangChainでは`Agent`と呼ばれるオブジェクトを利用してReActによる質問応答を実現することが出来ます。

LangChainの挙動を捉えて、どんなプロンプトをAgentがLLMに対して送信しているか検証した事例に下記があります。しかしこれはLangChainの`ChatAgent`のほうの検証であるため、Agentの送信するプロンプトとは中身が異なります。

https://blog.shikoan.com/langchain-agent-prompt/

そこで自らデバッガでLangChainのAgentの挙動を追い、発しているプロンプトの内容やその生成の流れを確認することにしました。その結果、下記のシーケンス図に示す流れがわかりました。

```mermaid
sequenceDiagram
    participant UserProgram as ユーザプログラム
    participant LangChain as LangChain
    participant LLM as LLM
    participant SerpAPI as SerpAPI

    UserProgram->>LangChain: Agent.run(入力文)
    LangChain->>LLM: 回答用フォーマット + 入力文
    LLM-->>LangChain: 入力への見解(Thought), 行動内容(Action=Search, Action Input=クエリテキスト)

    LangChain->>SerpAPI: Google検索API(クエリテキスト)
    SerpAPI-->>LangChain: 検索結果のJSON
    LangChain->>LLM: 回答用フォーマット + 入力文 + JSONから抽出した検索結果
    LLM-->>LangChain: 見解(Thought), 行動内容(Action=Final Answer, Action Input=回答文)
    LangChain->>UserProgram: 回答文
```

以下、LangChainのAgentとLLMの間でのやり取りをもう少し詳しく見ていくことにしましょう。

## プロンプトのフォーマット

AgentがLLMに渡すプロンプトは下記フォーマットを埋めたものになっています。

```text
Answer the following questions as best you can. You have access to the following tools:

Search: useful for when you need to answer questions about current events
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: {input}
Thought:{agent_scratchpad}
```

`Begin!`より前の部分は、LLMが回答を出力する際に守ってほしいフォーマットや、LLMが取れる行動の説明になっています。先のシーケンス図で「回答用フォーマット」と書いていたものがこれです。行動の説明部分の`[Search, Calculator]`は、ユーザプログラムがAgentの行動に使えるツールとして指定したものになっています[^5]。ここでSearchはSerpAPIによるGoogle検索、CalculatorはWolframAlphaによる数値計算にあたります。

[^5]: 具体的な実装方法は、[筆者が以前書いた記事](https://qiita.com/kzkymn/items/a72796c89ffc696034c8#6-wolfram-alpha%E3%81%A8%E9%80%A3%E6%90%BA%E3%81%99%E3%82%8B)などを参照ください。

`Begin!`よりあとの部分は、Agentがユーザプログラムから受け取った入力文やこれまでの行動結果を埋めてLLMに渡すためのものです。`{input}`の箇所に入力文、`{agent_scratchpad}`の箇所に行動履歴が入ります。

最初にLLMにプロンプトを渡すときには行動履歴がないので、フォーマットを埋めた結果は下記のようになっています。ここでは入力文を「エベレストの標高を教えて」としています。

```
Answer the following questions as best you can. You have access to the following tools:

Search: useful for when you need to answer questions about current events
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: エベレストの標高を教えて
Thought:
```

## LLMからのレスポンス

プロンプトを受け取ったLLMからは、以下3点を含む回答がAgentに返ってきます。
1. **思考**(**Thought**)
1. **行動**(**Action**)
1. **行動の際に与える入力**(**Action Input**)

例えば先の「エベレストの標高を教えて」に対しては、以下のような回答が返ってきます。

```
 I need to find the height of Mount Everest
Action: Search
Action Input: Height of Mount Everest
```

これを受け取ったAgentは、LLMが必要とする行動がSerpAPIを用いたGoogle検索(**Search**)であり、その入力が`"Height of Mount Everest"`であることを認識します。

## 検索結果の抽出

AgentはSerpAPIに`"Height of Mount Everest"`というクエリを渡し、その結果をJSONで取得します。人が見るには大きすぎるJSONなので、その冒頭の部分を一部マスキングしたものを以下に示します。

```text
{'search_metadata': 
  {'id': '644478c19d1586ab14d89973',
   'status': 'Success',
   'json_endpoint':'https://serpapi.com/searches/aaaaa/aaaaa.json',
   'created_at': '2023-04-23 00:16:01 UTC',
   'processed_at': '2023-04-23 00:16:01 UTC',
   'google_url': 'https://www.google.com/search?q=Mount+Everest+height&oq=Mount+Everest+height&hl=en&gl=us&sourceid=chrome&ie=UTF-8',
   'raw_html_file': 'https://serpapi.com/searches/bbbbb/bbbbb.html',
   'total_time_taken': 3.45}, 
(後略)
```

エベレストの標高のように、そのものズバリの値をGoogleが検索窓でサジェストできるケースでは、このJSONのどこかに`answer_box`という項目が存在します。LangChainのAgentは、その中にある`answer`の値を抽出します。
この場合は`29,032`を抽出します。エベレストの標高は29,032フィートというわけですね。

```text
'answer_box': {'type': 'organic_result',
 'title': 'Mount Everest/Elevation',
 (中略)
 'snippet': 'Mount Everest is a peak in the Himalaya mountain range. It is located between Nepal and Tibet, an autonomous region of China. At 8,849 meters (29,032 feet), it is considered the tallest point on Earth. In the nineteenth century, the mountain was named after George Everest, a former Surveyor General of India.',
 'answer': '29,032',
(後略)
```

上記のケースに当たらない一般的な質問の場合は、JSONに`answer_box`の項目がありません。そこで、生の検索結果の入っている項目をAgentが参照し、そこから質問の答えとなりうる情報を抽出します。具体的には、先頭の検索結果の冒頭部分のテキストをAgentは抽出します。

```
'Its elevation (snow height) of 8,848.86 m (29,031 ft 8+1⁄2 in) was most recently established in 2020 by the Chinese and Nepali authorities. Mount Everest. Mount ...'
```

## 検索結果を含むプロンプト

検索結果が得られた時点での行動履歴のテキストは下記のようになっています。先に述べた検索結果は行動の結果を観察したものにあたるため、`Observation: `の項目に記載する必要があります。最後の項目の`Thought: `は、この文を見たLLMに思考を促すためのものです。

```
 I need to find the height of Mount Everest
Action: Search
Action Input: Height of Mount Everest # ここまでは、LLMからLangChain Agentに渡した内容
Observation: 29,032 # ここからは、逆にAgentからLLMに渡した内容
Thought: 
```

前述の通り、行動履歴がある場合はプロンプトのフォーマットにそれを書き加えることになっているため、次にAgentがLLMに渡すプロンプト全体は下記のようになります。

```
Answer the following questions as best you can. You have access to the following tools:

Search: useful for when you need to answer questions about current events
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: エベレストの標高を教えて
Thought: I need to find the height of Mount Everest
Action: Search
Action Input: Height of Mount Everest
Observation: 29,032
Thought: 
```

これを見たLLMは下記の出力をAgentに返します。

```
 I now know the final answer
Final Answer: Mount Everest is 29,032′ tall.
```

ファイナルアンサーが得られたので、Agentは`"Mount Everest is 29,032′ tall."`という回答文をユーザプログラムに返して終了します。

## 処理フローのおさらい

先に記載したAgentの処理フローのシーケンス図を再掲します。これまでの説明を踏まえて改めて図を見ると、処理の流れが掴みやすいのではないかと思います。

```mermaid
sequenceDiagram
    participant UserProgram as ユーザプログラム
    participant LangChain as LangChain
    participant LLM as LLM
    participant SerpAPI as SerpAPI

    UserProgram->>LangChain: Agent.run(入力文)
    LangChain->>LLM: 回答用フォーマット + 入力文
    LLM-->>LangChain: 入力への見解(Thought), 行動内容(Action=Search, Action Input=クエリテキスト)

    LangChain->>SerpAPI: Google検索API(クエリテキスト)
    SerpAPI-->>LangChain: 検索結果のJSON
    LangChain->>LLM: 回答用フォーマット + 入力文 + JSONから抽出した検索結果
    LLM-->>LangChain: 見解(Thought), 行動内容(Action=Final Answer, Action Input=回答文)
    LangChain->>UserProgram: 回答文
```

[先ほどのフォーマット](https://qiita.com/kzkymn/items/de4e3a17db6e5363705d#%E3%83%97%E3%83%AD%E3%83%B3%E3%83%97%E3%83%88%E3%81%AE%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88)はChatGPTに対しても使えるので、プログラムを書かなくてもブラウザから手動でReActの振る舞いを確認することもできます。
Agentのものとは別のフォーマットを使うとどういう結果が得られるか検証したい場合には、手動のほうがお手軽かもしれません[^6]。

[^6]: チャット履歴が使えるので、ChatGPTを使う場合はフォーマット全体を逐一送信し直す必要は必ずしもなく、LLMの指示したActionに対するObservationと、次のThoughtをLLMに促す部分だけを送信すればよいはずです。

………すみません、**[こんなキャンペーン](https://qiita.com/official-events/b80ebe828fa453edf260)があるので、最後に取ってつけたようにChatGPT絡みの話をしてしまいました[^7]**……

[^7]: LangChainのAgentが呼ぶLLMは基本、ChatGPTのバックエンドで動いているものなので、本記事の話はそもそもChatGPTと無関係ではないのですが。

# 所感

最後に、ReActの論文を見て思ったことをいくつか列挙します。

- 検索が使える分だけ**ReActはCoTよりも質問応答タスクに有効だと思っていましたが、そうでなかった**のは意外でした。
- **強化学習的なタスクにReActが有効と分かった**ので、そちらの応用も実務で検討してみたいです。
  - 例のAIポートピア連続殺人事件をやらせてみてもいいかも…本記事で紹介したプロンプトの内容を変えて、ファイナルアンサーは出さずに常に次の行動を出し続けるようにすれば、進行のヒントになりうる行動を述べるようになります。ただし、GPT-4は必須です。
  - このやり方で、被害者と関連する人物を探しに京都に行くところまで進められました。
  - 行動の説明を短文にするように細かく指示すれば、プレイしやすいかもしれません。
- 検索などの外部ツールからもらえる`Observation`の内容がお粗末なら、それを読み込んだLLMからの出力の質も悪くなるはず(= Garvage in, garvage out)
  - 逆に言えば、**質問文を読んだLLMが適切な検索クエリを作れることがまず大事**ということになると思います。バックエンドのLLMにはなるべく優秀なものを使ったほうが良さそうです。
    - そういえば、BingのAI Chatが微妙な結果を返すときは大概クエリが怪しいように思います…
    - 検索結果の引用の仕方も回答の質に影響するはずです。LangChainのAgentのように検索結果の冒頭一部を引用するだけ、という作りはちょっと心もとない気がします。
- ReActとCoT-SCの併用は単純なアルゴリズムなので、実装してみたい… 

# おまけ

下記は、本記事を書くにあたって役立った情報です。

## 他のReAct解説記事

下記の記事もReAct論文の解説です。本記事で触れていない事柄にも言及しておられますので、あわせて読んでいただけるとよいのではと思います。

https://zenn.dev/ryo1443/articles/d727b2b9a6d08c

## ChatGPTでシーケンス図

実は…**先のシーケンス図は下記の記事を参考にChatGPTにたたき台を作成させた**ものです。

https://dev.classmethod.jp/articles/chatgpt-mermaid-diagram/

「本当か？」と思った人は是非下記のプロンプトをGPT-4をバックエンドにしたChatGPTに入力してMermaid構文が得られることを確認してください。
その中身がどうなっているかはQiitaのエディタなど、Mermaid対応のmarkdownビューアで是非とも確認してみてください。

```
次の処理を図解するシーケンス図をMermaid構文で書いて下さい。

1. ユーザプログラムはLangChainのAgent.runメソッドを実行する。runの引数は言語モデルに渡す入力文である。
2. LangChainはLLMに対し、回答用フォーマットと入力文を含む文字列を渡す。
3. LLMはフォーマットに従い、文章に対する見解(Thought)とそれに伴う行動内容(Action, Action Input)を返す。
4. ActionのカテゴリがFinal AnswerならばLLMの返した文字列の中にあるファイナルアンサー部分をユーザプログラムに返して終了。さもなくば、Actionに書かれた行動を実行する。例えば ActionがSerpAPIによるGoogle検索ならば、SerpAPIをコールして検索結果のテキストを受け取る。
5. もし4で処理終了でなければ、LangChainは行動の結果得たテキストを、上記2に示した回答用フォーマットおよび入力文とともにLLMに渡す
6. 3に戻る
```

## ReActでAIポートピア連続殺人事件をやってみる。

最初の質問は例えば下記の形式にします。

```text
Read the question and tell me what to do next. You have access to the following tools:

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. The description must be Japanese imperative form(e.g. 聞き込みをしろ) and must be no less than 3 and no more than 8 words in Japanese. The rule is that a single-sentence order cannot give instructions concerning more than one person. For example, 「文江に聞け」 and 「小宮に聞け」  are allowed by the rule, but 「文江と小宮に聞け」 is not.
Observation: the result of the action

When I give you the question, please respond with your Thought and Action according to the format above.
I will then return the resulting Observation and the text that you and I have exchanged so far,
and you will return a Thought and the necessary Action that you have considered based on that.

When the task is cleared, I will simply send you the string "Cleared!"

Begin!

Question: You are a detective investigating a murder case. You are in the streets of Kobe. What should you do next?
Thought: 
```

この質問に対しては、例えば以下のような回答が得られます。

```
 I should gather information and talk to the locals.
Action: 周辺の人々に話を聞け
```

「周辺の人々に話を聞け」では、残念ながら現在(23/4/25)のAIポートピアには通じないので「聞き込みをしろ」と言い換えます。そうすると、とある人物が事件当日から失踪したことがわかるので、その情報を含めたプロンプトをLLMに送信します。今後の送信には下記のフォーマットを使います。

```text
Observation: 先のアクションを通じて分かったことをここに書く
Thought:
```

Observationのところに書く内容は例えば、聞き込みで知った人物の職業・家族構成などの情報になります。ここは、英語で書いたほうがやり取りが安定しますが日本語でも構いません。ただし、ChatGPTの機嫌によっては「英語で書いて」と言われることがあります。

こうしたプロンプトのやり取りを続けると、ストーリーを進められるはずです。

![portpia.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/98e6833d-bb68-d725-6a24-e6a9838bf248.png)