Rystem.PlayFramework.Adapters 10.1.1

dotnet add package Rystem.PlayFramework.Adapters --version 10.1.1
                    
NuGet\Install-Package Rystem.PlayFramework.Adapters -Version 10.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Rystem.PlayFramework.Adapters" Version="10.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Rystem.PlayFramework.Adapters" Version="10.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Rystem.PlayFramework.Adapters" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Rystem.PlayFramework.Adapters --version 10.1.1
                    
#r "nuget: Rystem.PlayFramework.Adapters, 10.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Rystem.PlayFramework.Adapters@10.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Rystem.PlayFramework.Adapters&version=10.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Rystem.PlayFramework.Adapters&version=10.1.1
                    
Install as a Cake Tool

Rystem.PlayFramework.Adapters

Rystem.PlayFramework.Adapters is the Azure OpenAI adapter package for Rystem.PlayFramework.

It registers named IChatClient and IVoiceAdapter instances, defaults to the Azure OpenAI Responses API, and adds a file-upload wrapper for non-image, non-audio content.

Installation

dotnet add package Rystem.PlayFramework.Adapters

The package uses OpenAI 2.12.0 directly with Microsoft.Extensions.AI.OpenAI 10.9.0. Azure.AI.OpenAI is not included. The dependency range is exactly [2.12.0]. A direct consumer reference can override this range under NuGet's direct-dependency-wins rule, but restore emits NU1608; that graph is unsupported and should be treated as an error by CI.

Azure OpenAI v1 endpoint

Requests use the Azure OpenAI v1 surface. Both forms below are accepted:

  • https://your-resource.openai.azure.com/
  • https://your-resource.openai.azure.com/openai/v1/

Resource-root endpoints are normalized to /openai/v1 automatically. Legacy deployment-specific endpoints such as /openai/deployments/<deployment> are rejected. The configured deployment is sent as the v1 request's model value.

What this package adds

The public registration methods are:

  • AddAdapterForAzureOpenAI(...)
  • AddVoiceAdapterForAzureOpenAI(...)

These methods register singleton factory-backed services so they can be referenced by PlayFramework instance name.

Registering a chat adapter

Unnamed registration:

builder.Services.AddAdapterForAzureOpenAI(settings =>
{
    settings.Endpoint = new Uri("https://your-resource.openai.azure.com/");
    settings.ApiKey = builder.Configuration["AzureOpenAI:Key"];
    settings.Deployment = "gpt-4o";
});

Named registration, which is the most common pattern with PlayFramework:

builder.Services.AddAdapterForAzureOpenAI("default", settings =>
{
    settings.Endpoint = new Uri(builder.Configuration["AzureOpenAI:Endpoint"]!);
    settings.ApiKey = builder.Configuration["AzureOpenAI:Key"]!;
    settings.Deployment = builder.Configuration["AzureOpenAI:Deployment"] ?? "gpt-4o";
});

builder.Services.AddPlayFramework("default", framework =>
{
    framework.WithChatClient("default");
});

Managed identity mode:

builder.Services.AddAdapterForAzureOpenAI("default", settings =>
{
    settings.Endpoint = new Uri("https://your-resource.openai.azure.com/");
    settings.UseAzureCredential = true;
    settings.Deployment = "gpt-4o";
});

UseAzureCredential uses DefaultAzureCredential with the https://ai.azure.com/.default scope. In Azure, prefer Managed Identity and assign the identity an Azure OpenAI data-plane role such as Cognitive Services OpenAI User. Do not configure ApiKey when UseAzureCredential is enabled; the adapter rejects ambiguous authentication configuration during registration.

Chat adapter settings

AdapterSettings exposes:

Property Meaning
Endpoint Azure OpenAI endpoint
ApiKey API key when not using managed identity
UseAzureCredential use DefaultAzureCredential instead of ApiKey
Deployment deployment/model name, default gpt-4o
UseResponsesApi use GetResponsesClient(...) instead of chat completions
EnableFileUpload wrap the chat client with file-upload behavior
AudioMode None, MultiModal, or SpeechToText
SpeechToTextDeployment required when AudioMode is SpeechToText
SpeechToTextApiVersion optional override for the Audio Transcriptions api-version used by SpeechToTextDeployment (default supports both whisper-1 and gpt-4o-transcribe/gpt-4o-mini-transcribe)

If an existing application encounters a Responses-only service incompatibility, set UseResponsesApi = false to use Chat Completions temporarily. This disables automatic file upload because EnableFileUpload applies only to Responses.

Runtime tool declarations

Starting with 10.0.11-beta.22, the adapter is compatible with the declaration-only AIFunctionDeclaration instances published by PlayFramework when scene and local-tool descriptions are refreshed at runtime. Name, description, and JSON Schema are forwarded to Azure OpenAI through the official Microsoft.Extensions.AI.OpenAI bridge.

This applies to both adapter modes:

  • Chat Completions (UseResponsesApi = false)
  • Responses API (UseResponsesApi = true)

Declaration-only tools describe routing and callable tools to the model; PlayFramework keeps the executable function separately and invokes it after receiving the tool call.

File upload behavior

When both of these are true:

  • UseResponsesApi = true
  • EnableFileUpload = true

the adapter wraps the inner IChatClient with MultiModalChatClient.

That wrapper:

  • uploads non-image, non-audio DataContent through the Files API
  • replaces inline content with HostedFileContent(file_id)
  • caches uploads by SHA256 hash
  • tries remote file reuse by filename if hash lookup misses

Cache priority is:

  1. IDistributedCache
  2. IMemoryCache
  3. internal in-memory dictionary

Audio modes

The chat adapter supports three audio paths:

  • AudioMode.None - audio is passed through as-is
  • AudioMode.MultiModal - audio is sent inline to a model that natively supports it
  • AudioMode.SpeechToText - audio is transcribed through Whisper and injected as text

Example:

builder.Services.AddAdapterForAzureOpenAI("default", settings =>
{
    settings.Endpoint = new Uri("https://your-resource.openai.azure.com/");
    settings.ApiKey = "...";
    settings.Deployment = "gpt-4o";
    settings.AudioMode = AudioMode.SpeechToText;
    settings.SpeechToTextDeployment = "whisper";
});

Voice adapter

The same package also registers IVoiceAdapter for the PlayFramework voice pipeline.

builder.Services.AddVoiceAdapterForAzureOpenAI("default", settings =>
{
    settings.Endpoint = new Uri(builder.Configuration["AzureOpenAI:Endpoint"]!);
    settings.ApiKey = builder.Configuration["AzureOpenAI:Key"]!;
    settings.SttDeployment = "<transcription-deployment>";
    settings.TtsDeployment = "<speech-deployment>";
    settings.TtsVoice = "alloy";
});

builder.Services.AddPlayFramework("default", framework =>
{
    framework.WithVoice("default");
});

VoiceAdapterSettings exposes:

Property Meaning
Endpoint Azure OpenAI endpoint
ApiKey API key when not using managed identity
UseAzureCredential use DefaultAzureCredential
SttDeployment speech-to-text deployment, default whisper
SttApiVersion optional override for the Audio Transcriptions api-version used by SttDeployment (default supports both whisper-1 and gpt-4o-transcribe/gpt-4o-mini-transcribe)
TtsDeployment text-to-speech deployment, default tts-1
TtsVoice voice name, default alloy
TtsOutputFormat output format such as mp3, wav, pcm
TtsSpeed speech speed multiplier

The voice adapter requests verbose_json transcription first so detected language and audio duration remain available for language instructions and STT cost tracking. Some transcription models, including current gpt-4o-mini-transcribe deployments, reject that format. For those deployments the adapter retries with json; transcription text remains available, but detected language and duration are null, so language substitution and duration-based STT cost cannot be calculated from the service response.

Cost tracking

Set CostTracking on AdapterSettings to record input/output token costs per LLM call:

builder.Services.AddAdapterForAzureOpenAI("default", settings =>
{
    settings.Endpoint = new Uri(builder.Configuration["AzureOpenAI:Endpoint"]!);
    settings.ApiKey = builder.Configuration["AzureOpenAI:Key"]!;
    settings.Deployment = "gpt-4o";
    settings.CostTracking = new TokenCostSettings
    {
        Enabled = true,
        Currency = "USD",
        InputTokenCostPer1K = 0.005m,
        OutputTokenCostPer1K = 0.015m,
        CachedInputTokenCostPer1K = 0.0025m, // optional: cached input is usually ~50% of regular
    };
});

TokenCostSettings exposes:

Property Meaning
Enabled enable or disable cost tracking, default true
Currency currency label for display, default USD
InputTokenCostPer1K cost per 1 000 input tokens
OutputTokenCostPer1K cost per 1 000 output tokens
CachedInputTokenCostPer1K cost per 1 000 cached input tokens, default 0
ModelCosts Dictionary<string, ModelCostSettings> per-model price overrides
ClientCosts Dictionary<string, ClientCostSettings> per-client price overrides

When CostTracking is set the adapter wraps the underlying IChatClient with CostTrackingChatClient — a DelegatingChatClient that reads response.Usage after every LLM call and embeds a CostCalculation into AdditionalProperties. ChatClientManager picks that value up passively and surfaces it as AiSceneResponse.Cost (per-call) and AiSceneResponse.TotalCost (cumulative across the full request).

Critical: you must call .WithChatClient("name") in AddPlayFramework with the same factory name used for the adapter. Without it the framework falls back to a direct IChatClient resolution path that does not see the cost wrapper and reports zero for all costs.

See Example: cost tracking in the PlayFramework README for budget enforcement, response fields, and per-model / per-client pricing.


Important caveats

The streaming path blocks during file preprocessing

GetStreamingResponseAsync(...) preprocesses uploadable files with a sync wait via GetAwaiter().GetResult(). It works, but it is not the cleanest async path.

Remote file reuse is filename-based after hash miss

If the local content-hash cache misses, the wrapper checks Azure's existing file list by filename. That is convenient, but it is not a content-identity guarantee.

Images and audio are not part of the file-upload wrapper

The Files API wrapper only handles non-image, non-audio binary content. Images stay inline, and audio is handled through the adapter's audio-mode logic.

Factory names should match your PlayFramework names

If you register the adapter as "default", your PlayFramework builder should typically reference WithChatClient("default") and WithVoice("default").

Grounded by source files

  • src/AI/Rystem.PlayFramework.Adapters/ServiceCollectionExtensions.cs
  • src/AI/Rystem.PlayFramework.Adapters/MultiModalChatClient.cs
  • src/AI/Test/Rystem.PlayFramework.Api/Program.cs

Use this package when your PlayFramework backend should run on Azure OpenAI with optional file upload, speech-to-text, and voice output.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.1.1 133 8/28/2026
10.1.0-beta.4 77 8/26/2026
10.1.0-beta.3 73 8/26/2026
10.1.0-beta.2 65 8/26/2026
10.0.11-beta.25 87 7/27/2026
10.0.11-beta.20 60,698 5/27/2026
10.0.11-beta.19 71 5/26/2026
10.0.11-beta.18 73 5/25/2026
10.0.11-beta.17 77 5/22/2026
10.0.11-beta.16 69 5/22/2026
10.0.11-beta.15 93 5/13/2026
10.0.11-beta.14 71 5/13/2026
10.0.11-beta.13 93 3/27/2026
10.0.11-beta.12 71 3/27/2026
10.0.11-beta.11 81 3/25/2026
10.0.11-beta.10 81 3/23/2026
10.0.11-beta.9 73 3/20/2026
10.0.11-beta.8 76 3/19/2026
10.0.11-beta.7 70 3/18/2026
10.0.11-beta.6 76 3/17/2026
Loading failed

Migrates Azure OpenAI from Azure.AI.OpenAI to OpenAI 2.12.0 and the /openai/v1 API while preserving existing resource-root endpoint configuration through normalization. Supports Responses and Chat Completions with API-key or Microsoft Entra authentication and the required operation-specific scopes. Rejects malformed endpoints, ambiguous API-key plus Entra configuration, and empty STT API-version overrides during registration. Audio uses deployment-specific Azure routes with api-version 2025-04-01-preview; STT supports per-adapter API-version overrides, propagates cancellation, preserves verbose language and duration metadata when available, and retries with JSON when verbose_json is unsupported. Chat and audio clients share one DefaultAzureCredential per adapter. Targets Rystem.PlayFramework 10.1.0-beta.5 and pins the supported graph to OpenAI 2.12.0 with Microsoft.Extensions.AI.OpenAI 10.9.0. Inline audio remains dependent on the selected model and deployment capabilities.