概要
前回の記事
👉 C#の型をスキーマにして構造化出力させる方法
上の記事では、C#クラスをスキーマ化してAzureOpenAIに構造化出力を行わせる方法を紹介しました。
しかし、実際のユースケースでは次のような状況が出てきます。
「2つ以上の型のうち、どれかで応答してほしい」
C#単体ではUnion型がないため、
このような「複数型のいずれかで出力」を自然に扱うのが難しいです。
前提:JSON SchemaのoneOfを利用する
Azure OpenAIでは、構造化出力をJSON Schemaで制御できます。
このスキーマにoneOfを追加することで、複数の型のうちいずれかを許容する応答を得られます。
課題:NJsonSchemaではoneOfを生成できない
前述のとおりAzure OpenAIに渡すスキーマにoneOfが必要なのですが、NJsonSchema内で自動で追加するようなことはできないため、仕方なくスキーマに直接手を加えて構造化出力させます。(なにか他にいい方法が見つかりましたら、ぜひご教示ください。)
実装の流れ
以下のように処理すれば、
C#でも「Union型的な出力」をLLMに指示できます。
// 1️⃣ モデル型からスキーマを生成(処理の中身は前回の記事を参照)
var schema = ConvertToSchema(typeof(MainModel));
// 2️⃣ 特定のノードに oneOf を追加
if (schema.Definitions.TryGetValue(nameof(TargetNode), out var targetNode))
{
// 複数のサブ型(例:AType, BType)を oneOf に設定
targetNode.OneOf.Add(new JsonSchema { Reference = schema.Definitions[nameof(AType)] });
targetNode.OneOf.Add(new JsonSchema { Reference = schema.Definitions[nameof(BType)] });
}
// 3️⃣ Azure OpenAIに構造化出力を依頼
var options = new ChatCompletionOptions
{
ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
jsonSchemaFormatName: "MainModel",
jsonSchema: BinaryData.FromString(schema.ToJson())
)
};
var response = await client.GetChatClient("AzureOpenAIでデプロイしたモデル名")
.CompleteChatAsync(messages, options);
// 4️⃣ 結果をC#モデルにデシリアライズ
var result = JsonSerializer.Deserialize<ResultWrapper<MainModel>>(response.Value.Content.First().Text);
上記だけではC#モデルにデシリアライズする際にエラーとなるので下記のようなポリモーフィズムが必要です。
/// <summary>
/// 基底クラス
/// </summary>
[JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")]
[JsonDerivedType(typeof(AType), typeDiscriminator: "AType")]
[JsonDerivedType(typeof(BType), typeDiscriminator: "BType")]
public abstract class MainModel
{
}
public sealed class AType : MainModel
{
[Description("文字列「AType」で固定")]
[JsonPropertyName("Type")]
[Required]
public string Type { get; set; } = "AType";
//任意のプロパティを追加してください。
}
public sealed class BType : MainModel
{
[Description("文字列「BType」で固定")]
[JsonPropertyName("Type")]
[Required]
public string Type { get; set; } = "Btype";
//任意のプロパティを追加してください。
}