Snippet

Call Azure OpenAI for a meeting summary

A complete C# provider that sends a transcript to Azure OpenAI chat completions and returns structured meeting-summary text.

csharpOct 6, 2026MIT

AI-generated, human-reviewed: the first draft was generated by an AI agent; I fact-checked, tested, and edited it. AI use policy

What it does#

Calls Azure OpenAI chat completions from the backend to summarize a transcript. The provider fails before making a network call if the endpoint, API key, or deployment name is missing, or if the endpoint is not an absolute HTTPS URI without user info.

You need#

  • .NET SDK 10
  • A project with ImplicitUsings enabled (the dotnet new default)
  • An Azure OpenAI or Foundry model deployment that supports chat completions
  • Server-side configuration placeholders:
    • AzureOpenAI:Endpoint: https://<your-resource>.openai.azure.com
    • AzureOpenAI:ApiKey: <your-api-key>
    • AzureOpenAI:DeploymentName: <your-deployment-name>
    • AzureOpenAI:ApiVersion: 2024-10-21

Where it goes#

Save it in the API project as MeetingSummaryProvider.cs. Register it as a typed HttpClient service or construct it with an HttpClient in your composition root.

MeetingSummaryProvider.cs
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Sarang Datye
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

public sealed class MeetingSummaryProvider(HttpClient httpClient, AzureOpenAISettings settings)
{
    private const string SystemPrompt =
        "You summarize meeting transcripts. Produce exactly these sections: " +
        "'Overview:' (2-4 sentences), 'Key points:' (3-7 bullets), " +
        "'Decisions:' (bullets or 'None identified.'), " +
        "'Follow-ups:' (bullets or 'None identified.'). " +
        "Do not invent facts not present in the transcript. " +
        "If the transcript is too short or unclear, say so. " +
        "Do not produce final meeting minutes. Do not produce email text.";

    public async Task<MeetingSummaryResult> SummarizeAsync(
        string transcriptText,
        string locale,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(settings.Endpoint)
            || string.IsNullOrWhiteSpace(settings.ApiKey)
            || string.IsNullOrWhiteSpace(settings.DeploymentName))
        {
            throw new InvalidOperationException(
                "Azure OpenAI is not configured. Set endpoint, API key, and deployment name.");
        }

        if (!Uri.TryCreate(settings.Endpoint, UriKind.Absolute, out var endpointUri)
            || endpointUri.Scheme != Uri.UriSchemeHttps
            || !string.IsNullOrEmpty(endpointUri.UserInfo))
        {
            throw new InvalidOperationException(
                "Azure OpenAI endpoint must be an absolute HTTPS URI without user info.");
        }

        var requestUri =
            $"{endpointUri.AbsoluteUri.TrimEnd('/')}/openai/deployments/{settings.DeploymentName}/chat/completions?api-version={settings.ApiVersion}";

        var payload = new
        {
            messages = new object[]
            {
                new { role = "system", content = SystemPrompt },
                new { role = "user", content = $"Transcript locale: {locale}\n\nTranscript:\n{transcriptText}" }
            }
        };

        using var request = new HttpRequestMessage(HttpMethod.Post, requestUri)
        {
            Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
        };
        request.Headers.Add("api-key", settings.ApiKey);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        using var response = await httpClient.SendAsync(request, cancellationToken);
        var body = await response.Content.ReadAsStringAsync(cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"Azure OpenAI summary request failed with status {(int)response.StatusCode}.");
        }

        var summaryText = ExtractSummaryText(body);
        if (string.IsNullOrWhiteSpace(summaryText))
        {
            throw new ProviderResponseException(
                "Azure OpenAI summary response did not include summary content.");
        }

        return new MeetingSummaryResult(summaryText, settings.DeploymentName);
    }

    private static string ExtractSummaryText(string responseBody)
    {
        using var document = JsonDocument.Parse(responseBody);
        if (document.RootElement.TryGetProperty("choices", out var choices)
            && choices.ValueKind == JsonValueKind.Array
            && choices.GetArrayLength() > 0)
        {
            var first = choices[0];
            if (first.TryGetProperty("message", out var message)
                && message.TryGetProperty("content", out var content)
                && content.ValueKind == JsonValueKind.String)
            {
                return content.GetString()?.Trim() ?? string.Empty;
            }
        }

        return string.Empty;
    }
}

public sealed record AzureOpenAISettings(
    string Endpoint,
    string ApiKey,
    string DeploymentName,
    string ApiVersion = "2024-10-21");

public sealed record MeetingSummaryResult(string SummaryText, string Provider);

public sealed class ProviderResponseException(string message) : InvalidOperationException(message);

Check that it works#

Construct the provider with blank settings and call SummarizeAsync. It throws before making any network call. Then test with a fake HttpMessageHandler that returns a chat-completions JSON body and assert the summary text is extracted.