OpenAI SDK with .NET

The official OpenAI .NET SDK gives C# and .NET applications a strongly typed way to communicate with the OpenAI API.

Instead of manually building:

HTTP requests
JSON payloads
Authorization headers
Response parsers
Streaming handlers
Tool-call processing

you can use .NET client classes such as:

ChatClient
ResponsesClient
EmbeddingClient
ImageClient
AudioClient
OpenAIFileClient
VectorStoreClient
ModerationClient
RealtimeClient
OpenAIClient

The official SDK is generated from OpenAI's OpenAPI specification in collaboration with Microsoft. The current NuGet release verified for this article is OpenAI 2.14.0, released on September 15, 2026. The package supports .NET 8 and higher and .NET Standard 2.0.

The SDK's overall architecture is:

                  .NET Application
                         |
                         v
                  OpenAI SDK
                         |
        +----------------+----------------+
        |                |                |
        v                v                v
    ChatClient     ResponsesClient    Other Clients
        |                |                |
        +----------------+----------------+
                         |
                         v
                    OpenAI API

The official repository currently documents chat completions, streaming, tools and function calling, structured outputs, audio, Responses streaming and reasoning, file search, web search, embeddings, images, audio transcription, Azure OpenAI integration, testing, retries, and observability.

What Is the OpenAI SDK for .NET?

The OpenAI SDK is a .NET library that wraps the OpenAI REST API with C# client classes and request/response types.

Without an SDK:

C#
 |
 +-- HttpClient
 +-- JSON
 +-- Headers
 +-- Authentication
 +-- Response parsing
 +-- Streaming parsing
 +-- Error handling
 |
 v
OpenAI API

With the SDK:

C#
 |
 v
ChatClient
 |
 v
OpenAI API

or:

C#
 |
 v
ResponsesClient
 |
 v
OpenAI API

This is particularly valuable in larger applications because the SDK exposes API concepts through C# objects rather than requiring every service to construct HTTP payloads manually.

Current OpenAI NuGet Package

Install the current verified package:

dotnet add package OpenAI --version 2.14.0

The current NuGet package page lists 2.14.0 as the latest published version, dated September 15, 2026.

The project can contain:

<ItemGroup>
  <PackageReference Include="OpenAI" Version="2.14.0" />
</ItemGroup>

For future projects, check NuGet before installing so the package version is current at the time the project is created.

.NET Version Compatibility

The current package targets .NET 8.0 and is also compatible with later versions, and the package also supports .NET Standard 2.0.

For a modern application, a typical project is:

dotnet new console -n OpenAISdkDemo

or:

dotnet new webapi -n OpenAISdkApi

For the examples in this article, .NET 10 syntax is used where convenient. The official SDK documentation currently uses .NET 10 examples.

API Key Setup

The SDK needs an OpenAI API key.

The official SDK documentation recommends keeping the key in a secure location and using environment variables or configuration instead of placing it in source control.

For Windows PowerShell:

$env:OPENAI_API_KEY="your-api-key"

For a persistent Windows environment variable:

setx OPENAI_API_KEY "your-api-key"

For Linux/macOS:

export OPENAI_API_KEY="your-api-key"

Read it from C#:

var apiKey =
    Environment.GetEnvironmentVariable("OPENAI_API_KEY")
    ?? throw new InvalidOperationException(
        "OPENAI_API_KEY is not configured.");

Never place the real API key in:

Git
GitHub
appsettings.json committed to source control
JavaScript
browser code
Blazor WebAssembly
mobile application binaries

Understanding the SDK Namespace Organization

The current SDK organizes clients by API feature area.

The official README lists namespaces and their corresponding clients as follows: OpenAI.Chat → ChatClient, OpenAI.Responses → ResponsesClient, OpenAI.Audio → AudioClient, OpenAI.Embeddings → EmbeddingClient, OpenAI.Images → ImageClient, OpenAI.Files → OpenAIFileClient, OpenAI.Models → OpenAIModelClient, OpenAI.Moderations → ModerationClient, OpenAI.Realtime → RealtimeClient, and others.

The important mapping is:

OpenAI.Chat
    -> ChatClient

OpenAI.Responses
    -> ResponsesClient

OpenAI.Embeddings
    -> EmbeddingClient

OpenAI.Images
    -> ImageClient

OpenAI.Audio
    -> AudioClient

OpenAI.Files
    -> OpenAIFileClient

OpenAI.VectorStores
    -> VectorStoreClient

OpenAI.Moderations
    -> ModerationClient

OpenAI.Realtime
    -> RealtimeClient

This organization makes it easier to locate the appropriate client for a capability.

The OpenAIClient Class

The SDK also provides a parent:

using OpenAI;

with:

OpenAIClient

The official documentation describes OpenAIClient as a convenience for creating multiple feature-specific clients while sharing implementation details.

Example:

using OpenAI;

var apiKey =
    Environment.GetEnvironmentVariable("OPENAI_API_KEY")
    ?? throw new InvalidOperationException(
        "OPENAI_API_KEY is not configured.");

OpenAIClient client =
    new(apiKey);

Then create feature clients:

var chatClient =
    client.GetChatClient(model);

var audioClient =
    client.GetAudioClient(audioModel);

This is useful when one application works with multiple OpenAI APIs.

Direct Client vs OpenAIClient

You can construct:

ChatClient chatClient =
    new(model, apiKey);

or:

OpenAIClient openAIClient =
    new(apiKey);

ChatClient chatClient =
    openAIClient.GetChatClient(model);

Use the parent client when you expect multiple OpenAI feature clients to share configuration and implementation details.

For a tiny application, directly constructing ChatClient is perfectly reasonable.

Your First OpenAI SDK Application

Create:

dotnet new console -n OpenAISdkDemo
cd OpenAISdkDemo
dotnet add package OpenAI --version 2.14.0

Then:

using OpenAI.Chat;

var apiKey =
    Environment.GetEnvironmentVariable("OPENAI_API_KEY")
    ?? throw new InvalidOperationException(
        "OPENAI_API_KEY is not configured.");

var model =
    Environment.GetEnvironmentVariable("AI_MODEL")
    ?? throw new InvalidOperationException(
        "AI_MODEL is not configured.");

ChatClient client =
    new(model, apiKey);

ChatCompletion completion =
    await client.CompleteChatAsync(
        "Explain dependency injection in ASP.NET Core.");

Console.WriteLine(
    completion.Content[0].Text);

The official SDK documents CompleteChat and CompleteChatAsync as the basic ChatClient operations.

Understanding ChatClient

ChatClient belongs to:

OpenAI.Chat

It is designed for chat-completion operations.

The architecture is:

ChatClient
   |
   +-- CompleteChat
   +-- CompleteChatAsync
   +-- CompleteChatStreaming
   +-- CompleteChatStreamingAsync
   |
   +-- tools
   +-- structured outputs
   +-- audio

The SDK documents the synchronous and asynchronous API pairs.

For ASP.NET Core applications, prefer the asynchronous operations:

await client.CompleteChatAsync(...);

rather than blocking calls.

Creating Chat Messages

For a simple prompt:

var completion =
    await client.CompleteChatAsync(
        "What is dependency injection?");

For a conversation with roles:

using OpenAI.Chat;

List<ChatMessage> messages =
[
    new SystemChatMessage(
        "You are a professional .NET instructor."),

    new UserChatMessage(
        "Explain dependency injection.")
];

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages);

This gives you explicit role separation.

System Prompt

A system message can define application behavior:

var messages =
    new List<ChatMessage>
    {
        new SystemChatMessage(
            """
            You are a .NET programming assistant.

            Prefer practical C# examples.
            Do not invent API names.
            Explain important assumptions.
            """),

        new UserChatMessage(
            "Explain dependency injection.")
    };

Then:

var completion =
    await client.CompleteChatAsync(
        messages);

This integrates directly with the system-prompt and prompt-template architecture developed in earlier topics.

Conversation History

You can maintain messages yourself:

List<ChatMessage> messages =
[
    new SystemChatMessage(
        "You are a helpful assistant.")
];

messages.Add(
    new UserChatMessage(
        "What is ASP.NET Core?"));

ChatCompletion first =
    await client.CompleteChatAsync(
        messages);

messages.Add(
    new AssistantChatMessage(first));

messages.Add(
    new UserChatMessage(
        "How does dependency injection work?"));

ChatCompletion second =
    await client.CompleteChatAsync(
        messages);

The important pattern is:

System
  |
User
  |
Assistant
  |
User
  |
Assistant

This fits directly into the context-management concepts covered earlier in the series.

Reusing ChatClient

The official SDK states that its clients are thread-safe and can safely be registered as singletons in ASP.NET Core dependency injection.

Therefore, avoid:

public async Task<string> AskAsync()
{
    var client =
        new ChatClient(model, apiKey);

    ...
}

on every request.

Prefer dependency injection.

Dependency Injection with ASP.NET Core

The current official repository documents dependency-injection support for the OpenAI clients and states that they can be registered as singletons.

A typical application architecture is:

Program.cs
   |
   v
ChatClient
   |
   v
AI Service
   |
   v
Controller / Endpoint

For example:

builder.Services.AddSingleton(
    _ =>
    {
        var apiKey =
            builder.Configuration["OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is missing.");

        var model =
            builder.Configuration["OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is missing.");

        return new ChatClient(
            model,
            apiKey);
    });

Then:

public sealed class AIService
{
    private readonly ChatClient _client;

    public AIService(ChatClient client)
    {
        _client = client;
    }
}

Using OpenAIClient with Dependency Injection

When several capabilities are required:

builder.Services.AddSingleton(
    _ =>
    {
        var apiKey =
            builder.Configuration["OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is missing.");

        return new OpenAIClient(apiKey);
    });

Then create feature-specific clients inside a factory or service composition layer.

For example:

public sealed class OpenAIServiceFactory
{
    private readonly OpenAIClient _client;
    private readonly IConfiguration _configuration;

    public OpenAIServiceFactory(
        OpenAIClient client,
        IConfiguration configuration)
    {
        _client = client;
        _configuration = configuration;
    }

    public ChatClient CreateChatClient()
    {
        var model =
            _configuration["OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is missing.");

        return _client.GetChatClient(model);
    }
}

For larger systems, registering the exact feature clients your services consume is usually clearer.

Configuration

Use:

{
  "OpenAI": {
    "Model": "your-model-name"
  }
}

Keep the API key in:

User Secrets
Environment Variables
Azure Key Vault
Secure Secret Store

For local development:

dotnet user-secrets init
dotnet user-secrets set "OpenAI:ApiKey" "your-api-key"

Then:

var apiKey =
    configuration["OpenAI:ApiKey"];

Strongly Typed Configuration

Create:

public sealed class OpenAIOptions
{
    public string? ApiKey { get; init; }

    public string? Model { get; init; }
}

Register:

builder.Services.Configure<OpenAIOptions>(
    builder.Configuration.GetSection("OpenAI"));

Then use:

public sealed class OpenAIChatService
{
    private readonly ChatClient _client;

    public OpenAIChatService(
        IOptions<OpenAIOptions> options)
    {
        var settings = options.Value;

        if (string.IsNullOrWhiteSpace(settings.ApiKey))
        {
            throw new InvalidOperationException(
                "OpenAI API key is missing.");
        }

        if (string.IsNullOrWhiteSpace(settings.Model))
        {
            throw new InvalidOperationException(
                "OpenAI model is missing.");
        }

        _client =
            new ChatClient(
                settings.Model,
                settings.ApiKey);
    }
}

For an actual application, registering the client itself as a singleton through DI is preferable to constructing it inside every service instance.

ChatCompletion

The result of a ChatClient request is:

ChatCompletion

You can inspect:

completion.Content
completion.FinishReason
completion.ToolCalls
completion.Usage

Conceptually:

ChatCompletion
|
+-- Content
+-- FinishReason
+-- ToolCalls
+-- Usage
+-- Metadata

The exact available members depend on the SDK version and request type.

Reading Text Safely

A basic example is:

var text =
    completion.Content[0].Text;

For production code, don't blindly assume there is always at least one content item.

Use a helper:

private static string ExtractText(
    ChatCompletion completion)
{
    foreach (
        ChatMessageContentPart part
        in completion.Content)
    {
        if (!string.IsNullOrWhiteSpace(part.Text))
        {
            return part.Text;
        }
    }

    return string.Empty;
}

Then:

var text =
    ExtractText(completion);

This becomes more robust when responses contain different content parts.

Chat Options

The SDK provides:

ChatCompletionOptions

for request configuration.

For example:

ChatCompletionOptions options =
    new()
    {
        MaxOutputTokens = 500
    };

Then:

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages,
        options);

The exact properties available should be checked against the installed SDK version because OpenAI's API and model capabilities evolve.

Structured Outputs

The official SDK supports JSON Schema-based structured outputs for Chat Completions.

The current official example uses:

ChatResponseFormat.CreateJsonSchemaFormat(...)

and configures:

jsonSchemaIsStrict: true

before sending the request.

Example:

var options =
    new ChatCompletionOptions
    {
        ResponseFormat =
            ChatResponseFormat.CreateJsonSchemaFormat(
                jsonSchemaFormatName:
                    "ticket_analysis",
                jsonSchema:
                    BinaryData.FromString(
                        """
                        {
                          "type": "object",
                          "properties": {
                            "category": {
                              "type": "string"
                            },
                            "priority": {
                              "type": "string"
                            },
                            "summary": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "category",
                            "priority",
                            "summary"
                          ],
                          "additionalProperties": false
                        }
                        """),
                jsonSchemaIsStrict: true)
        };

Then:

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages,
        options);

Parse:

using JsonDocument json =
    JsonDocument.Parse(
        completion.Content[0].Text);

The official SDK's current structured-output example follows this general pattern.

Streaming with ChatClient

The SDK supports streaming chat completions.

Non-streaming:

ChatCompletion completion =
    await client.CompleteChatAsync(
        prompt);

Streaming:

AsyncCollectionResult<StreamingChatCompletionUpdate>
    updates =
        client.CompleteChatStreamingAsync(
            prompt);

Then:

await foreach (
    StreamingChatCompletionUpdate update
    in updates)
{
    foreach (
        ChatMessageContentPart part
        in update.ContentUpdate)
    {
        Console.Write(
            part.Text);
    }
}

The official README documents both CompleteChatStreaming and CompleteChatStreamingAsync.

Why Streaming Matters

Without streaming:

Request
   |
   v
Wait
   |
   v
Complete response

With streaming:

Request
   |
   +-- Chunk 1
   +-- Chunk 2
   +-- Chunk 3
   +-- Chunk 4
   |
   v
Complete response

Streaming is useful for:

chat applications
AI assistants
long answers
interactive applications
real-time user interfaces

Streaming in ASP.NET Core

A service can expose an async stream:

public async IAsyncEnumerable<string> StreamAsync(
    string prompt,
    [EnumeratorCancellation]
    CancellationToken cancellationToken = default)
{
    AsyncCollectionResult<StreamingChatCompletionUpdate>
        updates =
            _client.CompleteChatStreamingAsync(
                prompt,
                cancellationToken);

    await foreach (
        var update in updates
            .WithCancellation(cancellationToken))
    {
        foreach (
            var content
            in update.ContentUpdate)
        {
            if (!string.IsNullOrEmpty(content.Text))
            {
                yield return content.Text;
            }
        }
    }
}

Then the ASP.NET Core layer can forward the chunks through:

SSE
HTTP streaming
SignalR

depending on the UI architecture.

Tools and Function Calling

The SDK provides first-class tool support.

The official README demonstrates creating tools with:

ChatTool.CreateFunctionTool(...)

and supplying them through:

ChatCompletionOptions.Tools

When the model returns ChatFinishReason.ToolCalls, the application executes the requested functions and sends their results back to the model.

The architecture is:

User
 |
 v
ChatClient
 |
 v
OpenAI
 |
 v
Tool Call
 |
 v
.NET Application
 |
 v
Execute Tool
 |
 v
Tool Result
 |
 v
OpenAI
 |
 v
Final Response

Creating a Tool

Example:

ChatTool getOrderStatusTool =
    ChatTool.CreateFunctionTool(
        functionName:
            "get_order_status",
        functionDescription:
            "Gets the current status of an order.");

For arguments:

ChatTool getOrderTool =
    ChatTool.CreateFunctionTool(
        functionName:
            "get_order",
        functionDescription:
            "Gets an order by its ID.",
        functionParameters:
            BinaryData.FromString(
                """
                {
                  "type": "object",
                  "properties": {
                    "orderId": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "orderId"
                  ],
                  "additionalProperties": false
                }
                """));

Supplying Tools

ChatCompletionOptions options =
    new()
    {
        Tools =
        {
            getOrderStatusTool,
            getOrderTool
        }
    };

Then:

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages,
        options);

Handling Tool Calls

Inspect:

if (completion.FinishReason ==
    ChatFinishReason.ToolCalls)
{
    ...
}

The official SDK's documented pattern is:

1. Receive tool calls.
2. Add the assistant message containing those calls to history.
3. Execute the application functions.
4. Add tool results.
5. Call CompleteChat again.
6. Repeat if needed.

The SDK README explicitly warns that model-generated function arguments may be hallucinated and must be parsed and validated before the function is executed.

Tool Argument Validation

Suppose the model requests:

{
  "orderId": "ORD-10025"
}

Parse:

using JsonDocument arguments =
    JsonDocument.Parse(
        toolCall.FunctionArguments);

if (!arguments.RootElement.TryGetProperty(
        "orderId",
        out JsonElement orderIdElement))
{
    throw new InvalidOperationException(
        "orderId is required.");
}

string? orderId =
    orderIdElement.GetString();

Then validate:

if (string.IsNullOrWhiteSpace(orderId))
{
    throw new InvalidOperationException(
        "Order ID is invalid.");
}

The SDK documentation specifically emphasizes validation of tool arguments before calling the application function.

Tool Security

A model requesting:

deleteOrder

does not mean your application should execute it.

Use:

Authentication
Authorization
Input Validation
Tool Allowlist
Resource Ownership
Business Rules

The SDK handles the API interaction.

Your application controls the business operation.

Responses API with ResponsesClient

The current SDK also contains:

OpenAI.Responses.ResponsesClient

The official repository documents Responses support including:

streaming
reasoning
file search
web search

and other capabilities.

A basic example is:

#pragma warning disable OPENAI001

using OpenAI.Responses;

var client =
    new ResponsesClient(apiKey);

ResponseResult response =
    await client.CreateResponseAsync(
        model,
        "Explain dependency injection in ASP.NET Core.");

Console.WriteLine(
    response.GetOutputText());

#pragma warning restore OPENAI001

The current .NET SDK marks some Responses APIs experimental, which is why the current official examples use the OPENAI001 suppression.

Why ResponsesClient Is Important

The SDK exposes both:

ChatClient

and:

ResponsesClient

The distinction is:

ChatClient
    |
    +-- Chat Completions API

ResponsesClient
    |
    +-- Responses API

For new application architecture, it is useful to understand both rather than treating them as interchangeable client classes.

Responses Streaming

The official SDK documents streaming for Responses as well.

Conceptually:

ResponsesClient
      |
      v
CreateResponseStreamingAsync
      |
      v
StreamingResponseUpdate

This is useful for interactive applications and agent-style workflows.

Responses Reasoning

The current 2.14.0 SDK includes additional reasoning-related capabilities, including expanded reasoning-effort values and control over how reasoning context is preserved across turns. The 2.14.0 changelog documents ResponseReasoningContext with Auto, CurrentTurn, and AllTurns, plus a corresponding property on ResponseReasoningOptions.

This is an example of why pinning and reviewing SDK versions matters: the SDK's capabilities evolve quickly.

Custom Tools in Responses

The current 2.14.0 release also added support for Custom Tools in OpenAI.Responses.

Custom tools allow arbitrary string input instead of requiring the model to produce JSON arguments matching a function schema. The 2.14.0 changelog documents CustomTool, custom-tool call/output items, and CustomToolTextFormat and CustomToolGrammarFormat.

Conceptually:

Traditional Function Tool
        |
        v
JSON Arguments

Custom Tool
        |
        v
Arbitrary String / Grammar

This can be useful when the desired tool input is not naturally represented as an ordinary JSON object.

OpenAI 2.14.0 New Features

The current SDK release also added several capabilities.

Audio streaming

AudioClient gained streaming protocol methods including:

GenerateSpeechStreamingAsync
TranscribeAudioStreamingAsync

in version 2.14.0.

Image streaming

The release added streaming protocol methods for image generation and image editing.

Responses custom tools

Custom tools were added to Responses.

Cache-write usage information

Responses now expose the number of input tokens newly written to the cache through usage details.

OpenTelemetry GenAI conventions

Chat gained opt-in support for newer experimental OpenTelemetry GenAI semantic conventions.

These features show how the official SDK is moving beyond basic chat completion into a broader AI application platform.

Embeddings with EmbeddingClient

The SDK provides:

OpenAI.Embeddings.EmbeddingClient

for text embeddings.

The architecture is:

Text
 |
 v
EmbeddingClient
 |
 v
Embedding Vector
 |
 v
Vector Store

A simple application may use embeddings for:

semantic search
RAG
similarity
document indexing
recommendations
classification

The official SDK lists EmbeddingClient as part of its feature-specific client organization.

Embedding Architecture

Document
    |
    v
Chunk
    |
    v
EmbeddingClient
    |
    v
Vector
    |
    v
Vector Database

Later in the roadmap, embeddings and vector databases will be covered in much greater detail.

Image Generation with ImageClient

The SDK includes:

OpenAI.Images.ImageClient

which is the client for image-related operations.

Architecture:

Prompt
 |
 v
ImageClient
 |
 v
OpenAI
 |
 v
Image Result

The current 2.14.0 SDK also added streaming protocol methods for image generation and image editing.

Audio with AudioClient

The SDK includes:

OpenAI.Audio.AudioClient

for audio functionality.

Typical uses include:

speech generation
transcription
audio processing
voice applications

The 2.14.0 release adds streaming operations for speech generation and transcription.

Files with OpenAIFileClient

The SDK includes:

OpenAI.Files.OpenAIFileClient

for file operations.

File-based workflows can support:

document processing
retrieval
file search
AI knowledge bases

Vector Stores

The SDK also exposes:

OpenAI.VectorStores.VectorStoreClient

which becomes useful for retrieval-based architectures.

Later topics in this series will combine:

EmbeddingClient
+
VectorStoreClient
+
ResponsesClient

to build RAG applications.

Moderation

The SDK provides:

OpenAI.Moderations.ModerationClient

for moderation operations.

A conceptual application pipeline is:

User Input
    |
    v
Moderation
    |
    v
AI Request
    |
    v
Response
    |
    v
Application

The exact policy depends on the application and its safety requirements.

Realtime

The SDK also includes:

OpenAI.Realtime.RealtimeClient

for realtime functionality.

This becomes relevant for:

voice applications
realtime assistants
audio streaming
low-latency interactions

The 2.14.0 release also expands extensibility across realtime client commands, server updates, items, and tools.

OpenAI SDK and Microsoft.Extensions.AI

A major .NET architecture option is adapting the OpenAI SDK to:

IChatClient

Microsoft's Microsoft.Extensions.AI.OpenAI integration exposes:

AsIChatClient(ChatClient)

and also an overload for adapting ResponsesClient to IChatClient.

Example:

using Microsoft.Extensions.AI;
using OpenAI;

IChatClient chatClient =
    new OpenAIClient(apiKey)
        .GetChatClient(model)
        .AsIChatClient();

This gives:

OpenAI SDK
    |
    v
ChatClient
    |
    v
AsIChatClient()
    |
    v
IChatClient

Why Use IChatClient?

Direct SDK:

Application
    |
    v
ChatClient
    |
    v
OpenAI

Provider-neutral:

Application
    |
    v
IChatClient
    |
    v
OpenAI

The second architecture allows the application layer to work with other providers later.

This is especially useful for:

multi-provider applications
local AI
Azure OpenAI
testing
AI abstraction layers

ResponsesClient as IChatClient

Microsoft's current OpenAI integration also provides:

AsIChatClient(
    ResponsesClient,
    defaultModelId)

for adapting Responses to IChatClient. The overload is currently marked experimental.

This means an application can use:

ResponsesClient
      |
      v
IChatClient

rather than writing a separate abstraction around Responses.

IEmbeddingGenerator

Microsoft's OpenAI extension also provides an adapter from:

EmbeddingClient

to:

IEmbeddingGenerator<TInput,TEmbedding>

The current extension API documents AsIEmbeddingGenerator.

This becomes particularly useful in later RAG and vector-search topics.

Other Microsoft AI Adapters

The current Microsoft OpenAI integration also exposes adapters for:

ImageClient
AudioClient
OpenAIFileClient

into abstractions such as:

IImageGenerator
ISpeechToTextClient
ITextToSpeechClient
IHostedFileClient

The current extension catalog lists these adapters.

This means an application can build around Microsoft abstractions while using the official OpenAI SDK underneath.

Choosing Direct SDK vs IChatClient

A useful architecture decision is:

Need provider-specific feature?
        |
        v
Official OpenAI SDK

or:

Need provider portability?
        |
        v
Microsoft.Extensions.AI

Both approaches can coexist.

For example:

Application
    |
    +---- IChatClient
    |        |
    |        v
    |     OpenAI
    |
    +---- OpenAI-specific service
             |
             v
        ResponsesClient

ASP.NET Core Application

Create a Web API:

dotnet new webapi -n OpenAISdkApi
cd OpenAISdkApi
dotnet add package OpenAI --version 2.14.0

Create configuration:

{
  "OpenAI": {
    "Model": "your-model-name"
  }
}

Keep the API key in User Secrets:

dotnet user-secrets init
dotnet user-secrets set "OpenAI:ApiKey" "your-api-key"

Registering ChatClient

using OpenAI.Chat;

builder.Services.AddSingleton(
    _ =>
    {
        var apiKey =
            builder.Configuration[
                "OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is missing.");

        var model =
            builder.Configuration[
                "OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is missing.");

        return new ChatClient(
            model,
            apiKey);
    });

The official SDK documents singleton registration as safe because its clients are thread-safe.

AI Service

using OpenAI.Chat;

public sealed class AIService
{
    private readonly ChatClient _client;

    public AIService(
        ChatClient client)
    {
        _client = client;
    }

    public async Task<string> AskAsync(
        string question,
        CancellationToken cancellationToken = default)
    {
        ChatCompletion completion =
            await _client.CompleteChatAsync(
                question,
                cancellationToken);

        foreach (
            ChatMessageContentPart content
            in completion.Content)
        {
            if (!string.IsNullOrWhiteSpace(
                    content.Text))
            {
                return content.Text;
            }
        }

        return string.Empty;
    }
}

Register:

builder.Services.AddScoped<AIService>();

Minimal API

app.MapPost(
    "/api/ai",
    async (
        AIRequest request,
        AIService service,
        CancellationToken cancellationToken) =>
    {
        var answer =
            await service.AskAsync(
                request.Message,
                cancellationToken);

        return Results.Ok(
            new
            {
                answer
            });
    });

Request:

public sealed class AIRequest
{
    public required string Message { get; init; }
}

Architecture:

POST /api/ai
      |
      v
AIRequest
      |
      v
AIService
      |
      v
ChatClient
      |
      v
OpenAI

Official SDK Dependency Injection Configuration

The current OpenAI .NET repository also documents an IHostApplicationBuilder extension such as:

builder.AddChatClient("Clients:ChatClient");

for binding client settings from configuration and registering the client. The repository contains a dedicated ASP.NET Core dependency-injection example.

This can be useful when you want configuration-driven client construction instead of manually creating the client in Program.cs.

Configuration-Driven Client

A conceptual configuration structure is:

{
  "Clients": {
    "ChatClient": {
      "Model": "your-model-name",
      "Credential": {
        "Key": "..."
      }
    }
  }
}

The official ASP.NET Core sample similarly uses a named configuration section and client settings.

Because the exact configuration surface can evolve, use the version-matched SDK sample when adopting this pattern in production.

Custom Endpoint

The SDK supports custom base URLs.

The official README demonstrates supplying an ApiKeyCredential and OpenAIClientOptions.Endpoint.

Example:

using OpenAI;
using System.ClientModel;

var client =
    new ChatClient(
        model: "configured-model",
        credential:
            new ApiKeyCredential(apiKey),
        options:
            new OpenAIClientOptions
            {
                Endpoint =
                    new Uri(
                        "https://your-endpoint/")
            });

This can be useful for:

OpenAI-compatible services
proxies
custom gateways
special deployments

The target endpoint must implement the API surface expected by the SDK.

SDK and OpenAI-Compatible APIs

An important advantage of the SDK's configurable endpoint is that your C# application can sometimes communicate with an OpenAI-compatible service through the same client abstractions.

Architecture:

Application
     |
     v
OpenAI SDK
     |
     v
Custom Endpoint
     |
     v
OpenAI-Compatible Service

Do not assume complete compatibility merely because an endpoint advertises an OpenAI-like API. Test the specific operations your application uses.

Async API

The official SDK provides asynchronous counterparts for client methods.

For example:

CompleteChat

has:

CompleteChatAsync

The current README explicitly documents this pattern.

For ASP.NET Core:

await client.CompleteChatAsync(...);

should generally be preferred.

CancellationToken

Pass cancellation tokens through your service layers:

var completion =
    await client.CompleteChatAsync(
        messages,
        cancellationToken);

Architecture:

HTTP Request
    |
    v
CancellationToken
    |
    v
AI Service
    |
    v
OpenAI SDK
    |
    v
OpenAI

When the user cancels a request, your application can stop work instead of continuing unnecessarily.

Chat Tool Workflow

A complete tool workflow looks like:

User
 |
 v
ChatClient
 |
 v
OpenAI
 |
 +---- Stop ------> Final Answer
 |
 +---- ToolCalls -> Application
                      |
                      v
                 Validate Arguments
                      |
                      v
                 Authorize Tool
                      |
                      v
                 Execute Tool
                      |
                      v
                 ToolChatMessage
                      |
                      v
                   ChatClient
                      |
                      v
                    OpenAI

The SDK's official tool-calling example uses this loop.

Structured Output Workflow

A typed application can define:

public sealed class TicketAnalysis
{
    public string? Category { get; set; }

    public string? Priority { get; set; }

    public string? Summary { get; set; }
}

Then define JSON Schema:

var options =
    new ChatCompletionOptions
    {
        ResponseFormat =
            ChatResponseFormat.CreateJsonSchemaFormat(
                "ticket_analysis",
                BinaryData.FromString(
                    """
                    {
                      "type": "object",
                      "properties": {
                        "category": {
                          "type": "string"
                        },
                        "priority": {
                          "type": "string"
                        },
                        "summary": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "category",
                        "priority",
                        "summary"
                      ],
                      "additionalProperties": false
                    }
                    """),
                jsonSchemaIsStrict: true)
    };

This uses the direct OpenAI SDK's Chat Completions structured-output API.

SDK Structured Output vs Microsoft.Extensions.AI

Direct SDK:

ChatCompletionOptions
    |
    v
ChatResponseFormat.CreateJsonSchemaFormat
    |
    v
ChatCompletion

Provider-neutral abstraction:

IChatClient
    |
    v
GetResponseAsync<T>
    |
    v
ChatResponse<T>

The latter is often cleaner for a provider-neutral application.

Prompt Templates with the SDK

The prompt-template layer from the previous article works directly with the SDK.

Template:

Summarize the following document for {{audience}}.

Document:
{{document}}

Render:

var prompt =
    renderer.Render(
        template,
        new Dictionary<string, string>
        {
            ["audience"] =
                "senior .NET developers",
            ["document"] =
                document
        });

Then:

ChatCompletion completion =
    await chatClient.CompleteChatAsync(
        prompt);

The SDK should not own your prompt-management system.

Your application should.

System Prompts with the SDK

Use:

List<ChatMessage> messages =
[
    new SystemChatMessage(
        systemPrompt),

    new UserChatMessage(
        userPrompt)
];

Then:

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages);

This gives you the separation:

System Prompt
    =
behavior

User Prompt
    =
request

OpenAI SDK
    =
transport / model interaction

Context Management with the SDK

A production chat application should not blindly send unlimited history.

Architecture:

Conversation History
        |
        v
Context Manager
        |
        +-- reduction
        +-- summary
        +-- relevance
        +-- token budget
        |
        v
ChatClient

This connects directly to the earlier topic, AI Context Management with .NET.

SDK and JSON Responses

You can use the SDK with:

ChatResponseFormat

for JSON.

For example:

ChatCompletionOptions options =
    new()
    {
        ResponseFormat =
            ChatResponseFormat.JsonObject
    };

Use the exact response-format member supported by the SDK version you install; for strict contracts, prefer CreateJsonSchemaFormat.

Then:

ChatCompletion completion =
    await client.CompleteChatAsync(
        prompt,
        options);

Parse with:

using JsonDocument json =
    JsonDocument.Parse(
        completion.Content[0].Text);

SDK and System.Text.Json

The OpenAI SDK does not eliminate the need to understand JSON.

You may still use:

JsonSerializer
JsonDocument
JsonElement
JsonNode

for:

tool arguments
structured-output processing
dynamic API payloads
custom schemas
logging
testing

The SDK gives you API-level C# types.

System.Text.Json handles general-purpose application JSON.

SDK and Embeddings

Create:

EmbeddingClient

for embedding operations.

A typical service might be:

public sealed class EmbeddingService
{
    private readonly EmbeddingClient _client;

    public EmbeddingService(
        EmbeddingClient client)
    {
        _client = client;
    }
}

Then:

Text
 |
 v
EmbeddingClient
 |
 v
Vector
 |
 v
Vector Store

The official SDK documents EmbeddingClient as the client for embeddings.

SDK and Images

Use:

ImageClient

for image-related operations.

Architecture:

Prompt
 |
 v
ImageClient
 |
 v
OpenAI
 |
 v
Image

The SDK also added streaming methods for image generation and image editing in version 2.14.0.

SDK and Audio

Use:

AudioClient

for audio functionality.

Version 2.14.0 adds:

GenerateSpeechStreamingAsync
TranscribeAudioStreamingAsync

to the audio client.

This allows:

Audio input
    |
    v
AudioClient
    |
    v
Transcription

Text input
    |
    v
AudioClient
    |
    v
Speech output

SDK and Realtime

For realtime scenarios:

RealtimeClient

is available.

Architecture:

Client Application
       |
       v
RealtimeClient
       |
       v
OpenAI Realtime
       |
       +-- audio
       +-- events
       +-- tools
       +-- responses

Version 2.14.0 adds additional extensibility around realtime command/update/item/tool type hierarchies.

SDK and Files

Use:

OpenAIFileClient

for file operations.

This becomes useful when building:

document assistants
file search
knowledge systems
RAG
document extraction

SDK and Vector Stores

Use:

VectorStoreClient

for vector-store related operations.

A later RAG application might look like:

Documents
   |
   v
OpenAIFileClient
   |
   v
Vector Store
   |
   v
Retrieval
   |
   v
ResponsesClient

SDK and Moderation

Use:

ModerationClient

for moderation workflows.

A complete service might have:

Input
 |
 v
Moderation
 |
 v
Prompt
 |
 v
OpenAI
 |
 v
Output

The exact policy should depend on the application.

Error Handling

Do not wrap every OpenAI call in:

try
{
    ...
}
catch
{
    return "Something went wrong.";
}

That hides useful diagnostic information.

A better structure is:

OpenAI SDK
    |
    v
Known SDK Exception
    |
    v
Application Error Mapping
    |
    +-- client error
    +-- rate limit
    +-- transient error
    +-- cancellation
    +-- model/configuration error

Then return appropriate API responses.

Cancellation

Always pass the cancellation token:

await client.CompleteChatAsync(
    messages,
    cancellationToken);

This is especially important in ASP.NET Core.

Retry Strategy

The official SDK documents automatic retry capabilities as an advanced scenario.

Still, application-level retry behavior should be carefully designed.

Use retries mainly for transient conditions.

Avoid blindly retrying:

invalid request
authentication failure
invalid tool arguments
schema configuration errors

SDK Testing

The official SDK documents mocking clients as an advanced testing scenario.

For application code, an additional abstraction can make testing even simpler:

public interface IAIChatService
{
    Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

Production:

IAIChatService
     |
     v
OpenAI ChatClient

Test:

IAIChatService
     |
     v
Fake AI Service

This keeps unit tests independent of the network.

Integration Testing

Use real OpenAI calls for selected integration tests.

Test:

SDK authentication
model availability
structured output
tool calling
streaming
expected response shape

Do not make every unit test call a remote AI model.

Mocking at the SDK Boundary

Your application can depend on an interface:

public interface IAIClient
{
    Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

Implementation:

public sealed class OpenAIClientAdapter
    : IAIClient
{
    private readonly ChatClient _client;

    public OpenAIClientAdapter(
        ChatClient client)
    {
        _client = client;
    }

    public async Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default)
    {
        var result =
            await _client.CompleteChatAsync(
                prompt,
                cancellationToken);

        return result.Content.Count == 0
            ? string.Empty
            : result.Content[0].Text;
    }
}

Test implementation:

public sealed class FakeAIClient
    : IAIClient
{
    public Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default)
    {
        return Task.FromResult(
            "Test response");
    }
}

Observability

The official SDK includes observability guidance, and the current 2.14.0 release added opt-in support for newer experimental OpenTelemetry GenAI semantic conventions in Chat.

A useful observability model is:

ASP.NET Trace
     |
     v
AI Operation
     |
     +-- Model
     +-- Prompt Version
     +-- Duration
     +-- Token Usage
     +-- Finish Reason
     +-- Error

SDK Telemetry

The 2.13.0 SDK introduced structured SDK platform metadata headers and an opt-out mechanism for SDK telemetry via:

OpenAI.DisableTelemetry

or:

OPENAI_DISABLE_TELEMETRY

when enabled, according to the changelog.

This is different from your application's AI observability.

Your own telemetry should still record the application-level information necessary to operate the service.

Request Correlation

A production application can maintain:

TraceId
CorrelationId
OpenAI Request ID
Prompt Version
Model

Example:

TraceId: 4F8C...
Operation: ResumeAnalysis
PromptVersion: v3
Model: configured-model
OpenAIRequestId: ...

This makes failures much easier to diagnose.

Client Lifetime

The official SDK states that OpenAI clients are thread-safe and can be safely registered as singletons.

Therefore:

Good

DI
 |
 v
Singleton ChatClient
 |
 +-- Request 1
 +-- Request 2
 +-- Request 3

rather than:

Every HTTP Request
 |
 +-- new ChatClient

The singleton approach also supports more efficient resource and HTTP connection reuse according to the official documentation.

Multiple Models

One application might use different models for different operations:

Chat Service
    |
    +-- model A

Summarization Service
    |
    +-- model B

Embedding Service
    |
    +-- embedding model

Audio Service
    |
    +-- audio model

The parent OpenAIClient is useful for creating multiple feature clients that share implementation details.

Model Routing

A centralized model configuration can look like:

{
  "OpenAI": {
    "ChatModel": "your-chat-model",
    "EmbeddingModel": "your-embedding-model",
    "ImageModel": "your-image-model",
    "AudioModel": "your-audio-model"
  }
}

Then application services select the appropriate client.

Do not assume one model is optimal for every operation.

Cost Management

SDK integration should include usage tracking.

Track:

operation
model
prompt version
response schema
input usage
output usage
duration
success/failure

Then:

Resume Analysis
    |
    +-- requests
    +-- tokens
    +-- failures
    +-- average latency

Customer Support
    |
    +-- requests
    +-- tokens
    +-- failures

This allows better cost analysis.

Prompt Version Tracking

Because the prompt is application behavior, record:

Prompt Name
Prompt Version
Model
SDK Version
Schema Version

A production request can then be reproduced more reliably.

SDK Version Tracking

Because the OpenAI SDK changes over time, record its version as part of application build metadata.

For this article:

OpenAI SDK: 2.14.0

Version 2.14.0 was released on September 15, 2026.

Do not silently mix code written for one SDK version with assumptions from another.

Experimental APIs

The official repository states that some client APIs are marked [Experimental] while their .NET design is still evolving, and using them requires explicit compiler-warning acknowledgement.

This matters especially for newer capabilities.

For example:

#pragma warning disable OPENAI001

// Experimental API usage.

#pragma warning restore OPENAI001

Do not suppress the warning globally without understanding which API is experimental.

A better strategy is:

Experimental Feature
       |
       v
Isolate in one service
       |
       v
Version carefully
       |
       v
Test during upgrades

SDK Upgrade Strategy

When upgrading:

dotnet add package OpenAI --version 2.14.0

then:

dotnet restore
dotnet build
dotnet test

Review:

CHANGELOG
breaking changes
experimental APIs
API behavior changes
serialization changes
new capabilities

The official changelog is the primary source for release-level changes.

OpenAI SDK with .NET

Why SDK Version Matters

Imagine your application uses:

OpenAI SDK 2.10

and later upgrades to:

OpenAI SDK 2.14

Between those versions, the SDK added capabilities such as audio streaming, image streaming, Responses custom tools, expanded reasoning controls, additional usage information, and OpenTelemetry improvements.

This shows why SDK versions should be treated as part of your application's technical contract.

SDK and Clean Architecture

A strong enterprise structure is:

Presentation
     |
     v
Application
     |
     +-- IAIChatService
     +-- IEmbeddingService
     +-- IImageService
     +-- IAudioService
     |
     v
Infrastructure
     |
     +-- OpenAIChatService
     +-- OpenAIEmbeddingService
     +-- OpenAIImageService
     +-- OpenAIAudioService
     |
     v
OpenAI SDK

The application layer should not need to know:

ChatClient
ResponsesClient
OpenAI API endpoints
API keys

unless the architecture deliberately chooses to expose those details.

AI Service Interface

public interface IAIChatService
{
    Task<string> GenerateAsync(
        IReadOnlyList<ChatMessage> messages,
        CancellationToken cancellationToken = default);
}

Implementation:

public sealed class OpenAIChatService
    : IAIChatService
{
    private readonly ChatClient _client;

    public OpenAIChatService(
        ChatClient client)
    {
        _client = client;
    }

    public async Task<string> GenerateAsync(
        IReadOnlyList<ChatMessage> messages,
        CancellationToken cancellationToken = default)
    {
        ChatCompletion result =
            await _client.CompleteChatAsync(
                messages,
                cancellationToken);

        foreach (
            ChatMessageContentPart part
            in result.Content)
        {
            if (!string.IsNullOrWhiteSpace(
                    part.Text))
            {
                return part.Text;
            }
        }

        return string.Empty;
    }
}

Now:

Application
     |
     v
IAIChatService
     |
     v
OpenAIChatService
     |
     v
ChatClient

OpenAI SDK and Prompt Management

The earlier prompt-management topics should remain independent.

Use:

PromptRepository
     |
     v
PromptTemplate
     |
     v
PromptRenderer
     |
     v
OpenAI SDK

The SDK's responsibility is:

model communication

Your application's prompt-management layer owns:

templates
versions
variables
evaluation
approval
rollback

OpenAI SDK and Context Management

Similarly:

Conversation
     |
     v
Context Manager
     |
     v
Selected Messages
     |
     v
ChatClient

The SDK should not decide your business-specific context policy.

Production Architecture

A complete production architecture can look like:

                         ASP.NET Core
                              |
                              v
                     Application Services
                              |
        +---------------------+---------------------+
        |                     |                     |
        v                     v                     v
 Prompt Management      Context Management    Business Services
        |                     |                     |
        +---------------------+---------------------+
                              |
                              v
                       AI Abstractions
                              |
             +----------------+----------------+
             |                                 |
             v                                 v
       Microsoft.Extensions.AI          OpenAI SDK
             |                                 |
             +----------------+----------------+
                              |
                              v
                         OpenAI API
                              |
          +-------------------+-------------------+
          |                   |                   |
          v                   v                   v
       Responses           Chat               Other APIs
          |                   |                   |
          +-------------------+-------------------+
                              |
                              v
                       Validation / Mapping
                              |
             +----------------+----------------+
             |                |                |
             v                v                v
         SQL Server         Queue             API

Practical Project: Build an OpenAI SDK Chat API

Create:

ASP.NET Core Web API

Install:

dotnet add package OpenAI --version 2.14.0

Implement:

POST /api/chat

Request:

{
  "message": "Explain dependency injection."
}

Architecture:

POST /api/chat
       |
       v
ChatRequest
       |
       v
ChatService
       |
       v
ChatClient
       |
       v
OpenAI
       |
       v
ChatCompletion
       |
       v
API Response

Add:

system prompt
prompt template
conversation history
streaming
logging
cancellation
structured output

Practical Project: Build an AI Ticket Router

Input:

{
  "message": "My credit card was charged twice."
}

AI output:

{
  "category": "Billing",
  "priority": "High",
  "summary": "Customer reports a duplicate charge."
}

Pipeline:

User Ticket
    |
    v
Prompt Template
    |
    v
ChatClient
    |
    v
Structured Output
    |
    v
C# DTO
    |
    v
Validation
    |
    v
Routing
    |
    +-- Billing Queue
    +-- Technical Queue
    +-- Delivery Queue
    +-- Account Queue

Practical Project: Build an AI Tool-Calling Assistant

Build tools:

getOrder
searchProducts
getCustomer

Application architecture:

                 ChatClient
                     |
                     v
                   OpenAI
                     |
             +-------+-------+
             |               |
             v               v
         Final Answer      Tool Call
                              |
                              v
                       Tool Dispatcher
                              |
                 +------------+------------+
                 |            |            |
                 v            v            v
             getOrder    getCustomer   searchProducts
                 |
                 v
             Validation
                 |
                 v
             Authorization
                 |
                 v
             Execution

The SDK handles the model/tool-message protocol.

Your application owns tool security and business logic.

Practical Project: Build a Structured Resume Analyzer

Define:

public sealed class ResumeAnalysis
{
    public string? CandidateName { get; set; }

    public string? CurrentRole { get; set; }

    public int? YearsOfExperience { get; set; }

    public List<string> Skills { get; set; } = [];

    public List<string> Companies { get; set; } = [];
}

Then use the OpenAI SDK with a JSON Schema response format.

After deserialization:

AI Result
    |
    v
ResumeAnalysis
    |
    v
Validation
    |
    v
Database / Search Index

Do not write the AI result directly into your production database without validation.

Practical Project: Build a Provider-Neutral AI Service

Create:

public interface IAIChatService
{
    Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

Implementation 1:

OpenAI

Implementation 2 later:

Azure OpenAI

Implementation 3:

Ollama

Architecture:

               IAIChatService
                      |
        +-------------+-------------+
        |             |             |
        v             v             v
     OpenAI      Azure OpenAI    Ollama

This is where the Microsoft IChatClient abstraction becomes particularly valuable.

Common Mistakes

Installing an outdated SDK version

Always verify the current NuGet version.

For this article, the verified current version is:

OpenAI 2.14.0

released September 15, 2026.

Copying old SDK tutorials

OpenAI's .NET SDK evolves quickly.

A tutorial written against an older release may contain obsolete APIs.

Hard-coding API keys

Use secure configuration.

Creating SDK clients for every request

Use dependency injection and appropriate singleton lifetime. The SDK documents its clients as thread-safe.

Putting OpenAI code inside controllers

Use an application service.

Ignoring cancellation

Pass CancellationToken.

Executing tool calls without validation

Tool arguments come from the model and must be validated. The official SDK documentation explicitly warns about hallucinated tool arguments.

Treating structured output as automatically correct

Schema compliance does not guarantee factual correctness.

Mixing prompt management with SDK infrastructure

Keep prompt templates and versions in your application layer.

Globally suppressing experimental warnings

Isolate experimental APIs and track the SDK version.

Using one model for every operation

Chat, embeddings, audio, image, and specialized tasks may require different models.

Returning SDK objects directly from public APIs

Map them to your own DTOs.

Frequently Asked Questions

What is the official OpenAI SDK package for .NET?

The official NuGet package is:

OpenAI

The current verified version for this article is 2.14.0.

What namespace contains ChatClient?

OpenAI.Chat

The official SDK namespace mapping documents ChatClient under OpenAI.Chat.

What namespace contains ResponsesClient?

OpenAI.Responses

What is OpenAIClient?

It is the parent SDK client that can create feature-specific clients while sharing implementation details.

Can OpenAI clients be registered as singletons?

Yes. The official SDK states that its clients are thread-safe and can safely be registered as singletons in ASP.NET Core.

Can I use streaming?

Yes. ChatClient provides CompleteChatStreaming and CompleteChatStreamingAsync, and the SDK also documents Responses streaming.

Can I use function calling?

Yes. ChatClient supports tools and function calling, including ChatTool.CreateFunctionTool.

Can I use structured outputs?

Yes. The official SDK supports JSON Schema-based structured outputs through ChatResponseFormat.CreateJsonSchemaFormat.

Can I use IChatClient with OpenAI?

Yes. Microsoft provides AsIChatClient adapters for OpenAI ChatClient and ResponsesClient.

Can EmbeddingClient be adapted to Microsoft.Extensions.AI?

Yes. The current OpenAI extension catalog includes AsIEmbeddingGenerator.

Can ImageClient be adapted to Microsoft.Extensions.AI?

Yes. The current extension catalog includes AsIImageGenerator.

Can AudioClient be adapted to Microsoft.Extensions.AI?

Yes. Current OpenAI extension APIs include adapters for speech-to-text and text-to-speech clients.

Can I use a custom endpoint?

Yes. The official SDK supports custom endpoints through OpenAIClientOptions.Endpoint.

Are some SDK APIs experimental?

Yes. The official README states that some client APIs are marked [Experimental] while their .NET design continues to evolve.

Why does OPENAI001 appear?

It is an experimental API diagnostic used by some SDK surfaces. Applications using those APIs must explicitly acknowledge the diagnostic according to the SDK's current design.

What was added in OpenAI SDK 2.14.0?

Version 2.14.0 added audio and image streaming protocol methods, Responses custom tools, cache-write token usage information, expanded reasoning options, and improvements around OpenTelemetry GenAI conventions and realtime extensibility.

Should application services directly depend on ChatClient?

They can, but IChatClient or your own application-level AI interface is often preferable when provider portability matters.

Should I use ChatClient or ResponsesClient?

Use the API/client surface that matches your application's needs. ChatClient targets Chat Completions, while ResponsesClient targets the Responses API.

Can I use the SDK from a worker service?

Yes. The same dependency-injection and asynchronous patterns work in BackgroundService and hosted services.

Can I use the SDK from Blazor?

Yes, but keep the API key on the server for browser-based Blazor applications. Client applications should call your protected backend rather than exposing the OpenAI secret.

Can I use the SDK from .NET MAUI?

Yes, but do not embed a long-lived OpenAI API key in the shipped mobile application. Use a controlled backend when secret protection is required.

Can I use the SDK for RAG?

Yes. The SDK exposes embeddings, files, vector-store functionality, Responses file search, and other building blocks useful for RAG.

Does the SDK replace prompt management?

No.

The SDK handles API communication.

Your application should manage:

prompts
templates
versions
context
evaluation
business rules

Interview Questions

What is the OpenAI .NET SDK?

It is the official .NET library that provides strongly typed access to the OpenAI REST API.

What NuGet package is used?

OpenAI

What is the current verified package version?

2.14.0

released September 15, 2026.

What is ChatClient?

The SDK client for Chat Completions.

What is ResponsesClient?

The SDK client for the Responses API.

What is OpenAIClient?

A parent convenience client used to create feature-specific OpenAI clients.

What is the difference between a direct feature client and OpenAIClient?

A feature client such as ChatClient directly performs one category of operations.

OpenAIClient can create multiple feature clients and share underlying implementation details.

Why register ChatClient as a singleton?

The official SDK states that its clients are thread-safe and can be safely registered as singleton services in ASP.NET Core.

How do you stream Chat Completions?

Use:

CompleteChatStreamingAsync(...)

and enumerate the returned async collection.

How do you define a function tool?

Use:

ChatTool.CreateFunctionTool(...)

and add the tool to ChatCompletionOptions.Tools.

How do you know the model requested a tool?

Check:

completion.FinishReason ==
    ChatFinishReason.ToolCalls

The official SDK documents this workflow.

Why validate tool arguments?

Because the model can produce incorrect or hallucinated arguments. The SDK documentation explicitly warns about this.

How do you request structured output?

Use:

ChatResponseFormat.CreateJsonSchemaFormat(...)

through ChatCompletionOptions.ResponseFormat.

Why use IChatClient?

It provides a provider-neutral abstraction that can reduce application coupling to OpenAI.

How do you convert ChatClient to IChatClient?

Use:

chatClient.AsIChatClient()

from the Microsoft OpenAI integration.

Can ResponsesClient also be adapted?

Yes. Microsoft provides an AsIChatClient(ResponsesClient, String) overload. The current overload is marked experimental.

What is EmbeddingClient?

The OpenAI SDK client for embeddings.

What is ImageClient?

The OpenAI SDK client for image operations.

What is AudioClient?

The OpenAI SDK client for audio functionality.

What is VectorStoreClient?

The SDK client for vector-store operations.

What is RealtimeClient?

The SDK client for realtime operations.

Why isolate experimental APIs?

Because experimental APIs can change as the .NET SDK evolves. The official SDK explicitly identifies such APIs with [Experimental].

What should be tracked when upgrading the SDK?

Track:

SDK version
API surface
model configuration
prompt versions
schema versions
experimental APIs
regression tests

Best Practices

Pin the SDK version in production

For example:

<PackageReference
    Include="OpenAI"
    Version="2.14.0" />

Then upgrade deliberately.

Read the changelog before upgrading

The official changelog is the best source for version-specific changes.

Use singleton SDK clients

The official library states that its clients are thread-safe.

Keep API keys out of source control

Use secure configuration.

Use async methods

For web applications and workers:

await CompleteChatAsync(...)

Pass cancellation tokens

This avoids unnecessary work after request cancellation.

Put SDK calls behind an application service

Keep provider code out of controllers.

Use IChatClient when provider portability matters

Keep direct SDK clients for OpenAI-specific functionality where needed.

Validate tool arguments

Never blindly execute model-generated tool parameters.

Validate structured output

A correct JSON shape does not guarantee correct business data.

Keep prompts separate

Prompt management belongs in a dedicated application layer.

Track usage and diagnostics

Record the metadata needed for operations and troubleshooting.

Isolate experimental APIs

Do not spread experimental APIs throughout your entire codebase.

Keep model IDs configurable

Model availability and behavior change over time.

Test before SDK upgrades

Use:

unit tests
integration tests
AI evaluations
tool tests
structured-output tests
streaming tests

Complete OpenAI SDK Chat Service

A practical production-style service:

using OpenAI.Chat;

public interface IAIChatService
{
    Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

public sealed class OpenAIChatService
    : IAIChatService
{
    private readonly ChatClient _client;
    private readonly ILogger<OpenAIChatService> _logger;

    public OpenAIChatService(
        ChatClient client,
        ILogger<OpenAIChatService> logger)
    {
        _client = client;
        _logger = logger;
    }

    public async Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(prompt))
        {
            throw new ArgumentException(
                "Prompt is required.",
                nameof(prompt));
        }

        ChatCompletion completion =
            await _client.CompleteChatAsync(
                prompt,
                cancellationToken);

        _logger.LogInformation(
            "OpenAI chat request completed with finish reason {FinishReason}.",
            completion.FinishReason);

        foreach (
            ChatMessageContentPart part
            in completion.Content)
        {
            if (!string.IsNullOrWhiteSpace(part.Text))
            {
                return part.Text;
            }
        }

        return string.Empty;
    }
}

Register:

builder.Services.AddSingleton(
    _ =>
    {
        var apiKey =
            builder.Configuration[
                "OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is missing.");

        var model =
            builder.Configuration[
                "OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is missing.");

        return new ChatClient(
            model,
            apiKey);
    });

builder.Services.AddScoped<
    IAIChatService,
    OpenAIChatService>();

Complete ASP.NET Core Endpoint

app.MapPost(
    "/api/chat",
    async (
        ChatRequest request,
        IAIChatService service,
        CancellationToken cancellationToken) =>
    {
        var response =
            await service.GenerateAsync(
                request.Message,
                cancellationToken);

        return Results.Ok(
            new
            {
                response
            });
    });

public sealed class ChatRequest
{
    public required string Message { get; init; }
}

The final architecture is:

HTTP Request
    |
    v
ChatRequest
    |
    v
IAIChatService
    |
    v
OpenAIChatService
    |
    v
ChatClient
    |
    v
OpenAI
    |
    v
ChatCompletion
    |
    v
API Response

Complete Provider-Neutral Architecture

For a larger application, use:

ASP.NET Core
       |
       v
AI Application Service
       |
       v
IChatClient
       |
       v
OpenAI Adapter
       |
       v
ChatClient
       |
       v
OpenAI API

Registration:

using Microsoft.Extensions.AI;
using OpenAI;

builder.Services.AddSingleton<IChatClient>(
    _ =>
    {
        var apiKey =
            builder.Configuration[
                "OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is missing.");

        var model =
            builder.Configuration[
                "OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is missing.");

        return new OpenAIClient(apiKey)
            .GetChatClient(model)
            .AsIChatClient();
    });

Microsoft currently documents exactly this type of ChatClient → IChatClient adapter.

Practical Exercise

Create a console application that:

1. Installs OpenAI 2.14.0.
2. Reads the API key from an environment variable.
3. Reads the model from configuration.
4. Registers ChatClient.
5. Sends a system message.
6. Sends a user message.
7. Prints the response.
8. Supports cancellation.

Then extend it with:

streaming
conversation history
structured output
tool calling
logging

Advanced Exercise

Create an ASP.NET Core AI service with:

ChatClient
ResponsesClient
EmbeddingClient
ImageClient
AudioClient

Use:

dependency injection
configuration
singleton clients
application services
structured logging
error handling
cancellation

Then expose:

POST /api/chat
POST /api/summary
POST /api/ticket/analyze
POST /api/resume/analyze

Advanced Project: OpenAI SDK Gateway

Build:

                    Client Applications
                            |
                            v
                    ASP.NET Core API
                            |
                            v
                    AI Application Layer
                            |
        +-------------------+-------------------+
        |                   |                   |
        v                   v                   v
    Chat Service       Embedding Service    Media Service
        |                   |                   |
        v                   v                   v
    ChatClient         EmbeddingClient      Audio/Image
        |                   |                   |
        +-------------------+-------------------+
                            |
                            v
                         OpenAI

Add:

prompt management
tenant configuration
model routing
usage tracking
rate limits
logging
metrics
tool authorization
structured output
streaming
retry policies
evaluation

Learning Path

The OpenAI SDK path is:

1. Install OpenAI NuGet package
        |
        v
2. Configure API key
        |
        v
3. Learn OpenAIClient
        |
        v
4. Learn ChatClient
        |
        v
5. Learn ChatMessage
        |
        v
6. Learn ChatCompletion
        |
        v
7. Learn ChatCompletionOptions
        |
        v
8. Learn streaming
        |
        v
9. Learn tools
        |
        v
10. Learn structured output
        |
        v
11. Learn ResponsesClient
        |
        v
12. Learn embeddings
        |
        v
13. Learn image/audio clients
        |
        v
14. Learn realtime
        |
        v
15. Integrate with Microsoft.Extensions.AI
        |
        v
16. Build production AI services

Key Takeaways

The official OpenAI .NET SDK provides strongly typed C# clients over the OpenAI REST API. It is generated from OpenAI's OpenAPI specification in collaboration with Microsoft.

The current verified package version is:

OpenAI 2.14.0

released September 15, 2026.

The major clients include:

ChatClient
ResponsesClient
EmbeddingClient
ImageClient
AudioClient
OpenAIFileClient
VectorStoreClient
ModerationClient
RealtimeClient
OpenAIClient

For ordinary chat:

ChatClient client =
    new(model, apiKey);

var completion =
    await client.CompleteChatAsync(
        prompt);

For streaming:

await foreach (
    var update
    in client.CompleteChatStreamingAsync(
        prompt))
{
    ...
}

For tools:

ChatTool.CreateFunctionTool(...)

and:

ChatCompletionOptions.Tools

For structured output:

ChatResponseFormat.CreateJsonSchemaFormat(...)

For provider-neutral architecture:

chatClient.AsIChatClient()

The most useful production architecture is:

ASP.NET Core
      |
      v
Application Service
      |
      v
IChatClient / AI Interface
      |
      v
OpenAI SDK
      |
      +-- ChatClient
      +-- ResponsesClient
      +-- EmbeddingClient
      +-- AudioClient
      +-- ImageClient
      +-- Other Clients
      |
      v
OpenAI API

This allows the OpenAI SDK to remain an infrastructure component rather than becoming tightly coupled to every part of the application.

The next topic in the roadmap is OpenAI Chat Applications with ASP.NET Core where this SDK foundation can be turned into a complete web-based AI chat application with conversation state, APIs, dependency injection, streaming, prompt management, and production architecture.

Post a Comment

Previous Post Next Post