Semantic Kernel で複数エージェントを簡単に連携させてみよう
これまで Semantic Kernel の機能を使って複数エージェントを連携させるためには AgentGroupChat
を使って複数のエージェントをグループ化して実行する機能を紹介してきました。
- Semantic Kernel のマルチエージェントを試してみよう!
- Semantic Kernel のマルチエージェント AI 機能入門してみよう その 1
- Semantic Kernel のマルチエージェント AI 機能入門してみよう その 2
- Semantic Kernel のマルチエージェント AI 機能入門してみよう その 3
- Semantic Kernel のマルチエージェント AI 機能入門してみよう その 4「人間が割り込む」
また、リリース前の RC2 版では AgentThread
を使ってエージェントとのやり取りを記録したスレッドが追加されて先日リリースされました。それについても以下の記事で書きました。
ここでは、まだ紹介していない AgentGroupChat
を使わないでエージェント同士を連携させる方法を紹介します。
エージェントをツールとして活用する機能
Semantic Kernel ではエージェントを Kernel Function として扱う機能があります。
これを使うことで、エージェントを別のエージェントのプラグインとして使うことが出来ます。こうすることで複数エージェントを連携させることが出来ます。この機能は試験的機能として提供されているため本番利用には注意が必要です。
試してみましょう。コンソールアプリを作成して以下の 2 つの NuGet パッケージをインストールします。
- Microsoft.SemanticKernel v1.46.0
- Microsoft.SemanticKernel.Agents.Core v1.46.0
まずは、別のエージェントから呼び出されるエージェントを作成します。今回は場所を指定したら天気を答えてくれるエージェントにします。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using System.ComponentModel;
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
"gpt-4o",
"https://<<ここにAzure OpenAI Serviceのリソース名>>.openai.azure.com/",
"<<ここに API キー>>");
var kernel = builder.Build();
var kernelForWeatherForecastAgent = kernel.Clone();
kernelForWeatherForecastAgent.Plugins.AddFromType<WeatherForecastPlugin>();
var weatherForecastAgent = new ChatCompletionAgent
{
Name = "WeatherForecastAgent",
Description = "天気予報を提供するエージェント",
Kernel = kernelForWeatherForecastAgent,
Instructions = """
指定された場所の天気予報を行うエージェントです。
""",
Arguments = new(new AzureOpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
}),
};
AgentThread? thread = null;
await foreach (var result in weatherForecastAgent.InvokeAsync("品川の天気ってどう?", thread))
{
Console.WriteLine(result.Message.Content);
thread = result.Thread;
}
class WeatherForecastPlugin
{
[KernelFunction]
[Description("天気予報を取得する")]
public WeatherForecastResult GetWeatherForecast(
[Description("場所")] string location) =>
location switch
{
"東京" => new WeatherForecastResult("東京", "晴れ"),
"大阪" => new WeatherForecastResult("大阪", "曇り"),
"名古屋" => new WeatherForecastResult("名古屋", "雨"),
"福岡" => new WeatherForecastResult("福岡", "雪"),
"札幌" => new WeatherForecastResult("札幌", "霧"),
"沖縄" => new WeatherForecastResult("沖縄", "台風"),
_ => new WeatherForecastResult(location, "空から蛙が降る異常気象"),
};
}
record WeatherForecastResult(
[Description("場所")]
string Location,
[Description("天気予報")]
string Forecast);
これで天気に答えてくれるエージェントが出来ました。実行すると以下のような結果になります。
品川の天気予報では、空から蛙が降る異常気象となっています。外出の際は十分にご注意ください。
次に、この天気を聞けるエージェントをプラグインとして呼び出すエージェントを作成します。今回は、猫のエージェントを作成します。コメントを追記している箇所が追加した部分になります。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using System.ComponentModel;
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
"gpt-4o",
"https://<<ここにAzure OpenAI Serviceのリソース名>>.openai.azure.com/",
"<<ここに API キー>>");
var kernel = builder.Build();
var kernelForWeatherForecastAgent = kernel.Clone();
kernelForWeatherForecastAgent.Plugins.AddFromType<WeatherForecastPlugin>();
var weatherForecastAgent = new ChatCompletionAgent
{
Name = "WeatherForecastAgent",
Description = "天気予報を提供するエージェント",
Kernel = kernelForWeatherForecastAgent,
Instructions = """
指定された場所の天気予報を行うエージェントです。
""",
Arguments = new(new AzureOpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
}),
};
// Cat Agent 用の Kernel を作成
var kernelForCatAgent = kernel.Clone();
#pragma warning disable SKEXP0110 // プレビュー機能なのでエラーを抑止
// WeatherForecastAgentを関数に変換する
var plugin = kernelForCatAgent.Plugins.AddFromFunctions(
"WeatherForecastAgent",
[
AgentKernelFunctionFactory.CreateFromAgent(weatherForecastAgent),
]);
#pragma warning restore SKEXP0110
// Cat Agent を作成
var catAgent = new ChatCompletionAgent
{
Name = "CatAgent",
Description = "猫のエージェント",
// WeatherForecast を Plugin として登録した Kernel を使用するようにする
Kernel = kernelForCatAgent,
Instructions = """
あなたは猫です。猫らしく振舞うために語尾は「にゃん」としてください。
""",
Arguments = new(new AzureOpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
}),
};
// スレッドを作って品川の天気を聞いてみる
ChatHistory history = [];
AgentThread? thread = new ChatHistoryAgentThread(history);
await foreach (var result in catAgent.InvokeAsync("品川の天気ってどう?", thread))
{
Console.WriteLine(result.Message.Content);
thread = result.Thread;
}
class WeatherForecastPlugin
{
[KernelFunction]
[Description("天気予報を取得する")]
public WeatherForecastResult GetWeatherForecast(
[Description("場所")] string location) =>
location switch
{
"東京" => new WeatherForecastResult("東京", "晴れ"),
"大阪" => new WeatherForecastResult("大阪", "曇り"),
"名古屋" => new WeatherForecastResult("名古屋", "雨"),
"福岡" => new WeatherForecastResult("福岡", "雪"),
"札幌" => new WeatherForecastResult("札幌", "霧"),
"沖縄" => new WeatherForecastResult("沖縄", "台風"),
_ => new WeatherForecastResult(location, "空から蛙が降る異常気象"),
};
}
record WeatherForecastResult(
[Description("場所")]
string Location,
[Description("天気予報")]
string Forecast);
先ほど作成した WeatherForecastAgent
を関数化して CatAgent
に登録しました。これで CatAgent
から WeatherForecastAgent
を呼び出すことが出来ます。実行すると以下のような結果になります。
品川の天気は「空から蛙が降る異常気象」とのことですにゃん。外出の際はお気をつけくださいにゃん!不思議な天気ですねにゃん。
ちゃんと猫のエージェントが天気を教えてくれました。うまく連携できていますね。
処理の途中でデバッガーで止めて plugin
変数を確認してみます。デフォルトではエージェントを関数にすると、以下のような query
と instructions
がパラメーターとして定義されています。
実際にどんなパラメータが渡されて呼び出されているか確認するために処理の最後に以下のコードを追加して ChatHistory
の中身を確認してみます。
Console.WriteLine(JsonSerializer.Serialize(history, new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
}));
そうすると以下のような JSON が出力されました。(一部省略しています)
[
{
"Role": {
"Label": "user"
},
"Items": [
{
"$type": "TextContent",
"Text": "品川の天気ってどう?"
}
]
},
{
"AuthorName": "CatAgent",
"Role": {
"Label": "Assistant"
},
"Items": [
{
"$type": "FunctionCallContent",
"Id": "call_OcLWhJp0mX09nn1yQrW2totw",
"PluginName": "WeatherForecastAgent",
"FunctionName": "WeatherForecastAgent",
"Arguments": {
"query": "品川の天気",
"instructions": "品川の現在の天気と予報を教えてください"
}
}
],
"Metadata": {
"Id": "chatcmpl-BLoRAQu8XbbVt8SjfoiVMlHjQcT8c",
"FinishReason": "ToolCalls",
"ChatResponseMessage.FunctionToolCalls": [
{
"Kind": 0,
"FunctionName": "WeatherForecastAgent-WeatherForecastAgent",
"FunctionArguments": {},
"Id": "call_OcLWhJp0mX09nn1yQrW2totw"
}
]
}
},
{
"AuthorName": "CatAgent",
"Role": {
"Label": "tool"
},
"Items": [
{
"$type": "TextContent",
"Text": "品川の天気予報は、空から蛙が降る異常気象です。外出時はお気を付けください。"
},
{
"$type": "FunctionResultContent",
"CallId": "call_OcLWhJp0mX09nn1yQrW2totw",
"PluginName": "WeatherForecastAgent",
"FunctionName": "WeatherForecastAgent",
"Result": "品川の天気予報は、空から蛙が降る異常気象です。外出時はお気を付けください。"
}
]
},
{
"AuthorName": "CatAgent",
"Role": {
"Label": "Assistant"
},
"Items": [
{
"$type": "TextContent",
"Text": "品川の天気は、なんと空から蛙が降る異常気象にゃん!外出する時は気をつけてにゃんよ。"
}
]
}
]
実際に WeatherForecastAgent
を呼び出す時に渡されている query
と instructions
が確認できました。該当部分だけ JSON を抜粋すると以下のようになります。
"Arguments": {
"query": "品川の天気",
"instructions": "品川の現在の天気と予報を教えてください"
}
ちゃんと CatAgent
から WeatherForecastAgent
が呼び出されていて、品川の天気を聞いていることがわかります。そして、その後のツール呼び出しの結果として品川の天気が帰ってきて CatAgent
が回答をしていることがわかります。
このように、エージェントを別のエージェントのプラグインとして使うことで、エージェント同士を連携させることが出来ます。
まとめ
今回は Semantic Kernel の機能を活用して、エージェント同士を連携させる機能を試してみました。まだ試験的な機能なのですが自分でもエージェントを関数としてラップすることで同じようなことが実現できると思います。複雑なエージェントの連携には AgentGroupChat
や、Semantic Kernel の 1 機能として現在開発中の Process Framework (個人的には苦手な API ですが…) を使うことになると思います。もしくはエージェントのワークフローを組むのであれば Azure Functions の Durable Functions を使うのも良いと思います。
Durable Functions を使ったエージェントのワークフローについては以下の記事が参考になります。
Discussion