LangChain Agentの核となる考え方は、LLMを自動的に考える脳として使用し、意思決定を行い、さまざまなアクションを実行して最終的に目標のタスクを達成することです。

ヒント:開発の観点からは、これは事前にさまざまなAPIを開発し、その後にエージェントにタスクを与えて、LLMによってどのAPIを呼び出してタスクを完了するかを分析させるということを意味します。

LangChain Agentが解決しようとする問題をよりよく理解するために、例を考えてみましょう。

例えば:

もしも「Dockerを本番展開ソリューションとして使用できるかどうか」を調査したい場合、まずBaiduで「Dockerの紹介」を検索し、検索結果を閲覧し、「Docker展開の利点と欠点」をさらに検索して結果を閲覧し、という具合に、結論に至るまで続けます。

LangChain Agentは、このプロセスをシミュレートすることを目指しています。一連のツール(例: Baidu検索、URLコンテンツ抽出ツール)を事前にパッケージ化し、エージェントに「Dockerを本番展開ソリューションとして使用できるか?」という目標タスクを与えると、エージェントはLLMを呼び出すためのプロンプトを構築します。このタスクを達成するために次に実行するアクション(つまり、どのツールを呼び出すか)を実行します。AIは呼び出すツールを返し、コードはこのツールを実行し、その結果をAIに返し、次にどのツールを実行するかを尋ねます。このプロセスを繰り返すことで、前述のタスクが完了します。

ヒント: GPTモデルのリリース以来、これは爆発的な能力であり、LLMを脳として行動させ、さまざまな開発したAPIを呼び出すことが可能になりました。これにより、LLMの能力が大幅に向上します。現在、この機能は実験段階にあり、LLMを繰り返し呼び出すため、かなりのトークンを消費します。タスクを完了するのに、数万のトークンを数分で消費することもあります。お金を節約したい場合は、まずLLMに簡単な論理判断のタスクを実行させることをお勧めします。

核となる概念

それでは、関連するコンポーネントと概念を紹介します。

エージェント

エージェントは、我々を代表して行動し、意思決定を行うための私たちのアシスタントとして理解できます。LangChain Agentの基盤的な実装において、次のアクション(またはAPI呼び出し)はLLMを介して決定されます。ReActモードはAIの意思決定プロセスを説明します。興味のある方はこれをさらに探求できます。

LangChainは、さまざまなシナリオに応じたいくつかのタイプのエージェントを提供しています。

ツール

ツールは、LLMの機能を拡張するために設計されたさまざまな機能的なAPIであるAPIとして理解されると思います。LLMは、どの具体的なAPIを呼び出してタスクを完了するかを決定します。

ツールキット

ツールキットは通常、LLMに1つまたは2つのツールだけでなく、複数のツールを提供して、LLMがタスクを達成する際により多くの選択肢を持つようにします。

AgentExecutor

プロキシエグゼキューターは、LLMによって選択されたツール(API)を実行する責任があります。このランタイムの擬似コードは以下の通りです。

next_action = agent.get_action(...)
while next_action != AgentFinish:
    observation = run(next_action)
    next_action = agent.get_action(..., next_action, observation)
return next_action

実行プロセスは複雑ではありませんが、エグゼキューターは以下の詳細な問題を処理します:

  1. エージェントが存在しないツールを選択する場合の処理
  2. ツールエラーの状況の処理
  3. エージェントがツール呼び出しとして解決できない出力を生成する状況の処理
  4. デバッグの問題

クイックスタート

このセクションでは、LangChain Agentの基本的な使用方法を紹介します。

1. LLMのロード

まず、エージェントを制御するために使用する言語モデル(LLM)をロードしましょう。

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

2. ツールの定義

次に、エージェントが呼び出すためのいくつかのツールを定義します。私たちは、入力単語の長さを計算する非常にシンプルなPython関数を記述します。

from langchain.agents import tool

@tool
def get_word_length(word: str) -> int:
    """単語の長さを返します。"""
    return len(word)

get_word_length.invoke("abc")

注意: 関数のコメントは非常に重要です。それらはLLMにそれらを呼び出すことで解決できる問題を教えます。get_word_length関数は、単語の長さを計算できることをLLMに伝えます。

3

一連のツールを定義します

tools = [get_word_length]

3. プロンプトの作成

さて、プロンプトを作成しましょう。OpenAI Function Callingはツールの使用に最適化されているため、理由や出力形式に関する指示はほとんど必要ありません。inputagent_scratchpadの2つの入力変数しかありません。inputはユーザーの入力質問を表し、agent_scratchpadはエージェントの呼び出し手順のプレースホルダーであり、ツールの呼び出しコマンドの実行時にプロンプトテンプレートに挿入されます。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "あなたは非常に強力なアシスタントですが、現在の状況を理解していません。",
        ),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ]
)

4. ツールをLLMにバインドする

エージェントがどのツールを使用できるかはどのように知るのでしょうか?

これはOpenAIのツール呼び出し機能(多くのモデルが類似の機能をサポートしています)に依存しています。定義されたツール呼び出し形式をモデルに教えるだけです。

llm_with_tools = llm.bind_tools(tools)

5. エージェントの作成

前のコンテンツを統合したので、プロキシプログラムの作成に進むことができます。前段項の中間ステップ(プロキシアクション、ツールの出力)を入力メッセージにフォーマットするための実用関数と、出力メッセージをプロキシアクション/プロキシエンドに変換するための別の実用関数をインポートします。

from langchain.agents.format_scratchpad.openai_tools import format_to_openai_tool_messages
from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser

agent = (
    {
        "input": lambda x: x["input"],
        "agent_scratchpad": lambda x: format_to_openai_tool_messages(x["intermediate_steps"]),
    }
    | prompt
    | llm_with_tools
    | OpenAIToolsAgentOutputParser()
)

エージェントエグゼキューターの定義

from langchain.agents import AgentExecutor

agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

次の例を通じてプロキシプログラムの操作をデモンストレーションします:

list(agent_executor.stream({"input": "単語'eudca'には何文字ありますか?"}))

エージェントの出力ログ例

> 新しいエージェント実行チェーンに入る...

呼び出し: パラメータ`{'word': 'educa'}`を使用して`get_word_length`を実行

単語"educa"には5文字あります。

> 実行チェーンが完了しました。

この例を通じて、プロキシプログラムの完全なプロセスをデモンストレーションしました。

エージェントにメモリ機能を追加する

エージェントが以前の会話を記憶するようにしたい場合、実際には非常に簡単です:AIによって返されるコンテンツをプロンプトに挿入し、一緒にAIに送信すればよいだけです。

プロンプトテンプレートの修正

以下で、会話履歴のテンプレート変数を含めるようにプロンプトテンプレートを修正します。

from langchain.prompts import MessagesPlaceholder

MEMORY_KEY = "chat_history"
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "あなたは素晴らしいアシスタントですが、単語の長さを計算するのが得意ではありません。",
        ),
        MessagesPlaceholder(variable_name=MEMORY_KEY),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ]
)

エージェントプロセスの定義の修正

以下のように、プロンプトテンプレートに会話履歴データを提供するようにエージェントプロセスの定義を修正します。chat_historyパラメータのハンドリングを追加します。

agent = (
    {
        "input": lambda x: x["input"],
        "agent_scratchpad": lambda x: format_to_openai_tool_messages(
            x["intermediate_steps"]
        ),
        "chat_history": lambda x: x["chat_history"],
    }
    | prompt
    | llm_with_tools
    | OpenAIToolsAgentOutputParser()
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

エージェントを呼び出す際に会話履歴データを提供する

chat_history = []

input1 = "「educa」という単語には何文字ありますか?"
result = agent_executor.invoke({"input": input1, "chat_history": chat_history})
chat_history.extend(
    [
        HumanMessage(content=input1),
        AIMessage(content=result["output"]),
    ]
)
agent_executor.invoke({"input": "この単語は本当に存在しますか?", "chat_history": chat_history})

実際のビジネスシナリオでは、会話履歴をデータベースに保存し、ビジネス要件に応じて必要に応じてプロンプトにデータを挿入することができます。

ヒント: 大規模モデル(LLM)のメモリ機能は現在、主に過去の会話内容をプロンプトに挿入し、LLMに提出することで実現されています。LangChainは単にいくつかのカプセル化を提供しています。使用しないことを選択し、会話履歴を手動でプロンプトテンプレートに連結することができます。