LangChainにおけるReActフレームワークの実装と対話の流れ

# 概要
- LangChainにおける、Few-shotによるReActフレームワークの実装である、`REACT_DOCSTORE`を通してReActが実際にどのようにLLMと対話しているのかをコードを読みながらシーケンス図に書き起こして調査しました。
特に、筆者は以下が不明だったため、整理が必要だったものとなります。
    - ReActのプロンプトは実際にはどのようなものを用意すればよいのか？
    - 対話を進めるごとにLLMに具体的にどのようなプロンプトを送っているのか？
    - ReActを用いたLLMとの対話の流れをどうやってコントロールしているのか？

# 対象とする読者
- ReActについてなんとなく概念は知っているが、LangChainを使って実際に手を動かそうとしたときに、どう動いているのかイメージが付かない人。

- LangChainを用いてReActを使う予定であり、プロンプトなどをカスタマイズしてみたいが、標準で提供されている機能の流れをこれから掴もうとしている人

- シーケンス図が分かる人

# ReActのプロンプトは実際にはどのようなものを用意すればよいのか？
ReActの論文については以下の通りとなります。LLMに論理的思考をシミュレーションさせ、目的の解答を求めるための手法の1つであり、LLMが外部ツールなどを用いながら、Thought（思考）、Action（実行）、Observation（観察）を繰り返して解答を求めるフレームワークです。

https://arxiv.org/abs/2210.03629

- LangChainには、ReActフレームワーク実行用に標準で提供されているエージェントとして、
`initialize_agent`で指定できる2種類の`AgentType`が実装されています。
    - `ZERO_SHOT_REACT_DESCRIPTION`
        - 0-shotでReActを実行するフレームワーク。ReActの流れと、利用できるツールをLLMに教え込ませ、問題を解かせる方式。
          [実際のコード](https://github.com/hwchase17/langchain/blob/master/langchain/agents/mrkl/prompt.py)の通り、ReActの実行方法を初期のプロンプトとして渡している。
        - 本題以外に使用する文字数がReActの説明文だけになるので、Few-shotに対し、比較的余分な文字数が必要なくなる可能性がある。
            - ReActの思考を繰り返すことにより、いずれにせよ文字数がどんどんかさましされていくことに注意。

    - `REACT_DOCSTORE`
        - Few-shotのプロンプトを用いて、Wikipediaを検索しながら質問の解答を見つけるフレームワーク。
        ReActの流れそのものをいくつかのケースとして入力し、問題を解かせる。
        [実際のコード](https://github.com/hwchase17/langchain/blob/master/langchain/agents/react/wiki_prompt.py)では、サンプルとして6例ほど、ReActの流れを入力している。
        - Few-shotでReActの流れを記述するため、0-shotに対し、比較的初期時点の文字数が多くなりやすく、その分本題を解かせるために入力できる文字数が少なくなる可能性がある。
:::note info
**0-shotとFew-shotの選択について**
OpenAIのベストプラクティスにある通り、まずは0-shotを用いて検証し、
想定通りの結果が出せない場合はfew-shotで検証するのが良いと思われます。
:::

https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-openai-api#h_eae065300d


# 対話を進めるごとにLLMに具体的にどのようなプロンプトを送っているのか？
- [LangChainの公式ドキュメントにおけるReActのページ](https://python.langchain.com/docs/modules/agents/agent_types/react_docstore.html)では、以下のような記載があります。
```python:コード
from langchain import OpenAI, Wikipedia
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
from langchain.agents.react.base import DocstoreExplorer
docstore=DocstoreExplorer(Wikipedia())
tools = [
    Tool(
        name="Search",
        func=docstore.search,
        description="useful for when you need to ask with search"
    ),
    Tool(
        name="Lookup",
        func=docstore.lookup,
        description="useful for when you need to ask with lookup"
    )
]

llm = OpenAI(temperature=0, model_name="text-davinci-002")
react = initialize_agent(tools, llm, agent=AgentType.REACT_DOCSTORE, verbose=True)

question = "Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?"
react.run(question)
```
```:出力
> Entering new AgentExecutor chain...

Thought: I need to search David Chanoff and find the U.S. Navy admiral he collaborated with. Then I need to find which President the admiral served under.

Action: Search[David Chanoff]

Observation: David Chanoff is a noted author of non-fiction work. His work has typically involved collaborations with the principal protagonist of the work concerned. His collaborators have included; Augustus A. White, Joycelyn Elders, Đoàn Văn Toại, William J. Crowe, Ariel Sharon, Kenneth Good and Felix Zandman. He has also written about a wide range of subjects including literary history, education and foreign for The Washington Post, The New Republic and The New York Times Magazine. He has published more than twelve books.
Thought: The U.S. Navy admiral David Chanoff collaborated with is William J. Crowe. I need to find which President he served under.

Action: Search[William J. Crowe]

Observation: William James Crowe Jr. (January 2, 1925 – October 18, 2007) was a United States Navy admiral and diplomat who served as the 11th chairman of the Joint Chiefs of Staff under Presidents Ronald Reagan and George H. W. Bush, and as the ambassador to the United Kingdom and Chair of the Intelligence Oversight Board under President Bill Clinton.
Thought: William J. Crowe served as the ambassador to the United Kingdom under President Bill Clinton, so the answer is Bill Clinton.

Action: Finish[Bill Clinton]

> Finished chain.
```

ここでの出力は、まるでLLMが1回で結果を出力しているかのように見えますが、
見やすさのためにユーザー用に出力しているだけのため、実際にはLLMと何度かやりとりしている結果をまとめたものとなります。

では、実際にLLMとはどのようにデータのやり取りを行っているのかをイメージアップするため、
LLMと3回ほどやり取りする例を以下に記述しました。
シーケンス図のようなポンチ絵にして記述してみたものとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2249065/1e2fa0fd-ee7d-dec2-160b-ae3cb020ce28.png)




上記における特徴は以下と考えています。
- LLMに対する情報の入力はあくまでステートレス。
    - ReActの実行中に、過去のReActの履歴を改変して送ることも可能。
- ReActの試行を繰り返せば繰り返すほど、LLMに送信する文字数は増えるが、LLMが1度に処理できる文字数（トークン数）に制限があるため、ReActの繰り返しは制限があるため注意が必要。


# ReActを用いたLLMとの対話の流れをどうやってコントロールしているのか？
こちらに対する回答の主旨は、LangChain内におけるクラス間のやり取りをイメージアップすることにより、自分でカスタマイズするための情報を得られることであると想定しています。

LangChainにおける主要クラス間の関連は以下の通りとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2249065/dcdea37e-d962-8041-ea87-47ebff2b8e36.png)
（使い勝手からOpenAIではなく、AzureChatOpenAIを使っている例になっています。）


また、上述のシーケンス図のポンチ絵をさらにLangChainのモジュールを用いて細かめに表現したものは以下となります。
`AgentExecutor`の`_call`内のLoopがミソとなると思います。
（参考のために、各クラスが継承する親クラスも追記してあります。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2249065/38f37daa-cc73-4f3f-bd46-d6297a51f3af.png)




# 確認時点のLangChainのバージョン

https://github.com/hwchase17/langchain/tree/master

- v0.0.198


# 参考資料
- LangChainにおけるReActの説明は以下となります。

https://python.langchain.com/docs/modules/agents/agent_types/react