Migrate from OpenAI 1.11.0 to OpenAI 2.0.0-beta.1+
mainOpenAIAPI client to specialized clients for each API service. Additionally, models must now be explicitly specified during client instantiation rather than per-call.repository·main·Indexed 25 days ago
https://github.com/openai/openai-dotnetA high-level, type-safe wrapper for accessing the OpenAI REST API from .NET applications. The library provides feature-specific clients such as ChatClient and AudioClient, supports asynchronous streaming of completions, and includes tool and function calling capabilities. It is compatible with .NET Standard 2.0 and provides integration for ASP.NET Core via dependency injection and configuration-driven registration for clients like ResponsesClient.
OpenAIAPI client to specialized clients for each API service. Additionally, models must now be explicitly specified during client instantiation rather than per-call.To constrain chat completion content to a specific JSON schema, use the ChatResponseFormat.CreateJsonSchemaFormat method. This is supported on specific model snapshots like gpt-4o-mini, gpt-4o-mini-2024-07-18, and gpt-4o-2024-08-06.
ChatCompletionOptions, set the ResponseFormat property using ChatResponseFormat.CreateJsonSchemaFormat.jsonSchemaIsStrict: true to ensure the model adheres strictly to the provided schema.JsonDocument.ChatCompletionOptions options = new()
{
ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
jsonSchemaFormatName: "math_reasoning",
jsonSchema: BinaryData.FromBytes("""
{
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"explanation": { "type": "string" },
"output": { "type": "string" }
},
"required": ["explanation", "output"],
"additionalProperties": false
}
},
"final_answer": { "type": "string" }
},
"required": ["steps", "final_answer"],
"additionalProperties": false
}
"""u8.ToArray()),
jsonSchemaIsStrict: true)
};
ChatCompletion completion = client.CompleteChat(messages, options);
using JsonDocument structuredJson = JsonDocument.Parse(completion.Content[0].Text);The samples are standalone .cs files that can be run using dotnet run from the docs directory. This utilizes .NET 10's single-file application feature.
docs directory.dotnet run <path-to-file>.cd docs
dotnet run quickstart/responses/developer_quickstart.csThe samples require an OpenAI API key stored in the OPENAI_API_KEY environment variable.
bash/zsh:
export OPENAI_API_KEY="your-api-key-here"PowerShell:
$env:OPENAI_API_KEY = "your-api-key-here"Windows (PowerShell):
[Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "your-api-key-here", "User")Windows (Command Prompt):
setx OPENAI_API_KEY "your-api-key-here"bash/zsh:
echo 'export OPENAI_API_KEY="your-api-key-here"' >> ~/.bashrc
source ~/.bashrcexport OPENAI_API_KEY="your-api-key-here"To build an assistant capable of analyzing uploaded documents (RAG), follow these steps:
OpenAIClient to obtain an OpenAIFileClient (from OpenAI.Files) and an AssistantClient (from OpenAI.Assistants).AssistantClient is marked [Experimental]. You must suppress the OPENAI001 warning to use it.OpenAIFileClient.UploadFile with FileUploadPurpose.Assistants to make files available to the assistant.AssistantClient.CreateAssistant with AssistantCreationOptions. To enable RAG, include FileSearchToolDefinition in the Tools collection and configure ToolResources using a VectorStoreCreationHelper to index your uploaded files.AssistantClient.CreateThreadAndRun.AssistantClient.GetRun in a loop, checking threadRun.Status.IsTerminal to wait for completion.AssistantClient.GetMessages to fetch the conversation history, including assistant responses and any generated files (like images).// 1. Initialize Clients
OpenAIClient openAIClient = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
OpenAIFileClient fileClient = openAIClient.GetOpenAIFileClient();
AssistantClient assistantClient = openAIClient.GetAssistantClient();
// 2. Upload File
OpenAIFile salesFile = fileClient.UploadFile(
document,
"monthly_sales.json",
FileUploadPurpose.Assistants);
// 3. Create Assistant with RAG capabilities
AssistantCreationOptions assistantOptions = new()
{
Name = "Example: Contoso sales RAG",
Instructions = "You are an assistant that looks up sales data...",
Tools =
{
new FileSearchToolDefinition(),
new CodeInterpreterToolDefinition(),
},
ToolResources = new()
{
FileSearch = new()
{
NewVectorStores =
{
new VectorStoreCreationHelper([salesFile.Id]),
}
}
},
};
Assistant assistant = assistantClient.CreateAssistant("gpt-5.1", assistantOptions);
// 4. Create Thread and Run
ThreadCreationOptions threadOptions = new()
{
InitialMessages = { "How well did product 113045 sell in February? Graph its trend over time." }
};
ThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Id, threadOptions);
// 5. Poll for completion
do
{
Thread.Sleep(TimeSpan.FromSeconds(1));
threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id);
} while (!threadRun.Status.IsTerminal);
// 6. Get Messages
CollectionResult<ThreadMessage> messages = assistantClient.GetMessages(threadRun.ThreadId, new MessageCollectionOptions() { Order = MessageCollectionOrder.Ascending });The .cs files in the target framework subdirectories are auto-generated and should not be modified manually. If you change the public API surface (adding, removing, or renaming public types or members), you must regenerate these files by running the export script.
API listings are organized by target framework and namespace (e.g., api/net10.0/OpenAI.Chat.net10.0.cs).
./scripts/Export-Api.ps1The OpenAI .NET SDK supports distributed tracing and metrics using .NET tracing and metrics APIs, following OpenTelemetry Semantic Conventions for Generative AI systems.
Note: Instrumentation is currently experimental and in development. The volume and semantics of telemetry items may change.
To enable observability, you must perform two steps:
// Step 1: Enable the feature flag
AppContext.SetSwitch("OpenAI.Experimental.EnableOpenTelemetry", true);
// Step 2: Configure OpenTelemetry
builder.Services.AddOpenTelemetry()
.WithTracing(b =>
{
b.AddSource("OpenAI.*")
.AddOtlpExporter();
})
.WithMetrics(b =>
{
b.AddMeter("OpenAI.*")
.AddOtlpExporter();
});You can enable the model to call functions by defining ChatTool instances and passing them via ChatCompletionOptions.
ChatTool.CreateFunctionTool to describe your functions. You can provide a functionName, functionDescription, and a JSON schema for functionParameters using BinaryData.Tools property of a ChatCompletionOptions instance.CompleteChat, check the FinishReason of the resulting ChatCompletion. If it is ChatFinishReason.ToolCalls, you must:AssistantChatMessage (containing the tool calls) to your message history.ToolCalls.ToolChatMessage for each result to the message history.CompleteChat again with the updated history to get the final response.Note: Arguments returned by the model in toolCall.FunctionArguments are stringified JSON objects. You should parse and validate them before execution.
// 1. Define tools
ChatTool getCurrentLocationTool = ChatTool.CreateFunctionTool(
functionName: nameof(GetCurrentLocation),
functionDescription: "Get the user's current location"
);
ChatTool getCurrentWeatherTool = ChatTool.CreateFunctionTool(
functionName: nameof(GetCurrentWeather),
functionDescription: "Get the current weather in a given location",
functionParameters: BinaryData.FromBytes("""
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. Boston, MA"
},
"unit": {
"type": "string",
"enum": [ "celsius", "fahrenheit" ],
"description": "The temperature unit to use. Infer this from the specified location."
}
},
"required": [ "location" ]
}
"""u8.ToArray())
);
// 2. Set options
ChatCompletionOptions options = new()
{
Tools = { getCurrentLocationTool, getCurrentWeatherTool },
};
// 3. Execution loop pattern
List<ChatMessage> messages = [ new UserChatMessage("What's the weather like today?") ];
bool requiresAction;
do
{
requiresAction = false;
ChatCompletion completion = client.CompleteChat(messages, options);
switch (completion.FinishReason)
{
case ChatFinishReason.Stop:
messages.Add(new AssistantChatMessage(completion));
break;
case ChatFinishReason.ToolCalls:
messages.Add(new AssistantChatMessage(completion));
foreach (ChatToolCall toolCall in completion.ToolCalls)
{
// Execute local function and add ToolChatMessage to history
string toolResult = ExecuteLocalFunction(toolCall);
messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
}
requiresAction = true;
break;
// ... handle other cases
}
} while (requiresAction);The library is designed for testability:
virtual, allowing them to be overridden by mocking frameworks like [Moq].OpenAIAudioModelFactory) to instantiate API output models that do not have public constructors.You can enable the experimental OpenTelemetry instrumentation using one of the following two methods. This must be done before initializing any OpenAI clients.
OPENAI_EXPERIMENTAL_ENABLE_OPEN_TELEMETRY to "true".AppContext.SetSwitch with the key OpenAI.Experimental.EnableOpenTelemetry set to true.AppContext.SetSwitch("OpenAI.Experimental.EnableOpenTelemetry", true);OpenAI clients are thread-safe and should be registered as singletons in ASP.NET Core to maximize resource efficiency and HTTP connection reuse.
// In Program.cs
builder.AddChatClient("Clients:ChatClient");
// In a Controller
[ApiController]
public class ChatController : ControllerBase
{
private readonly ChatClient _chatClient;
public ChatController(ChatClient chatClient)
{
_chatClient = chatClient;
}
[HttpPost("complete")]
public async Task<IActionResult> CompleteChat([FromBody] string message)
{
ChatCompletion completion = await _chatClient.CompleteChatAsync(message);
return Ok(new { response = completion.Content[0].Text });
}
}Starting with version 2.10.0, you can use a configuration-driven approach to register a ResponsesClient. Use the AddResponsesClient extension method on IHostApplicationBuilder to bind a named configuration section to ResponsesClientSettings. This automatically handles the registration of the ResponsesClient in the Dependency Injection (DI) container and resolves credentials from the Credential subsection (e.g., Clients:ResponsesClient:Credential:Key).
Note: ResponsesClientSettings is currently marked with the [Experimental("SCME0002")] attribute.
builder.AddResponsesClient("Clients:ResponsesClient");