
Unlock Azure OpenAI for .NET with Azure.AI.OpenAI
Azure OpenAI gives .NET developers production-grade AI with enterprise controls. This guide shows how to use the Azure.AI.OpenAI
client library for:
- Chat/completions
- Streaming
- Function calling (tools)
- Structured outputs (JSON Schema)
- Assistants via the Microsoft Agent Framework
All samples target .NET 8/9 and work in console apps, APIs, and Blazor.
Prerequisites
- Azure subscription + Azure OpenAI resource
- A chat model deployment (for example:
gpt-4o
orgpt-4o-mini
) - NuGet packages:
dotnet add package Azure.AI.OpenAI
dotnet add package Azure.Identity
# For Assistants/Agents (optional)
dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
Recommended environment variables:
AZURE_OPENAI_ENDPOINT
= https://.openai.azure.com AZURE_OPENAI_KEY
=(only for key auth)
Create the client
using System; using Azure; using Azure.AI.OpenAI; using Azure.Identity; // Using API key var endpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!); var key = new AzureKeyCredential(Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY")!); var aoai = new AzureOpenAIClient(endpoint, key); // Or, using Microsoft Entra ID (Managed Identity, Visual Studio/CLI, etc.) // var aoai = new AzureOpenAIClient(endpoint, new DefaultAzureCredential()); // Use your deployment name (NOT the base model name) var chat = aoai.GetChatClient("gpt-4o-mini"); // e.g., "my-gpt4o-deployment"
Notes
- In Azure, pass the deployment name you created in the Azure AI Foundry portal.
- For production, prefer Microsoft Entra ID over key-based auth.
1) Chat/Completions (basic)
using OpenAI.Chat; var result = await chat.CompleteChatAsync([ new SystemChatMessage("You are a concise, helpful assistant."), new UserChatMessage("Explain Dependency Injection in one sentence.") ]); // Result content can have multiple parts (text, tool-calls, etc.) foreach (var part in result.Value.Content) { if (part is TextContent text) { Console.WriteLine(text.Text); } }
2) Streaming
Streaming delivers tokens incrementally " great for responsive UIs (e.g., Blazor Server/Wasm).
await foreach (var update in chat.CompleteChatStreamingAsync([ new SystemChatMessage("You stream answers word-by-word."), new UserChatMessage("Write a 1-2 sentence summary of CQRS.") ])) { if (update is StreamingChatCompletionUpdate u) { foreach (var part in u.ContentUpdate) { if (part is TextContent text) { Console.Write(text.Text); // append incrementally } } } } Console.WriteLine();
Blazor tip: push streamed chunks into a StringBuilder
, call StateHasChanged()
(throttled), and render.
3) Function Calling (Tools)
Let the model call your code via tool definitions. Typical loop:
- Send user/system messages + tool definitions
- If the model returns tool calls, execute them in .NET
- Send the tool results back to the model to get the final answer
- Define a function tool with parameters via JSON Schema
using System.Text.Json; using OpenAI.Chat; var getWeather = ChatTool.CreateFunctionTool( name: "get_weather", description: "Get current weather for a city", // Minimal JSON schema for parameters parameters: BinaryData.FromString(""" { "type": "object", "additionalProperties": false, "properties": { "location": { "type": "string", "description": "City name" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] } }, "required": ["location", "unit"] } """));
- Ask the model; allow auto tool choice
var first = await chat.CompleteChatAsync( messages: [ new UserChatMessage("What's the weather in Paris in celsius?") ], options: new ChatCompletionOptions { Tools = { getWeather }, ToolChoice = ChatToolChoice.Auto });
- If the model decided to call a tool, execute it
var followUpMessages = new List<ChatMessage>(); foreach (var part in first.Value.Content) { if (part is FunctionCallContent call) { // Parse arguments var args = JsonDocument.Parse(call.Arguments); var location = args.RootElement.GetProperty("location").GetString(); var unit = args.RootElement.GetProperty("unit").GetString(); // Your .NET function var weather = await GetWeatherAsync(location!, unit!); // 4) Send tool result back using a tool message tied to the call id followUpMessages.Add(new ToolChatMessage(call.Id, JsonSerializer.Serialize(weather))); } }
- Ask the model to produce the final natural language answer
var final = await chat.CompleteChatAsync([ new UserChatMessage("What's the weather in Paris in celsius?"), ..followUpMessages ]); foreach (var part in final.Value.Content) { if (part is TextContent text) { Console.WriteLine(text.Text); } } // Example app function static Task<object> GetWeatherAsync(string location, string unit) { // Replace with a real API call var tempC = 18.4; var tempF = (tempC * 9 / 5) + 32; return Task.FromResult<object>(new { location, unit, temperature = unit == "fahrenheit" ? tempF : tempC, condition = "Cloudy" }); }
Tips
- Keep tool schemas strict and small; validate inputs server-side.
- Return machine-friendly JSON; the model will translate for the user.
4) Structured Outputs (JSON Schema)
Use structured outputs to force the model to return JSON that matches a schema " ideal for parsing, validation, and multi-step workflows.
using OpenAI.Chat; var issueSchema = """ { "name": "GitHubIssue", "schema": { "type": "object", "additionalProperties": false, "properties": { "title": { "type": "string" }, "labels": { "type": "array", "items": { "type": "string" } }, "priority": { "type": "string", "enum": ["low","medium","high"] }, "assignee": { "type": ["string","null"] } }, "required": ["title","labels","priority","assignee"] }, "strict": true } """; var response = await chat.CompleteChatAsync( messages: [ new SystemChatMessage("Extract a GitHub issue object from the user's description."), new UserChatMessage("Bug: Search page throws 500 when query is empty. Probably needs null-check. Assign to Alice. Labels: bug, backend. Priority high.") ], options: new ChatCompletionOptions { ResponseFormat = ChatResponseFormat.CreateJsonSchema(BinaryData.FromString(issueSchema)) }); // Guaranteed to match the schema (strict mode) " safe to parse var json = response.Value.ToJson(); // or iterate parts and read text Console.WriteLine(json);
Important
- Structured outputs require models/APIs that support
json_schema
withstrict: true
. - Some features (e.g., Assistants/Agents, certain audio models) may not support structured outputs.
- Ensure each object has
additionalProperties: false
and every field is listed inrequired
.
5) Assistants with the Microsoft Agent Framework (ChatCompletion Agents)
If you prefer a higher-level agent runtime (tools, state, and streaming built-in), use the Microsoft Agent Framework over Chat Completions.
using System; using Azure.AI.OpenAI; using Azure.Identity; using Microsoft.Agents.AI; using OpenAI.Chat; var aoai = new AzureOpenAIClient(new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!), new DefaultAzureCredential()); var chat = aoai.GetChatClient("gpt-4o-mini"); AIAgent agent = chat.CreateAIAgent( instructions: "You are a helpful engineering assistant.", name: "EngAssistant"); // Simple run Console.WriteLine(await agent.RunAsync("Give me 3 tips for API resiliency.")); // Streaming await foreach (var chunk in agent.RunStreamingAsync("Explain circuit breakers in 2 sentences.")) { Console.Write(chunk); }
Notes
- You can register function tools with the agent and handle tool invocations similarly.
- Prefer Agents for multi-step problems where orchestration/state helps.
Choosing between features
- Chat/completions: flexible, low-level control; you own orchestration
- Streaming: best UX for interactive apps (e.g., Blazor)
- Function calling: model calls your APIs; great for retrieval, actions, and tool-use
- Structured outputs: enforce JSON shape for robust parsing and workflows
- Assistants/Agents: higher-level runtime when you want built-in planning and streaming
Production tips
- Prefer Entra ID auth; scope credentials via
DefaultAzureCredential
- Log prompts, tool calls, and token usage (exclude secrets/PII)
- Timeouts, retries, and circuit breakers around network calls
- Validate and bound inputs to tools; sanitize outputs
- Version your prompts and schemas; add unit tests for prompts and tools
- Cache non-personal results where appropriate (e.g., Azure Cache for Redis)
References
- Azure OpenAI " Chat Completions: https://learn.microsoft.com/azure/ai-foundry/openai/how-to/chatgpt
- Structured Outputs (JSON Schema): https://learn.microsoft.com/azure/ai-foundry/openai/how-to/structured-outputs
- REST Reference (response_format, tools): https://learn.microsoft.com/azure/ai-foundry/openai/reference#chat-completions
- Microsoft Agent Framework " Azure OpenAI ChatCompletion Agents: https://learn.microsoft.com/agent-framework/user-guide/agents/agent-types/azure-openai-chat-completion-agent