【LangChainと外部APIの連携】 Agents×Tools完全攻略 (v0.0.165)

# 完成品

## デモ動画

![demo-ReAct.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/402201a1-f904-0f63-0eee-35755acbdf57.gif)


## 全体像

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/f06f87d7-2452-4adf-b3a4-c6413adcd00c.png)



## ソースコード

- Webサーバ (Flaskで簡易的なものを構築)

```py:
# Webサーバ (notebook外で実行)
from flask import Flask, request
import hashlib

app = Flask(__name__)

# チャット・メールなどを含む社内情報を収集し、トピックごとに分類されたデータをWebサーバが保持していると仮定。

topics = {
    "プロジェクト管理の効率化" : "チームのプロジェクト管理プロセスを改善し、効率的なタスク管理とスケジュール管理を実現するための施策は、ｘｘｘｘｘｘ",
    "技術トレンドの研究と導入" : "最新の技術トレンドを追い、適切な案件においてその導入を検討し、競争力を高める。現状はｙｙｙｙｙｙ",
    "チームメンバーのスキル開発": "チームメンバーのスキル開発を促進し、トレーニングや教育プログラムの導入により技術力と専門知識の向上を図る。現在、ｚｚｚｚｚｚ",
}

@app.route('/api/list_topics', methods=['GET'])
def list_topics():
    return list(topics.keys())

@app.route('/api/get_topic', methods=['POST'])
def get_topic():
    topic_name = request.form.get('topic_name')
    topic = topics[topic_name]
    
    return topic 
     
if __name__ == '__main__':
    app.run()
```


- LangChain (Agents×Tools)

```py:
from langchain.agents import initialize_agent, AgentType
from langchain.tools import BaseTool
from langchain.callbacks.manager import CallbackManagerForToolRun, AsyncCallbackManagerForToolRun
from typing import Optional, Type, Callable
from pydantic import Field
import requests
import json

# APIキーをセット (変数名はLangChain側で決められています)
from langchain.llms import OpenAI
import os
open_api_key = os.environ["openai_api_key"]

# 言語モデルを指定
llm = OpenAI(temperature=0)

# WebサーバのAPIエンドポイント
url = "http://localhost:5000/api/"


# トピック一覧を取得するツール
class ListTopicTool(BaseTool): # BaseToolクラスのサブクラスとして、クラスを自作
    name = "ListTopic"
    description = """
    Retrieve a list of topics. 
    If a user is requesting a list of topics, Use this functionality.
    For example, the question is about List of Topic, like 'What are the topics for this week?' or 'What kind of topics are available?'"
    """
    
    # エンドポイントにGETリクエストを送信
    def _run(self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
        response = requests.get(url + "list_topics")
        topics = response.json()
        return topics
    
    # 非同期実行の関数も必須
    async def _arun(self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None) -> str:
        raise NotImplementedError("ListTopicTool does not support async")

        

def _print_func(text: str) -> None:
    print("\n")
    print(text)

# ユーザにトピック選択を促すツール
class HumanInputTool(BaseTool):
    name = "Human"
    description = """
    If you have obtained topics or if the Human`s desired "topic_name" is ambiguous, 
    you have must ask a Human which topics they are interested in. 
    The input should be a question for the Human. "
    You can also consult a Human when you feel stuck or unsure about what to do next."
    """
    
    # ユーザへの質問を表示する関数
    prompt_func: Callable[[str], None] = Field(default_factory=lambda: _print_func)
    # 入力を受け付ける関数
    input_func: Callable = Field(default_factory=lambda: input)
        
    def _run(self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
        # ユーザへの質問を表示する
        self.prompt_func(query)
        # 入力を受け付ける関数を実行
        return self.input_func()

    # 非同期実行関数の定義は必須
    async def _arun(self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None,) -> str:
        raise NotImplementedError("Human tool does not support async")

    
# 指定トピックの内容を取得するツール
class GetTopicTool(BaseTool):  # BaseToolクラスのサブクラスとして、クラスを自作
    name = "GetTopic"
    description = """
    Retrieve a specific topic requested by the user.
    This functionality is used when the user is seeking the content of a particular topic, 
    such as when they specify a topic name like "Cloud Expansion Strategy" or when they ask a question like "Tell me about skill development for young employees."
    When executing this functionality, you need to provide input in the form of a dictionary with key-value pairs. 
    The key should be "topic_name" in Japanese and the value should be the specified topic name by the user or the extracted topic name from the user's query.
    """
    
    # エンドポイントにPOSTリクエストを送信
    def _run(self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
        query = query.replace("'", '"').encode('utf-8')
        query_dict= json.loads(query)
        response = requests.post(url + "get_topic", query_dict)
        topic_content = response.text
        return topic_content
        
    # 非同期実行関数の定義は必須
    async def _arun(self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None) -> str:
        raise NotImplementedError("ListTopicTool does not support async")
            
            
# ツールを設定
tools = [
    ListTopicTool(),
    HumanInputTool(),
    GetTopicTool()
]

# エージェントを定義
agent = initialize_agent(tools, OpenAI(temperature=0), agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)

# エージェント起動
agent.run("トピックの一覧を表示し、トピックの内容を把握したいです。")
```
-----------------------

# 背景・概要

Reasoning and Act(ReAct)を目指して、LangChainを触り始めました。
寄り道してきましたが、今回ReActはReAct実装の本命である**Agents機能**を取り扱います。

今までは、LlaMaから派生したオープン言語をLangChainに組み込んで遊んでいましたが、今回はOpenAI APIを使っていきます。

APIとの連携部分が楽しすぎてあまり幅広くは触れられていません、、
（完全攻略は言い過ぎですね、、:zipper_mouth:）

--------------------

# Agents機能

ユーザが指示したことを達成するために「**思考→行動→観察**」を行ってくれる機能です。
まさにReasoning and Actですね。

「**行動**」はLangChain側で用意されているものに加え、ユーザでカスタマイズすることもできます。**Tools**と呼ばれています。
大規模言語モデルを使ったアプリケーション開発には必須そうですよね。

Microsoftから公開されている```Azure Cognitive Search × GPT```のソースコードを覗きましたが、
しっかりAgent機能が使われていましたね。(一部を記載しておきます。)

```py:app/backend/approaches/readretrieveread.py\:91行目付近
from langchain.agents import Tool, ZeroShotAgent, AgentExecutor

#####
agent_exec = AgentExecutor.from_agent_and_tools(
    agent = ZeroShotAgent(llm_chain = chain, tools = tools),
    tools = tools, 
    verbose = True, 
    callback_manager = cb_manager
)
result = agent_exec.run(q)
#####
```

https://github.com/Azure-Samples/azure-search-openai-demo/


以下は、LangChain-Agentに関するドキュメントとソースコードです。

https://python.langchain.com/en/latest/modules/agents/getting_started.html#

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



----------------------

# Tools機能
Agents機能はToolを使って目的達成を行うため、Tools機能をまとめておきます。
```Tool=Agentに付与する能力```といったイメージです。

LangChainに公式ページでは、
「**最新のｘｘに関する情報を検索して、ちょっとした計算をして**」みたいな指示を与えていました。

この時必要な能力は、「検索 & 計算」です。
そのため、検索に該当するToolと計算に該当するToolをAgentに付与する必要があります。

## 提供されているツール (一部)

公式ドキュメントに実装例のコード付きで各ツールが紹介されているので、気になる方はぜひドキュメント覗いてみてください。



### Serpapi（Serp APIを使用した検索エンジン)
>現在の出来事に関する質問に答えるための検索エンジン。検索クエリを入力します。

### Request (API連携)
>特定のサイトから特定のコンテンツを取得するためのツール。
Pythonのrequestsモジュールを使用します。GET/POSTに対応しています。

### Terminal（コマンド実行)
>ターミナルでコマンドを実行するためのツール。有効なコマンドを入力し、そのコマンドの実行結果が出力されます。subprocessを使用してコマンドを実行します。

### Google Serper（低コストのGoogle検索API
>現在の出来事に関する質問に答えるための低コストのGoogle検索API。検索クエリを入力します。serper.devのGoogle検索APIを呼び出し、結果を解析します。

### Google Search（Google検索)
>Google検索のラッパー。現在の出来事に関する質問に答えるためのツール。検索クエリを入力します。Google Custom Search APIを使用します。

### Wikipedia (Wikipedia検索)
 >Wikipediaのラッパー。人物、場所、企業、歴史的な出来事、その他の主題に関する一般的な質問に答えるためのツール。検索クエリを入力します。wikipedia Pythonパッケージを使用してMediaWiki APIを呼び出し、結果を解析します。

### Human (人間の入力)
> Agentsがユーザのクエリに回答できない場合や、
スタックした場合に人間に入力を求めます。

### Calculator（電卓)
>数学に関する質問に答えるためのツール。

### ユーザカスタムのTool
>提供されているツールだけでなく、ユーザでカスタマイズしたツールも使うことができます。
自作関数を定義し、それをキックできる。



-------------------


# [1] Agents×Toolsを試す

## 必要なもの

- Serpapi APIキー
    - https://serpapi.com
    - FreeプランでSignupしました。(要メール/電話認証です)
- OpenAI APIキー
- Pythonパッケージ
 
```:requirements.txt
openai
google-search-results
langchain
```

## まずはGetting Startedを試す
なにはともあれ触っていきます。
環境変数にAPIキーを埋め込んでjupyter notebookを起動しています。

```sh:Windowsの場合
$env:openai_api_key="your api-key"
$env:serpapi_api_key="your api-key"
jupyter notebook
```

公式ドキュメントの例にならって実行してみます。

```py:
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI
import os

# APIキーをセット (変数名はLangChain側で決められています)
open_api_key = os.environ["openai_api_key"]
serpapi_api_key = os.environ["serpapi_api_key"]

# 言語モデルを指定し、ツールをセットアップ
llm = OpenAI(temperature=0)
tools = load_tools(["serpapi", "llm-math"], llm=llm)

# エージェントに能力を付与し、Zero-Shot & ReActで機能させることを定義 
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)

# デフォルトの場合、裏側がtext-davince-003なので日本語もいけます
agent.run("東京都の人口は、日本の総人口の何パーセントを占めていますか？")
```

以下は上記のコードを実行した際の出力です。
ちゃんとReActしている:point_up:

「**思考→行動→観察**」というプロセスを経て、ユーザの指示を達成していますね。
「**ReActは推論系タスクに応用が利くよ。**」というようなことが論文に書かれていました。
たしかに、色々なツールをAgentに対して付与することで、難解なタスクも勝手に考えて解いてくれそうですよね。

```ps:
[指示]：
東京都の人口は、日本の総人口の何パーセントを占めていますか？

[Agents]：
- Thought:(思考)
    - まず、東京と日本の人口を収集する必要があるな
- Action: Search(行動)
    -「東京 人口」でSearch実行
- Observation: (観察)
    - 1396万人=13.96 million
- Thought: (思考)
    - 次は日本の人口を収集する必要があるな
- Action: Search(行動)
・
・
・
- Thought:(思考)
    - 東京の人口と日本の人口を使って割合を計算する必要があるな
- Action: Calculator(行動)
    - 13.96 / 125.50
- Observation: (観察)
    - 回答: 0.1112 (←指示達成)
- Final Answer: (回答)
    - 東京の人口は日本の総人口の11.12%を占めています。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/17b5e965-ec7e-1da7-42fc-20cf3d76f7b9.png)



**補足:```from langchain.llms import OpenAI```のデフォルトモデル**

- ソースコードを見ると、```text-davinci-003```でした

https://github.com/hwchase17/langchain/blob/master/langchain/llms/openai.py#L123

```py:OpenAIのデフォルトモデル
class BaseOpenAI(BaseLLM):
    """Wrapper around OpenAI large language models."""

    client: Any  #: :meta private:
    model_name: str = "text-davinci-003"
    """Model name to use."""
    temperature: float = 0.7
    """What sampling temperature to use."""
    max_tokens: int = 256
    """The maximum number of tokens to generate in the completion.
    -1 returns as many tokens as possible given the prompt and
    the models maximal context size."""
    top_p: float = 1
...
```

# [2] AgentとAPIを連携してみる

※Toolsの中のRequestsは今回使っていません。

Requetsについて再掲
>特定のサイトから特定のコンテンツを取得するためのツール。
Pythonのrequestsモジュールを使用します。GET/POSTに対応しています。

## ①APIエンドポイントを作る

チャット・メールなどの情報を収集し、トピック分類してまとめてくれているWebサーバを用意します。
（こちらについての詳細は次回以降まとめます。）

Agentに持たせる機能(Tool)は以下の通りとします。

- ①トピック一覧を取得する
    - Webサーバに対して、GETリクエストを送信
- ②指定したトピックを閲覧
    - Webサーバに対して、トピック名を指定した上でPOSTクエストを送信


上記機能に対応する、エンドポイントをWebサーバ側で用意しておきます。


```py: Webサーバ
from flask import Flask, request
import hashlib

app = Flask(__name__)

# チャット・メールなどを含む社内情報を収集し、トピックごとに分類されたデータをもっていると仮定。
topics = {
    # プロジェクト管理の効率化
    "Streamlining Project Management" : "チームのプロジェクト管理プロセスを改善し、効率的なタスク管理とスケジュール管理を実現するための施策は、ｘｘｘｘｘｘ",
    # 技術トレンドの研究と導入
    "Researching and Implementing Technology Trends": "最新の技術トレンドを追い、適切な案件においてその導入を検討し、競争力を高める。現状はｙｙｙｙｙｙ",
    # チームメンバーのスキル開発
    "Enhancing Team Members' Skills Development": "チームメンバーのスキル開発を促進し、トレーニングや教育プログラムの導入により技術力と専門知識の向上を図る。現在、ｚｚｚｚｚｚ",
}

@app.route('/api/list_topics', methods=['GET'])
def list_topics():
    return list(topics.keys())

@app.route('/api/get_topic', methods=['POST'])
def get_topic():
    topic_name = request.form.get('topic_name')
    topic = topics[topic_name]
    
    return topic 
     
if __name__ == '__main__':
    app.run()
```

実行確認します。

```py:
curl localhost:5000/api/list_topics
###
# ["プロジェクト管理の効率化","技術トレンドの研究と導入","チームメンバーのスキル開発"]
###

curl -X POST -d "topic_name=プロジェクト管理の効率化" http://localhost:5000/api/get_topic
###
# チームのプロジェクト管理プロセスを改善し、効率的なタスク管理とスケジュール管理を実現するための施策は、ｘｘｘｘｘｘ
###
```

## ②AgentからAPIを叩きにいく

## AgentとToolの定義例

以下はAgentとToolの定義例です。

```py:
from typing import Optional, Type
from langchain.tools import BaseTool, StructuredTool, Tool, tool
from langchain.callbacks.manager import CallbackManagerForToolRun, AsyncCallbackManagerForToolRun
import requests
import json

url = "http://localhost:5000/api/"


# トピック一覧を取得するツール
class ListTopicTool(BaseTool): # BaseToolクラスのサブクラスとして、クラスを自作
    name = "ListTopic"
    description = """
    Retrieve a list of topics. 
    If a user is requesting a list of topics, Use this functionality.
    For example, the question is about List of Topic, like 'What are the topics for this week?' or 'What kind of topics are available?'"
    """
    
    # エンドポイントにGETリクエストを送信
    def _run(self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
        response = requests.get(url + "list_topics")
        topics = response.json()
        return topics
    
    # 非同期実行の関数も必須
    async def _arun(self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None) -> str:
        raise NotImplementedError("ListTopicTool does not support async")
    
    
# 指定トピックの内容を取得するツール
class GetTopicTool(BaseTool):  # BaseToolクラスのサブクラスとして、クラスを自作
    name = "GetTopic"
    description = """
    Retrieve a specific topic requested by the user.
    This functionality is used when the user is seeking the content of a particular topic, 
    such as when they specify a topic name like "Cloud Expansion Strategy" or when they ask a question like "Tell me about skill development for young employees."
    When executing this functionality, you need to provide input in the form of a dictionary with key-value pairs. 
    The key should be "topic_name" in Japanese and the value should be the specified topic name by the user or the extracted topic name from the user's query.
    """
    
    # エンドポイントにPOSTリクエストを送信
    def _run(self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
        query = query.replace("'", '"').encode('utf-8')
        query_dict= json.loads(query)
        response = requests.post(url + "get_topic", query_dict)
        topic_content = response.text
        return topic_content
        
    # 非同期実行の関数も必須
    async def _arun(self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None) -> str:
        raise NotImplementedError("ListTopicTool does not support async")
                
# ツールを設定
tools = [
    ListTopicTool(),
    GetTopicTool()
]


# エージェントを定義
agent = initialize_agent(tools, OpenAI(temperature=0), agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
```

### Toolの引数```description```の重要性

Toolを定義する時の**description**が大切です。**Toolの実行タイミング**や**振る舞いを制御**するためのプロンプトエンジニアリングが必要です。

トピック一覧の取得(GETリクエスト)を例にとると、今回は以下のように設定しています。

```ps:ListTopicToolのdescription
トピックの一覧を取得できる機能です。
もしユーザがトピック一覧を求めている場合に使用してください。
例えば、
「今週のトピック一覧は？」
「どのようなトピックがある？」などの
質問が来た時にこれを使ってください。
```

ユーザのクエリに基づいて、Agentが**思考**を行い、
それによってどのToolを使うかが決まります。

このことから、descriptionの役割は、
**どのようなクエリが来たら、どのToolを使うか？**
といったことを制御していると言えます。

指定トピックの内容取得(POSTリクエスト)の方は、色々と制御しないことが多いため長くなりました。

Toolでは、**ユーザからの入力**を受け取った後、**description**を基にどのようなアクションを実行するかを決めます。
POSTリクエストは{Key:Value}でデータをPOSTする必要があるので、このための振る舞いをプロンプトで制御します。


```ps:GetTopicToolのdescription
ユーザが指定したトピック取得を行う機能です。
ユーザがあるトピックの内容を求めている時にこの機能を使います。
例えば、「クラウド拡販戦略」のようにトピック名が指定された場合や、
「若手社員のスキルアップについて教えて」といった質問が来た時に使います。

この機能を実行する際は、入力としてkey:value形式の辞書型データを指定する必要がある。
keyはtopic_nameで、valueはユーザが指定したトピック名またはユーザのクエリから抽出したトピック名です。
```

****

### 1.GETリクエストの発火を促すクエリでAgent実行

- GETリクエストは、トピック一覧を取得する機能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/17f8fc4d-af9f-f46a-018b-b9e485ef0fae.png)



### 2.POSTリクエストの発火を促すクエリでAgent実行

- POSTリクエストは、指定したトピックの内容を取得する機能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/e9d6b132-3373-5f06-eebf-469035be8f10.png)


## ③ Agentと人間の対話を実現するToolの活用

①②を通して気づいた方もいるかもしれませんが、これAgentを二回に分けて実行しないといけない構成になっていますよね、、:frowning2:

- ①:トピックの一覧を取得
- ②:取得した中からユーザが選択したトピックの内容を取得

こんな具合に、処理と処理の間に人間の入力を挟むことで、振る舞いを制御することって結構あると思います。

そのような機能を実現するToolが提供されていました。
しかし、ソースコードを覗いてみると以下のようなクラスになっていて、そのままでは使えなさそうです:anguished:

```py:
class HumanInputRun(BaseTool):
    """Tool that adds the capability to ask user for input."""

    name = "Human"
    description = (
        "You can ask a human for guidance when you think you "
        "got stuck or you are not sure what to do next. "
        "The input should be a question for the human."
    )
    prompt_func: Callable[[str], None] = Field(default_factory=lambda: _print_func)
    input_func: Callable = Field(default_factory=lambda: input)

    def _run(
        self,
        query: str,
        run_manager: Optional[CallbackManagerForToolRun] = None,
    ) -> str:
        """Use the Human input tool."""
        self.prompt_func(query)
        return self.input_func()

    async def _arun(
        self,
        query: str,
        run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
    ) -> str:
        """Use the Human tool asynchronously."""
        raise NotImplementedError("Human tool does not support async")
```

**description**を見ると、以下のように設定されていました。

>**途中で行き詰まったり、次に何をすべきかわからない場合は、ユーザに相談することができます。
ユーザに向けた質問を入力してください。**

今回実現したいのは**取得対象となるトピックをユーザに入力させること**なので、descriptionを書き換えます。
BaseToolクラスを継承した自作クラスとして定義します。


```py:
def _print_func(text: str) -> None:
    print("\n")
    print(text)

# ユーザにトピック選択を促すツール
class HumanInputTool(BaseTool):
    name = "Human"
    description = """
    If you have obtained topics or if the Human`s desired "topic_name" is ambiguous, 
    you have must ask a Human which topics they are interested in. 
    The input should be a question for the Human. "
    You can also consult a Human when you feel stuck or unsure about what to do next."
    """
    
    # ユーザへの質問を表示する関数
    prompt_func: Callable[[str], None] = Field(default_factory=lambda: _print_func)
    # 入力を受け付ける関数
    input_func: Callable = Field(default_factory=lambda: input)
        
    def _run(self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:
        # ユーザへの質問を表示する
        self.prompt_func(query)
        # 入力を受け付ける関数を実行
        return self.input_func()

    async def _arun(self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None,) -> str:
        raise NotImplementedError("Human tool does not support async")
```


これをAgentsにToolとして付与して実行すると、以下のような結果を得られました、期待通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/05b9be1e-dd1c-a54c-c217-f27d9356c1a9.png)


**ユーザが求めているトピックを教えてください**。の後の、「技術トレンドの研究と導入」は手入力です。

# [3] Agents×Toolsの鍵を握る```description```

色々調べてToolを扱っていて思ったのですが、**description**が肝ですね。(先ほども少し触れましたが、、)

ドキュメントを眺めるといくつか実装例が載っています。

https://python.langchain.com/en/latest/modules/agents/agents/custom_llm_agent.html

その中で、今回は以下の例を取り扱って考えてみます。

```py:
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
from langchain.llms import OpenAI
from langchain import LLMMathChain, SerpAPIWrapper

# Serpapiのラッパーをインスタンス化
search = SerpAPIWrapper()

# ツールを定義
tools = [
    Tool(
        name = "Search",
        func=search.run,
        description="useful for when you need to answer questions about current events"
    ),
    Tool(
        name="Music Search",
        # 自作関数を指定可能
        func=lambda x: "'All I Want For Christmas Is You' by Mariah Carey.", #Mock Function
        description="A Music search engine. Use this more than the normal search if the question is about Music, like 'who is the singer of yesterday?' or 'what is the most popular song in 2022?'",
    )
]

agent = initialize_agent(tools, OpenAI(temperature=0), agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
```

上記エージェントに「**クリスマスに関連する歌で最も有名な曲は？**」と投げかけると、以下のような出力が得られました。
ちゃんと**思考**し、必要な**行動**を選んで実行してくれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/856fb25c-e2a1-436d-a40d-721a6ad1d168.png)



これを見ていて、**description**がAgentsの振る舞いを決めているよね？と思いました。

なので、Toolを設定している親玉を探してみたところ、```BaseTool```クラスで**description**は使われるようです。

https://github.com/hwchase17/langchain/blob/master/langchain/tools/base.py#L305

```py:
class BaseTool(ABC, BaseModel, metaclass=ToolMetaclass):
    """Interface LangChain tools must implement."""

    name: str
    """The unique name of the tool that clearly communicates its purpose."""
    description: str
    """Used to tell the model how/when/why to use the tool.
    
    You can provide few-shot examples as a part of the description.
    """
    args_schema: Optional[Type[BaseModel]] = None
    """Pydantic model class to validate and parse the tool's input arguments."""
    return_direct: bool = False
    """Whether to return the tool's output directly. Setting this to True means
    
    that after the tool is called, the AgentExecutor will stop looping.
    """
    verbose: bool = False
    """Whether to log the tool's progress."""

    callbacks: Callbacks = Field(default=None, exclude=True)
    """Callbacks to be called during tool execution."""
    callback_manager: Optional[BaseCallbackManager] = Field(default=None, exclude=True)
    """Deprecated. Please use callbacks instead."""
```

「**どんな時にToolを使うかモデルに教えるよ。few-shotを使えるよ。**」と、
ちゃんとコメント書いてありますね。

じゃあこの**description**はどこでどうやって使われるんだ？と気になってソースコードを読んでいました。
以下のソースコードが該当しているっぽいというところまで突き止めて力尽きました、、:frowning2:

```py:/langchain/tools/base.py:240~
run_manager = callback_manager.on_tool_start(
    {"name": self.name, "description": self.description},
    tool_input if isinstance(tool_input, str) else str(tool_input),
    color=start_color,
    **kwargs,
)
```

# ちょっと面白エラー

- 別手法で試してみる人間っぽさ:ok_hand:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/7ed5b092-c712-aef2-9c63-aa538951fa75.png)

# ちょっと嫌なエラー

- 無限ループ編
    - 再試行しまくってました:frowning2:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/754308b8-0ebc-3493-7d48-ce11eaffc916.png)


# まとめ
Agents機能楽しい。
Toolsのカスタマイズで無限に応用が利きそう。
LangChainに触れ始めたのが遅かったため、必死に勉強しています。

# 今後の発展
今後は、**APIを介したAgentsとAgents**を取り扱いたいと思います。
DNS風の構成で、**AgentとAgentが対話**したら面白そうだなぁと思っています。

今回色々と触ってみて、
- エージェントの連携先が増えれば増えるほど、
- ツールが増えれば増えるほど

適切なアクションを実行するのが難しくなるんじゃないのかなと思いました。
なので、
>**各領域に特化したエージェントを作って、
自分の専門領域でない場合は他エージェントに聞きにいく。**

みたいな構成が面白そうだなと思っています。