2025年 生成AIの新たな波「AI エージェント」の可能性

本記事は日本オラクルが運営する下記Meetupで発表予定の内容になります。発表までに今後、内容は予告なく変更される可能性があることをあらかじめご了承ください。

https://oracle-code-tokyo-dev.connpass.com/event/342044/

※セミナー実施時の動画。以降の記事と同じ内容です。

<iframe width="560" height="315" src="https://www.youtube.com/embed/h-F6aVHJqOM?si=ad2_70vxpyyOzghr" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


※セミナー実施時に沢山のご質問をいただきありがとうございました。セミナー時間内に回答できなかったご質問や、正確に回答できなかったご質問の補足を最後のコメント欄に纏めましたので併せてご確認ください。

# 生成AIアプリのコンセプトは「AIエージェント」へ

生成AIを利用したアプリケーションの様々なリファレンス実装がある中、企業用途では世界中のユーザーが**RAGと呼ばれるソリューション**にフォーカスしたのが2023年、2024年だったと思います。

RAGは「応答品質がよくない」、「LLMはウソをつく」などと言われながらも**極めて有用なソリューション**であることは疑いようがなく、今後もエンハンスされ続け**定番のソリューションになることは間違いありません**。

※RAGとは何ですか？という方はこちらの記事をご参照ください。

https://qiita.com/ksonoda/items/ba6d7b913fc744db3d79

そして各テック企業が **RAGの次に打ち出しているコンセプトが本記事の主題、「AIエージェント」です**。

RAGに限らず、**これまでの生成AIアプリは「LLMに回答させる」** というかなりシンプルな実装が中心だったのに対して、**AIエージェントは「LLMに働かせる」** という、より柔軟で汎用性のある実装だと言えます。

「旅行に行きたい」とAIに話しかけると、「旅行先の提案、スケジュールの確認、フライト、ホテル、タクシーの予約など、会話しながら全てをAIがやってくれるという至れり尽くせりのアレです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/11e3fb4c-4c10-4ef3-962a-77bb541066ce.png)

ビジネスの世界でいうと、「自社の人気商品の売り上げ分析を行いマーケティングや在庫適正化をしたい」とAIに話しかけると、AIが自律的にSNSで人気商品を調査したり、調査結果からデータを分析し、社内のSCMに連携して在庫の過不足をコントロールしてくれるようなイメージです。

俯瞰すると、**「AIエージェント」とは、「人間の代わりに複雑なタスクを自律的に実行する」システム** と定義でき、現在各テック企業はこのようなSF映画さながらの世界観を実現するベースの技術としてAIエージェントの開発に完全にシフトしている状況です。

::: note info
現在、「AIエージェント」はこれ以外に「Agentic AI」や「Agentic ワークフロー」などと呼ばれることがあり、呼称だけでなく、その定義や対象の製品も曖昧です。本記事に記載している実装はその一つであり、その他、オンプレミスやクラウド(SaaS/PaaS/IaaS)の製品をLLMでエンハンスした機能を総称してAIエージェントと呼んでいたりすることもあります。
:::

# AIエージェントの要素技術

**実はAIエージェントで使われる要素技術はLLMが流行りだしたかなり初期のころから既にありました。** 現在各テック企業が開発しているAIエージェント関連のライブラリやクラウドサービスは主に下記の要素技術がベースになっています。

もちろんこれ以外にも沢山の技術が応用されていますが、上述したような世界観の実現に直接的に影響している仕組みが下記になり、いずれも生成AIアプリ界隈では以前からよくでてくるキーワードですので既にご存じの方々も多いはず。

- **エージェント・システム** 
- **Function Calling(もしくは MCP)**
- **マルチエージェント**

### エージェント・システム
エージェントは日本語で代理人という意味です。つまりエージェント・システムとは **ユーザーの代わりに、様々な処理(検索、調査、分析、プログラムなどなど)を実行してくれる代理人(プログラム)** ということになります。

このエージェントと呼んでいるプログラムがユーザーの望む処理を **自律的に行っているように見える仕組みは、LLMによってエージェントに「役割」が与えられることで成り立っています**。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/3375e10f-2ca0-4837-a800-c34eb8588b17.png)


例えば上図のような例の場合、**「SNSを検索し、ユーザーのプロンプトに応答する」という「役割」がLLMによってエージェントに与えられる**というイメージです。この役割によって、ユーザーのプロンプトから**①どのような処理が必要なのかを「計画」し、②その処理を「実行」し、③その実行結果から最終的な「応答を生成」** します。

1. **計画：SNSを検索し人気の商品を探す**
2. **実行：SNSの検索プログラムを実行する**
3. **応答：SNSの検索結果をもとに応答を生成する**

このようにユーザーのプロンプトに応じて、**自律的に必要な処理を判断し、実行してくれる仕組みのベースとなる技術がエージェントシステム**です。


::: note info
LLMを使ったアプリケーションとして、「ドキュメント検索(RAG)」や「データベース分析」などがありますが、実はこれも上述したようなエージェントシステムの一種です。

ドキュメント検索(RAG)はユーザープロンプトの内容に関係した文書をベクトルデータベースから検索するという役割を持ったエージェントですし、「データベース分析」も同様にユーザープロンプトの内容をSQLに変換しRDBを検索(分析)するという役割を持ったエージェントだということになります。

RAG(文書検索)
https://qiita.com/ksonoda/items/9a40c1017613d3ccf1e0
データベース分析
https://qiita.com/ksonoda/items/60ccc2a4fde15906654a
:::

ユーザープロンプトの内容に応じて、様々な処理を自律的に実行してくれるエージェントですが、この **「様々な処理を実行する」という部分がどのような仕組みで成り立っているのか？** というところが非常に重要な要素技術になります。

つまり、上述した例でいうと、「②SNSの検索プログラムを実行する」という部分がどのように自律的に実行されるのか？ということです。**そのベースとなる技術がFunction Callingです**。


### Function Calling(Tool Calling、Tool Use、Tools)

ChatGPTやGeminiなど出来上がったサービスのみを利用している方には想像しにくいかもしれませんが、**LLM単体では「与えられたテキストに基づいたテキストの生成」しかできず、ウェブ検索を実行したり、SNSを検索したりということはできません**。

**LLMは過去のデータを学習した機械学習の予測モデルのため現在のデータ(リアルタイムのデータ)に関する応答はできません。LLM単体では今何時なのかですら答えることはできないんです。**

現在のバージョンのChatGPTやGeminiなどがリアルタイムにウェブ検索を実行してくれるのは、**LLM以外にそのようなプログラムが追加で実装されているから**です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/08181200-cbc3-4b87-ba80-cc333d87623a.png)


例えば上図のように、今日の天気予報というリアルタイムの情報を含んだテキストを生成したい場合、OpenWeatherなど天気情報を提供しているサービスに都度アクセスし、天気情報を取得する実装が必要になります。(上図の赤枠の部分)このように、**LLMと「LLMの外の世界」を連携させるプログラムを開発できる仕組みがFunction Calling**です。

**LLM単体では実行できない「動的(リアルタイム)な情報取得」や「外部システムとの連携」も可能**になり、**生成AIアプリの適用範囲を大幅に拡張することができる極めて重要な仕組みとなります**。

そして、ここでいう **「LLMの外の世界」** とは例えば以下のようなものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/c7423424-46da-4c7a-9e06-6ae8a869aff6.png)

- **インターネットに公開されているサービスとの連携**
毎日私達が利用するようなインターネット上で公開されている様々なサービスはAPIでアクセスできるようになっているものが多いのでそれらを利用してFunction Callingを作るパターン

- **社内システムとの連携**
現状、ユースケースとして挙げられるケースは少ないように思いますが、今後生成AIアプリの適用範囲が広がれば財務、人事、生産、調達、販売システムなどをFunction Callingで連携するパターン

- **業務アプリ、ユーザー開発アプリとの連携**
社内システムと近しいケースですが、RDB、NoSQL、DWH、データレイク、ETL、BIツールなどと連携するパターン

このように沢山のサービスと連携することで、より便利で汎用的なアプリケーションになることはおわかりになると思います。また、API CallだけでなくスクレイピングやOSコマンドなど様々なタイプの処理を Function Callingとして定義できますから、 **事実上、連携できないものありません**。

特に、**「社内システムとの連携」や「業務アプリ、ユーザー開発アプリとの連携」の場合は、Function Callingで受発注の処理を行ったり、データ分析などデータの処理を行ったりすることができるようになります**。これが意味するところは、単に **「LLMの外の世界から情報を得る」** というよりむしろ、 **「LLMの外の世界を操作する」** という処理になり、まさに冒頭に説明したAIエージェントの世界観を実現するための仕組みだと言えます。


::: note info
Function Callingや Tool CallingはOpenAI用語です。その他の製品ではTools, Tool, Tool Useなどという呼称がよくでてきますが、基本的に同じことを言っていると思って問題ありません。
:::

※Function Callingの詳細については別記事がありますのでご興味のある方はご参照ください。

https://qiita.com/ksonoda/items/1ba3916c9ee9f4d9c10c

https://qiita.com/shukawam/items/14ee6efc69f75ace0a0e

https://qiita.com/tkote/items/0dcf64cfba7e310041b5



このように、AIエージェントのアプリケーションでは様々な処理をFunction Callingとして定義し、エージェントに関連付けることで有用なアプリケーションを開発することができるようになっているのですが、**沢山のFunction Callingをエージェントに関連付けるといくつかの問題が発生することがあります**。

その主だったものが下記になります。

- **プロンプトが複雑だったり抽象的すぎるとエージェントが理解できない**
- **沢山のFunction Callingを定義すると想定したFunction Callingが選択されない**
- **沢山のFunction Callingを定義すると想定した順序でFunction Callingが実行されない**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/d831a100-629f-46bf-834a-c39fac942a7e.png)


このようなことが起こる理由は、**Function Callingの選択と実行順序が気まぐれなLLMによって決まるからです**。(LLMとは機械学習で作られた予測モデルであり、予測モデルが出力する結果はあくまで**推論結果**、つまり**常に100%正しいということにはならないから**です。)

便利なアプリケーションには往々にして複雑な処理がつきもので、そのために様々なFunction Callingを定義したいのに、それをすると想定通りの動作にならないという本末転倒な状況が発生してしまいます。

このような状況を可能な限り回避する手法としてマルチ・エージェントがあります。

:::note info
最近では、Function Calling の代替手法として MCP（Model Context Protocol）が非常に注目を集めています。LLM が外部サービスと連携しLLMが学習していない情報を得たり外部システムを操作できるという点では Function Calling と同様ですが、MCPは何といってもAnthropic社が業界標準のプロトコルを目指して開発した技術です。様々な企業が自社サービスのMCP(ツール)をこの共通プロトコルで開発し、市場に提供することで、開発者はそれらを簡単に自社サービスに取り込むことができるようになります。今後、MCPのような業界標準仕様が一般的に利用されることにより、ツールの可搬性および市場での流通が活発化することは言うまでもなく、まさにAIエージェントの技術が爆発的に普及してゆく足がかりとなるでしょう。2025年から始まったAIエージェントの世界はMCPにより早くも新たな局面を迎えようとしています。
:::

MCPについては下記記事も併せてご参照ください。

https://qiita.com/ksonoda/items/1c681a563a95a93975ff


### マルチ・エージェント

マルチ・エージェントとはその名の通り、上述した**エージェントを複数構成する手法で**、現在、各ベンダーが開発している**AIエージェントの中核的な技術です**。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/ad39b2e1-7b98-4143-ad45-ade988d521e7.png)

おさえておきたいポイントとしては下記3つです。

- **複数のエージェントに役割を分けて**、各エージェントには**その役割だけに専念させる**ことでLLMの推論ミスの可能性を減らします。例えば、上の図では「一週間のヨーロッパ旅行を計画してください。」というプロンプトに対して、下記4つの役割に細分化し、この役割を4つの各エージェントに割り当てています。
    - エージェント1の役割＝「人気の観光地を検索して提案する」
    - エージェント2の役割＝「宿泊先の提案と予約」
    - エージェント3の役割＝「訪問先への移動経路の提案」
    - エージェント4の役割＝「予算の見積もりと代替案の提案」

- 同様に、各エージェントには**その役割に関係するFunction Callingのみを関連付ける**ことにより、Function Callingの選択やその実行順序の推論ミスを軽減します。上図の例では各エージェントの役割に沿った以下のようなfunction callingを割り当てます。
    - エージェント1に割り当てるfunction calling = 「Xで人気の観光地を検索する処理」
    - エージェント2に割り当てるfunction calling = 「airbnbで宿泊先を検索、提案、予約する処理」
    - エージェント3に割り当てるfunction calling = 「Google Mapで訪問先の移動経路を検索する処理」
    - エージェント4に割り当てるfunction calling = 「移動先、宿泊費、食費など旅行に必要な費用を計算し、予算内でなければ再度他のエージェントに代替を提案させる処理」

- そして**マルチ・エージェントにおいて、この複数のエージェントをどのように連携させるか？** という点が最も重要な設計ポイントになります。例えば、**複雑なプロンプトのタスクを分割して「分担」させたり、同じ役割を持たせて一番良い結果を選べるよう「協力」させたり、エージェント同士を「競争や交渉」をさせたり、あるエージェントの提案内容を「批判」させたりといった具合です。**　上図の場合、おおよそ下記のような連携関係だということができます。
    - エージェント1によって観光地が提案され、その観光地の近くの宿泊先をエージェント2が提案、予約するというシナリオのため「分担」に近い連携と言えます。
    - エージェント1、とエージェント2の提案内容によりエージェント3が移動経路を検索するというシナリオのためこちらも「分担」に近い連携と言えます。
    -  エージェント1、とエージェント2、エージェント3の提案内容により旅行にかかる費用が決まり、その費用が予算内でなければやり直しさせるというシナリオのため、「批評」に近い連携と言えます。

::: note info
上述3のエージェント同士の連携のロジックを、JavaやPythonなどといった高等言語で、全てのパターンを考慮しながら定義することは極めて難しい(そもそも全てのパターンを洗い出すことができない)ということになりますが、そこを言葉で定義できるところがLLMアプリケーションの最も有用な点であり、生成AIアプリの本質だと個人的には考えています。
:::

このようにして、**複雑なプロンプトを実行したり、またその結果の精度を更に向上させることができるようになる仕組みがマルチエージェント**です。


AIエージェントの要素技術の主だった考え方は上述したような内容になりますが、これをゼロから自分で開発するのはなかなか骨が折れる作業ということになるので当然、**様々なプロバイダーからAIエージェント開発用の製品がリリースされており、既に広範なエコシステムが形成されている状況**です。

# AIエージェントのエコシステム

現在、AIエージェントを開発するための製品(**特にマルチエージェントにフォーカスした製品**)は本当に様々なものが開発されており、その全てをリストすることも、それらを綺麗にカテゴライズすることも非常に難しい状況ですが、その中で有名どころをいくつかご紹介します。
 

**Pythonライブラリ**

| **名称** | **開発元** | **製品概要** |
|----------|----------|----------------|
| **Autogen** | Microsoft Research | マルチエージェントシステムの構築にフォーカスしたOSSライブラリ。グループチャットなどAIエージェント同士の対話やタスク分担を自動化するためのライブラリが特徴的。OpenAI以外の様々なモデルが利用可能かつ、LangChain、Dockerなどその他のライブラリとの連携も可能。|
| **LangGraph** | LangChain Inc. | LLMフレームワークで著名なLangChainの姉妹ライブラリとなり、マルチエージェントシステムの構築にフォーカスしたOSS。LangChainの幅広いエコシステムとの連携が可能で様々なプロバイダのLLMが利用可能。|
| **Llama Stack** | Meta | OSS LLMプロバイダの巨人Metaが手掛けるマルチエージェントシステム構築可能なOSSライブラリ。MetaのLLMのみが利用可能。|
| **OpenAI Assistant API** | OpenAI | OpenAIのマルチエージェントシステム開発用の機能。OpenAIのLLMのみサポート。エージェント同士の会話スレッドがOpenAI側で自動的に管理されたり、Code InterpreterやRetrieval(ファイル検索)のツールが事前定義されていたりと有償サービスらしい特徴あり。|


※LangGraphについては過去のセッションで取り扱いましたのでよろしければ下記記事をご参照ください。

https://qiita.com/ksonoda/items/92a224e3f56255182140

**総合クラウドベンダー**

| **開発元** | **名称** | **サービス概要** |
|----------|----------|----------|
|**Microsoft**|**Azure AI Agent Service**|AIエージェントを開発するためのクラウドサービス。Azure SDK for PythonのAgentTeamクラスを使用してマルチエージェントの定義が可能。ローコードでの開発も可能。|
|**AWS**|**Amazon Bedrock Agent**|AIエージェントを開発するためのクラウドサービス。同サービスのマルチエージェントコラボレーションと呼ばれる機能によりマルチエージェントの構築が可能。ローコードで開発可能。|
|**Google**|**Google Vertex AI Agent Builder**|マルチエージェントの構築が可能なローコード開発サービス。|
|**Oracle**|**Oracle Generative AI Agents Platform**|ローコードでのエージェント開発サービス。OCI Agent Development Kitによりマルチエージェントも実装可能。|

その他、この市場での様々なプレーヤー(**最近は特に[Dify](https://dify.ai/jp)のようなAIアプリ開発ツールの専業ベンダーが人気のようです**)がAIエージェントをキーワードに自社製品やサービスをエンハンスしています。

そのような状況の中、以降のパートでは導入ハードルが低い**オープンソースソフトウエアかつ、比較的有名でよく使われているAutoGen**を取り上げたいと思います。

# AutoGenの概要
Microsoftとペンシルベニア州立大学の共同プロジェクトとして始まり2024年に初期リリース、その後何度かのエンハンスを経て2025年2月現在は初期のころと比べると使い勝手がずいぶんよくなりました。

MicrosoftというとLLMフレームワークのSematic Kernelが有名で、様々なタイプの生成AIアプリ全般をターゲットにリリースされていますが、**AutoGenは完全にマルチエージェントにフォーカス**されており両者のポジショニングの棲み分けは明確です。

AutoGen単体だけではなく、**LangChainやDockerなどその他様々なツールと連携できるように作られており、便利に運用できるだけでなく、生成AIにおける様々な要件に対応できる非常に広範なエコシステムをカバーしています**。この点、昨今リリースされるライブラリにおいては重要視される項目です。

:::note info
LangChainはマルチエージェント以外の汎用的なエージェントシステム構築のためのフレームワークで古くから人気があるためそのエコシステムは非常に広範です。Autogenではカバーしていないこの広範なエコシステムとそのまま連携できる点は非常に大きなメリットだと思います。

LangChainのエコシステム
https://qiita.com/ksonoda/items/ba6d7b913fc744db3d79#langchain
:::


### AutoGenのコンポーネント
Autogenの中のコンポーネント構成は下図のようになっており、主に6つのクラスライブラリ群から構成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/97537a41-edfd-43d9-bb8d-12b11d110d44.png)


[出典 : AutoGen v0.4: Reimagining the foundation of agentic AI for scale, extensibility, and robustness](https://www.microsoft.com/en-us/research/blog/autogen-v0-4-reimagining-the-foundation-of-agentic-ai-for-scale-extensibility-and-robustness/)

各コンポーネントの概要説明は以下の通りです。


| **コンポーネント** | **概要** |
|----------|----------------|
|**Core**|エージェントシステムを定義するための基本フレームワークとなるライブラリ。エージェントのメッセージ管理、タスク分割、ツール(Function Calling)の定義などフレームワークの中心を開発するためのAPI。|
|**AgentChat**|Coreで提供している各エージェントの連携パターンのうちグループチャット方式の実装が更に抽象化されたAPI。|
|**Extension**|AutoGen以外のコンポーネントと連携するためのAPI。様々なプロバイダのLLM、ローカルにホストされたLLM、Dockerとの連携時に利用。|
|**Magentic-One**|ウェブ検索、ファイル操作、コード生成・実行を専門とするエージェントと、それらを指揮エージェントによって構成されたマルチエージェントシステム。汎用的な用途を目的に複雑なタスクを段階的に解決してゆくAIエージェントのリファレンス実装。|
|**Studio**|エージェントのローコード開発ツール。開発したAIエージェントのコードを生成することも可能。本番システムではなく研究用途が推奨。※AutoGenの全ての関数が利用できるわけではない点に注意|
|**Bench**|エージェントのパフォーマンスを測定・評価するためのベンチマークスイート。Docker必須。予め決められたベンチマークシナリオにおいて総コスト、タスク完了時間、会話ターン数などをスコアリング。|



基本的な開発の流れとしては

- **CoreかAgentChat(もしくはその両方の混在)とextensionを使ってマルチエージェントのコードを書いてゆきます。**
- 基本はコーディングベースの開発ですが、**GUIで開発したい場合はStudioを使います**。ただし、現状は**本番としての利用は非推奨**だとのこと。エージェントの挙動を確認するくらいの利用用途かと思います。
- マルチエージェントの動作を評価したい場合はBenchを利用。

という感じになります。

今回は分かりやすいAgentChatをメインにサンプルコードを概説します。

### AgentChatのプログラミングモデル
AutogenChatでは、その名の通り、**私達が普段利用している一般的なグループチャットの挙動をそのままAPIとして実装**しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/ce37ac54-77dd-4f8b-980e-a4214c1972ff.png)

まず、上図に記載されている赤字の名称はそれぞれ下記のものを指します。(名称から想像できると思いますが。)

- Assistant Agent : エージェント
- Tool : Function Calling
- Model : LLM
- グループチャット(チーム)：複数のエージェントから構成されたグループ
- チャット：エージェント同士の会話
- メッセージ：エージェントの出力テキスト
- TERMINATE：会話終了の合図

Model(LLM)とTool(Function Calling)が関連付けられたエージェントをAssistant Agentと呼んでおり、**ある役割をもった複数のAssistant Agentをグルーピングし、グループチャットを定義**します。

Assistant Agentの出力テキストはメッセージとしてグループ内の他のAssistant Agentに発信され、そのAssistant Agentがその内容に呼応してメッセージをまたグループ内に発信するという、まさに**私達が普段使っているチャットツールの仕組みがそのままプログラミングモデル**になっています。

**最終的に問題のない応答テキストが生成されるとエージェントは会話終了の合図であるTERMINATEをグループに送信し、チャット完了、その内容をユーザーに返します**。会話の最大ターン数など、チャットの挙動に関する様々なパラメータを定義することもできるようになっています。

# サンプルコードの概説

AutoGenとAgentChatについて理解したところで簡単なサンプルコードを5パターン動かしてみたいと思います。

①**役割分担型**マルチエージェントのサンプルコード
②役割分担型に**Function Callingを追加**したパターン
③**ユーザーがグループチャットに介入**するパターン
④**交渉型**マルチエージェントのサンプルコード
⑤**OpenAI以外のLLM**を使ってみる(**OCI Generative AI ServiceのCohere Command R Plus**)


## ①役割分担型マルチエージェントのサンプルコード

まずは一番ベーシックなサンプルコードです。旅行する際に旅程を提案してくれるエージェントの例です。4つのエージェントに役割を分担したパターンになります。

まずは上述たautogenのagentchat、extensionなどのライブラリをインポートします。

```python
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
import os
import getpass
```
OpenAIのAPIキーを入力します。

```python
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API Key: ")
```

エージェントを定義します。全体の旅程を立てるエージェント、地元の観光地やアクティビティを提案するエージェント、訪問先での公用語について教えてくれるエージェント、旅程全体をサマリーするエージェントの4つに役割分担する例です。

```python
# 一つ目のエージェント：予定の全体をプランするエージェントplanner_agentを定義
planner_agent = AssistantAgent(
    "planner_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="旅行の計画を立ててくれる便利なアシスタント",
    system_message="あなたは、ユーザーのリクエストに基づいて旅行プランを提案できる便利なアシスタントです。",
)

# 二つ目のエージェント：訪問先の観光地やアクティビティを提案するエージェントlocal_agentを定義
local_agent = AssistantAgent(
    "local_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="地元のアクティビティや訪問先を提案できる地元アシスタント",
    system_message="あなたは、本物で興味深い地元のアクティビティや訪問する場所をユーザーに提案し、提供されたコンテキスト情報を利用できる便利なアシスタントです。",
)

# 三つ目のエージェント：訪問先の公用語について教えてくれるエージェントlanguage_agentを定義
language_agent = AssistantAgent(
    "language_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="特定の目的地に関する言語のヒントを提供できる便利なアシスタント",
    system_message="あなたは、旅行計画を検討し、特定の目的地での公用語やコミュニケーションの課題に対処する最善の方法に関する重要なヒントについてフィードバックを提供できる便利なアシスタントです。計画に言語に関するヒントがすでに含まれている場合は、その計画が満足のいくものであることを根拠を添えて言及できます。",
)

# 四つ目のエージェント：上記３つのエージェントの提案をサマリーし最終的な旅程を提案するエージェントtravel_summary_agentを定義
travel_summary_agent = AssistantAgent(
    "travel_summary_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="旅行計画をまとめるのに役立つアシスタント",
    system_message="あなたは、他のエージェントからの提案やアドバイスをすべて取り入れ、詳細な最終的な旅行計画を提供できる、役に立つアシスタントです。最終計画が統合され、完全であることを確認する必要があります。最終的な対応は完全な計画でなければなりません。計画が完了し、すべてのパースペクティブが統合されたら、TERMINATE で応答できます。",
)
```

会話を終えるterminationの条件を定義します。この例ではエージェントの出力に TERMINATE という文字が入っている場合に会話終了です。上述したtravel_summary_agentのsystem_messageに「処理が完了すれば TERMINATE」という旨のシステムメッセージを入れており、これと連動して動作します。

```python
termination = TextMentionTermination("TERMINATE")
```

次にグループチャットの定義です。今回はRoundRobinGroupChatを使ってみます。この定義では、あるエージェントが出力したメッセージはグループチャット内の他の全てのエージェントに送信されます。(文字通りグループチャットです。)そのメッセージを受け他のエージェントが順番に(Round Robin)応答メッセージを出力します。チャットがエンドレスにならないようにTERMINATEの条件を定義し、チャットの最大ターン数も念のため10に定義しています。

```python
group_chat = RoundRobinGroupChat(
    [planner_agent, local_agent, language_agent, travel_summary_agent],
    termination_condition=termination,
    max_turns=10  # 最大10ターンで終了
)
```

::: note info
現在のバージョンではRoundRobinGroupChat以外にSelectorGroupChatがあります。SelectorGroupChat
各メッセージの後に次のエージェントを選択し、順番にメッセージを送信するという制御になります。

Selector Group Chat
https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/selector-group-chat.html
:::

下記のような旅程のリクエストをしてみます。

```python
await Console(group_chat.run_stream(task="ネパールへの3日間の旅行を計画してください。"))
```

出力はこちら。

```python
---------- user ----------
ネパールへの3日間の旅行を計画してください。
---------- planner_agent ----------
ネパールへの3日間の旅行は、短いながらも豊富な体験ができる素晴らしいプランです。以下はそのサンプルプランです。

### 1日目: カトマンズ探訪
- **午前:**
  - カトマンズに到着後、ホテルにチェックイン。
  - パシュパティナート寺院を訪問。ヒンズー教の重要な寺院で、素晴らしい彫刻と巡礼者の姿を楽しめます。

- **午後:**
  - バグマティ川沿いにあるスワヤンブナート（モンキーテンプル）を訪れます。360度のパノラマビューが魅力です。

- **夜:**
  - タメル地区で夕食。地元のレストランでネパール料理を楽しんでください。

### 2日目: パタンとバクタプル訪問
- **午前:**
  - パタン（ラリトプル）市を訪れます。パタン・ダルバール広場の素晴らしい建築物と博物館を見学します。

- **午後:**
  - バクタプルに移動。中世の雰囲気が残る町を散策し、バクタプル・ダルバール広場やナヤタポラ寺院を訪問します。
  - バクタプルで有名な「キングカード」を味わいます。

- **夜:**
  - カトマンズに戻り、ホテルでリラックス。

### 3日目: ネイチャーとカルチャー体験
- **午前:**
  - ナガルコットへ日の出を見に出発（早朝）。ヒマラヤの美しい景色を楽しむことができます。

- **午後:**
  - 午後はカトマンズに戻り、時間があればボダナート・ストゥーパを訪れます。チベット仏教の象徴であるこの場所で祈りの旗を見ることができます。

- **夜:**
  - 最後の夕食をタメル地区で取り、カトマンズの夜を楽しみます。現地の民俗音楽を楽しめる場所を探すのも良いでしょう。

### 注意事項:
- 天候により景観が変わることがありますので、現地の気象情報を確認してください。
- 移動はタクシーや現地のツアーガイドを利用すると便利です。
- ネパールは高地に位置しているので、十分な水分補給と体調管理を心がけてください。

このプランを基に、あなたの興味に合わせてカスタマイズしてみてください。良い旅を！
---------- local_agent ----------
ネパールへの3日間の旅行は、短期間で素晴らしい文化や自然の魅力を体験する絶好の機会です。以下のプランを参考に、あなただけの旅を楽しんでください。

### 1日目: カトマンズの文化と歴史を探索
- **午前:**
  - カトマンズに到着後、ホテルにチェックインし、身支度を整えましょう。
  - 伝統的で荘厳なパシュパティナート寺院を訪問しましょう。ここはスピリチュアルな体験と、寺院の美しい建築を楽しむことができます。

- **午後:**
  - スワヤンブナート（モンキーテンプル）を訪れ、丘の上からカトマンズの全景を楽しみましょう。ここではヒマラヤ山脈の雄大な景色を眺めることができます。

- **夜:**
  - タメル地区で現地のレストランを訪れて、新鮮なネパール料理を堪能しましょう。賑やかな通りでのショッピングもお楽しみください。

### 2日目: パタンとバクタプルの中世都市訪問
- **午前:**
  - パタン市（ラリトプル）へ向かい、パタン・ダルバール広場を散策。ユネスコの世界遺産に指定されており、魅力的な建物や博物館がたくさんあります。

- **午後:**
  - バクタプルに移動して、中世の雰囲気に浸りましょう。バクタプル・ダルバール広場やナヤタポラ寺院を訪れ、この地域の工芸品や特産品を楽しんでください。
  - ここで有名な「ジュジュ・ダウ（王のカスタード）」を味わうことをお忘れなく。

- **夜:**
  - カトマンズに戻り、リラックスした夜を過ごします。

### 3日目: 自然とリラックス
- **早朝:**
  - 日の出前にナガルコットに向かい、美しいヒマラヤのお茶を祖とした景色を楽しむ日の出観賞を体験してください。

- **午後:**
  - カトマンズに戻り、チベット仏教最大のボダナート・ストゥーパを訪れ、その平和な雰囲気を楽しんでください。ここはスピリチュアルな体験ができる場所です。

- **夜:**
  - タメル地区での最後の夕食を楽しみ、美味しいネパール料理と地元の音楽を楽しんでください。

### 旅行のヒント:
- 天候は変わりやすいので、旅行前に天気予報を確認しましょう。
- 移動にはタクシーや現地のツアーを利用するのが便利です。
- 高地のため、体調管理と水分補給をしっかり行いましょう。

この旅程を基に、あなた自身の興味に合うようにカスタマイズしていただけます。素晴らしい旅をお楽しみください！
---------- language_agent ----------
ネパールへの3日間の旅行プランは、文化、歴史、自然が巧みに組み合わさった素晴らしいものです。以下にいくつか特に気を付けるべき言語とコミュニケーションのポイントをお伝えします。

### 言語とコミュニケーション:
- **公用語**: ネパールの公用語はネパール語ですが、観光地やカトマンズのような都市部では英語も広く通じます。
- **ヒンディー語**: インドと近い文化的背景を持つため、特に観光関連のエリアではヒンディー語も理解されることがあります。
- **基本フレーズ**: 簡単なネパール語のあいさつ「ナマステ」や感謝の言葉「ダンニャバード」を覚えておくと、地元の人との交流がさらにスムーズになります。

### おすすめの対応策:
- **ガイドブックやアプリ**: 英語-ネパール語のガイドブックや翻訳アプリを事前にダウンロードしておくと便利です。
- **現地のガイド**: 言語の壁をスムーズに越えるため、現地のガイドを利用するのも良い選択です。ガイドは観光地の詳細情報を提供してくれます。

この旅行プランには公用語とコミュニケーションのタイムリーなアドバイスが含まれており、非常に満足のいく内容です。旅行の際はこれらのポイントを活かして、ネパールネパールの魅力を存分に楽しんでくださいね。
---------- travel_summary_agent ----------
ネパールへの3日間の旅行計画が整いました。文化、歴史、自然、そしてコミュニケーションのコツを備えた豊かな体験をお楽しみください。

---

### 1日目: カトマンズの文化と歴史を探索
- **午前:**
  - カトマンズに到着後、ホテルにチェックイン。
  - スピリチュアルなパシュパティナート寺院を訪問。ヒンズー教の文化を体験し、見事な彫刻を楽しみましょう。

- **午後:**
  - スワヤンブナート（モンキーテンプル）へ足を運び、丘の上からカトマンズの全景を楽しめます。ここではヒマラヤ山脈の壮大な景色を見渡せます。

- **夜:**
  - タメル地区で夕食。地元のレストランでネパール料理を堪能し、賑やかな通りでショッピングも楽しんでみてください。

### 2日目: パタンとバクタプルの中世都市を探訪
- **午前:**
  - パタン市（ラリトプル）を訪問。パタン・ダルバール広場でユネスコの世界遺産に指定された建築物や博物館を散策。

- **午後:**
  - バクタプルへ移動。バクタプル・ダルバール広場やナヤタポラ寺院で中世の魅力を堪能。特に有名な「ジュジュ・ダウ（王のカスタード）」を試してみましょう。

- **夜:**
  - カトマンズに戻り、ホテルでリラックス。

### 3日目: 自然と静寂を求めて
- **早朝:**
  - 日の出前のナガルコットで、美しいヒマラヤの景色を楽しむ日の出観賞をします。

- **午後:**
  - カトマンズに戻り、ボダナート・ストゥーパを訪問。チベット仏教の平和な雰囲気に浸りましょう。

- **夜:**
  - タメル地区での最後の夕食。地元の音楽を聴きながら、旅行の最後の夜を楽しんでください。

---

### 言語とコミュニケーション:
- **基本フレーズ**: 簡単なネパール語の「ナマステ」（こんにちは）や「ダンニャバード」（ありがとう）を覚えて、地元の人々とより良い交流を！
- **ガイドブックやアプリ**: 英語-ネパール語の翻訳アプリを活用し、コミュニケーションをスムーズに。

---

### 旅行のヒント:
- **天候**: 高地のネパールでは天候が変わりやすいので、服装には注意してください。
- **移動手段**: タクシーや現地ツアーガイドを利用することで、効率よく移動できます。

このプランを参考に、あなたの旅を個性的で充実したものにしてください。素晴らしい旅をお楽しみください！

TERMINATE
```

複数のエージェントによって役割分担され、それらがグループチャットにより会話しながら最終的な旅程を決定している様子がわかると思います。





## ②Function Callingを追加したパターン

上記の役割分担型のコードにFunction Callingを追加してみたいと思います。旅程提案のAIエージェントが訪問先の通貨の為替レートをヤフーファイナンスで事前にチェックしてくれるというシナリオです。

必要なライブラリをインポートします。ヤフーファイナンスのAPIサービスをCallしますのでそのライブラリyahooqueryのインポートを追加しています。

```python
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
import os
import getpass
from yahooquery import Ticker
```

OpenAIのAPIキーを入力

```python
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API Key: ")
```

ヤフーファイナンスで現在の為替レートをクエリする関数を定義します。

```python
def get_exchange_rate(currency_code: str) -> dict:
    """
    指定した国の通貨と日本円（JPY）の為替レートを取得する。

    Args:
        currency_code (str): 通貨コード（例: "USD", "EUR", "GBP"）

    Returns:
        dict: 為替レート情報（通貨ペア、レート、取得時間）
    """
    symbol = f"{currency_code}JPY=X"  # 例: "USDJPY=X"
    ticker = Ticker(symbol)
    data = ticker.price[symbol]

    if "regularMarketPrice" in data:
        return {
            "currency_pair": f"{currency_code}/JPY",
            "exchange_rate": data["regularMarketPrice"],
            "timestamp": data["regularMarketTime"]
        }

    return {"error": "為替レートが取得できませんでした"}

```

上述の関数を実行して動作確認をします。

```python
rate_info = get_exchange_rate("USD")  # USD/JPY のレート取得
print(rate_info)
```

出力は以下の通り。

```python
{'currency_pair': 'USD/JPY', 'exchange_rate': 150.542, 'timestamp': '2025-02-20 02:09:08'}
```

上述の関数をFunction Callingとして定義します。

```python
from autogen_core.tools import FunctionTool

get_exchange_rate_tool = FunctionTool(
    get_exchange_rate, description="現在の為替レートを取得します。"
)
```

ここからエージェントの定義です。先のコードで作成したエージェント4つに為替レートを調べるexchange_agentを追加しています。AssistantAgentの引数toolsに上述したfunction callingを定義するというシンプルなものです。

```python
planner_agent = AssistantAgent(
    "planner_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="旅行の計画を立ててくれる便利なアシスタント",
    system_message="あなたは、ユーザーのリクエストに基づいて旅行プランを提案できる便利なアシスタントです。",
)

local_agent = AssistantAgent(
    "local_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="地元のアクティビティや訪問先を提案できる地元アシスタント",
    system_message="あなたは、本物で興味深い地元のアクティビティや訪問する場所をユーザーに提案し、提供されたコンテキスト情報を利用できる便利なアシスタントです。",
)

language_agent = AssistantAgent(
    "language_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="特定の目的地に関する言語のヒントを提供できる便利なアシスタント",
    system_message="あなたは、旅行計画の中にある訪問先での公用語に関する情報を提供できる便利なアシスタントです。",
)

# 追加のエージェント。 引数toolsでfunction Callingを定義。
exchange_agent = AssistantAgent(
    name = "exchange_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="訪問先での通貨に関する情報を提供できる便利なアシスタント",
    system_message="あなたは、旅行計画の中にある訪問先での通貨と日本円の為替レートをget_exchange_rate_toolを使って提供できる便利なアシスタントです。",
    tools=[get_exchange_rate_tool]
)

travel_summary_agent = AssistantAgent(
    "travel_summary_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="旅行計画をまとめるのに役立つアシスタント",
    system_message="あなたは、他のエージェントからの提案やアドバイスをすべて取り入れ、詳細な最終的な旅行計画を提供できる、役に立つアシスタントです。最終計画が統合され、完全であることを確認する必要があります。最終的な対応は完全な計画でなければなりません。計画が完了し、すべてのパースペクティブが統合されたら、TERMINATE で応答できます。",
)
```

terminationの条件とグループチャットの定義。

```python
termination = TextMentionTermination("TERMINATE")
group_chat = RoundRobinGroupChat(
    [planner_agent, local_agent, language_agent, exchange_agent, travel_summary_agent],
    termination_condition=termination
)
```

下記のようなプロンプトで旅程をリクエスト。

```python
await Console(group_chat.run_stream(task="ヨーロッパの3か国を訪れる3日間の旅行を計画してください。"))
```

出力がこちら。
```python
---------- user ----------
ヨーロッパの3か国を訪れる3日間の旅行を計画してください。
---------- planner_agent ----------
ヨーロッパの3か国を3日間で訪れる旅行は駆け足になりますが、いくつかの国が近接しているため、可能です。以下に、効率的に移動できるプランを提案します。

**1日目: フランス (パリ)**
- 朝: パリに到着。シャルル・ド・ゴール空港またはオルリー空港に着陸後、市内中心部へ移動。
- 午前: エッフェル塔を訪れ、パリの象徴的な景色を楽しみましょう。
- 昼: セーヌ川沿いでランチ。クレープやマカロンはいかがでしょうか。
- 午後: ルーヴル美術館を訪れてみることをお勧めします。主要な作品を見学する時間は確保できるでしょう。
- 夜: モンマルトル地区を散策し、サクレ・クール寺院を訪れるのも良いでしょう。

**2日目: ベルギー (ブリュッセル)**
- 朝: 高速列車Thalysでブリュッセルへ移動（約1時間30分）。
- 午前: グランプラスを訪れ、美しい市庁舎を見学。周囲の雰囲気を楽しむことができます。
- 昼: ベルギー名物のワッフルやチョコレートを楽しみましょう。
- 午後: 小便小僧を見に行った後、王立美術館でアート鑑賞をしてみてはいかがでしょうか。
- 夜: 欧州議会の周辺を散策し、現代的なブリュッセルの一面を体感します。

**3日目: オランダ (アムステルダム)**
- 朝: Thalysでアムステルダムへ移動（約2時間）。
- 午前: ゴッホ美術館または国立博物館を訪れ、ヨーロッパ芸術の歴史を感じましょう。
- 昼: 運河沿いのカフェでランチを取り、リラックス。
- 午後: アムステルダムの美しい運河をクルーズ船で巡りましょう。
- 夕方: ダム広場や旧市街を散策し、お土産を購入する時間を設けましょう。

このプランがご参考になれば幸いです。時間に余裕を持ちつつ、安全で楽しい旅をお過ごしください！
---------- local_agent ----------
ヨーロッパの3か国を訪れる3日間の旅行は駆け足になりますが、いくつかの国が近接しているため、実現可能です。以下に効率的に移動できるプランを提案します。

**1日目: イタリア (ミラノ)**
- 朝: ミラノ・マルペンサ空港に到着。市内中心部へ移動します。
- 午前: ドゥオーモやヴィットーリオ・エマヌエーレ2世のガレリアを見学。
- 昼: 地元のリストランテでイタリアン・ランチを楽しみましょう。
- 午後: ブレラ美術館やレオナルド・ダ・ヴィンチの「最後の晩餐」を見学。
- 夕方: スフォルツェスコ城を訪れ、ミラノの歴史に触れましょう。

**2日目: スイス (チューリッヒ)**
- 朝: ミラノから電車でチューリッヒへ移動（約3時間）。
- 午前: バンホーフ通りを散策し、ショッピングやカフェを楽しみましょう。
- 昼: スイス名物のチーズフォンデュやロシュティを堪能。
- 午後: 歴史のある旧市街を訪れ、チューリッヒ湖周辺を散策。
- 夜: リンデンホーフの丘で美しい夕日を眺めます。

**3日目: ドイツ (ミュンヘン)**
- 朝: 電車でミュンヘンへ移動（約4時間）。
- 午前: マリエン広場で有名な新市庁舎の鐘を鑑賞。
- 昼: ビアホールでプレッツェルとソーセージのランチを。
- 午後: 英国庭園を散策し、リラックスした時間を。
- 夕方: フラウエン教会を訪れ、ドイツのゴシック建築を堪能。

この旅程でヨーロッパの異なる文化と歴史を短期間で体験できます。ぜひ、現地の美食と美しい風景を楽しんでください！
---------- language_agent ----------
お役に立てて嬉しいです。訪れる国々の公用語をまとめておきますので、現地でのコミュニケーションの参考にしてください。

- **イタリア (ミラノ)**: イタリア語
- **スイス (チューリッヒ)**: ドイツ語、フランス語、イタリア語が公用語ですが、チューリッヒでは主にドイツ語が話されています。
- **ドイツ (ミュンヘン)**: ドイツ語

それぞれの言語での基本的な挨拶やフレーズを覚えていくと、より楽しい旅になるでしょう。素晴らしいヨーロッパ旅行をお楽しみください！
---------- exchange_agent ----------
[FunctionCall(id='call_QNxSHsIOwn4ckkht8RviYxdg', arguments='{"currency_code": "EUR"}', name='get_exchange_rate'), FunctionCall(id='call_fe0WHyCKt3vcRg3GMrD3U5hv', arguments='{"currency_code": "CHF"}', name='get_exchange_rate')]
---------- exchange_agent ----------
[FunctionExecutionResult(content="{'currency_pair': 'EUR/JPY', 'exchange_rate': 157.01, 'timestamp': '2025-02-20 02:27:02'}", call_id='call_QNxSHsIOwn4ckkht8RviYxdg'), FunctionExecutionResult(content="{'currency_pair': 'CHF/JPY', 'exchange_rate': 166.702, 'timestamp': '2025-02-20 02:27:01'}", call_id='call_fe0WHyCKt3vcRg3GMrD3U5hv')]
---------- exchange_agent ----------
{'currency_pair': 'EUR/JPY', 'exchange_rate': 157.01, 'timestamp': '2025-02-20 02:27:02'}
{'currency_pair': 'CHF/JPY', 'exchange_rate': 166.702, 'timestamp': '2025-02-20 02:27:01'}
---------- travel_summary_agent ----------
ヨーロッパの3か国を訪れる3日間の旅行プランを最終調整しました。以下が詳細な旅程です。

**1日目: イタリア (ミラノ)**
- **朝**: ミラノ・マルペンサ空港に到着。市内中心部へ移動します。
- **午前**: ミラノ大聖堂（ドゥオーモ）やヴィットーリオ・エマヌエーレ2世のガレリアを見学します。
- **昼**: 地元のリストランテでイタリアン・ランチを楽しみましょう。
- **午後**: ブレラ美術館を訪れ、レオナルド・ダ・ヴィンチの「最後の晩餐」を鑑賞します。
- **夕方**: スフォルツェスコ城を訪れ、ミラノの歴史に触れましょう。

**2日目: スイス (チューリッヒ)**
- **朝**: ミラノから電車でチューリッヒへ移動（約3時間）。
- **午前**: バンホーフ通りを散策し、ショッピングやカフェを楽しみます。
- **昼**: スイス名物のチーズフォンデュやロシュティを堪能。
- **午後**: 歴史ある旧市街を訪れ、チューリッヒ湖周辺を散策します。
- **夜**: リンデンホーフの丘で美しい夕日を観賞します。

**3日目: ドイツ (ミュンヘン)**
- **朝**: 電車でミュンヘンへ移動（約4時間）。
- **午前**: マリエン広場で有名な新市庁舎の鐘を鑑賞します。
- **昼**: ビアホールでプレッツェルとソーセージのランチをいただきます。
- **午後**: 英国庭園を散策し、リラックスした時間を過ごしましょう。
- **夕方**: フラウエン教会を訪れ、ドイツのゴシック建築を堪能します。

**コミュニケーション**:
- イタリア語（ミラノ）
- ドイツ語（チューリッヒ、ミュンヘン）

**為替レート**（参考）:
- 1ユーロ (EUR) = 157.01円 (JPY)
- 1スイスフラン (CHF) = 166.702円 (JPY)

このプランが、あなたの旅の参考になれば幸いです。異なる文化、美食、そして魅力的な歴史に触れる素晴らしい旅をお楽しみください！安全で楽しい旅をお祈りしています。TERMINATE
```
exchange_rateエージェントによってfunction callingが実行され現在の為替レートが最終の旅程に反映されていることがわかります。


## ③ユーザーがグループチャットに介入するパターン
グループチャットの最後にユーザー(人間)が介入しエージェントの動きをメッセージで制御するパターンです。(Human in the loopと呼んでいたりします。)

下記のサンプルコートはこれまでと同じ旅程を決めるシナリオになりますが、エージェントの提案にユーザーが追加のリクエストを行い、エージェントがそのリクエストにそって旅程を再提案するといシナリオです。

実ビジネスだと、例えばサプライチェーンシステムなどの発注システムに連携してAIエージェントが発注処理を実行する前に人間が確認・判断を行う必要がある場合など、全ての判断をエージェントに任せられないようなケースでの利用が想定されます。

必要なライブラリをインポートします。

```python
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
import os
import getpass
```
OpenAIのAPIキーを入力。

```python
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API Key: ")
```

エージェントを定義します。これまでのコードとほぼ変わりませんが、**travel_summary_agentのシステムメッセージにTERMINATEの条件に関する文言が入りません。これがこれまでのコードと違う点です**。

```python
planner_agent = AssistantAgent(
    "planner_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="旅行の計画を立ててくれる便利なアシスタント",
    system_message="あなたは、ユーザーのリクエストに基づいて旅行プランを提案できる便利なアシスタントです。",
)

local_agent = AssistantAgent(
    "local_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="地元のアクティビティや訪問先を提案できる地元アシスタント",
    system_message="あなたは、本物で興味深い地元のアクティビティや訪問する場所をユーザーに提案し、提供されたコンテキスト情報を利用できる便利なアシスタントです。",
)

language_agent = AssistantAgent(
    "language_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="特定の目的地に関する言語のヒントを提供できる便利なアシスタント",
    system_message="あなたは、旅行計画を検討し、特定の目的地での言語やコミュニケーションの課題に対処する最善の方法に関する重要なヒントについてフィードバックしてください。",
)

travel_summary_agent = AssistantAgent(
    "travel_summary_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-4o"),
    description="旅行計画をまとめるのに役立つアシスタント",
    system_message="あなたは、他のエージェントからの提案やアドバイスをすべて取り入れ、詳細な最終的な旅行計画を提供できる、役に立つアシスタントです。最終計画が統合され、完全であることを確認する必要があります。最終的な対応は完全な計画でなければなりません。",

)
```

ユーザーがグループチャットに介入するためのエージェントを新たに追加します。**これまではAssistantAgentという関数でエージェントを定義しましたが、ユーザーに処理を渡す(ユーザーの入力を求める)必要があるため、別の関数UserProxyAgentでエージェントを定義します**。

UserProxyAgentタイプのエージェントがグループチャットに存在すると、**グループチャットの最後に必ずこのUserProxyAgentに処理が渡されユーザーの入力を求める**というシーケンスになります。

```python
from autogen_agentchat.agents import UserProxyAgent

user_proxy = UserProxyAgent("user_proxy", input_func=input)
```

TERMINATIONの条件を定義します。以下は**ユーザーが入力するメッセージに『APPROVE』の文字があった場合にチャット終了**という条件でグループチャットを定義しています。

```python
termination = TextMentionTermination("APPROVE")

group_chat = RoundRobinGroupChat(
    [user_proxy, planner_agent, language_agent, travel_summary_agent],
    termination_condition=termination,
    max_turns=10  # 最大10ターンで終了
)
```

定義したグループチャットを実行します。

```python
stream = group_chat.run_stream()
await Console(stream)
```

UserProxyAgentが存在するグループチャットなので、初めにユーザーが入力を求められます。

```python
Enter your response:
```

これまで同様「ヨーロッパの3か国を訪れる3日間の旅行を計画してください。」と入力してみます。

```python
Enter your response: ヨーロッパの3か国を訪れる3日間の旅行を計画してください。
```

このメッセージに反応し、他のエージェントがチャットを始めます。

```python
Enter your response:  ヨーロッパの3か国を訪れる3日間の旅行を計画してください。
---------- user_proxy ----------
ヨーロッパの3か国を訪れる3日間の旅行を計画してください。
---------- planner_agent ----------
ヨーロッパで3日間の旅行はとても短期間ですが、効率よく計画すれば3つの異なる国を訪れることができます。以下は、アクセスしやすい都市を選んだモデルプランです。

### 1日目: フランス（パリ）
**午前:**
- シャルル・ド・ゴール空港に到着。
- ホテルにチェックイン。
- 朝食後、エッフェル塔を訪問。観光バスを利用すると時間を節約できます。

**午後:**
- ルーブル美術館を訪問。名作を堪能しつつ、ガイドツアーで効率的に観覧。
- 昼食はルーブル周辺のカフェでフランス料理を満喫。

**夜:**
- セーヌ川クルーズで夜景を楽しみながらパリの美しさを堪能。
- オルセー美術館の近くでディナー。

### 2日目: ベルギー（ブリュッセル）
**午前:**
- 午前中の早い時間にタリス高速列車でブリュッセルへ移動（約1時間半）。
- グランプラスを見学し、周辺の観光スポットを散策。

**午後:**
- 昼食はベルギーワッフルを試しつつ、地元のビールを楽しむ。
- アトミウムへ訪れ、アートと建築の融合を体験。

**夜:**
- サント・カトリーヌ広場周辺のレストランで夕食。
- ブリュッセル北駅近くのホテルに宿泊。

### 3日目: オランダ（アムステルダム）
**午前:**
- 朝早く出発して、インターシティエクスプレスでアムステルダムへ移動（約2時間）。
- 市内の中心、ダム広場を訪問し、王宮を観光。

**午後:**
- ライクスミュージアムかアムステルダム国立美術館を訪問し、オランダの芸術を堪能。
- 市内運河沿いのカフェでランチ。

**夜:**
- ヴォルヘルスト公園を訪れ、自然を楽しむ。
- ユニークなハーリングやストロープワッフルを試しつつ、軽食を楽しみながらアムステルダム中央駅へ。
- 帰国のためのフライトに備えてスキポール空港へ移動。

このプランでは、なるべく公共交通機関を利用して時間を節約し、各都市の主要スポットを楽しむようにしています。滞在時間は短いですが、それぞれの文化と観光地を最大限に堪能できることでしょう。旅行に必要なビザやチケット、宿泊先は事前に予約しておくのをお忘れなく。
---------- language_agent ----------
旅行計画に関して、ヨーロッパでの言語とコミュニケーションの課題に対処するためのいくつかのヒントを提供します。

1. **基本フレーズを学ぶ**: 各国の基本的なフレーズ（挨拶、感謝、尋ねる言葉など）をあらかじめ学んでおくと便利です。小さなジェスチャーが地元の人々との距離を縮める助けになります。

2. **翻訳アプリを活用**: Google翻訳やDeepLのような翻訳アプリをスマートフォンにダウンロードしておけば、リアルタイムでテキストや音声を翻訳することができ、緊急時やサポートが必要なときに役立ちます。

3. **地図アプリのオフライン利用**: オフラインで使える地図アプリをダウンロードしておくと、通信が不安定な場所でも道に迷わずに済みます。事前に主要地点をマークしておくと便利です。

4. **非言語コミュニケーション**: 必要な情報を伝える際、ジェスチャーや手ぶりを使って意思疎通を図ることも可能です。特に、指差しや数量を示すジェスチャーは言語を超えて理解できます。

5. **現地の言語サポートブックを持参**: 簡単なガイドブックやポケット辞書を持っていると、現地で必要な言語をすぐに確認できます。

6. **宿泊先のスタッフに聞く**: 宿泊先のスタッフに現地の情報やおすすめのスポットを尋ねると、地元の観光情報を得たり、コミュニケーションの仲介をしてもらったりできます。

これらのヒントを活用すれば、旅行中のコミュニケーションの課題をよりスムーズに乗り越えることができるでしょう。楽しい旅をお過ごしください！
---------- travel_summary_agent ----------
素晴らしい旅行計画を最大限に活用するために、言語のヒントも取り入れた最終的なヨーロッパ旅行計画をご案内します。

### 1日目: フランス（パリ）
**午前:**
- シャルル・ド・ゴール空港に到着後、タクシーやシャトルでホテルにチェックイン。
- エッフェル塔を訪問。事前にチケット予約をしておくと待ち時間を短縮できます。観光バスツアーを利用すると効率的です。

**午後:**
- ルーブル美術館を訪問。名作を効率的に観覧するためにガイドツアーを利用。館内の看板は多言語対応ですが、翻訳アプリが役立つときもあります。
- 昼食はルーブル周辺のカフェでフランス料理を満喫。

**夜:**
- Google翻訳を活用しつつ、セーヌ川クルーズで夜景を楽しむ（音声ガイドが多言語で利用できる場合もあり）。
- オルセー美術館の近くでディナー。基本的なフランス語挨拶を試してみてください。

### 2日目: ベルギー（ブリュッセル）
**午前:**
- 午前中のタリス高速列車でブリュッセルへ移動（約1時間半）。列車には多言語案内があり、翻訳アプリも補助になります。
- グランプラスを訪れ、周辺を散策。基本的なオランダ語やフランス語のフレーズを使ってみましょう。

**午後:**
- ベルギーワッフルとビールを楽しんでランチ。
- アトミウムを訪問、翻訳アプリで案内を理解しつつ、展示を楽しむ。

**夜:**
- サント・カトリーヌ広場周辺で夕食。地図アプリを活用しながらレストランを探してみましょう。
- ブリュッセル北駅近くのホテルにチェックイン。

### 3日目: オランダ（アムステルダム）
**午前:**
- 早朝にインターシティエクスプレスでアムステルダムへ（約2時間）。駅での案内は多言語対応です。
- ダム広場を観光し、王宮内を散策。英語での案内も多く、コミュニケーションは容易です。

**午後:**
- ライクスミュージアムを訪問。名作を鑑賞し、翻訳機能を使用しながら説明を理解。
- 運河沿いのカフェでランチ、地図アプリで現地情報を確認しつつ移動。

**夜:**
- ヴォルヘルスト公園で自然を楽しむ。
- ハーリングやストロープワッフルを試しながら、アムステルダム中央駅へ。帰国のフライト準備としてスキポール空港に向かいます。

3日間とはいえ、事前準備とテクノロジーの活用を駆使することで、3つの国の魅力をしっかり探索できます。各国の基本フレーズをおさえつつ、アプリを使いこなして、素晴らしい旅をお楽しみください。

Enter your response:
```

一通りのエージェントがチャットを終え、処理がUserProxyAgentに戻り、再度入力を求められます。ここで内容に問題がなければ『APPROVE』と入力すればチャット完了です。

今回は、訪問先の一部を別の国に変更するようなプロンプトを入力してみます。

```python
Enter your response: ベルギーを別の国に変更してください。
```

上述のプロンプトを受け、以降、グループチャットのやり直しという状況になります。

```python
---------- user_proxy ----------
ベルギーを別の国に変更してください。
---------- planner_agent ----------
かしこまりました。では、ベルギーの代わりにドイツを旅程に組み込みます。以下は、ドイツを含む3日間の旅行プランです。

### 1日目: フランス（パリ）
**午前:**
- シャルル・ド・ゴール空港に到着。
- ホテルにチェックイン。
- 朝食後、エッフェル塔を訪問。観光バスを利用すると時間を節約できます。

**午後:**
- ルーブル美術館を訪問。名作を堪能しつつ、ガイドツアーで効率的に観覧。
- 昼食はルーブル周辺のカフェでフランス料理を満喫。

**夜:**
- セーヌ川クルーズで夜景を楽しみながらパリの美しさを堪能。
- オルセー美術館の近くでディナー。

### 2日目: ドイツ（ケルン）
**午前:**
- 午前中にタリス高速列車またはICEでパリからケルンへ移動（約3時間）。
- ケルン大聖堂を訪問。ケルンの象徴的な建築物で、ガイドツアーがあるので利用するのもおすすめです。

**午後:**
- ライン川沿いを散策しながらリラックス。地元のレストランでランチ。
- ケルン市立美術館や応用工芸美術館を訪問。地方の文化や歴史を堪能。

**夜:**
- ケルンの旧市街で夕食。伝統的なドイツ料理を楽しんでみましょう。
- ケルンのホテルに宿泊。

### 3日目: オランダ（アムステルダム）
**午前:**
- 朝早く出発して、インターシティエクスプレスでアムステルダムへ移動（約3時間）。
- 市内の中心、ダム広場を訪問し、王宮を観光。

**午後:**
- ライクスミュージアムやアムステルダム国立美術館を訪れ、オランダの芸術を堪能。
- 市内運河沿いのカフェでランチ。

**夜:**
- ヴォルヘルスト公園を訪れ、自然を楽しむ。
- ユニークなハーリングやストロープワッフルを試しつつ、軽食を楽しんで、アムステルダム中央駅へ。

このプランでは、各都市の主要スポットを効率的に楽しむことを重視しています。ドイツ、特にケルンの歴史的建造物や美術館も、短い滞在でも印象に残る観光地です。旅行の準備には、各都市での宿泊や交通機関の予約を忘れずに行ってください。楽しい旅をお過ごしください！
---------- language_agent ----------
言語のヒントを活用しながらの旅行計画を以下にご案内します。

### 1日目: フランス（パリ）
**午前:**
- シャルル・ド・ゴール空港に到着後、ホテルにチェックイン。到着時にフランス語の基本フレーズを使うと好印象。
- エッフェル塔を訪問。事前にオンラインでチケット予約がおすすめです。

**午後:**
- ルーブル美術館へ。ガイドツアーは多言語対応、フランス語の案内を翻訳アプリで補完できます。
- 昼食には、フランス料理を堪能し、メニューを翻訳アプリで確認。

**夜:**
- セーヌ川のクルーズで夜景を楽しむ際、音声ガイドが多言語対応か確認。
- パリのカフェでディナー時に簡単な挨拶を練習して。

### 2日目: ドイツ（ケルン）
**午前:**
- ドイツに向かう国際列車内では、ドイツ語の基本フレーズを勉強。車内案内は多言語が基本。
- ケルン大聖堂を訪問、観光情報は英語でも広く提供されているので便利です。

**午後:**
- ライン川沿いへ。景観と歴史を楽しみながら昼食。翻訳アプリでメニューを確認。
- ケルン市内美術館を訪れる前に、展示情報をオンラインで確認。

**夜:**
- ドイツのビアホールで夕食を。地元料理を楽しみつつ、店員にドイツ語での注文を試みます。
- ケルンの中心地に宿泊。

### 3日目: オランダ（アムステルダム）
**午前:**
- アムステルダムに向かう列車では、オランダ語の基本的な挨拶をマスター。到着時に役立ちます。
- ダム広場に到着、歴史的な地点を観光し、王宮内部を見学。

**午後:**
- ライクスミュージアムやゴッホ美術館を巡り、オランダの芸術を楽しんでください。展示案内は多言語対応が多いです。
- カフェでランチ。翻訳アプリで通常のメニューも確認可能。

**夜:**
- ヴォルヘルスト公園で夕方をリラックス。マーケットで地元のお菓子を試食。
- アムステルダム中央駅からスキポール空港へ行き、帰国準備。

翻訳アプリと基本的な言語スキルを駆使することで、国境を越える短期間の旅行でもしっかりとした文化体験を得られるはずです。安全で楽しい旅をお楽しみください！
---------- travel_summary_agent ----------
最終プランとして、言語のヒントを盛り込んだ3日間のヨーロッパ旅行をご案内します。

### 1日目: フランス（パリ）
**午前:**
- シャルル・ド・ゴール空港に到着後、タクシーやシャトルでホテルにチェックイン。フランス語の基本フレーズ（「Bonjour」で挨拶、「Merci」で感謝）を試してみてください。
- エッフェル塔を訪問。事前にオンラインでチケット予約をし、観光バスツアーを利用すると効率的です。

**午後:**
- ルーブル美術館を訪問。ガイドツアーが多言語に対応しており、翻訳アプリで補足情報を確認できます。
- 昼食はルーブル周辺のカフェでフランス料理を楽しみましょう。翻訳アプリでメニューを確認するとスムーズです。

**夜:**
- セーヌ川クルーズで美しい夜景を楽しむ。音声ガイドは多言語対応。
- オルセー美術館近くのカフェでディナー。店員にフランス語の簡単な挨拶とフレーズを試してみてください。

### 2日目: ドイツ（ケルン）
**午前:**
- ドイツのケルンへの移動はタリス高速列車またはICEで。車内アナウンスは多言語対応。簡単なドイツ語のフレーズ（「Hallo」で挨拶、「Danke」で感謝）も活用してみましょう。
- ケルン大聖堂を訪問。観光情報や案内板は英語にも対応しています。

**午後:**
- ライン川散策と地元レストランでのランチ。ドイツ料理を堪能。翻訳アプリでメニュー確認が便利。
- ケルン市立美術館を訪問。展示情報をHPで確認し、見学を効率よく。

**夜:**
- 旧市街での夕食。ビアホールでの注文で簡単なドイツ語を試みてみましょう。
- ケルン市内のホテルに宿泊。

### 3日目: オランダ（アムステルダム）
**午前:**
- アムステルダムへはインターシティエクスプレスで移動。車内でオランダ語の基本フレーズ（「Hoi」で挨拶、「Dank je」で感謝）を学んでみましょう。
- ダム広場訪問、王宮を見学。案内は英語でもあります。

**午後:**
- ライクスミュージアムを訪問。オランダの芸術を楽しみ、翻訳アプリで展示説明を確認。
- 市内運河沿いのカフェでランチ。メニューを翻訳アプリでチェック。

**夜:**
- ヴォルヘルスト公園でリラックス。地元のマーケットでストロープワッフルなどのお菓子を試食。
- アムステルダム中央駅からスキポール空港へ移動し、帰国準備。

この旅行プランは、各国の主要な観光スポットを訪れつつ、それぞれの国での基本的な言語スキルを実践する機会を提供します。テクノロジーを利用してコミュニケーションをサポートしながら、充実した文化体験を満喫してください。良い旅をお過ごしください！

Enter your response: 
```

一通りエージェントのチャットが処理されたので、再度User Proxy Agentに処理が渡り、入力を求められます。『Approve』を入力するとチャット終了です。

```
Enter your response: APPROVE
---------- user_proxy ----------
APPROVE
```

エージェントの会話にユーザーが介入する様子がおわかりいただけたのではないでしょうか。




## ④交渉型マルチエージェントのサンプルコード
不動産の売り手エージェントと不動産の買い手エージェントが交渉を行い、交渉仲介エージェントがそれを纏めるという交渉型マルチエージェントのシナリオです。

不動産の買い手エージェントにのみ、不動産の市場価格情報を検索できるFunction Callingを定義してみました。


必要なライブラリのインポート

```python
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_core.tools import FunctionTool
from typing import List
import os
import getpass
```
OpenAIのAPIキーの入力
```python
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API Key: ")
```

不動産価格情報として架空の市場データ定義します。本来であればこの部分は[不動産情報ライブラリ(国土交通省)](https://www.reinfolib.mlit.go.jp/)などのサービスにAPI Callするfunction callingを作成すべきですが今回は簡易的に下記のデータを検索するfunction callingを定義します。

```python
real_estate_data = {
    "新宿駅": [
        {"価格（万円）": 8500, "広さ（㎡）": 60, "駅徒歩（分）": 5},
        {"価格（万円）": 10500, "広さ（㎡）": 80, "駅徒歩（分）": 10},
        {"価格（万円）": 15000, "広さ（㎡）": 100, "駅徒歩（分）": 10},
    ],
    "渋谷駅": [
        {"価格（万円）": 9200, "広さ（㎡）": 55, "駅徒歩（分）": 15},
        {"価格（万円）": 15500, "広さ（㎡）": 75, "駅徒歩（分）": 10},
        {"価格（万円）": 9200, "広さ（㎡）": 55, "駅徒歩（分）": 4},
    ],
    "品川駅": [
        {"価格（万円）": 9700, "広さ（㎡）": 60, "駅徒歩（分）": 5},
        {"価格（万円）": 14000, "広さ（㎡）": 90, "駅徒歩（分）": 10},
        {"価格（万円）": 13000, "広さ（㎡）": 80, "駅徒歩（分）": 15},
    ],
}
```

不動産価格情報のデータを検索する関数を定義。(これを後のステップでFunction Callingにします。

```python
def search_real_estate_info(station_name: str) -> List:
    """
    指定された駅の不動産情報（価格・広さ・駅徒歩）をすべてリスト型で返す。

    Args:
        station_name (str): 検索する駅名。

    Returns:
        List[Dict[str, Any]]: 指定駅の不動産情報リスト。
            - 例: [{"価格（万円）": 8500, "広さ（㎡）": 60, "駅徒歩（分）": 5}, {...}]
            - データがない場合: [{"エラー": "データなし"}]
    """
    return real_estate_data.get(station_name, [{"エラー": "データなし"}])
```

定義した関数の動作確認。品川駅で検索してみる。

```python
search_real_estate_info("品川駅")
```

下記が出力結果。品川駅の3件が検索できている。

```python
[{'価格（万円）': 9700, '広さ（㎡）': 60, '駅徒歩（分）': 5},
 {'価格（万円）': 14000, '広さ（㎡）': 90, '駅徒歩（分）': 10},
 {'価格（万円）': 13000, '広さ（㎡）': 80, '駅徒歩（分）': 15}]
```

定義した関数を Function Callingとして定義。

```python
search_real_estate_tool = FunctionTool(
    search_real_estate_info, description="指定された駅の不動産価格情報を調査します。"
)
```

エージェントを定義。
不動産の売り手エージェント、買い手エージェント、両者をとりもつ交渉エージェントを定義。

```python
# 売り手エージェント（できるだけ高い価格を提示）
seller_agent = AssistantAgent(
    "seller_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-3.5-turbo"),
    description="できるだけ高い価格で売りたい不動産の売り手エージェント",
    system_message=(
        "あなたはユーザーの依頼を受け、不動産を売却する売り手エージェントです。最初に高めの価格を提示し、できるだけ高い価格で売ることを目指してください。 "
        "ただし、あまりにも強気すぎると買い手が離れる可能性がありますので注意してください。"
    )
)

# 買い手エージェント（できるだけ低い価格を提示）
buyer_agent = AssistantAgent(
    "buyer_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-3.5-turbo"),
    description="できるだけ低い価格で買いたい不動産の買い手エージェント",
    system_message=(
        "あなたは不動産の買い手エージェントです。"
        "購入を希望する駅の不動産情報をツール 'search_real_estate_info' で検索し、価格・広さ・駅徒歩を考慮して交渉してください。"
    ),
    tools=[search_real_estate_tool]
)

# 交渉エージェント（合意可能な価格を決定）
negotiation_agent = AssistantAgent(
    "negotiation_agent",
    model_client=OpenAIChatCompletionClient(model="gpt-3.5-turbo"),
    description="売り手と買い手の意見を調整し、交渉をまとめるエージェント",
    system_message=(
        "あなたは交渉の進行役です。両者の提示価格、広さ、駅徒歩を考慮し、公平な合意価格を提案してください。\n"
        "交渉が決まったら『交渉成立: 最終価格は○○万円です 』と出力してください。\n"
        "交渉が決裂する場合は『交渉決裂』と出力してください。"
    )
)
```

会話終了の条件とグループチャットの定義。

```python
# 終了条件：「交渉成立」または「交渉決裂」
termination = TextMentionTermination("交渉成立 | 交渉決裂")

group_chat = RoundRobinGroupChat(
    [seller_agent, buyer_agent, negotiation_agent],
    termination_condition=termination,
    max_turns=10  # 最大10ターンで終了
)
```

下記のプロンプトで交渉スタート。

```python
await Console(group_chat.run_stream(task="東京都内でありながら緑豊かな眺望、近隣には多くのレストランやショッピング施設、公園などがあり、品川駅から徒歩10分、広さ70㎡のマンションを売却したいです。"))
```

出力結果は下記の通り。

```python
---------- user ----------
東京都内でありながら緑豊かな眺望、近隣には多くのレストランやショッピング施設、公園などがあり、品川駅から徒歩10分、広さ70㎡のマンションを売却したいです。
---------- seller_agent ----------
それは素晴らしい物件ですね。同様の物件は市場で高い需要があります。このような魅力的な条件を考慮して、価格設定できるだけ高めに設定することが重要です。まずは周辺の類似物件の価格や売り出し価格を調査し、相場を把握することが大切です。そして、その上で70㎡のマンションとして特に魅力的な点を強調し、高めの価格で提示することをお勧めします。買い手も利点を理解し、魅力に引かれることで、より高い価格で取引が成立する可能性が高まります。
---------- buyer_agent ----------
[FunctionCall(id='call_AIO4RmaVL2lZPSAwrpZxQdO5', arguments='{"station_name":"品川駅"}', name='search_real_estate_info')]
---------- buyer_agent ----------
[FunctionExecutionResult(content="[{'価格（万円）': 9700, '広さ（㎡）': 60, '駅徒歩（分）': 5}, {'価格（万円）': 14000, '広さ（㎡）': 90, '駅徒歩（分）': 10}, {'価格（万円）': 13000, '広さ（㎡）': 80, '駅徒歩（分）': 15}]", call_id='call_AIO4RmaVL2lZPSAwrpZxQdO5')]
---------- buyer_agent ----------
[{'価格（万円）': 9700, '広さ（㎡）': 60, '駅徒歩（分）': 5}, {'価格（万円）': 14000, '広さ（㎡）': 90, '駅徒歩（分）': 10}, {'価格（万円）': 13000, '広さ（㎡）': 80, '駅徒歩（分）': 15}]
---------- negotiation_agent ----------
上記の類似物件価格を参考にしつつ、あなたのマンションの特長を踏まえて価格を設定することが重要です。素晴らしい眺望、周辺施設の魅力、品川駅からのアクセスの良さなどを強調し、信頼性のある査定価格サイトなどで比較検討を行うことをお勧めします。それに基づき、価格面で購入希望者と合意に達することが肝要です。価格交渉を進めるときには、相手の提示価格や条件を十分に把握し、柔軟な交渉を心掛けましょう。

提示価格: 11000万円
広さ: 70㎡
駅徒歩: 10分

これらの条件を踏まえ、11000万円での売却をご検討いただけますでしょうか。ご担当者さまのご提案をお待ちしております。
---------- seller_agent ----------
提示価格は中間的であり、競合物件の価格に依拠する同時に高めの価格を提示しています。柔軟な価格設定をし、交渉を進めることで、買い手との合意が得られる可能性を高めています。買い手エージェントからの反応に注目しましょう。
---------- buyer_agent ----------
価格交渉の進展を願っています。必要に応じて、査定サイトや不動産エージェントのアドバイスなども活用して、円滑な取引が進むことを期待しています。
---------- negotiation_agent ----------
交渉成立: 最終価格は11000万円です
---------- seller_agent ----------
交渉成立おめでとうございます！11000万円での売却が成立し、円滑な取引が行われました。買い手エージェントの提案に合意できる条件が整い、上手く合意に達したようですね。今後の手続きや引き渡しに向けて、買い手エージェントと共にスムーズな進行を図ることが大切です。何かご不明点やお手続きの支援が必要な場合はいつでもお知らせください。
---------- buyer_agent ----------
円滑な取引成立おめでとうございます！引き続き買い手エージェントおよび関係者との協力を通じて、手続きを円滑に進めていくことが重要です。何かお手伝いできることがあればお知らせください。
---------- negotiation_agent ----------
ありがとうございます！円滑に取引が成立して大変喜ばしいです。引き続きの手続きや問題解決において、必要に応じてサポートさせていただきます。よろしくお願いいたします。
---------- seller_agent ----------
円滑な取引成立、おめでとうございます！今後の手続きもスムーズに進むことを願っております。引き続きご協力させていただきますので、何かお困りのことやご相談がございましたらいつでもお知らせください。ありがとうございました。
```

上記は交渉成立のパターンですが、何度も実行していると交渉決裂のパターンも現れます。





## ⑤OpenAI以外のLLMを使ってみる(OCI Generative AI ServiceのCohere Command R Plus)

AutoGenはOpenAI以外のモデルも動かせるようになっています。その際はLLMをAutoGenのカスタムLLMとして定義する必要があります。AutoGenが直接サポートしているLLMもあれば、OllamaやLangChainで定義されているモデルをカスタムLLMとして利用することもできます。


以下のコードはOracle Cloudで提供されているGenerative AI ServiceのLLMをLangChain経由でAutoGenにカスタムLLMとして認識させるパターンです。エージェントの構成としては旅程提案のシナリオになります。

まずはGenerative AI ServiceのLLMをLangChainのカスタムLLMとして定義します。この際、Oracle CloudのAccelerated Data Scienceというデータサイエンティスト向けのライブラリを利用すると下記のように定義がシンプルになります。

```python
import ads
from ads.llm.chat_template import ChatTemplates
from ads.llm.autogen.client_v02 import LangChainModelClient, register_custom_client

# カスタムLLMの定義
register_custom_client(LangChainModelClient)

# OCI Generative AIのLLMの設定をAutogenのconfig_list形式に変換
config_list_custom = [
    {
        "model_client_cls": "LangChainModelClient",
        "langchain_cls": "langchain_community.chat_models.oci_generative_ai.ChatOCIGenAI",
        "client_params": {
            "model_id": "cohere.command-r-plus-08-2024",
            "compartment_id": "<コンパートメントのOCID>",
            "model_kwargs": {
                "temperature": 0,
                "max_tokens": 4000
            },
            "service_endpoint": "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
            "auth_type": "API_KEY",                      
            "auth_profile": "DEFAULT",
        },
    }
]

# LLMのパラメータ
llm_config={
        "seed": 0,
        "config_list": config_list_custom,
        "temperature": 0,
}
```

ユーザー介入型のUserProxyAgentを定義しますが、今回はhuman_input_mode="NEVER"とし、ユーザーの入力は求めず、全てエージェントだけでやり切ってもらいます。

また、注意すべき点としては、今回のLLMのケースでは AgentChatがサポートしていないのでAutogen Coreのほうのライブラリを利用します。従って、agentchatではなく、autogen直下のUserProxyAgentクラスを使ってエージェントを定義します。

```python
from autogen import UserProxyAgent

user_proxy = UserProxyAgent(
    name="User_proxy",
    system_message="A human admin.",
    code_execution_config=False,

    human_input_mode="NEVER",
    max_consecutive_auto_reply=0,
    is_termination_msg=lambda msg: "TERMINATE" in msg["content"],
)
```

AssistantAgentも同様にautogen直下のクラスを利用して定義します。そこに、llm_configという引数で事前に定義したOCI Generative AIのLLMをカスタムLLMとして指定することでLLMの定義は完了です。

```python
from autogen import AssistantAgent

planner_agent = AssistantAgent(
    name = "planner_agent",
    llm_config=llm_config,
    description="旅行の計画を立ててくれる便利なアシスタント",
    system_message="あなたは、ユーザーのリクエストに基づいて旅行計画を提案できるアシスタントです。",
)

local_agent = AssistantAgent(
    name = "local_agent",
    llm_config=llm_config,
    description="訪問先での観光名所を提案できる便利なアシスタント",
    system_message="あなたは、旅行計画の中にある訪問先の有名な観光名所とその見どころをユーザーに提案できるアシスタントです。",
)

language_agent = AssistantAgent(
    name = "language_agent",
    llm_config=llm_config,
    description="訪問先での公用語に関する情報を提供できる便利なアシスタント",
    system_message="あなたは、旅行計画の中にある訪問先での公用語に関する情報を提供できる便利なアシスタントです。",
)

travel_summary_agent = AssistantAgent(
    name = "travel_summary_agent",
    llm_config=llm_config,
    description="旅行計画をまとめるのに役立つアシスタント",
    system_message="あなたは、他のエージェントからの提案やアドバイスをすべて取り入れ、詳細な最終的な旅行計画を提供できる、便利なアシスタントです。最終計画を取り纏め、それが完全であることを確認する必要があります。最終的な対応は完全な計画でなければなりません。計画が完了すると TERMINATE で応答できます。",
)
```

次にエージェントの実行順序を定義します。今回のタイプのLLMではagentchatのRoundRobinGroupChatクラスは利用できないため下記のようにautogenのグラフのクラスを使って有効巡回グラフの形式で定義します。

```python
from autogen.graph_utils import visualize_speaker_transitions_dict 

agents = [user_proxy, planner_agent, local_agent, language_agent, travel_summary_agent]

# エージェントを順番に遷移できるように定義
agent_graph = {
    user_proxy: [planner_agent],
    planner_agent: [local_agent],
    local_agent: [language_agent],
    language_agent: [travel_summary_agent],
    travel_summary_agent: [user_proxy],
}

# エージェントの順序を可視化
visualize_speaker_transitions_dict(agent_graph, agents)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/a8c71224-0359-4b44-9144-efa1156cd2c7.png)


次にグループチャットを定義しますが、上述した通り、今回のLLMではAgentChatのRoundRobinGroupChatはサポートされていませんので、coreのGroupChat関数を利用します。その場合、定義したGroupChatをGroupChatManagerに渡すことで、グループチャットが制御されるような仕組みになっています。

```python
from autogen import GroupChat

group_chat = GroupChat(
    agents=agents,
    messages=[],
    max_round=20,
    allowed_or_disallowed_speaker_transitions=agent_graph,
    speaker_transitions_type="allowed"
)

from autogen import GroupChatManager

chat_manager = GroupChatManager(
    groupchat=group_chat, llm_config=llm_config, code_execution_config=False
)
```

下記プロンプトでチャットを開始してみます。

```python
user_proxy.initiate_chat(
    chat_manager, message="ヨーロッパの3か国の旅行を計画してください。"
)
```
出力はこちら。

```python
User_proxy (to chat_manager):

ヨーロッパの3か国の旅行を計画してください。

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

Next speaker: planner_agent

planner_agent (to chat_manager):

ヨーロッパの3か国への旅行プランをご提案します！この旅では、文化、歴史、そして美しい景色を満喫できるでしょう。

**1. フランス (パリ)**
- エッフェル塔を訪れる：パリの象徴的なランドマークであるエッフェル塔は必見です。塔に登り、街の素晴らしい景色を眺めましょう。
- ルーヴル美術館：世界的に有名なルーヴル美術館では、モナ・リザやヴィーナス・ド・ミロなどの芸術作品を鑑賞できます。
- ノートルダム大聖堂：ゴシック様式の傑作である大聖堂を探索し、その建築の美しさに感動しましょう。
- セーヌ川クルーズ：ロマンチックなセーヌ川クルーズで、パリの景色を異なる視点から楽しみましょう。

**2. イタリア (ローマ)**
- コロッセオ：古代ローマの円形闘技場であるコロッセオは、歴史的価値のある見どころです。その壮大な構造と歴史に浸りましょう。
- ローマ教皇庁：バチカン市国を訪れ、システィーナ礼拝堂とサン・ピエトロ大聖堂の素晴らしい芸術と建築を体験しましょう。
- トレヴィの泉：伝説のトレヴィの泉でコインを投げ、再びローマを訪れることを願いましょう。
- ローマのフォロ・ロマーノ：古代ローマの遺跡を散策し、その歴史的な重要性を実感しましょう。

**3. スペイン (バルセロナ)**
- サグラダ・ファミリア：アントニ・ガウディの未完の傑作であるサグラダ・ファミリアは、その独特な建築様式で有名です。
- グエル公園：ガウディが設計したもう一つの素晴らしい作品で、色彩豊かでユニークな公園です。
- ランブラス通り：活気溢れるランブラス通りを散策し、ストリートパフォーマーや市場を楽しみましょう。
- バルセロネータビーチ：地中海の美しいビーチでリラックスし、ビーチ沿いのレストランで食事を楽しみましょう。

旅程は、パリで3日間、ローマで2日間、バルセロナで3日間滞在することをお勧めします。各都市間の移動は、飛行機または高速鉄道を利用できます。ヨーロッパの都市間を移動する際には、事前に列車のチケットを予約するか、格安航空券をチェックすることをお勧めします。

宿泊施設に関しては、各都市の中心部にあるホテルやアパートメントを検討してください。事前に予約することで、好みの宿泊施設を確保しやすくなります。

食事に関しては、フランス料理、イタリア料理、スペイン料理を存分に味わえるでしょう。各都市で地元のレストランやカフェを訪れ、伝統的な料理やワインを楽しんでください。

この旅行プランは、ヨーロッパの文化と歴史の豊かさを体験するのに最適です。芸術、建築、そして素晴らしい食事を満喫できるでしょう。旅の詳細については、各都市の観光局や旅行ガイドをご覧になることをお勧めします。

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

Next speaker: local_agent

local_agent (to chat_manager):

素晴らしいですね！この旅行プランで、ヨーロッパの魅力に触れる旅ができそうですね。

## フランス (パリ)
パリは、芸術、ファッション、美食の都として世界的に有名です。

- **凱旋門**：パリの有名な凱旋門は、ナポレオン時代の栄光を記念して建てられました。頂上からの眺めは見逃せません。
- **オルセー美術館**：印象派やポスト印象派の作品が豊富な美術館です。モネ、ルノワール、ゴッホなどの名作を鑑賞できます。
- **シャンゼリゼ通り**：パリで最も有名な通りの一つで、高級ブティックやカフェが並びます。散策しながらショッピングを楽しめます。
- **モンマルトル**：芸術家の街として知られるモンマルトルは、サクレクール寺院やテルトル広場で有名なエリアです。

## イタリア (ローマ)
ローマは、古代の歴史と現代の魅力が融合した都市です。

- **パンテオン**：古代ローマの建築の傑作であるパンテオンは、その巨大なドームが特徴的です。内部にはラファエロの墓もあります。
- **スペイン広場**：美しい階段と噴水で知られる広場です。映画『ローマの休日』で有名になりました。
- **ボルゲーゼ公園**：緑豊かな公園で、散歩やピクニックに最適です。公園内にはボルゲーゼ美術館もあり、芸術作品を鑑賞できます。
- **トラステヴェレ地区**：ローマの伝統的な雰囲気が残るエリアで、地元料理やワインバーが人気です。

## スペイン (バルセロナ)
バルセロナは、ガウディの作品と地中海の魅力が溢れる都市です。

- **カサ・バトリョ**：ガウディが設計したユニークな建物で、波打つような外観が印象的です。
- **バルセロナ大聖堂**：ゴシック様式の大聖堂で、その荘厳な内装は必見です。
- **ボケリア市場**：活気溢れる市場で、新鮮な食材や地元の食べ物を楽しめます。
- **モンセラット**：バルセロナ郊外にある山で、独特な形の岩山とモンセラット修道院が有名です。

パリ、ローマ、バルセロナはいずれも見どころ満載の都市ですので、旅程に余裕があれば、各都市でさらに滞在期間を延長しても充実した旅になるでしょう。移動手段や宿泊施設は、ご予算やご希望に応じて選択できます。

ヨーロッパの食文化を体験するのも旅の醍醐味です。各都市で地元のレストランやカフェを訪れ、フランス料理、イタリア料理、スペイン料理の味覚の旅もお楽しみください。

より詳細な情報やおすすめのレストランなどについては、各都市の観光ガイドや旅行ブログをご参照ください。素晴らしい旅になりますように！

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

Next speaker: language_agent

language_agent (to chat_manager):

素晴らしい旅程をさらに充実させるために、各都市での体験をいくつか追加しました！

## フランス (パリ)

- **セーヌ川ディナークルーズ**：ロマンチックなセーヌ川でのディナークルーズは、パリの夜景を楽しむのに最適です。美味しい食事とワインを楽しみながら、エッフェル塔やノートルダム大聖堂のライトアップされた姿に感動しましょう。
- **ルーヴル美術館ガイドツアー**：膨大なコレクションを持つルーヴル美術館を効率的に回るには、ガイドツアーがお勧めです。専門のガイドがモナ・リザやその他の有名な作品について解説し、美術館の歴史や裏話も教えてくれます。
- **ベルサイユ宮殿**：パリ郊外にあるベルサイユ宮殿は、フランス王ルイ14世の栄華を物語っています。豪華な宮殿と美しい庭園は必見です。

## イタリア (ローマ)

- **バチカン美術館とシスティーナ礼拝堂**：バチカン市国にあるバチカン美術館は、膨大な芸術作品を収蔵しています。ミケランジェロの天井画で有名なシスティーナ礼拝堂は、その美しさに圧倒されるでしょう。
- **コロッセオ地下と上層階ツアー**：コロッセオの一般公開されていないエリアを探索するツアーに参加しましょう。地下や上層階から、古代ローマの闘技場の裏側を覗き見ることができます。
- **ローマの伝統料理教室**：地元のシェフからローマの伝統料理を学ぶ料理教室はいかがでしょう？ フレッシュパスタやティラミスなどの料理を作り、イタリアの食文化を体験できます。

## スペイン (バルセロナ)

- **ガウディ建築ツアー**：バルセロナにはガウディの作品が数多くあります。サグラダ・ファミリア、カサ・バトリョ、カサ・ミラなど、ガウディの建築ツアーに参加して、その独特な世界観を深く理解しましょう。
- **タパスバー巡り**：バルセロナのタパスバーやワインバーを巡る食べ歩きツアーは、地元のグルメを体験するのに最適です。様々なタパスやワインを楽しみながら、バルセロナの食文化に触れましょう。
- **モンセラット半日ツアー**：バルセロナから日帰りでモンセラットを訪れるツアーは人気です。独特な景観のモンセラット山を楽しみ、モンセラット修道院では黒人聖母像を見学しましょう。

これらの追加アクティビティは、各都市の文化や歴史をより深く体験するのに役立ちます。食事、芸術、歴史、そしてユニークな体験を組み合わせることで、忘れられないヨーロッパの旅になるでしょう。

旅の計画を立てる際は、各都市の観光局や旅行ブログで詳細な情報を入手し、ご自身の好みに合わせて旅程をカスタマイズすることをお勧めします。

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

Next speaker: travel_summary_agent

travel_summary_agent (to chat_manager):

## 最終的な旅行計画

### フランス (パリ)
**滞在期間：4日間**
- エッフェル塔
- ルーヴル美術館ガイドツアー
- セーヌ川ディナークルーズ
- 凱旋門
- オルセー美術館
- シャンゼリゼ通りでのショッピング
- モンマルトル散策
- ベルサイユ宮殿見学

### イタリア (ローマ)
**滞在期間：3日間**
- コロッセオとコロッセオ地下ツアー
- ローマ教皇庁とシスティーナ礼拝堂
- パンテオン
- スペイン広場
- ボルゲーゼ公園と美術館
- トラステヴェレ地区で地元料理を堪能
- ローマの伝統料理教室

### スペイン (バルセロナ)
**滞在期間：4日間**
- サグラダ・ファミリア
- グエル公園
- カサ・バトリョ
- バルセロナ大聖堂
- ランブラス通り
- ボケリア市場
- バルセロネータビーチでのリラックス
- ガウディ建築ツアー
- タパスバー巡り
- モンセラット半日ツアー

**旅程概要：**
1. パリに4日間滞在し、エッフェル塔、ルーヴル美術館、セーヌ川クルーズなどの名所を満喫します。
2. 高速鉄道や飛行機でローマに移動し、3日間滞在。コロッセオ、バチカン市国、スペイン広場などを訪れます。
3. 再び高速鉄道や飛行機でバルセロナへ。4日間滞在し、サグラダ・ファミリア、グエル公園、ランブラス通り、モンセラットなど、ガウディの作品と地中海の魅力を満喫します。

**移動手段：**
パリとローマ、ローマとバルセロナの間は、高速鉄道や格安航空券を利用して移動します。事前に列車のチケットを予約するか、航空券をチェックしておくとスムーズです。

**宿泊施設：**
各都市の中心部にあるホテルやアパートメントを予約します。事前に予約することで、好みの宿泊施設を確保しやすくなります。

**食事：**
フランス料理、イタリア料理、スペイン料理を存分に味わいましょう。各都市で地元のレストランやカフェを訪れ、伝統的な料理やワインを楽しみましょう。特に、パリのセーヌ川ディナークルーズ、ローマの伝統料理教室、バルセロナのタパスバー巡りはおすすめです。

**追加情報：**
- ベルサイユ宮殿はパリ郊外にありますが、見学には半日程度必要です。
- ローマの料理教室では、事前に予約が必要な場合があります。
- バルセロナのモンセラット半日ツアーは人気があるため、事前に予約することをお勧めします。

この旅行計画では、ヨーロッパの3都市で芸術、歴史、食文化を満喫できます。各都市の見どころを効率的に回りながら、充実した時間を過ごせるでしょう。

TERMINATE

--------------------------------------------------------------------------------
```

カスタムLLMを使う場合はAgentChatと多少コードが異なりますが、エージェントを定義してグループチャットという流れは変わらないということがおわかりいただけたのではないでしょうか。


::: note info
マルチエージェントデザインパターンはAutoGenのマニュアルにもいつくか掲載されていますので是非ご参考に。

https://microsoft.github.io/autogen/stable/user-guide/core-user-guide/design-patterns/intro.html
:::

本記事に掲載した内容やサンプルコードはAutoGenのごく一部になります。これら以外にも様々な実装ができますが、まとめとして特徴的な点を挙げるとすると

- **グループチャットという私達が普段慣れ親しんだ仕組みをそのままプログラミングモデルとしてAPIに落とし込んでいるので理解しやすい**
- **ライブラリの抽象度が非常に高く、複雑な処理をシンプルにコーディングできる**
- **チュートリアルやリファレンス実装が沢山あり、マニュアルもわかりやすい**
- **著名なライブラリのためインターネットに情報が多い**
- **LangChainとの連携が可能(LangChainのAgentやToolと連携可能)**

というところでしょうか。動作が怪しい点はまだありますが非常に利用しやすく、マルチエージェント入門としては現状、最適なライブラリではないかという印象です。

# おわりに
大規模言語モデルは今のところ人類が作り出した最も頭の良い人工知能だと言えます。そのような知能を持った複数のエージェントがお互いの知能をぶつけ合って生成したデータが人間の予想を超えてしまうことは想像に易く、これはまさにマルチエージェントの技術がAIシンギュラリティの門をくぐろうとしている歴史的な瞬間なのかもしれません。