Semantic Kernel で Bing Chat みたいに自然言語から検索をして結果を自然言語で返したい
Semantic Kernel には Bing 検索をするスキルがあるので、そこら辺をうまく使えば出来そうな気がします。ということでやってみましょう。
今回はコンソールアプリでやってみます。
プロジェクトの初期設定
プロジェクトに以下のパッケージを追加します。
- Microsoft.SemanticKernel
- Microsoft.SemanticKernel.Skills.Web
プログラムの実装
では作っていきます。まずは自然言語から検索キーワードをとってきたいですね。
これは普通のセマンティックスキルで出来そうです。
さくっと実装出来ますね。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.CoreSkills;
var kernel = Kernel.Builder
.Configure(config =>
{
config.AddAzureChatCompletionService(
"gpt-35-turboのモデルのデプロイ名",
"https://リソース名.openai.azure.com/",
"APIキー");
})
.Build();
kernel.ImportSkill(new TimeSkill(), "time");
var extractKeywords = kernel.CreateSemanticFunction(
"""
与えられた文章で質問されていることを検索するためのキーワードをカンマ区切りで抽出してください。
<例1>
<入力>今日の東京の天気を教えて</入力>
<出力>2023/05/13,東京,天気</出力>
</例1>
<例2>
<入力>品川駅のお勧めのレストランを教えて</入力>
<出力>品川,レストラン,お勧め</出力>
</例2>
<例3>
<入力>C#で今日の日付を求める方法</入力>
<出力>C#,今日,日付,取得</出力>
</例3>
キーワードを抽出する際に以下の情報を参考にしてください。
<参考情報>
<今日の日付>{{time.today}}</今日の日付>
</参考情報>
与えられた文章は以下になります。
<入力>{{$input}}</入力>
<出力>
""",
stopSequences: new[] { "</出力>" });
var userQuestion = "ゼルダの伝説の主人公の名前";
var keywords = await kernel.RunAsync(userQuestion, extractKeywords);
Console.WriteLine(keywords.Result);
これを実行すると以下のような結果になりました。いい感じですね。
ゼルダの伝説,主人公,名前
では、ここに回答を追加してもらいましょう。
WebSearchEngineSkill
というスキルが Semantic Kernel にはあって、これは名前の通り Web 検索をしてくれるスキルです。コンストラクタでコネクターを受け取って、Bing と Google のコネクタが提供されています。ここでは Bing のコネクタを指定しようと思います。Bing のコネクタを作る時には Bing の API キーが必要です。Azure で Bing Search のリソースを作って API キーを準備しておきましょう。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.CoreSkills;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Skills.Web;
using Microsoft.SemanticKernel.Skills.Web.Bing;
// Kernel を作成
var kernel = Kernel.Builder
.Configure(config =>
{
config.AddAzureChatCompletionService(
"gpt-35-turboのモデルのデプロイ名",
"https://リソース名.openai.azure.com/",
"APIキー");
})
.Build();
// 時間とBingのスキルを登録
kernel.ImportSkill(new TimeSkill(), "time");
kernel.ImportSkill(new WebSearchEngineSkill(new BingConnector("Bing の API キー")), "bing");
// 質問から検索キーワードを抽出する関数
var extractKeywords = kernel.CreateSemanticFunction(
"""
与えられた文章で質問されていることを検索するためのキーワードをカンマ区切りで抽出してください。
<例1>
<入力>今日の東京の天気を教えて</入力>
<出力>2023/05/13,東京,天気</出力>
</例1>
<例2>
<入力>品川駅のお勧めのレストランを教えて</入力>
<出力>品川,レストラン,お勧め</出力>
</例2>
<例3>
<入力>C#で今日の日付を求める方法</入力>
<出力>C#,今日,日付,取得</出力>
</例3>
キーワードを抽出する際に以下の情報を参考にしてください。
<参考情報>
<今日の日付>{{time.today}}</今日の日付>
</参考情報>
与えられた文章は以下になります。
<入力>{{$input}}</入力>
<出力>
""",
stopSequences: new[] { "</出力>" });
// WebSerachEngineSkill スキルを使用して参考情報を取得して、その内容をもとに質問に答える関数
var summary = kernel.CreateSemanticFunction(
"""
以下の質問に対して参考情報を元に「ユーザーの質問」に回答をしてください。参考情報以外の情報は含めないで参考情報に記載されている内容だけで回答をしてください。
<ユーザーの質問>{{$userQuestion}}</ユーザーの質問>
<参考情報>
{{bing.search $input}}
</参考情報>
<回答>
""",
maxTokens: 4000,
stopSequences: new[] { "</回答>" });
// 質問文からキーワードを抽出
var userQuestion = "ゼルダの伝説の主人公の名前";
var keywords = await kernel.RunAsync(userQuestion, extractKeywords);
Console.WriteLine(keywords.Result);
// キーワードを使って検索して回答を取得
var variables = new ContextVariables(keywords.Result);
variables.Set("userQuestion", userQuestion);
// デフォルトで WebSerachEngineSkill はデフォルトで 1 件しか結果を返さないので 10 件を指定
variables.Set(WebSearchEngineSkill.CountParam, "10");
// 呼び出して結果を表示
var answer = await kernel.RunAsync(variables, summary);
Console.WriteLine(answer.Result);
実行結果は以下のようになりました。なんか思ってたのと違う…。
ゼルダの伝説,主人公,名前
ゼルダの伝説の主人公の名前は、作品によって異なります。例えば、「ブレス オブ ザ ワイルド」では「リンク」という名前になっています。また、「オカリナの時空」では「リンク」、「トワイライトリンセス」では「リンク」、「スカイウォードソード」では「リンク」、「風のタクト」では「リンク」、「時のオカリナ」では「リンク」、「四つの剣+」では「リンク」、「神々のトライフォース」では「リンク」、「アドベンチャー・オブ・リンク」では「リンク」、「ハイラルの城塞」では「リンク」、「トライフォース三部作」では「リンク」、「ミニッシュキン」では「リンク」、「フォーソードアドベンチャーズ」では「リンク」、「フォーソードアドベンチャーズ+」では「リンク」、「トライフォースヒーローズ」では「リンク」、「ハイラルウォリアーズ」では「リンク」、「ハイラルエンサイクロペディア」では「リンク」となっています。
リンクでいいじゃん!
もう少しコードを書き換えてみましょう。今は別々にセマンティック関数を呼び出していますが、これは kernel.RunAsync
で一気に呼び出せます。スキルなどの定義部分は同じなので最期のスキルを使っている箇所だけのコードを記載します。
// 変数を設定
var userQuestion = "ゼルダの伝説の主人公の名前";
var variables = new ContextVariables(userQuestion);
variables.Set(nameof(userQuestion), userQuestion);
variables.Set(WebSearchEngineSkill.CountParam, "10");
// extractKeywords と summary を順番に実行
var answer = await kernel.RunAsync(variables, extractKeywords, summary);
// 結果を出力
Console.WriteLine(answer.Result);
これを実行すると以下のように表示されます。
ゼルダの伝説の主人公の名前は、作品によって異なります。例えば、「ブレス オブ ザ ワイルド」では「リンク」という名前になっています。また、「オカリナの時空」では「リンク」、「トワイライトプリンセス」では「リンク」、「スカイウォードソード」では「リンク」、「風のタクト」では「リンク」、「時のオカリナ」では「リンク」、「神々のトライフォース」では「リンク」、「四つの剣+」では「リンク」、「アドベンチャー・オブ・リンク」では「リンク」となっています。
再実行するといい感じの回答が返ってきました。ぶれますね。
「リンク」という名前です。
その他に、コンテキストとして今日の日付を渡しているので「今日は何の日?」といったような質問にも対応できるはずです。
参考情報によると、今日は5月15日であり、国際家族デー、琉球復帰記念日、ハンバーガーの日、ユニセフの日などがあるようです。また、365日全てに何かしらの記念日や行事があるとされています。
まぁまぁですね。
デフォルトの WebSearchEngineSkill
の実装が検索結果のページの概要だけしか返さないので、自前で Bing API などを叩いて本文を含めるようなスキルを作るともう少しいい感じの結果になるかもしれません。
ということで、試してみたメモでした。
Discussion
先週のGlobal Azure 見てSemantic Kernel 興味出てきました👀 (まだ触ってはいない