DEV Community

Cover image for MAI-Thinking-1 Is Now in Foundry — Here's What It Means If You Write C#
Taswar Bhatti
Taswar Bhatti

Posted on Originally published at taswar.zeytinsoft.com

MAI-Thinking-1 Is Now in Foundry — Here's What It Means If You Write C#

Every time Microsoft ships a new model into Foundry, I ask myself the same boring-but-important question: does this change anything for the way I actually write code? Most of the time the answer is "cool demo, doesn't affect my day job." This time it's different.

MAI-Thinking-1 — Microsoft's first reasoning model — just went into public preview in Microsoft Foundry. It's not a chat model with a "think harder" flag bolted on. It's built from the ground up for multi-step reasoning: the kind of work where the model has to plan, reconsider, call tools, and stitch together a long chain of context before it gives you an answer worth trusting.

If you've read anything else I've written, you know where this is going: no Python required, no notebook gymnastics, just Microsoft.Extensions.AI and a dotnet run.

What MAI-Thinking-1 Actually Is

A few things worth knowing before you touch any code:

  • Mixture-of-Experts (MoE) architecture. Instead of activating the entire model for every request, it only activates the parts it needs. Translation for us: you get reasoning depth without paying full-model compute cost on every call.
  • Trained from scratch, no distillation. Microsoft trained it on clean data rather than distilling from a third-party model — worth knowing if procurement ever asks "where did this model come from."
  • Competitive on SWE-Bench Pro at a lower price point than other models in its weight class. Translation: it's genuinely usable for coding-adjacent agent workloads, not just benchmark bragging rights.
  • Pricing: $2 per 1M input tokens, $8 per 1M output tokens through the Foundry Model Catalog. Cheap enough that "always-on reasoning agent" stops being a scary line item.

None of that matters if you can't get it working in fifteen minutes, so let's do that.

Why This Matters for .NET Developers Specifically

Most "reasoning model" content is written for people gluing together Python scripts and LangChain. That's not how most of us ship software. If you're building:

  • Agents that call tools — CRM lookups, ERP queries, ticketing systems — and need to reason across the results before responding
  • Long-document analysis — contracts, filings, transcripts — where the model needs to hold context and reason step-by-step instead of pattern-matching a summary
  • Decision-support features — root-cause analysis, recommendation generation, anything where "just guess the most likely next token" isn't good enough

MAI-Thinking-1 is aimed squarely at you, and it slots into the same IChatClient interface you're already using for GPT-4o or any other Foundry model. Swapping models is a config change, not a rewrite.

Getting Started: The Boring Setup Part

dotnet new console -n MaiThinkingDemo
cd MaiThinkingDemo
dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.Extensions.AI
dotnet add package Azure.Identity
dotnet user-secrets init
dotnet user-secrets set "AZURE_AI_ENDPOINT" "https://your-resource.services.ai.azure.com"
Enter fullscreen mode Exit fullscreen mode

Deploy MAI-Thinking-1 from the Foundry Model Catalog to your Foundry project, same as you would any other model. Grab your endpoint and deployment name.

A Multi-Step Reasoning Example

Here's the thing about reasoning models: the interesting part isn't a single prompt/response, it's giving the model something that actually requires reasoning across steps. Let's build a small "contract risk triage" service — the kind of long-document, multi-step reasoning task MAI-Thinking-1 is designed for.

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;

var config = new ConfigurationBuilder()
    .AddUserSecrets<Program>()
    .AddEnvironmentVariables()
    .Build();

var deploymentName = config["AZURE_OPENAI_DEPLOYMENT"] ?? "mai-thinking-1";
var endpoint = new Uri(config["AZURE_AI_ENDPOINT"]
    ?? throw new InvalidOperationException(
        "AZURE_AI_ENDPOINT is not set. Run: dotnet user-secrets set \"AZURE_AI_ENDPOINT\" \"<your-endpoint>\""));

IChatClient chatClient = new AzureOpenAIClient(endpoint, new DefaultAzureCredential())
    .GetChatClient(deploymentName)
    .AsIChatClient();

var systemPrompt = """
    You are a contract risk analyst. When given a contract excerpt, you must:
    1. Identify every clause that creates financial or legal risk.
    2. Reason step-by-step about *why* each clause is risky before concluding.
    3. Rank the risks by severity (High / Medium / Low).
    4. Output a final structured summary  do not skip the reasoning steps.
    """;

var contractExcerpt = """
    Section 9.2: Client may terminate this Agreement for convenience with 5 days'
    written notice. Vendor shall be entitled to fees for Services rendered through
    the termination date only, with no early-termination compensation.

    Section 11.4: Vendor's total liability under this Agreement shall not exceed
    the total fees paid in the preceding twelve (12) months, except in cases of
    gross negligence, in which liability is uncapped.
    """;

var messages = new List<ChatMessage>
{
    new(ChatRole.System, systemPrompt),
    new(ChatRole.User, $"Analyze this contract excerpt:\n\n{contractExcerpt}")
};

var response = await chatClient.GetResponseAsync(messages);
Console.WriteLine(response.Text);
Enter fullscreen mode Exit fullscreen mode

Notice what's not here: no special "reasoning mode" flag, no separate SDK, no different message format. It's the exact same IChatClient call you'd make against any Foundry model. The reasoning happens because of how the model was built, not because of extra plumbing you have to write.

Sample output

# Contract Risk Analysis

Based off of the provided excerpt, I have identified the following clauses that pose financial or legal risk.

---

## RISK 1: Section 9.2 - Termination for Convenience

### Reasoning Step By Step:
1. **Short Notice Period** - The client only has to provide 5 days' written notice prior to terminating the agreement. This is an extremely short window for the vendor to adjust their operations.
2. **Unrecoverable Costs** - The clause only allows for fees for services rendered up to the termination date, with no early termination compensation. This means any upfront costs (i.e: staffing, software, third party vendors, onboarding, or materials) that were procured for the remainder of the work will not be reimbursed.
3. **Opportunity Cost** - With such a short notice period, the vendor may be unable to reallocate their resources to another client in time. This leads to lost revenue outside of what was already performed.
4. **Imbalance of Power** - This is a unilateral right for the client with no reciprocal protections for the vendor, opening up the door for abuse of this clause.

### Risk Severity: `HIGH`

---

## RISK 2: Section 11.4 - Liability Cap

### Reasoning Step By Step:
1. **Disproportionate Cap** - By limiting the vendor's total liability to only the fees paid in the preceding 12 months, the damages awarded could be massively disproportionate to the harm caused. For example, if a breach causes millions of dollars worth of damages, however only a small amount of fees were paid within the last 12 months, the vendor's exposure would be negligible compared to the loss.
2. **Uncertainty With The Exception** - The only exception to this cap is "gross negligence", which is a very high and fact specific legal standard to prove. This creates litigation uncertainty for both parties, as disputes may arise over whether the conduct met this threshold as opposed to ordinary negligence.
3. **Uncapped Exposure** - While the uncapped liability for gross negligence may seem reasonable on the surface, it creates uninsurable or unpredictable exposure for the vendor. Depending on the claim, this can be catastrophic from a financial standpoint.
4. **Jurisdictional Enforceability** - Depending on the governing law, liability caps are not always enforceable for certain claims (i.e: fraud, willful misconduct, statutory violations, or certain types of data breaches), which could render parts of this clause void, causing further unpredictability.

### Risk Severity: `HIGH`

---

## Final Summary

| Risk | Clause | Key Concern | Severity | Recommended Mitigation |
| :-- | :-- | :-- | :-- | :-- |
| Termination For Convenience | 9.2 | 5 day notice with no early termination fee, leading to unrecoverable costs and opportunity loss. | High | Negotiate a longer notice period (e.g: 15-30 days), a termination fee based off of work scheduled, or a wind down reimbursement for committed costs. |
| Liability Cap | 11.4 | Cap to 12 months of fees is disproportionate, with an ambiguous "gross negligence" exception that can lead to uncapped, unpredictable exposure. | High | Expand the cap carveouts carefully, define "gross negligence", consider a higher liability cap (or a mutually agreed upon amount), and confirm compatibility with insurance coverage. |
Enter fullscreen mode Exit fullscreen mode

Tool-Augmented Reasoning (The Agentic Part)

The use case Microsoft calls out explicitly — connecting to CRM, ERP, and ticketing systems through native tool calling — is where reasoning models earn their keep. A non-reasoning model will happily call a tool with garbage arguments and move on. A reasoning model is far more likely to check its own work before it commits to a tool call.

Here's a minimal tool-calling setup using Microsoft.Extensions.AI's function tools, written as a .NET single-file app (#:package directives, no .csproj needed — just dotnet run app.cs):

#:package Azure.AI.OpenAI@2.9.0-beta.1
#:package Azure.Identity@1.21.0
#:package Microsoft.Extensions.AI@10.9.0
#:package Microsoft.Extensions.AI.OpenAI@10.3.0
#:package Microsoft.Extensions.Configuration@10.0.0
#:package Microsoft.Extensions.Configuration.EnvironmentVariables@10.0.0
#:package Microsoft.Extensions.Configuration.UserSecrets@10.0.0
#:property UserSecretsId=f8d4253e-5e0c-4aac-b93b-4e8e1d5d7b25

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using System.ComponentModel;
using Microsoft.Extensions.AI;

await FunctionSample.RunAsync();

public class FunctionSample
{
    public static async Task RunAsync()
    {
        var config = new ConfigurationBuilder()
            .AddUserSecrets<FunctionSample>()
            .AddEnvironmentVariables()
            .Build();

        var deploymentName = config["AZURE_OPENAI_DEPLOYMENT"] ?? "mai-thinking-1";
        var endpoint = new Uri(config["AZURE_AI_ENDPOINT"]
            ?? throw new InvalidOperationException(
                "AZURE_AI_ENDPOINT is not set. Run: dotnet user-secrets set \"AZURE_AI_ENDPOINT\" \"<your-endpoint>\""));

        IChatClient chatClient = new AzureOpenAIClient(endpoint, new DefaultAzureCredential())
            .GetChatClient(deploymentName)
            .AsIChatClient()
            .AsBuilder()
            .UseFunctionInvocation()
            .Build();

        var chatOptions = new ChatOptions
        {
            Tools = [AIFunctionFactory.Create(GetTicketStatus)]
        };

        var response = await chatClient.GetResponseAsync(
            "Ticket TCK-4471 has been open a while — what's going on and what should we do?",
            chatOptions);

        Console.WriteLine(response.Text);
    }

    // A stand-in for a real ticketing system lookup
    [Description("Looks up the current status and priority of a support ticket by ID.")]
    public static string GetTicketStatus(string ticketId)
    {
        return ticketId switch
        {
            "TCK-4471" => "Status: Escalated. Priority: Critical. Opened 6 days ago, SLA breached.",
            _ => "Ticket not found."
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

The model decides whether it needs to call GetTicketStatus based on its own reasoning about the request — not because you hardcoded "if the user mentions a ticket ID, call the tool." That's the actual value proposition here: less prompt-engineering gymnastics to force sensible tool use.

Output

## Ticket TCK-4471 — Current Status
- **Status:** Escalated
- **Priority:** Critical
- **Opened:** 6 days ago
- **SLA:** Breached

So: it's a critical issue, already escalated, and it's been open long enough that the committed response/resolve time has been missed.

## What's going on (interpretation)
- **Critical impact**: This is likely affecting production, revenue, security, or a major customer. Treat as high urgency.
- **Escalated**: Support has already bumped it up (likely to Tier 2/3, engineering, management, or a partner team). It's no longer being handled at the frontline only.
- **SLA breached (6 days open)**: The agreed SLA clock has expired. Expect pressure from stakeholders and potential contractual/experience impact.

## What you should do now
1. **Acknowledge & set comms**: Update the customer/stakeholder that it's escalated and you're actively driving it. Share next check-in time.
2. **Own the escalation**: Identify the escalation owner (engineer/lead) and a single point of contact. Confirm who is driving resolution.
3. **Find the blocker**: Check internal notes (linked incident, reproduction, affected env, logs, PRs). Is it waiting on engineering, a vendor, data fix, or release?
4. **Mitigate**: Is there a workaround (feature flag, rollback, hotfix, config change)? If yes, document and apply/communicate.
5. **Escalate to leadership if stalled**: If no movement today, loop in the escalation manager/incident commander; also consider incident post if prod is impacted.
6. **Track and document**: Log actions, update the ticket with ETA and next steps, and record SLA breach reason per process (for reporting/compliance).

## Quick questions to answer next
- Is there a linked incident (e.g., SEV-1/INC-…)? What's the customer impact (users affected, downtime)?
- Who is the current assignee and last update time? Any dependencies (third-party/vendor)?
- What's the target resolution path (rollback vs. fix vs. workaround)?

If you paste the latest internal note or assignee, I can suggest a tighter action plan (who to ping, what to ask, and a message template).
Enter fullscreen mode Exit fullscreen mode

Where This Fits (and Where It Doesn't)

Be honest with yourself about when you need this:

Reach for MAI-Thinking-1 when:

  • The task genuinely requires multi-step reasoning — plan → check → revise → answer
  • You're processing long documents where a shallow summary isn't good enough
  • Your agent needs to reason about whether and how to call a tool, not just execute a fixed script

Don't reach for it when:

  • You need a fast autocomplete-style response (use a smaller/cheaper model — reasoning models trade latency for depth)
  • The task is simple classification or extraction with no ambiguity to reason through
  • You're cost-sensitive on high-volume, low-complexity calls — the MoE efficiency helps, but it's still priced above a lightweight model

Structured output, defensive parsing, and resilience patterns all still apply here exactly like they do with any other Foundry model — if you've read the earlier chapters on that, nothing changes; you're not throwing away any of the patterns you already have.

Wrapping Up

MAI-Thinking-1 is Microsoft's first real swing at a reasoning model, and the fact that it drops straight into the same IChatClient abstraction as every other Foundry model is, frankly, the best part. No new SDK to learn, no separate reasoning-specific message format — just a model that's better at the multi-step, tool-calling, long-context work that "just answer the prompt" models tend to fumble.

If you've been holding off on agentic workloads because the reasoning quality wasn't there yet, this is worth a real evaluation — not just a demo.

Source code can be found at: github.com/taswar/MaiThinkingDemo


Building AI features in C#? I write about practical, no-hype prompt engineering and Azure AI patterns for .NET developers. Check out Prompt Engineering for .NET Developers — free, no Python required. Also subscribe to my mailing list for the latest blogs, tips and tricks I share.

Top comments (0)