🤖

Azure OpenAI の Responses API を .NET で使う方法 その2「IChatClient の利用」

に公開

はじめに

前回の記事の Azure OpenAI の Responses API を .NET で使う方法 では公式 SDK の Azure.AI.OpenAI パッケージを使って Responses API を利用する方法を紹介しました。今回は .NET の Unified AI Building Blocks の Microsoft.Extensions.AI を使って Responses API を利用する方法を紹介します。

IChatClient の利用

Microsoft.Extensions.AI パッケージのチャットを扱うための共通インターフェースの IChatClient を使って様々な LLM に対して共通の API でアクセスが可能です。今までは Chat Completions API 互換の API でよく使われていましたが、Responses API についても実はサポートしています。

使用するためには Azure.AI.OpenAI パッケージと Microsoft.Extensions.AI パッケージと Microsoft.Extensions.AI.OpenAI パッケージをインストールする必要があります。今回使用した各パッケージのバージョンは以下の通りです。

  • Azure.AI.OpenAI: 2.3.0-beta.2
  • Microsoft.Extensions.AI: 9.8.0
  • Microsoft.Extensions.AI.OpenAI: 9.8.0-preview.1.25412.6

この他にも認証のために以下のパッケージも追加しています。

  • Azure.Identity: 1.15.0

チャットクライアントを使用するには前の記事で紹介した OpenAIResponseClientIChatClient に変換する AsIChatClient 拡張メソッドを使用します。

コードは以下のようになります。

// Responses API のクライアントは評価目的のみで提供されているため、これを書かないとエラーになる
#pragma warning disable OPENAI001
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;

// 普通に AOAI クライアントを作る
var aoaiClient = new AzureOpenAIClient(
    new("https://<<Your AOAI Endpoint>>"),
    new AzureCliCredential());

// GetOpenAIResponseClient で Responses API クライアントを作り IChatClient に変換
var chatClient = aoaiClient.GetOpenAIResponseClient("gpt-5")
    .AsIChatClient();

この後のコードは、このコードがあることを前提に記載します。

チャットを呼び出そう

単に 1 ターンのチャットを行うのは簡単です。以下のように普通に GetResponseAsync メソッドを呼び出すだけです。

var first = await chatClient.GetResponseAsync("こんにちは!私の名前は Kazuki Ota です。");
Console.WriteLine(first.Text);

var second = await chatClient.GetResponseAsync("私の名前を教えてください。", 
    new ChatOptions 
    { 
        ConversationId = first.ConversationId 
    });
Console.WriteLine("==============================");
Console.WriteLine(second.Text);

これで Responses API を使ったチャットが実行できます。ポイントは1つ前の会話を表すプロパティは ConversationId である点です。プロパティ名が違うので注意してください。

実行すると以下のようになります。ちゃんと過去の会話の内容を覚えていることが確認できます。

Kazuki Otaさん、はじめまして。今日はどのようにお手伝いできますか?
また、呼び方は「Kazukiさん」「Otaさん」など、どちらがよろしいでしょうか。
==============================
あなたの名前は「Kazuki Ota」です。
呼び方は「Kazukiさん」でよろしいですか?

ステートレスにしたい

残念ながら IChatClientGetResponseAsync メソッドで指定する ChatOptions には StoredOutputEnabled 相当のプロパティがありません。抽象化レイヤーを挟んでしまうと抽象化レイヤーがサポートしていない機能を使うためにはちょっとだけ道を外れないといけないのは、どんなライブラリを使っていても同じです。

再々にも Microsoft.Extensions.AIChatOptionsRawRepresentationFactory というプロパティを持っており、ここで任意のネイティブライブラリ用のオプション (今回の場合は ResponseCreationOptions) を作成するデリゲートを設定することが出来ます。そのため以下のようにすることでステートレスにも対応可能です。

// 会話履歴を保持するためのリスト
List<ChatMessage> messages = [
    new ChatMessage(ChatRole.User, "こんにちは!私の名前は Kazuki Ota です。")
];
var first = await chatClient.GetResponseAsync(messages,
    new ChatOptions()
    {
        // ステートレスモードの ResponseCreationOptions を作成する処理を設定
        RawRepresentationFactory = CreateStatelessResponseCreationOptions,
    });
Console.WriteLine(first.Text);

// Reasoning 系のメッセージは現状ステートレスだとサーバーサイドで保持するのが必要なので、除外して会話履歴に追加する
messages.AddRange(first.Messages.Select(x => new ChatMessage(x.Role, x.Contents.Where(x => x is not TextReasoningContent).ToList())));
messages.Add(new ChatMessage(ChatRole.User, "私の名前は?"));

var second = await chatClient.GetResponseAsync(messages,
    new ChatOptions
    {
        // ステートレスモードの ResponseCreationOptions を作成する処理を設定
        RawRepresentationFactory = CreateStatelessResponseCreationOptions,
    });
Console.WriteLine("==============================");
Console.WriteLine(second.Text);

// ステートレスモードの ResponseCreationOptions を作成する処理
ResponseCreationOptions CreateStatelessResponseCreationOptions(IChatClient _) =>
    new() { StoredOutputEnabled = false };

実行すると以下のようになります。ちゃんと過去の会話の内容を覚えていることが確認できます。

こんにちは、はじめまして!Kazuki Otaさん、自己紹介ありがとうございます。
「Kazukiさん」「Otaさん」など、どのようにお呼びするのがよろしいでしょうか?日本語・英語どちらでも対応できます。今日はどのようにお手伝いしましょうか?
==============================
Kazuki Ota さんです。

ツール呼び出し

ツール呼び出しは Microsoft.Extensions.AI の機能を使って非常に簡単に実装できます。UseFunctionInvocation 拡張メソッドを使ってミドルウェアを仕込むことで、ツール呼び出しを実装できます。

// GetOpenAIResponseClient で Responses API クライアントを作り IChatClient に変換
var chatClient = aoaiClient.GetOpenAIResponseClient("gpt-5")
    .AsIChatClient()
    // 自動関数呼び出し機能を追加
    .AsBuilder()
    .UseFunctionInvocation()
    .Build();

// 天気を聞いて GetWeather 関数を呼び出すように仕向ける
var response = await chatClient.GetResponseAsync("2025/09/01 の東京の天気は?",
    new ChatOptions()
    {
        Tools = [AIFunctionFactory.Create(GetWeather)],
        ToolMode = ChatToolMode.Auto,
    });
Console.WriteLine(response.Text);

// 天気を取得する関数
[Description("指定された場所と日付の天気を取得します。")]
string GetWeather(
    [Description("天気を取得する場所")] string location,
    [Description("天気を取得する日付")] DateTimeOffset date) => 
    $"{date}{location}の天気は雷雨です。";

ここら辺は Microsoft.Extensions.AI の強みですね。実行すると以下のような結果になります。ちゃんと関数から得た情報を使って回答しています。

2025年9月1日の東京の天気は雷雨の予報です。外出の際は雨具をご用意ください。

Structured Output

前の記事では Json Schema を書くのがめんどくさくてやっていなかったのですが Structured Output も Microsoft.Extensions.AI の機能を使うことで簡単に実装できます。

ChatOptionsResponseFormat プロパティに ChatResponseFormat.ForJsonSchema メソッドで生成した ChatResponseFormat を指定するだけです。あとは AIJsonUtilities.CreateJsonSchema メソッドに C# の型を渡すことで JSON Schema を自動生成してくれます。

// 天気を聞いて GetWeather 関数を呼び出すように仕向ける
var response = await chatClient.GetResponseAsync("""
    以下のテキストから名前と年齢を抽出して JSON で返してください。
    ===
    私の名前は田中太郎 35歳ではなく大田 一希(44)ですが、心は永遠の17歳です。
    """,
    new ChatOptions()
    {
        ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(Person))),
    });
Console.WriteLine(response.Text);

record Person(
    [Description("名前")]
    string Name,
    [Description("年齢")]
    int Age);

実行すると以下のような結果になります。ちゃんと JSON 形式で回答しています。

{"name":"大田 一希","age":44}

まとめ

Responses API を Microsoft.Extensions.AI で使用してみました。一般的なユースケースの機能はサポートされていそうで、どうしてもだめな時の抜け道も RawRepresentationFactory で用意されているので大体なんとかなりそうです。Microsoft.Extensions.AI の自動関数呼び出しや Structured Output に渡す Json Schema の自動生成などの機能も使えるので、Responses API を使う場合でも Microsoft.Extensions.AI を使うメリットは大きいと思います。

ここら辺、本当に早く GA して SDK も含めて安定してほしいですね…。

Microsoft (有志)

Discussion