仕事の進め方を支援してくれるAIエージェントチームを作ろう（Langchain）複数のサブエージェントの実装

## はじめに
### 悩み
営業活動をする中で次の進め方を迷うことや提案方法を迷うことがしばしばあります。もちろん迷った際には上司に相談をして進めることもありますが、上司も多忙なため時間が取れないことやお客さんから早急に回答を求められるケースもあります。
またSIerという仕事上、お客さんから技術的な質問をいただくこともよくあります。その際に社内のスペシャリストを探して時間をとってもらうことは双方にとって労力がかかるため気軽な相談先が欲しいと思うことがしばしばあります。
上記のような話は営業だけではなく、誰もが仕事をする上で悩むところではないでしょうか。

### 現状
今までの解決策としてはChatGPTやGeminiなどの生成AIに営業やエンジニアの役割を与えてそれに対して質問をすることで「最初の問い合わせ先」として生成AIを利用していました。
しかし役割を与えた生成AIはそれぞれ独立しています。例えば1つのプロジェクトについて営業の役割を与えたAIに質問をしてその後エンジニアの役割を与えたAIに質問をする場合、過去にやり取りしたAIを探す必要がありました。
複数のプロジェクトで上記のようなやり取りを行うとAIが多くなり管理が煩雑になりました。

### ひらめき
2024年よりAIエージェントという単語を見聞きすることがありAIエージェントを実装することで手間を省くことができるのではないか、と考えました。
特に1つのプロンプトから複数のAIに質問できるマルチエージェントを利用することで手間を省くことができるのではないかと考えました。

### 実装した内容
マルチエージェントとして以下を実装しました。

以下3つのAIエージェント（サブエージェント）を実装
- 営業の進め方を提案する営業エージェント
- 技術的な質問に回答するエンジニアエージェント
- 進め方が問題ないかを確認する営業リーダーエージェント
- 質問に対して自動で割り振りを行うルートエージェント

今回はLangchain+Pythonで実装をしましたが、以下の記事を参考にマルチエージェントを実装させていただきました。

https://zenn.dev/google_cloud_jp/articles/c5fa102f468cdf

### 実装していない内容
多くの人の参考になればと思いマルチエージェントの外側だけの実装となります。
従いまして以下については今回の記事では実装していません。

- 特定のエージェントが回答した後に別のエージェントに渡すようなフロー
    - それぞれ個別に質問を行いたいためフローの実装はしていません
- 他のエージェントとの過去の会話のやり取りを考慮して別のエージェントが回答
    - 今後実装予定
- 複数のファイルを参照した上で回答を生成するエージェントの実装
    - 今後実装予定

## 実装手順
### 準備
Python：3.13.1
必要なライブラリは以下となります。
GOOGLE_API_KEYの設定も忘れず実施しましょう。

importするライブラリは以下になります。
詳細については順を追って説明してきます。
```python
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
from langchain_core.runnables import RunnableLambda, RunnablePassthrough, RunnableBranch
from langchain_google_genai import ChatGoogleGenerativeAI
```

今回は生成AIのモデルはGoogle社のGeminiにしました。
Geminiは画像や動画など様々なファイル形式に対応しており、大規模なトークン数も処理できることが選定理由になります。

### LLMの設定
まずはllmの設定を行います。
```python
GEMINI_MODEL_NAME = "gemini-2.0-flash-001"
llm = ChatGoogleGenerativeAI(model=GEMINI_MODEL_NAME, temperature=0.7, convert_system_message_to_human=Flase)
```
2025年5月時点で最新の安定版がリリースされている`gemini-2.0-flash-001`を利用します。

temperatureは0~1の間で設定し、1に近いほど回答のランダム性が高まります。今回はアイデアが欲しいのでランダム性が少し高めの0.7で設定しました。

SystemMessageを使う場合にはTrueにします。今回は使いませんのでFalseにしておきます。


### サブエージェントの実装
営業、エンジニア、リーダーのサブエージェントたちを実装していきます。
それぞれのサブエージェントは主にプロンプトの内容が異なるだけなのでコードの内容については営業エージェントを元に説明します。


#### 営業エージェント

```python
def sales_agent(input_data: dict) -> str:
    original_prompt = input_data.get("original_prompt", "")
    print("営業エージェントを呼び出しました")

    prompt = PromptTemplate(
        template="""
        あなたは優秀な営業担当者です。
        以下の顧客からの要望や状況に対して、営業の進め方や提案の方法について具体的なアドバイスをしてください。

        顧客の要望/状況:
        {user_query}
        """,
        input_variables=["user_query"],
    )
    chain = prompt | llm | StrOutputParser()
    response = chain.invoke({"user_query": original_prompt})
    return f"営業エージェントからのアドバイス:\n{response}"
```

`original_prompt`にはユーザーからの質問が含まれる形となります。なお`original_prompt`は辞書型ですが、"sales_agent"の関数は"-> str"とあるように最終的に文字列型の値を返します。
`original_prompt`というキーが存在しなかった場合にエラーを起こさないように空の文字列""を使用しています。

次のprint文ではどのエージェントが呼び出されたかわかるようにするためのものです。

`PromptTemplate`はLangchainライブラリが提供する機能の一つでAIに指示を出すためのプロンプトのひな形を作成します。
そして`template`の中で営業エージェントにペルソナを与えています。
{user_query}という部分は後で実際のユーザーの質問内容に置き換えられます。サブエージェントは{user_query}を読んで回答を生成します。

`input_variables`はPromptTemplateの中で外部から値を設定する必要がある変数の名前をリストで指定をします。
templateの中で{user_query}と指定しているので["user_query"]と指定をしています。

`chain`はLangchainの強力な機能で複数の処理ステップをパイプ演算子（"|"）でつなげて一連の処理を簡単に実行できるようにします。
`prompt`の内容を`llm`に読み込ませています。
`StrOutputParser`はLLM空の出力を文字列型に変換するための解析器です。

`response`ではchainを実行します。
PromptTemplateで指定した変数名の`user_query`をKey、ユーザーからの質問である`original_prompt`をValueとする辞書型で渡します。
これによってPromptTemplate内の{user_query}の部分がoriginal_promptの内容に書き換えられて、それがサブエージェントのLLMに渡されて回答を生成し、文字列として`response`に格納されます。

最後のprint文が営業エージェントの回答として`response`を出力します。
エンジニアエージェントと営業リーダーエージェントも同様なためコードのみ記載します。


#### エンジニアエージェント

```python
def engineer_agent(input_data: dict) -> str:
    original_prompt = input_data.get("original_prompt", "")
    print("エンジニアエージェントを呼び出しました。")

    prompt = PromptTemplate(
        template="""
        あなたは経験豊富なITエンジニアです。
        以下のITに関する技術的な質問に対して、専門知識に基づいて分かりやすく回答してください。

        技術的な質問:
        {user_query}
        """,
        input_variables=["user_query"],
    )
    chain = prompt | llm | StrOutputParser()
    response = chain.invoke({"user_query": original_prompt})
    return f"エンジニアエージェントからの回答:\n{response}"
```


#### 営業リーダーエージェント

```python
def sales_leader_agent(input_data: dict) -> str:
    original_prompt = input_data.get("original_prompt", "")
    print("営業リーダーエージェントを呼び出しました")

    prompt = PromptTemplate(
        template="""
        あなたは経験豊富な営業リーダーです。
        以下の営業担当者からの「営業の進め方」に関する提案や計画を受け取りました。
        これに対して、より成果を上げるための具体的な助言や改善点をフィードバックしてください。

        営業担当者からの営業の進め方:
        {user_query}
        """,
        input_variables=["user_query"],
    )
    chain = prompt | llm | StrOutputParser()
    response = chain.invoke({"user_query": original_prompt})
    return f"営業リーダーエージェントからの助言:\n{response}"
```
以上がサブエージェントの詳細となります。


### フォールバックエージェントの実装
想定しない質問をした際の呼び出し先のエージェント（フォールバックエージェント）も実装しました。
これによって予期せぬ質問にも対応することができます。

```python
def fallback_agent(input_data: dict) -> str:
    original_prompt = input_data.get("original_prompt", "")
    print("フォールバックエージェントを呼び出しました")
    
    response = "いただいた質問内容ではどのエージェンが回答すべきか判断できませんでした。\n以下のような質問をいただけますか。\n顧客への営業活動の進め方、提案方法に関する相談や質問。\nIT技術に関する具体的な質問、技術的な実現可能性の確認。\n提案内容に対するレビュー依頼、営業戦略に関する上位の相談。"
    return f"フォールバックエージェントからの応答:\n{response}"
```

fallback_agent()では適切な質問をするよう出力を返すのみです。


#### RunnableLambdaの実装
RunnableLambdaとはPythonの関数を処理の流れ（チェーン）に組み込むためのラッパー（包むもの）です。RunnableLambdaで包むことで複数の関数の処理を繋げることができます。
コードは以下のように記述します。こちらも営業エージェントを例に説明をします。

```python
sales_runnable = RunnableLambda(lambda x: sales_agent(x.get("input_data", {})))
engineer_runnable = RunnableLambda(lambda x: engineer_agent(x.get("input_data", {})))
sales_leader_runnable = RunnableLambda(lambda x: sales_leader_agent(x.get("input_data", {})))
fallback_runnable = RunnableLambda(lambda x: fallback_agent(x.get("input_data", {})))
```

`sales_runnable`とはRunnableオブジェクトとして格納する変数名です。後で特定の条件が満たされた（営業の進め方の質問が入力された）時に実行できるように条件分岐の処理（RunnableBranch）の際に使用します。

`RunnableLambda`の中身はPythonのLambda関数を使用して"input_data"というKeyの値を取り出します。取り出した"input_data"の値を、以前に定義した"sales_agent"関数の引数として私て呼び出しをしています。


#### ルートエージェントの実装
次にサブエージェントに依頼を行うルートエージェントの実装を行います。
まずはルーティングのロジックを実装します。
```python
routing_prompt_template ="""
以下のユーザーからのリクエストを分析し、どのエージェントに処理を依頼するのが最も適切か判断してください。
回答は以下のいずれかのカテゴリ名だけにしてください。

カテゴリ:
- 'sales_agent': 顧客への営業活動の進め方、提案方法に関する相談や質問。
- 'engineer_agent': IT技術に関する具体的な質問、技術的な実現可能性の確認など。
- 'sales_leader_agent': 営業担当者が作成した営業計画や提案内容に対するレビュー依頼、営業戦略に関する上位の相談。
- 'fallback_agent': sales_agentやenginner_agent, sales_leader_agentに分類できない質問。

ユーザーリクエスト: {user_prompt}
"""

routing_prompt = ChatPromptTemplate.from_template(routing_prompt_template)
router_chain = routing_prompt | llm | StrOutputParser()
```

`routing_prompt_template`ではAIに対するプロンプトを入力します。カテゴリの内容をもとにそれぞれのサブエージェントに依頼が行われます。
ユーザーリクエストの[user_prompt]の部分は後で実際のユーザーの質問内容に置き換えられます。

`routing_prompt`ではLangchainの`routing_prompt_template`をLangchainで扱える`ChatPromptTemplate`オブジェクトに変換をしています。

`router_chain`ではルーティング処理行うための一連の処理を実施します。
`routing_prompt`を`llm`に渡して`StrOutputParser()`で文字列型に変換をしています。


#### 回答生成チェーンの実装
```python
main_chain = RunnablePassthrough.assign(
    agent_decision=router_chain.with_config({"run_name": "AgentRouter"})
).assign(
    input_data=lambda x: prepare_input_for_subagent(x, x["agent_decision"])
) | RunnableBranch(
    (lambda x: x["agent_decision"] == "sales_agent", sales_runnable),
    (lambda x: x["agent_decision"] == "engineer_agent", engineer_runnable),
    (lambda x: x["agent_decision"] == "sales_leader_agent", sales_leader_runnable),
    fallback_runnable
)

def prepare_input_for_subagent(input_dict):
    return {"original_prompt": input_dict["user_prompt"]}
```

`main_chain`では複数の処理がパイプ演算子("|")や`.assign()`によって連結されています。
流れとしては`main_chain`が最初に受け取った入力（ユーザーからの質問）が`RunnablePassthrough`に渡されます。その後に`router_chain`がこの入力を使って実行されて、適切なエージェントのカテゴリ名（sales_agentなど）を返します。そのカテゴリ名が`agent_decision`というキーの値としてアサインされます。
結果としては{"user_prompt": "…", "agent_decision": "sales_agent"}という辞書型になります。
なお`AgentRouter`という名前はつけることでデバッグや監視のために識別しやすくするために設定しています。

`.assign()`ではその後の流れとして追加された辞書（agent_desicion）をさらに加工していきます。具体的には`agent_desicison`を引数として後述の`prepare_input_for_subagent`の関数を呼び出します。
この関数はユーザーからの質問を辞書型で返します。

結果としてはこのステップの出力は{"user_prompt": "…", "agent_decision": "sales_agent", "input_data": {original_prompt: "ユーザーの質問"}}のような辞書型になります。

パイプ演算子で上記の出力を次の`RunnableBranch`に渡します。`RunnableBranch`は条件に基づいて、実行する処理を分岐させるコンポーネントになります。
`agent_decision`のキーの値が`sales_agent`や`engineer_agent`などと等しいかを判定します。

`prepare_input_for_subagent()`の関数ではユーザーの元の質問を取り出してそれを{"original_prompt": …}という辞書型で返します。この辞書型がそれぞれのサブエージェントが受け取る入力データとなります。


#### 実行部分
```python
if __name__ == "__main__":
    test_input = {
        "user_prompt": """
        """
    }
        
    result = main_chain.invoke(test_input)
    print(result)
```

最後に"test_input"として{"user_prompt": … }として質問を入力します。
そして"main_chain"にて一連の処理を実行した結果を出力します。

### 実行結果
それでは問題なく動作するかいくつか質問をしてみましょう。

#### プロンプト1
営業的な進め方の相談なので営業エージェントが呼び出される想定をしています。
```python
    test_input = {
        "user_prompt": """
        大手製造業のA社が、工場の生産効率改善のために新しいSaaSソリューションの導入を検討しています。
        先方はDX推進室のB課長が窓口ですが、まだ具体的な課題感や予算感が明確ではありません。
        どのようにヒアリングを進め、関係構築をしながら提案につなげていくべきでしょうか？
        初回アポイントの日程は調整済みです。
        """
    }
```

#### 結果1
無事営業エージェントが呼び出されました。
Geminiということもあり非常に長い出力がされました。

```
営業エージェントを呼び出しました
営業エージェントからのアドバイス:
## 大手製造業A社へのSaaSソリューション導入に向けた営業戦略

A社の生産効率改善というニーズに対し、DX推進室のB課長を窓口として、具体的な課題感や予算感が不明確な状況ですね。初回アポイントメントを最大限に活かし、関係構築と提案につなげるための具体的な進め方を以下に提案します。

**1. 初回アポイントメントの準備：徹底的な事前調査**

*   **A社の事業と業界動向の把握:**
    *   A社の事業内容、主要製品、市場シェア、競合他社の動向などを徹底的に調査します。
    *   製造業全体のDX推進の現状、特にA社が属する業界における課題や成功事例を把握します。
    *   A社のIR情報やニュースリリースなどを確認し、経営戦略や中期計画におけるDXの位置づけを理解します。
*   **B課長の人物像の把握:**
    *   LinkedInなどでB課長の経歴、役職、興味関心などを調べます。
    *   過去の講演や記事があれば目を通し、B課長の考え方や問題意識を理解します。
*   **仮説の構築:**
    *   上記調査に基づき、A社が抱える可能性のある課題やニーズをいくつか仮説として立てておきます。
    *   例えば、「熟練技術者の高齢化による技術伝承の課題」「サプライチェーンの複雑化による在庫管理の課題」「設備の老朽化によるメンテナンスコストの増加」など。
*   **アジェンダの準備:**
    *   初回アポイントメントの目的を明確にし、時間配分を考慮したアジェンダを作成します。
    *   アジェンダは事前にB課長に共有し、認識のずれがないようにします。

**2. 初回アポイントメントでのヒアリングと関係構築**

*   **アイスブレイクと自己紹介:**
    *   丁寧な挨拶と自己紹介で、友好的な雰囲気を作ります。
    *   A社やB課長への敬意を示し、訪問の機会を与えていただいたことへの感謝を伝えます。
*   **ヒアリング:**
    *   **オープンな質問:** まずはA社の事業概要やDX推進の状況について、B課長に自由に語ってもらいます。「御社の事業内容について改めて教えていただけますか？」「現在、DX推進において注力されていることは何ですか？」など。
    *   **課題の深掘り:** 事前調査で立てた仮説を基に、具体的な課題を深掘りします。「生産現場における課題について、具体的にお聞かせいただけますか？」「技術伝承や人材育成に関して、何かお困りのことはありますか？」「設備メンテナンスのコスト削減は、重要な課題と認識されていますか？」など。
    *   **課題の優先順位:** 複数の課題が明らかになった場合は、それぞれの重要度や緊急度をB課長に確認します。「これらの課題の中で、最も優先順位が高いものはどれですか？」「どの課題から解決に取り組むことが、最も効果的だとお考えですか？」など。
    *   **成功の定義:** SaaSソリューション導入によって、どのような状態を目指したいのか、具体的な目標値をB課長に確認します。「生産効率を具体的に何％向上させたいですか？」「コストをどれくらい削減したいですか？」「どのような指標で効果測定を行いますか？」など。
    *   **意思決定プロセス:** 誰が最終的な導入決定権を持っているのか、社内の意思決定プロセスについて確認します。「今回のSaaSソリューション導入に関して、どのような社内プロセスが必要になりますか？」「他にキーパーソンとなる方はいますか？」など。
*   **傾聴と共感:**
    *   B課長の言葉に真摯に耳を傾け、相槌やうなずきで共感を示します。
    *   B課長の立場や状況を理解しようと努め、「おっしゃる通りですね」「それは大変ですね」など、共感の言葉を伝えます。
*   **価値提供:**
    *   ヒアリングを通じて得られた情報に基づき、自社のSaaSソリューションがA社の課題解決にどのように貢献できるのか、具体的な事例を交えながら説明します。
    *   ただし、初回アポイントメントでは詳細な機能説明は避け、あくまで概要レベルに留めます。
    *   A社にとってのメリット（コスト削減、生産性向上、リスク軽減など）を明確に伝えます。
*   **ネクストアクションの合意:**
    *   初回アポイントメントの成果をまとめ、今後の具体的な進め方についてB課長と合意します。
    *   例えば、「後日、A社の課題に特化したソリューション提案資料を送付する」「A社の現場担当者へのヒアリング機会を設ける」「競合他社の導入事例を紹介する」など。
    *   次回の打ち合わせ日程を調整します。

**3. 関係構築の継続と提案の具体化**

*   **迅速なフォローアップ:**
    *   初回アポイントメント後、お礼のメールを速やかに送付します。
    *   メールには、アポイントメントで話した内容の要約と、合意したネクストアクションを記載します。
*   **情報提供の継続:**
    *   A社の課題解決に役立つ情報（業界ニュース、成功事例、セミナー情報など）を定期的に提供します。
    *   B課長にとって有益な情報源となることで、信頼関係を構築します。
*   **社内関係者の巻き込み:**
    *   B課長からの情報提供や、追加ヒアリングを通じて、A社のキーパーソンを特定し、関係構築を図ります。
    *   デモンストレーションやトライアル導入などを提案し、SaaSソリューションの価値を具体的に体験してもらいます。
*   **提案書の作成:**
    *   ヒアリングを通じて得られた情報に基づき、A社の課題解決に特化した提案書を作成します。
    *   提案書には、課題分析、ソリューション概要、導入効果、費用対効果、導入スケジュールなどを具体的に記載します。
    *   競合他社との比較や、リスクと対策についても言及します。
*   **提案内容のプレゼンテーション:**
    *   提案書の内容を丁寧に説明し、A社の疑問や懸念点に真摯に対応します。
    *   導入事例や顧客の声などを活用し、SaaSソリューションの信頼性を高めます。
    *   導入後のサポート体制や、将来的な拡張性についても説明します。

**4. 契約と導入支援**

*   **条件交渉:**
    *   価格、契約期間、支払い条件などについて、A社と交渉します。
    *   A社の予算や要望を考慮し、柔軟な条件を提示します。
*   **契約締結:**
    *   合意した条件に基づき、契約書を作成し、A社と締結します。
*   **導入支援:**
    *   SaaSソリューションの導入をスムーズに進めるために、A社を全面的にサポートします。
    *   導入計画の策定、データ移行、システム連携、トレーニングなどを実施します。
*   **導入後のフォローアップ:**
    *   SaaSソリューション導入後も、定期的にA社に連絡を取り、利用状況や課題をヒアリングします。
    *   改善提案や追加機能の紹介などを通じて、A社のビジネス成長に貢献します。

**重要なポイント:**

*   **顧客視点:** 常にA社の視点に立ち、A社にとって最適なソリューションを提案することを心がけます。
*   **信頼関係:** B課長との信頼関係を構築し、長期的なパートナーシップを目指します。
*   **柔軟性:** A社の状況や要望に合わせて、柔軟な対応を心がけます。
*   **価値訴求:** 自社のSaaSソリューションがA社にもたらす具体的な価値を明確に伝えます。

これらのステップを着実に実行することで、A社との関係構築を深め、SaaSソリューションの導入につなげることができるでしょう。頑張ってください！
```

#### プロンプト2
技術的な質問になりますのでエンジニアエージェントが呼び出される想定です。
```python
    test_input = {
        "user_prompt": """
        当社のウェブサイトで、ユーザー認証にOAuth 2.0を導入しようと考えています。
        アクセストークンとリフレッシュトークンの適切な有効期限の設定について、
        セキュリティと利便性のバランスを考慮した一般的なベストプラクティスを教えてください。
        また、トークン失効のメカニズムについても概要を説明してください。
        """
    }
```

#### 結果2
こちらも非常に詳細な内容が出力されました。
```
エンジニアエージェントを呼び出しました。
エンジニアエージェントからの回答:
## OAuth 2.0 アクセストークンとリフレッシュトークンの有効期限設定、およびトークン失効について

OAuth 2.0の導入、おめでとうございます！ユーザー認証のセキュリティ強化と利便性の向上に大きく貢献するでしょう。ご質問のアクセストークンとリフレッシュトークンの有効期限設定、およびトークン失効について、セキュリティと利便性のバランスを考慮したベストプラクティスを以下に説明します。

**1. アクセストークンの有効期限:**

* **短く設定する:** アクセストークンは、OAuth 2.0フローにおいて、保護されたリソースへのアクセスを許可する鍵です。そのため、有効期限は可能な限り短く設定することがセキュリティ上重要です。
    * **推奨:** 5分から1時間程度。
    * **理由:**
        * **トークン漏洩時の影響軽減:** 短い有効期限であれば、トークンが漏洩した場合でも、被害を最小限に抑えることができます。
        * **再認証の強制:** 定期的な再認証を促すことで、ユーザーの認証状態を常に最新に保ち、セキュリティリスクを軽減します。
* **ユースケースに応じた調整:** 特定のユースケースにおいて、短い有効期限が利便性を著しく損なう場合は、多少長く設定することを検討できます。ただし、セキュリティリスクとのトレードオフを十分に考慮する必要があります。
    * **例:** 頻繁なAPIアクセスが必要なモバイルアプリケーションなど。
* **クライアント種別による考慮:** クライアントの種類（機密クライアント、公開クライアント）によって、リスクが異なるため、有効期限を調整することを検討してください。例えば、機密クライアント（サーバーサイドアプリケーションなど）は、トークンを安全に保管できるため、多少長めの有効期限を設定できます。一方、公開クライアント（モバイルアプリケーションなど）は、トークンが漏洩するリスクが高いため、短めの有効期限を設定すべきです。

**2. リフレッシュトークンの有効期限:**

* **長く設定する:** リフレッシュトークンは、アクセストークンを再発行するための鍵です。ユーザーエクスペリエンスを考慮し、アクセストークンよりも長く設定することが一般的です。
    * **推奨:** 数週間から数ヶ月程度。
    * **理由:**
        * **再認証の頻度削減:** 長い有効期限により、ユーザーが頻繁に再認証する必要がなくなり、利便性が向上します。
        * **アクセストークンの自動更新:** リフレッシュトークンを利用して、バックグラウンドでアクセストークンを自動的に更新することで、ユーザーは認証を意識することなくサービスを利用できます。
* **ローテーション:** セキュリティを強化するために、リフレッシュトークンのローテーションを実装することを強く推奨します。
    * **仕組み:** リフレッシュトークンを使用して新しいアクセストークンを発行する際に、古いリフレッシュトークンを無効化し、新しいリフレッシュトークンを発行します。
    * **メリット:** リフレッシュトークンが漏洩した場合でも、それを使用してアクセストークンを再発行できるのは一度だけになり、被害を最小限に抑えることができます。
* **非アクティブ期間:** 一定期間使用されていないリフレッシュトークンは、自動的に無効化する仕組みを導入することを検討してください。
    * **例:** 3ヶ月間使用されていないリフレッシュトークンは無効化する。
    * **理由:** 長期間使用されていないリフレッシュトークンは、漏洩している可能性が高いため、リスクを軽減できます。

**3. トークン失効のメカニズム:**

トークン失効は、不正利用されたトークンを無効化し、セキュリティを維持するために非常に重要なメカニズムです。

* **明示的な失効:** ユーザーがログアウトした場合や、アカウントが停止された場合など、明示的にトークンを失効させる必要があります。
    * **実装方法:** OAuth 2.0サーバーに失効APIエンドポイントを用意し、クライアントがそのAPIを呼び出すことでトークンを失効させます。
    * **リフレッシュトークンの失効:** アクセストークンだけでなく、関連するリフレッシュトークンも同時に失効させる必要があります。
* **暗黙的な失効:** 以下のような状況で、トークンを暗黙的に失効させる必要があります。
    * **パスワード変更:** ユーザーがパスワードを変更した場合、既存のすべてのトークンを失効させるべきです。
    * **アカウント停止:** ユーザーのアカウントが停止された場合、すべてのトークンを失効させる必要があります。
    * **権限変更:** ユーザーの権限が変更された場合、変更前の権限で発行されたトークンを失効させる必要があります。
* **トークン失効リスト:** 失効されたトークンのリストをOAuth 2.0サーバーで保持し、APIアクセス時にトークンが有効かどうかをチェックします。
    * **パフォーマンス:** トークン失効リストのサイズが大きくなると、パフォーマンスに影響を与える可能性があるため、適切なデータ構造（例：Bloom filter）を使用することを検討してください。
* **伝播:** トークン失効の情報を、関連するすべてのサービスに迅速に伝播する必要があります。
    * **リアルタイム伝播:** RedisやRabbitMQなどのメッセージキューイングシステムを使用して、リアルタイムにトークン失効情報を伝播します。
    * **定期的な同期:** 定期的にトークン失効リストを同期することで、伝播の遅延による問題を軽減します。


* **セキュリティに関する考慮事項:**
    * **HTTPS:** 常にHTTPSを使用し、トークンを安全に転送してください。
    * **トークンの保管:** トークンを安全に保管し、クライアント側（特に公開クライアント）でのトークンの漏洩を防ぐための対策を講じてください（例：キーチェーン、セキュアなストレージAPI）。
    * **クロスサイトスクリプティング（XSS）対策:** XSS攻撃を防ぐために、適切に入力を検証し、出力をエスケープしてください。

**補足:**

* 上記はあくまで一般的なベストプラクティスであり、実際の環境や要件に合わせて調整する必要があります。
* OAuth 2.0の仕様や実装に関する詳細は、RFC 6749などを参照してください。
* セキュリティは常に変化する脅威に対応していく必要があるため、定期的にセキュリティレビューを実施し、最新のベストプラクティスを適用するように心がけてください。

ご不明な点があれば、お気軽にご質問ください。 
```

#### プロンプト3
営業の進め方の助言が欲しいので営業リーダーエージェントが呼び出される想定です。
```python
    test_input = {
        "user_prompt": """
        営業戦略案:ターゲット顧客である中小企業向けに、新製品Xの初期導入キャンペーンとして、
        初年度利用料30%オフ、導入コンサルティング無料を提供します。
        WebセミナーとDMで集客し、個別相談会でクロージングを目指します。
        この進め方で問題ないでしょうか？改善点があれば指摘してください。
        """
    }
```

#### 結果3
具体的な助言をいただけました
```
〇〇さん、営業戦略案の提出ありがとうございます。中小企業向けの新製品X初期導入キャンペーン、WebセミナーとDMでの集客、個別相談会でのクロージングという流れは、ターゲット顧客と製品特性を考えると理にかなっていると思います。

しかし、より具体的な成果を上げるためには、以下の点を考慮し、戦略を深掘りすることをお勧めします。

**1. ターゲット顧客の深掘りと訴求ポイントの明確化:**

*   **現状の課題:** 中小企業と一口に言っても、規模、業種、抱える課題は様々です。どの規模、どの業種の中小企業に、新製品Xが最も効果を発揮するのか、具体的なペルソナを設定しましょう。
*   **ペルソナ設定:** ペルソナを具体的に設定することで、WebセミナーやDMの内容、個別相談会でのクロージングトークをより効果的にパーソナライズできます。例えば、「従業員数〇〇名、〇〇業種の企業で、〇〇という課題を抱えている企業」のように具体的に定義しましょう。
*   **訴求ポイントの明確化:** ペルソナが抱える課題に対して、新製品Xがどのように解決できるのか、具体的なメリットを明確にしましょう。「業務効率〇〇%向上」「コスト〇〇%削減」「売上〇〇%増加」など、定量的なデータを用いて訴求することが重要です。
*   **競合との差別化:** 競合製品と比較して、新製品Xが優れている点を明確にしましょう。価格、機能、サポート体制など、具体的な差別化ポイントを洗い出し、訴求ポイントに組み込みましょう。

**2. 集客戦略の具体性とKPI設定:**

*   **Webセミナー:**
    *   **テーマ設定:** ターゲット顧客が最も関心を持つテーマを設定しましょう。ペルソナが抱える課題を解決できるような、具体的なノウハウや事例を紹介するセミナーが効果的です。
    *   **コンテンツ:** セミナーの内容構成を具体的に検討しましょう。製品紹介だけでなく、導入事例、質疑応答、個別相談会への誘導など、参加者のエンゲージメントを高める工夫が必要です。
    *   **集客方法:** Webセミナーの集客方法を具体的に検討しましょう。DMだけでなく、SNS広告、業界メディアへの掲載、既存顧客への紹介など、複数のチャネルを活用することで集客効果を高めることができます。
    *   **KPI設定:** WebセミナーのKPIを設定しましょう。参加者数、アンケート回答率、個別相談会への申し込み数など、具体的な目標値を設定し、効果測定を行いましょう。
*   **DM:**
    *   **ターゲティング:** DMのターゲティングを具体的に検討しましょう。企業規模、業種、役職など、ターゲット顧客を絞り込むことで、DMの開封率や反応率を高めることができます。
    *   **コンテンツ:** DMの内容を具体的に検討しましょう。新製品Xの概要だけでなく、ターゲット顧客が抱える課題に対する解決策、導入事例、Webセミナーへの誘導など、DMを受け取った人が興味を持つような情報を盛り込みましょう。
    *   **KPI設定:** DMのKPIを設定しましょう。開封率、クリック率、問い合わせ数など、具体的な目標値を設定し、効果測定を行いましょう。

**3. 個別相談会の設計とクロージング戦略:**

*   **個別相談会の準備:** 個別相談会に向けて、顧客情報を事前に収集し、顧客の課題やニーズを把握しておきましょう。
*   **個別相談会の流れ:** 個別相談会の流れを具体的に設計しましょう。ヒアリング、製品デモンストレーション、質疑応答、見積もり提示など、顧客の課題解決に焦点を当てた内容にしましょう。
*   **クロージング戦略:** クロージング戦略を具体的に検討しましょう。顧客の購買意欲を高めるための提案、導入後のサポート体制の説明、導入事例の紹介など、顧客が安心して導入できるような情報を提供しましょう。
*   **KPI設定:** 個別相談会のKPIを設定しましょう。成約率、平均単価など、具体的な目標値を設定し、効果測定を行いましょう。

**4. キャンペーンの効果測定と改善:**

*   **効果測定:** キャンペーンの効果を定期的に測定し、改善点を見つけましょう。Webセミナーの参加者数、DMの開封率、個別相談会の成約率など、KPIを定期的にチェックし、目標値との乖離を分析しましょう。
*   **改善:** 効果測定の結果に基づいて、キャンペーンの内容を改善しましょう。Webセミナーのテーマ、DMのコンテンツ、個別相談会の流れなど、改善点があれば積極的に修正し、より効果的なキャンペーンを目指しましょう。

**まとめ:**

今回の提案は、新製品Xの初期導入キャンペーンとして、有望な戦略だと感じました。しかし、上記の点を考慮し、より具体的な計画を立てることで、より高い成果を期待できます。

特に、**ターゲット顧客の深掘り、訴求ポイントの明確化、集客戦略の具体性、個別相談会の設計、効果測定と改善**は、重要なポイントです。

これらの点を踏まえて、再度戦略案を見直してみてください。期待しています。
```

#### プロンプト4
関係ない質問をした際の挙動も見てみましょう

```python
    test_input = {
        "user_prompt": """
        今日の天気を教えて
        """
    }
```

#### 結果4
フォールバックエージェントが呼び出されました。
```
フォールバックエージェントを呼び出しました
フォールバックエージェントからの応答:
いただいた質問内容ではどのエージェンが回答すべきか判断できませんでした。
以下のような質問をいただけますか。
顧客への営業活動の進め方、提案方法に関する相談や質問。
IT技術に関する具体的な質問、技術的な実現可能性の確認。
提案内容に対するレビュー依頼、営業戦略に関する上位の相談。
```

## 今後の拡がり
今回の実装では複数のサブエージェントとそれぞれのサブエージェントに依頼をするルートエージェントの実装を行いました。
自分の業務に関係するため営業やエンジニアエージェントとしましたが、ペルソナを変えるだけで**様々な役割を持った複数のエージェントを実装することができます。**
またや会話履歴をセッションとして保存することで、あるエージェントの会話を参考にした上で別のエージェントと会話することも可能です。
またフローのようにあるエージェントの回答の後に別のエージェントに回答をさせることもできます。
そうなれば**さまざまな部署のメンバーで会議をするようなことも実現可能です。**
さらにファイルを参照させてそれを元に回答するエージェントを実装すれば**プロジェクト単位でのAIチームのような実装も可能です。**

今後の記事ではこのAIエージェントたちにさらに機能を追加していきたいと思います。

**最後までお読みいただきありがとうございました。**