ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

Azure OpenAI Service On Your Data を .NET アプリケーションから使ってみた

Azure OpenAI Service On Your Data は、Microsoft Build 2023 でパブリックプレビューが開始された機能です。今回は .NET エンジニアの視点で On Your Data を使ってみました。

Azure OpenAI Service On Your Dataとは

Azure OpenAI On Your Data では、インターネットには公開できない独自のドキュメントに基づいて LLM に回答させること(グラウンディング)ができます。しかも、Azure の PaaS として簡単にできるところが嬉しいです。
zenn.dev

シナリオ

社内の就業規則をもとにユーザーからの質問に回答する総務担当アシスタントを作成します。
事前準備として、就業規則のドキュメントを Blob Storage にアップロードします。

Azure OpenAI Sutido の ChatGPT プレイグラウンドで試します。モデルは、先日東日本リージョンで使えるようになった gpt-4 を使います。プレイグラウンドの「add your data」から、Cognitive Search にインデックスを作成します。

Cognitive Search に作成されたインデックスは、いい感じにドキュメントを分割して作られています。インデクサーは作成されないので、インデックスの更新は別途対応が必要です。

システムメッセージを設定して質問すると、就業規則をもとに回答してくれます。


REST API を呼び出してみる

今回のシナリオを .NET アプリケーションに組み込むためには、Azure OpenAI の Completions extensions API を呼び出す必要があります。
ドキュメントに記載されている仕様通りに、Postman で REST API を実行してみます。

まずは Azure OpenAI への認証として、ヘッダーに api-key をセットします。

リクエストの Body に dataSources と messages を JSON で渡すと、プレイグラウンドと同様の結果を取得できます。


.NET の API Client を自動生成する

HttpClient を使って HTTP リクエストを組み立てるのは面倒なので、NSwag を使って Swagger.json から .NET の API Client を自動生成します。
NSwag のコード生成機能にはいくつかのオプションがありますが、今回はシンプルに Windows デスクトップ アプリを使います。
NSwagStudio をインストールして、前述の Swagger.json を貼り付けます。

  • Runtime で .NET70 を指定
  • Namespace で MyApiClient を変更
  • Interface を使うように指定(好みの問題なのでオプションです)

Generate Outputs ボタンを押すと、C# のコードが自動生成されます。


コンソールアプリケーションから API Client を呼び出してみる

先ほどの自動生成した API Client を使って実装してみます。.NET 7 のコンソールアプリを作り、NuGet パッケージをインストールします。

Install-Package Newtonsoft.Json -Version 13.0.3
Install-Package Microsoft.Extensions.Configuration.Binder -Version 7.0.4
Install-Package Microsoft.Extensions.Configuration.UserSecrets -Version 7.0.0

Microsoft.Extensions.Configuration 関連の NuGet パッケージは、Azure OpenAI に接続する資格情報を管理するためにインストールしました。

{
  "AzureOpenAISettings": {
    "CognitiveSearchEndpoint": "https://xxx.search.windows.net",
    "CognitiveSearchKey": "xxx",
    "CognitiveSearchIndexName": "azureblob-index",
    "Endpoint": "https://xxx.openai.azure.com/openai",
    "ApiVersion": "2023-06-01-preview",
    "ApiKey": "xxx",
    "DeploymentId": "gpt-4"
  }
}

secret.json に各プロパティを定義したら、バインドするクラスを作ります。

internal class AzureOpenAISettings
{
    public required string CognitiveSearchEndpoint { get; set; }
    public required string CognitiveSearchKey { get; set; }
    public required string CognitiveSearchIndexName { get; set; }
    public required string Endpoint { get; set; }
    public required string ApiVersion { get; set; }
    public required string ApiKey { get; set; }
    public required string DeploymentId { get; set; }
}

ConfigurationBuilder を使って、secret.json を読み込んで AzureOpenAISettings クラスにバインドします。

var settings = new ConfigurationBuilder()
    .AddUserSecrets<Program>()
    .Build()
    .GetSection(nameof(AzureOpenAISettings)).Get<AzureOpenAISettings>() ?? throw new NullReferenceException();

リクエストの Body は、ExtensionsChatCompletionsRequest クラスが自動生成されています。DataSource には Cognitive Search の情報を設定し、Messages にはシステムロールにアシスタントのコンテキスト、ユーザーロールには質問を設定します。

Console.WriteLine("アシスタントのセットアップ中・・・"); 
var body = new ExtensionsChatCompletionsRequest
{
    DataSources = new[] {
            new DataSource {
                Type = "AzureCognitiveSearch",
                Parameters = new {
                    endpoint = settings.CognitiveSearchEndpoint,
                    key = settings.CognitiveSearchKey,
                    indexName = settings.CognitiveSearchIndexName
                }
            }
        },
    Messages = new[] {
            new Message {
                Role = MessageRole.System,
                Content = """
                    あなたは社内の総務担当です。就業規則をもとにして、ユーザーからの質問に回答してください。
                    回答の際には出典を出力してください。
                    回答が分からない場合は、「分かりません」と回答してください。
                """
            },
            new Message {
                Role = MessageRole.User,
                Content = "社員が結婚するときの特別休暇は何日ですか?"
            }
        },
    Max_tokens = 800,
    Temperature = 0d,
    Frequency_penalty = 0d,
    Presence_penalty = 0d,
};

Console.WriteLine("チャットを開始する");
foreach (var message in body.Messages)
{
    Console.WriteLine($"{message.Role}: {message.Content}");
}

Completions extensions API の呼び出しは、ExtensionsChatCompletionsClient クラスが自動生成されています。Azure OpenAI の api-key をセットした HttpClient のインスタンスを渡して、REST API を呼び出します。

using (var httpClient = new HttpClient())
{
    httpClient.DefaultRequestHeaders.Add("api-key", settings.ApiKey);
    var client = new ExtensionsChatCompletionsClient(httpClient);
    client.BaseUrl = settings.Endpoint;

    Console.WriteLine("就業規則を確認中・・・");
    var result = await client.CreateAsync(settings.DeploymentId, settings.ApiVersion, body);
    foreach (var choice in result.Choices)
    {
        foreach (var message in choice.Messages)
        {
            if (message.Role == MessageRole.Assistant)
            {
                Console.WriteLine($"{message.Role}: {message.Content}");
            }
        }
    }
}

コンソールアプリを実行すると、プレイグラウンドと同様の結果を取得できます。


まとめ

Azure OpenAI Service On Your Data を .NET アプリケーションから使ってみました。Retrieval Augmented Generation(RAG)と呼ばれるデザインパターンを素早く構築できる待望の機能です。Azure の PaaS として簡単にできるとはいえ、回答精度を継続的に改善しつつ維持していくためには、Cognitive Search のインデックスや検索対象ドキュメントの保守は必須となります。

今回のサンプルアプリは、こちらで公開しています。
github.com

Azure OpenAI Service で Semantic Kernel を使ってみた

前回の記事では、Azure OpenAI Client Library を使って Azure OpenAI で遊んでみました。
gooner.hateblo.jp
今回は、Semantic Kernel を使って、Azure OpenAI の要約を試してみます。

Semantic Kernel とは

Semantic Kernel は、Azure OpenAI などの AI サービスと C# や Python などのプログラミング言語を簡単に組み合わせることができる OSS の SDK です。
AI アプリケーションでプロンプト設計が重要なことは言うまでもありませんが、本格的に作りこむとなると複数のプロンプトを部品化して順番に呼び出すオーケストレーション的な設計が必要となり、Semantic Kernel のような LLM と C# を統合して扱える SDK が使いたくなります。
learn.microsoft.com

先日の Build 2003 で、Copilot(AI アプリケーション)を開発するフレームワークとして「Copilot stack」が発表されました。
qiita.com
Copilot stack の中心となる AI オーケストレーション レイヤーに Semantic Kernel を使い、複数のプロンプトを順番に呼び出すオーケストレーションを組み立て、AI アプリを作成できるようになります。

Microsoft Learn から引用

なお、Copilot stack との連携強化に伴い、Semantic Kernel で「スキル」と呼んでいた機能は「プラグイン」へリネームされています。

Semantic Kernel を使ってみる

Azure OpenAI で text-davinci-003 のモデルを使った要約を Semantic Kernel で実装してみます。.NET 7 のコンソールアプリを作り、NuGet パッケージをインストールします。

Install-Package Microsoft.SemanticKernel -Version 0.17.230629.1-preview
Install-Package Microsoft.Extensions.Configuration.Binder -Version 7.0.4
Install-Package Microsoft.Extensions.Configuration.UserSecrets -Version 7.0.0

Microsoft.Extensions.Configuration 関連の NuGet パッケージは、Azure OpenAI に接続する資格情報を管理するためにインストールしました。Managed ID を使ってもいいですが、環境によって認証が通らないケースもあるので、シンプルに .NET の UserSecrets を使います。誤って GitHub にプッシュするリスクがあるのでハードコーディングはやめましょう。

{
  "AzureOpenAISettings": {
    "Endpoint": "https://xxx.openai.azure.com/",
    "ApiKey": "xxx",
    "DeploymentName": "text-davinci-003"
  }
}

secret.json にエンドポイント、キー、モデル名を定義したら、バインドするクラスを作ります。

internal class AzureOpenAISettings
{
    public required string Endpoint { get; set; }
    public required string ApiKey { get; set; }
    public required string DeploymentName { get; set; }
}

ConfigurationBuilder を使って、secret.json を読み込んで AzureOpenAISettings クラスにバインドします。

var settings = new ConfigurationBuilder()
    .AddUserSecrets<Program>()
    .Build()
    .GetSection(nameof(AzureOpenAISettings)).Get<AzureOpenAISettings>() ?? throw new NullReferenceException();

ここで Semantic Kernel のアーキテクチャを簡単に説明しておきます。
Kernel がオーケストレーション全体の制御を行います。Kernel にロードされたプラグインやコネクタの実行順序をプランナーが決定し、パイプラインチェーンとして実行させる構造になっています。

Microsoft Learn から引用

まずは、Kernel のインスタンスを作成します。

var builder = new KernelBuilder();
builder.WithAzureTextCompletionService(settings.DeploymentName, settings.Endpoint, settings.ApiKey);
var kernel = builder.Build();

要約するプロンプトを定義し、セマンティック関数として Kernel にロードします。プラグインは使わず、パラメータを使ってプロンプトを実行させます。

// Running prompts with input parameters
Console.WriteLine("Running prompts with input parameters...");
var summarizePrompt = @"{{$input}}

One line TLDR with the fewest words.";
var summarize = kernel.CreateSemanticFunction(summarizePrompt);

要約するテキストを定義し、セマンティック関数に引数で渡して実行させます。テキストは、昨シーズンのアーセナル最終節のマッチレポートを使いました。

string text = @"
We rounded off the Premier League season with an emphatic win over Wolves, the highlight of which saw Granit Xhaka net his first brace for the club.
The Swiss midfielder took just 13 minutes of the first half to achieve the feat, with Bukayo Saka adding an excellent third before half-time to mark the week in which he pledged his future in the club in style.
We kept up the pressure in the second half and Gabriel Jesus added a fourth with a header just before the hour mark, and Jakub Kiwior bagged his first in red and white to complete the scoring, as well as a memorable campaign that has brought Champions League football back to the Emirates Stadium.";

Console.WriteLine(await summarize.InvokeAsync(text));

マッチレポートのテキストが要約されたことを確認できます。

Granit Xhaka nets brace as Arsenal beat Wolves 5-0 to secure Champions League football.


プロンプトをチェインしてみる

次に、プロンプトをチェインする方法を試してみます。日本語に翻訳するプロンプトを定義し、要約→翻訳の順序で実行します。

// Prompt chaining
Console.WriteLine("Prompt chaining...");
var translationPrompt = @"{{$input}}

Translate the following English text into Japanese.";
var translator = kernel.CreateSemanticFunction(translationPrompt);

Console.WriteLine(await kernel.RunAsync(text, summarize, translator));

マッチレポートのテキストが日本語で要約されたことを確認できます。日本語に翻訳するプロンプトを定義することもできるので、あくまでチェインを試すためのサンプルシナリオです。


まとめ

Semantic Kernel を使って、Azure OpenAI の要約を試してみました。
今回は非常にシンプルな例で試しましたが、次回からはプラグインを組み合わせたりカスタムのプラグインを作ったりしていきます。
GitHub にある Jupyter Notebooks のチュートリアルが便利なのでお勧めです。
github.com

今回のサンプルアプリは、こちらで公開しています。
github.com

.NET エンジニアが Azure OpenAI Service を使ってみた

Azure OpenAI Service は、2023 年 1 月に一般提供(GA)された Microsoft の OpenAI です。今回は .NET エンジニアの視点で Azure OpenAI を使ってみました。

Azure OpenAI Service とは

Azure OpenAI の概要は、こちらの記事が分かりやすいです。利用申請には企業ドメインのメールアドレスが必要(Gmail などは使えない)ですが、現在は申請すると数時間でアクティブになるようです。
zenn.dev

シナリオ

Azure OpenAI Sutido の ChatGPT プレイグラウンドを使って、ONEPIECE のチョッパーと会話してみます。モデルは、先日公開されたばかりの gpt-35-turbo-16k を使います。

アシスタントに雑なキャラ付けをセットアップするだけで、少し違和感ありますが、チョッパーと会話できました。

Azure OpenAI Client Library を使ってみる

先ほどのシナリオを Azure OpenAI Client Library を使って実装してみます。.NET 7 のコンソールアプリを作り、NuGet パッケージをインストールします。

Install-Package Azure.AI.OpenAI -Version 1.0.0-beta.5
Install-Package Microsoft.Extensions.Configuration.Binder -Version 7.0.4
Install-Package Microsoft.Extensions.Configuration.UserSecrets -Version 7.0.0

Microsoft.Extensions.Configuration 関連の NuGet パッケージは、Azure OpenAI に接続する資格情報を管理するためにインストールしました。Managed ID を使ってもいいですが、環境によって認証が通らないケースもあるので、シンプルに .NET の UserSecrets を使います。誤って GitHub にプッシュするリスクがあるのでハードコーディングはやめましょう。

{
  "AzureOpenAISettings": {
    "Endpoint": "https://xxx.openai.azure.com/",
    "ApiKey": "xxx",
    "DeploymentName": "gpt-35-turbo-16k"
  }
}

secret.json にエンドポイント、キー、モデル名を定義したら、バインドするクラスを作ります。

internal class AzureOpenAISettings
{
    public required string Endpoint { get; set; }
    public required string ApiKey { get; set; }
    public required string DeploymentName { get; set; }
}

ConfigurationBuilder を使って、secret.json を読み込んで AzureOpenAISettings クラスにバインドします。

var settings = new ConfigurationBuilder()
    .AddUserSecrets<Program>()
    .Build()
    .GetSection(nameof(AzureOpenAISettings)).Get<AzureOpenAISettings>() ?? throw new NullReferenceException();

システムロールにアシスタントの性格と応答する際のコンテキストを定義します。

var chatCompletionsOptions = new ChatCompletionsOptions
{
    MaxTokens = 200,
    Messages =
        {
            new ChatMessage(ChatRole.System, """
                おれの名前は、トニートニー・チョッパー。
                動物系悪魔の実「ヒトヒトの実」の能力者で、人の言葉を話せるトナカイ。
                海賊「麦わらの一味」の船医。
                夢は、何でも治せる医者になること。
                わたあめ大好き。
                一人称は、おれ。
                口調はフレンドリーで可愛い。
            """)
        }
};

ユーザーロールには、こちらからの送信メッセージを定義します。本来はチャットなのでインタラクティブなやり取りですが、サンプルアプリなのでメッセージを決め打ちにしました。

var userMessages = new String[] {
    "こんにちは。",
    "トナカイなのに人の言葉を話せるの?",
    "好きな食べ物は?",
};

Azure OpenAI の API はステートレスなので、API を呼び出す都度システムロールとユーザーロールの履歴を含めて送信します。

Console.WriteLine("アシスタントのセットアップ中・・・");
var client = new OpenAIClient(new Uri(settings.Endpoint), new AzureKeyCredential(settings.ApiKey));
await client.GetChatCompletionsAsync(settings.DeploymentName, chatCompletionsOptions);

Console.WriteLine("チャットを開始する");
foreach (var userMessage in userMessages)
{
    Console.WriteLine($"{ChatRole.User}: {userMessage}");
    chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.User, userMessage));
    var result = await client.GetChatCompletionsAsync(settings.DeploymentName, chatCompletionsOptions);

    foreach (var choice in result.Value.Choices)
    {
        Console.WriteLine($"{choice.Message.Role}: {choice.Message.Content}");
        chatCompletionsOptions.Messages.Add(new ChatMessage(choice.Message.Role, choice.Message.Content));
    }
}

プレイグラウンドと同様の結果を取得できたことが分かります。


まとめ

Azure OpenAI Client Library を使って、Azure OpenAI で遊んでみました。
アシスタントのセットアップでシステムロールに、

  • 動作に関する指示や応答の生成時に参照する必要があるコンテキスト
  • 何に答えるべきで何に答えてはいけないか
  • 応答の書式

などをどのように伝えるのかのメタプロンプトが重要となります。
また、業務向けに作りこむとなると、複数のプロンプトを部品化して順番に呼び出すオーケストレーション的な設計が必要となり、Semantic Kernel のような LLM と C# を統合して扱える SDK が使いたくなります。
次回は、Semantic Kernel についての記事を書きたいと思います。
gooner.hateblo.jp
今回のサンプルアプリは、こちらで公開しています。
github.com