LangChainのOpenAI Functions AgentとReAct Agent

LangChainでTool呼ぶAgentとしてどちらの方がいいのだろう、と気になって調べました。
**OpenAI Functions Agentは、品質良いけど使えるモデルが限定**(Open AIのみ？)。
**ReAct Agent は品質劣るけど全(?)モデルで使える**

# 出典
OpenAI Functions Agentは[Using OpenAI Functions Agent](https://python.langchain.com/v0.2/docs/integrations/toolkits/python/#using-openai-functions-agent)に書かれて通り
> This is probably the most reliable type of agent, but is only compatible with function calling

一方で、ReAct Agentは[Using ReAct Agent](https://python.langchain.com/v0.2/docs/integrations/toolkits/python/#using-react-agent)に書かれている通り
> This is a less reliable type, but is compatible with most models

OpenAI Functions Agentは、専用の訓練をしているらしく、redditにも同じことが書いてあります。

https://www.reddit.com/r/LangChain/comments/178lhnc/openai_functions_vs_langchain_react_agents/

# Prompt Template比較
LangSmithのPrompt Templateの比較です。

## [openai-functions-template](https://smith.langchain.com/hub/welcome/openai-functions-template)
シンプルなPromptです。 Toolsの情報はPromptと別に渡します。
`{instruction}`は、[Using OpenAI Functions Agent](https://python.langchain.com/v0.2/docs/integrations/toolkits/python/#using-openai-functions-agent)の例だと以下を使用。
> You are an agent designed to write and execute python code to answer questions.
You have access to a python REPL, which you can use to execute python code.
If you get an error, debug your code and try again.
Only use the output of your code to answer the question. 
You might know the answer without running any code, but you should still run the code to get the answer.
If it does not seem like you can write code to answer the question, just return "I don't know" as the answer.

```:langchain-ai/openai-functions-template
SYSTEM
{instructions}

PLACEHOLDER
chat_history

HUMAN
{input}

PLACEHOLDER
agent_scratchpad
```

## [react-agent-template](https://smith.langchain.com/hub/welcome/react-agent-template)
やはり、汎用的な分だけ複雑なPromptです。[openai-functions-template](https://smith.langchain.com/hub/welcome/openai-functions-template)と違ってToolsの情報がPrompt内にあります。

```:langchain-ai/react-agent-template
{instructions}

TOOLS:
------

You have access to the following tools:

{tools}

To use a tool, please use the following format:
```` ```
Thought: Do I need to use a tool? Yes
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
```` ```

When you have a response to say to the Human, or if you do not need to use a tool, you MUST use the format:

```` ```
Thought: Do I need to use a tool? No
Final Answer: [your response here]
```` ```

Begin!

Previous conversation history:
{chat_history}

New input: {input}
{agent_scratchpad}
```

# 実行時比較
本当は違いがわかりやすいものがあればいいのですが、時間をかけたくなかったので、自分が最近やったものをLangSmithで表示。

## OpenAI Functions Agent
[openai-functions-agent](https://python.langchain.com/v0.2/docs/templates/openai-functions-agent/)の記載どおりでChainを組みました。

全体のInputとOutput
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/f3b41d2d-5226-8984-ee3a-f400dca5549b.png)

各Step。真ん中にtavily_searchを介しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/3e5c2de4-46a7-c267-416c-1eecf1736feb.png)

1度目のChatOpenAI呼出。Promptと別にToolの情報を渡しているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/73336b88-77a4-f4a6-0e56-af4ddd5541e4.png)

2つ目は普通の検索ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/9d3c004a-fa25-a902-f76a-37b68d82b8be.png)

2度目のChatOpenAI呼出。内容多いので小さい・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/4ce74efa-b0fc-d719-0944-a19d02363ef2.png)

## ReAct Agent
今度は長いです。全然Apple２Appleではないですが、ToolにWikipedia使っています。
全体のInputとOutput。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/1b6c03a3-32e6-c2ce-009a-197ae2a8bd13.png)

Step。とても長いです。望む情報を取得できずに何度か検索語句を変えてWikipediaで検索しています。そして、最後にあきらめて、GPT-4そのものが答えを出しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/fac679dd-eb7b-52d6-ac11-fe2a49cf4ee2.png)

全ては省略しますが、いくつかのログをのせます。
1回目のChatOpenAI呼出。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/db197f52-a9d1-1c0d-c78b-d595c1385df0.png)

２回目のChatOpenAI呼出。検索語句を変えています。この呼出でも良い回答が無かったので、GPT-4が回答出しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/79553e53-3d08-3052-3f89-1ff7c6d7dee1.png)


プログラムです。Chainlitで画面作っています。

```python
import os

import chainlit as cl
from langchain import hub
from langchain.agents import AgentExecutor, create_react_agent
# from langchain.globals import set_debug
from langchain.schema.runnable.config import RunnableConfig
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain_openai import ChatOpenAI

@cl.on_chat_start
async def on_chat_start():
    wiki = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper(lang='ja', features="html.parser"))
    tools = [wiki]

    prompt = hub.pull("hwchase17/react")
    model = ChatOpenAI(model_name=os.environ["OPENAI_API_MODEL"], streaming=True)
    print(model)

    # Agentを作成
    agent = create_react_agent(model, tools, prompt)
    # AgentExecutorを作成
    agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

    cl.user_session.set("agent_executor", agent_executor)


@cl.on_message
async def on_message(message: cl.Message):
    runnable = cl.user_session.get("agent_executor")

    msg = cl.Message(content="")

    async for chunk in runnable.astream(
        {"input": message.content},
        config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()]),
    ):
        # Agent Action
        if "actions" in chunk:
            for action in chunk["actions"]:
                print(f"Calling Tool: `{action.tool}` with input `{action.tool_input}`")
        # Observation
        elif "steps" in chunk:
            for step in chunk["steps"]:
                print(f"Tool Result: `{step.observation}`")
        # Final result
        elif "output" in chunk:
            print(f'Final Output: {chunk["output"]}')
            await msg.stream_token(chunk['output'])
        print("---")
    await msg.send()
```

関連するバージョン情報です。

```toml:project.toml
python = "^3.12"
langchain = "^0.2.1"
langchain-openai = "^0.1.8"
langsmith = "^0.1.65"
chainlit = "^1.1.202"
langchainhub = "^0.1.17"
wikipedia = "^1.4.0"
langchain-community = "^0.2.1"
```