OpenAI .NET Library

repository·main·Indexed 25 days ago

https://github.com/openai/openai-dotnet

A 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.

Tokens
16.4K
Snippets
40
Records
47
Agent score
82%

What's inside openai-dotnet

  1. Migrate from OpenAI 1.11.0 to OpenAI 2.0.0-beta.1+

    main
    When migrating from the community-supported OpenAI 1.11.0 to the official OpenAI 2.0.0-beta.1 or higher, the primary architectural change is the shift from a single OpenAIAPI client to specialized clients for each API service. Additionally, models must now be explicitly specified during client instantiation rather than per-call.
  2. Use structured outputs for chat completions

    main

    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.

    1. Create Schema: Define your JSON schema as a string or byte array.
    2. Set Response Format: In ChatCompletionOptions, set the ResponseFormat property using ChatResponseFormat.CreateJsonSchemaFormat.
    3. Strict Mode: Use jsonSchemaIsStrict: true to ensure the model adheres strictly to the provided schema.
    4. Parse Result: The model's response text will be a JSON string that can be parsed using 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);
  3. Run OpenAI .NET 10 samples

    main

    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.

    1. Navigate to the docs directory.
    2. Run a specific sample file using dotnet run <path-to-file>.
    cd docs
    dotnet run quickstart/responses/developer_quickstart.cs
  4. Configure the OPENAI_API_KEY environment variable

    main

    The samples require an OpenAI API key stored in the OPENAI_API_KEY environment variable.

    Temporary (Current session only)

    bash/zsh:

    export OPENAI_API_KEY="your-api-key-here"

    PowerShell:

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

    Permanent

    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 ~/.bashrc
    export OPENAI_API_KEY="your-api-key-here"
  5. Implement Retrieval Augmented Generation (RAG) with Assistants

    main

    To build an assistant capable of analyzing uploaded documents (RAG), follow these steps:

    1. Initialize Clients: Use OpenAIClient to obtain an OpenAIFileClient (from OpenAI.Files) and an AssistantClient (from OpenAI.Assistants).
      • Note: The Assistants API is in beta. AssistantClient is marked [Experimental]. You must suppress the OPENAI001 warning to use it.
    2. Upload Files: Use OpenAIFileClient.UploadFile with FileUploadPurpose.Assistants to make files available to the assistant.
    3. Create Assistant: Use AssistantClient.CreateAssistant with AssistantCreationOptions. To enable RAG, include FileSearchToolDefinition in the Tools collection and configure ToolResources using a VectorStoreCreationHelper to index your uploaded files.
    4. Run Threads: Create a thread and run it using AssistantClient.CreateThreadAndRun.
    5. Poll Status: Use AssistantClient.GetRun in a loop, checking threadRun.Status.IsTerminal to wait for completion.
    6. Retrieve Messages: Use 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 });
  6. Regenerate auto-generated API files

    main

    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.ps1
  7. Enable OpenTelemetry observability

    main

    The 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:

    1. Enable the experimental feature flag.
    2. Configure OpenTelemetry to listen to the OpenAI sources and meters.
    // 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();
        });
  8. Use chat completions with tools and function calling

    main

    You can enable the model to call functions by defining ChatTool instances and passing them via ChatCompletionOptions.

    1. Define Tools: Use ChatTool.CreateFunctionTool to describe your functions. You can provide a functionName, functionDescription, and a JSON schema for functionParameters using BinaryData.
    2. Configure Options: Add the tools to the Tools property of a ChatCompletionOptions instance.
    3. Handle Tool Calls: When calling CompleteChat, check the FinishReason of the resulting ChatCompletion. If it is ChatFinishReason.ToolCalls, you must:
      • Add the AssistantChatMessage (containing the tool calls) to your message history.
      • Execute the local functions corresponding to the ToolCalls.
      • Add a ToolChatMessage for each result to the message history.
      • Call 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);
  9. Mock clients for testing

    main

    The library is designed for testability:

    • Client methods are virtual, allowing them to be overridden by mocking frameworks like [Moq].
    • Use Model Factories (e.g., OpenAIAudioModelFactory) to instantiate API output models that do not have public constructors.
  10. Configure the OpenAI experimental OpenTelemetry feature flag

    main

    You can enable the experimental OpenTelemetry instrumentation using one of the following two methods. This must be done before initializing any OpenAI clients.

    1. Environment Variable: Set OPENAI_EXPERIMENTAL_ENABLE_OPEN_TELEMETRY to "true".
    2. Application Code: Use AppContext.SetSwitch with the key OpenAI.Experimental.EnableOpenTelemetry set to true.
    AppContext.SetSwitch("OpenAI.Experimental.EnableOpenTelemetry", true);
  11. Register OpenAI clients with Dependency Injection

    main

    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 });
        }
    }
  12. Configure the ResponsesClient in ASP.NET Core

    main

    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");