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