🐕

【LangChain】エージェントを作って理解する(旧initialize_agentの書き換えも)

2024/03/20に公開

AIエージェントについて、pineconeの記事も非常に参考になります。
https://www.pinecone.io/learn/series/langchain/langchain-agents/

ただ、上記のサイトで紹介されている"initialize_agent"を実行すると非推奨と出るように、Langchain0.1では別の書き方が推奨されます。
(もちろん'zero-shot-react-description'もなくなっています)

エージェントやツールの概念は参考にできるのですが、書き方を0.1に合わせつつ、エージェントの概念を理解しました。

create_react_agent

langchainのdeprecated一覧を見ると、zero-shot-reactionの代わりにcreate_react_agentを使えと書いてあります
https://python.langchain.com/docs/changelog/langchain#deprecated

背景にある思想として、langchain0.0時代はエージェントを構築する際の設定としてzero-shot-reactionを設定していましたが、langchain0.1ではそもそも「会話をしないエージェント」としてcreate_react_agentを使ってエージェントを構築していると、私は解釈しています。

from langchain import hub
from langchain.agents import load_tools, AgentExecutor, create_react_agent
from langchain_community.llms import OpenAI

# ツールを作成(今回のエージェント実行には不要ですが、toolsは空っぽにできないので適当なツールをロードします)
tools = load_tools(['llm-math'],llm=llm)

prompt = hub.pull("hwchase17/react")

model = OpenAI()

agent = create_react_agent(model, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)

agent_executor.invoke({"input": "what is (4.5*2.1)^2.2?"})

#->{'input': 'what is (4.5*2.1)^2.2?', 'output': '139.94261298333066'}

少し話は逸れますが、冒頭のpineconeのLangChainハンドブックでは、LLMは計算が苦手とあります。

大言語モデル(LLM)は信じられないほど強力ですが、「最も愚かな」コンピューター プログラムが簡単に処理できる特別な能力がありません。ロジック、計算、検索は一般にコンピューターが得意とする分野の例ですが、LLM は苦戦しています。

コンピューターは信じられないほど複雑な数学の問題を解決できますが、GPT-4 に4.1 * 7.9の答えを教えてもらうと、失敗します。

GPT-4 に単純な計算の実行を依頼すると、多くの場合、不正確な答えが得られます。単純な計算機でもこれと同じ計算を問題なく実行できます。
簡単な計算によると、答えは小数点第 3 位を四捨五入して19.357になります。単純な電卓プログラムでこれができるのに、信じられないほど洗練された AI エンジンが失敗するというのは興味深いことではないでしょうか?

実際、2023年まではGPT-4でも単純な計算をミスしていましたが、2023年11月や2024年1月のアップデートで、さすがに単純計算はちゃんと答えを出してくれるようになりました。

今となっては単純計算のために、エージェントに計算系ツールを渡す必要性は少ないかもしれません。

さて、上記で作成したエージェントは計算のツールを与えただけですので、計算以外のことは何もできません。
試しに"おはよう"と言ってみるとエラーが返されます。
(ちなみに"おはよう"が日本語の挨拶と判断できているのは、LLM に渡したmodelがそう解釈してくれているおかげです)

agent_executor.invoke({"input": "おはよう"})

#->OutputParserException: Could not parse LLM output: ` You should greet someone with "good morning" in Japanese.

react_agentには会話履歴を与えることもできないので、会話もできません。
会話を成り立たせるには、次に示すopenai-tools-agentを使ってエージェントを構築する必要があります。

create_openai_tools_agent

基本的な書き方(propmt、model、toolsを渡すところやexecutorを実行するところ)はreact_agentと同じです。

from langchain import hub
from langchain_community.chat_models import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent

prompt = hub.pull("hwchase17/openai-tools-agent") # 変化点
model = ChatOpenAI()
tools = tools

agent = create_openai_tools_agent(model, tools, prompt) # 変化点
agent_executor = AgentExecutor(agent=agent, tools=tools)

agent_executor.invoke({"input": "what is (4.5*2.1)^2.2?"})
#-> {'input': 'what is (4.5*2.1)^2.2?',
# 'output': 'The result of (4.5 * 2.1) ^ 2.2 is approximately 139.94.'}

agent_executor.invoke({"input": "hi"})
#-> {'input': 'おはよう', 'output': 'おはようございます!何かお手伝いできることがありますか?'}

react_agentでは"おはよう"と挨拶してもエラーが返される塩対応でしたが、tools_agentでは会話を成立させようという返答が返ってきます。

以前の記事で解説した通り、LLMは人間がやるように過去の会話を記憶して返答しているわけではなく、過去の会話履歴と新たな質問をセットにして投げかけることで、あたかも会話を続けているように見せています。

from langchain_core.messages import AIMessage, HumanMessage
agent_executor.invoke(
    {
        "input": "What's is this?",
        "chat_history": [
            HumanMessage(content="Hi. I am Ken"),
            AIMessage(content="Hi, I am Tom. This is a pen"),
        ],
    }
)

#-> {'input': "What's is this?",
# 'chat_history': [HumanMessage(content='Hi. I am Ken'),
#  AIMessage(content='Hi, I am Tom. This is a pen')],
# 'output': 'This is a pen.'}

agent_scratchpad

tools_agentには、chat_historyの他にもう一点大事な概念として、agent_scratchpadがあります。

そもそも、create_openai_tools_agentでエージェントを作る時に渡したpromptは何をしているのでしょうか。
以下のコードでプロパティを読み取ってみましょう

prompt = hub.pull("hwchase17/openai-tools-agent")

prompt.dict()['input_types']

プロパティとして、'chat_history'と'agent_scratchpad'が作られていることがわかります。

※ちなみにreact_agentを作成した時に作成したhub.pull("hwchase17/react")promptでは、プロパティが何もないことがわかります。react_agentは会話をする必要がないのですから当然ですね

このagent_scratchpadとは何の情報を入れとくものなのでしょうか。
一言で説明すると、「中間エージェントのアクションとツールの出力メッセージ」を保存しておくものです。

エージェントは、各ツールを使ってネットから検索したり、集計計算したり、いろいろ作業をしています。
ネット検索で得られた結果を、次の計算工程に入れる時の情報受け渡しにagent_scratchpadを使っていると解釈しています。

(正確には、人間がエージェントの作業工程を検証するときにscratchpadを使うためなんですが、わかりやすく擬人化するためにこう解釈しています)

もしpromptをhubを使わずにテンプレートから作成するなら以下のようになります

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a helpful assistant"),
        MessagesPlaceholder("chat_history", optional=True),
        ("human", "{input}"),
        MessagesPlaceholder("agent_scratchpad"),
    ]
)

LLMが内部的に捌く情報である「chat_history」と「agent_scratchpad」は、MessagesPlaceholderというクラスで構築しています。

Discussion