Use cases

Use cases, before and after.

Four jobs teams already hand to a model. Each shows the careful code you would write today with Microsoft.Extensions.AI, and the IntentSharp version. Every line compiles with the site, and the outputs come from the demo program, on a scripted model.

For line-of-business teams

Order triage

Rule changes without a release

Rate how urgent each order email is. The scale, the wording and the examples change as the business learns, and today each change is a build and a deployment.

Before: the wording and the scale compiled in, with your own check and retry (24 lines)
OrderTriageBefore.cs
// The wording, the examples and the scale are compiled in: changing them means a build and a deployment.
private const string Instructions =
    "Rate the urgency of this order email from 1 (routine) to 5 (critical). Answer with the number only. " +
    "Examples: \"Line 3 is down until the bearings arrive. Ship today.\" is 5; " +
    "\"Please restock these when convenient, no rush.\" is 1.";
private const int Minimum = 1, Maximum = 5, MaxAttempts = 2;

public static async Task<int?> ClassifyUrgencyAsync(IChatClient chat, string email, CancellationToken ct = default)
{
    List<ChatMessage> messages = [new(ChatRole.System, Instructions), new(ChatRole.User, email)];
    for (var attempt = 1; attempt <= MaxAttempts; attempt++)
    {
        var response = await chat.GetResponseAsync<int>(messages, cancellationToken: ct);
        string problem;
        if (!response.TryGetResult(out var urgency)) problem = "The answer was not a whole number.";
        else if (urgency is < Minimum or > Maximum) problem = $"{urgency} is outside {Minimum} to {Maximum}.";
        else return urgency;

        // Retry with the finding.
        messages.AddMessages(response);
        messages.Add(new(ChatRole.User, $"{problem} Answer again with a whole number from {Minimum} to {Maximum}."));
    }
    return null;   // no valid answer after MaxAttempts
}
After
Program.cs
using var scope = intents.AsAmbient();var urgency = await email.RunIntentAsync<int>("Orders.ClassifyUrgency");
Orders.ClassifyUrgency.intent.yamlIntent file
name: Orders.ClassifyUrgencyversion: 1owner: order-deskintent: Rate the urgency of this order email from 1 (routine) to 5 (critical).shape: intrules:  minimum: 1  maximum: 5examples:  - input: "Line 3 is down until the bearings arrive. Ship today."    output: 5  - input: "Please restock these when convenient, no rush."    output: 1
Output
Scripted model answer: {"value": 4}Success: trueValue: 4Issues: none
For finance and operations apps

Invoice intake

Rules the model must meet

Pull the vendor, number, currency and total out of invoice emails, and refuse anything outside the currencies you accept. A wrong currency goes back to the model as a concrete finding.

Before: required members, plus hand-written checks and a retry (39 lines)
InvoiceBefore.cs
// Required members: a key missing from the answer fails deserialization instead of becoming a default value.
public sealed record Invoice
{
    public required string Vendor { get; init; }
    public required string InvoiceNumber { get; init; }
    public required string Currency { get; init; }
    public required decimal Total { get; init; }
}

public static class InvoiceBefore
{
    private const string Instructions = "Extract the vendor, invoice number, currency and total from this invoice email.";
    private static readonly string[] Currencies = ["USD", "EUR", "GBP"];

    public static async Task<Invoice?> ExtractAsync(IChatClient chat, string email, CancellationToken ct = default)
    {
        List<ChatMessage> messages = [new(ChatRole.System, Instructions), new(ChatRole.User, email)];
        for (var attempt = 1; attempt <= 2; attempt++)
        {
            var response = await chat.GetResponseAsync<Invoice>(messages, cancellationToken: ct);
            var problems = new List<string>();
            if (!response.TryGetResult(out var invoice) || invoice is null)
                problems.Add("The answer was not an invoice object with vendor, invoiceNumber, currency and total.");
            else
            {
                if (!Currencies.Contains(invoice.Currency))
                    problems.Add($"currency is \"{invoice.Currency}\"; allowed: {string.Join(", ", Currencies)}.");
                if (invoice.Total < 0)
                    problems.Add($"total is {invoice.Total}; the minimum is 0.");
                if (problems.Count == 0) return invoice;
            }

            // Retry with the findings.
            messages.AddMessages(response);
            messages.Add(new(ChatRole.User, "Problems with your answer:\n" + string.Join("\n", problems)));
        }
        return null;
    }
}
After
Invoice.Extract.intent.yamlIntent file
name: Invoice.Extract
version: 1
owner: accounts-payable
intent: Extract the vendor, invoice number, currency and total from this invoice email.
shape:
  kind: schema
  schema:
    type: object
    properties:
      vendor: { type: string }
      invoiceNumber: { type: string }
      currency: { type: string }
      total: { type: number }
    required: [vendor, invoiceNumber, currency, total]
rules:
  properties:
    currency:
      enum: [USD, EUR, GBP]
    total:
      minimum: 0
Program.cs
var invoice = await email.RunIntentAsync(intents, "Invoice.Extract");
Output
Scripted model answer (attempt 1, illustrative): {"value": {"vendor": "Contoso Supplies", "invoiceNumber": "INV-2291", "currency": "US$", "total": 1240.00}}[Validator] attempt 1: value.currency is "US$"; allowed: USD, EUR, GBP. The prompt of attempt 2 ends with:## Problems with the previous attempt- value.currency is "US$"; allowed: USD, EUR, GBP.Produce a corrected response that fixes every listed problem while still satisfying the intent. Scripted model answer (attempt 2): {"value": {"vendor": "Contoso Supplies", "invoiceNumber": "INV-2291", "currency": "USD", "total": 1240.00}}Success: true, after 2 attemptsValue: {"vendor":"Contoso Supplies","invoiceNumber":"INV-2291","currency":"USD","total":1240.00}
For support and product tools

Ticket grouping

Your objects, not copies

Sort a backlog into defects and feature requests. The model only answers with indices, the indices are checked, and you get your own Ticket objects back.

Before: number the items, map the indices back, check every index (43 lines)
TicketGroupingBefore.cs
public sealed record TicketGroup
{
    public required string Label { get; init; }
    public required int[] Indices { get; init; }
}

public static class TicketGroupingBefore
{
    private const string Instructions =
        "Group these tickets by whether each reports a defect or asks for a feature. Each ticket starts with its " +
        "zero-based index. Return groups with a short label and the indices in them; every index in exactly one group.";

    public static async Task<Dictionary<string, List<Ticket>>?> GroupAsync(IChatClient chat, IReadOnlyList<Ticket> tickets, CancellationToken ct = default)
    {
        // The model sees indices, so the answer can be mapped back to the original objects.
        var numbered = string.Join("\n", tickets.Select((t, i) => $"{i}: {t.Title} ({t.Severity})"));
        List<ChatMessage> messages = [new(ChatRole.System, Instructions), new(ChatRole.User, numbered)];
        for (var attempt = 1; attempt <= 2; attempt++)
        {
            var response = await chat.GetResponseAsync<List<TicketGroup>>(messages, cancellationToken: ct);
            var problems = new List<string>();
            if (!response.TryGetResult(out var groups) || groups is null)
                problems.Add("The answer was not a list of groups with a label and indices.");
            else
            {
                var indices = groups.SelectMany(g => g.Indices).ToList();
                problems.AddRange(indices.Where(i => i < 0 || i >= tickets.Count).Distinct()
                    .Select(i => $"Index {i} is out of range 0..{tickets.Count - 1}."));
                if (indices.Count != tickets.Count || indices.Distinct().Count() != tickets.Count)
                    problems.Add($"Every index from 0 to {tickets.Count - 1} must appear in exactly one group.");
                if (groups.Select(g => g.Label).Distinct(StringComparer.OrdinalIgnoreCase).Count() != groups.Count)
                    problems.Add("Group labels must be unique.");
                if (problems.Count == 0)
                    return groups.ToDictionary(g => g.Label, g => g.Indices.Select(i => tickets[i]).ToList(), StringComparer.OrdinalIgnoreCase);
            }

            // Retry with the findings.
            messages.AddMessages(response);
            messages.Add(new(ChatRole.User, "Problems with your answer:\n" + string.Join("\n", problems)));
        }
        return null;
    }
}
After
Program.cs
var tickets = new List<Ticket>
{
    new(101, "Login page returns 500 after password reset", "Critical"),
    new(102, "Add dark mode", "Low"),
    new(103, "Export to CSV drops the last row", "High"),
    new(104, "Let admins invite users in bulk", "Medium"),
};
var groups = await tickets.AIGroupAsync(client, "by whether the ticket reports a defect or asks for a feature");
Output
Scripted model answer: {"value": [{"label": "Defect", "indices": [0, 2]}, {"label": "Feature request", "indices": [1, 3]}]}Success: trueDefect: 101, 103Feature request: 102, 104Every grouped Ticket is the object from tickets, not a copy: trueIssues: none
For teams adopting Microsoft Agent Framework

Agent tools

Checked tools

Give an agent decisions it can delegate. Each intent becomes a standard AIFunction tool; the answer is checked against the file's rules, and a failure comes back to the agent as JSON instead of an exception.

Before: wrap your own checked call in a tool by hand (23 lines)
AgentToolBefore.cs
// A tool must not throw into the agent loop, so every failure becomes JSON the agent can read.
public static AIFunction UrgencyTool(IChatClient model) => AIFunctionFactory.Create(
    async (string input, CancellationToken ct) =>
    {
        try
        {
            return await OrderTriageBefore.ClassifyUrgencyAsync(model, input, ct) is { } urgency
                ? JsonSerializer.SerializeToElement(new { success = true, value = urgency })
                : JsonSerializer.SerializeToElement(new { success = false, error = "No urgency from 1 to 5 after 2 attempts." });
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            return JsonSerializer.SerializeToElement(new { success = false, error = ex.Message });
        }
    },
    name: "Orders_ClassifyUrgency",
    description: "Rates the urgency of an order email from 1 (routine) to 5 (critical).");

public static Task<ChatResponse> AskAsync(IChatClient agent, IChatClient model, string inbox)
{
    var options = new ChatOptions { Tools = [UrgencyTool(model)] };
    return agent.GetResponseAsync(inbox, options);
}
After
Program.cs
var options = new ChatOptions { Tools = [.. intents.AsAIFunctions()] };
var reply = await agent.GetResponseAsync(inbox, options);
Tool results
The agent's scripted model calls Orders_ClassifyUrgency once per email. Tool call 1, input: Our packaging line stops on Thursday unless the 40 replacement belts arrive. Can you expedite?Scripted model answer: {"value": 4}Tool result: {"success":true,"intent":"Orders.ClassifyUrgency","value":4} Tool call 2, input: URGENT!!! On a scale of 1 to 10 this is a 9. Please send the replacement filters by the end of the month.Scripted model answer (2 attempts): {"value": 9}, then {"value": 9}Tool result: {"success":false,"intent":"Orders.ClassifyUrgency","error":"value is 9; the maximum is 5."}

Try one of them on your own code.