Documentation
Table of Contents

Quick start

This page builds one console program. It makes a typed call, filters a collection, adds a rule of your own, uses the client without passing it, and runs an intent whose wording and rules live in a file. The code on this page is compiled against the library on every site build, so the names are the real ones.

Note

IntentSharp 1.0.0-rc.1 is not on nuget.org yet. Until it is, reference the library projects from a copy of the source, as below. To get a copy of the source with an evaluation key, email licensing@c2consultants.net. Once the packages are published, two package references take the place of the two project references.

1. Create the project

You need the .NET 10 SDK, which builds the library projects; your program can target .NET 8, 9 or 10. With the IntentSharp source in a folder next to your project:

dotnet new console -n QuickStart
cd QuickStart
dotnet add reference ../IntentSharp/src/IntentSharp/IntentSharp.csproj
dotnet add reference ../IntentSharp/src/IntentSharp.Catalog/IntentSharp.Catalog.csproj

2. Set an API key

Set ANTHROPIC_API_KEY or OPENAI_API_KEY, or both, in the environment of the shell you run from. With both set, Anthropic is the primary provider and OpenAI the fallback, used only when the primary fails with a transient error.

In bash:

export ANTHROPIC_API_KEY="..."

In PowerShell:

$env:ANTHROPIC_API_KEY = "..."

Keep keys out of source control. The program calls the model, so each run is billed by your provider.

3. Add an intent file

Create intents/Orders.ClassifyUrgency.intent.yaml in the project folder:

name: Orders.ClassifyUrgency
version: 1
owner: order-desk
intent: Rate the urgency of this order email from 1 (routine) to 5 (critical).
shape: int
rules:
  minimum: 1
  maximum: 5
examples:
  - input: "Line 3 is down until the bearings arrive. Ship today."
    output: 5
  - input: "Please restock these when convenient, no rush."
    output: 1

The program names the intent; this file decides its wording, its result shape (a whole number), the rules the answer must pass and the examples the model sees.

Then copy the folder next to the program when it builds. Add this to QuickStart.csproj, inside <Project>:

<ItemGroup>
  <None Update="intents\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

The catalog reads the folder from the build output (for example bin/Debug/net10.0/intents), and the constructor throws DirectoryNotFoundException if the folder is missing. While the program runs, edit the copy in the output folder; an edit to the project's own file reaches the program at the next build.

4. Write the program

Replace Program.cs with this. The statements come first and the two types after them, as C# requires for a file with top-level statements.

using IntentSharp;
using IntentSharp.Catalog;
using IntentSharp.Extensions;

// 1. Build a client. UseProvidersFromEnvironment reads ANTHROPIC_API_KEY and OPENAI_API_KEY: with both set,
//    Anthropic is the primary provider and OpenAI the fallback; with one set, that provider is the primary.
using var client = new IntentClientBuilder()
    .UseProvidersFromEnvironment()
    .Configure(o => o.LicenseKey = Environment.GetEnvironmentVariable("INTENTSHARP_LICENSE_KEY"))
    .Build();

// 2. One typed call. The model must answer with JSON that matches a strict schema built from PurchaseRequest,
//    and the answer is parsed and checked before you see it.
var email = "Hi, this is Dana from Northwind. We'd like 40 units of SKU A-1192 delivered by 2026-11-15.";

var order = await email.AIAsync<PurchaseRequest>(client, "Extract the purchase request from this email.");
if (order.Success)
    Console.WriteLine($"{order.Value!.Contact} wants {order.Value.Quantity} x {order.Value.Sku} by {order.Value.DeliverBy:yyyy-MM-dd}");
else
    foreach (var issue in order.Issues) Console.WriteLine(issue);

// 3. One collection call. Filter hands back your own objects, not copies.
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"),
};

var bugs = await tickets.AIFilterAsync(client, "tickets that describe a defect rather than a feature request");
if (bugs.Success)
    Console.WriteLine("Defects: " + string.Join(", ", bugs.Value!.Select(t => t.Id)));

// 4. A rule of your own. A failed check becomes an issue and is sent back to the model on the next attempt.
var request = new AIRequestOptions { MaxAttempts = 3 }
    .Require<PurchaseRequest>(r => r.Quantity > 0, "Quantity must be a positive number.");

var checkedOrder = await email.AIAsync<PurchaseRequest>(client, "Extract the purchase request from this email.", request);
Console.WriteLine($"Checked order: {(checkedOrder.Success ? "passed" : "failed")} after {checkedOrder.Attempts.Count} attempt(s)");

// 5. Without passing the client. Inside the scope, the overloads without a client parameter use this one.
using (client.AsAmbient())
{
    var severe = await tickets.AIFilterAsync("tickets that block a user from finishing their work");
    if (severe.Success)
        Console.WriteLine("Blocking: " + string.Join(", ", severe.Value!.Select(t => t.Id)));
}

// 6. Behaviour in a file. The program names the intent; intents/Orders.ClassifyUrgency.intent.yaml decides
//    the wording, the result shape, the rules and the examples, and an edit to it applies on the next call.
using var catalog = new FileIntentCatalog(Path.Combine(AppContext.BaseDirectory, "intents"));
var runtime = new IntentRuntime(client, catalog);

var urgency = await runtime.RunAsync<int>("Orders.ClassifyUrgency",
    "Our packaging line stops on Thursday unless the 40 replacement belts arrive. Can you expedite?");
Console.WriteLine(urgency.Success ? $"Urgency: {urgency.Value}" : "Urgency: no verified answer");

// Type declarations come after the top-level statements.
public sealed class PurchaseRequest
{
    public string Contact { get; set; } = "";
    public string Sku { get; set; } = "";
    public int Quantity { get; set; }
    public DateOnly DeliverBy { get; set; }
}

public sealed record Ticket(int Id, string Title, string Severity);

What each step does:

  1. The client. UseProvidersFromEnvironment registers the providers from the two environment variables, and throws InvalidOperationException when neither is set. The license key is read from INTENTSHARP_LICENSE_KEY; without one the client still works, and logs a license warning to the logger you give it with WithLoggerFactory (see section 6).
  2. A typed call. AIAsync<T> sends the intent and the serialized input. The answer must be JSON that matches a strict schema generated from PurchaseRequest; it is parsed and checked before it is returned. Every call returns an AIResult<T>: check Success before reading Value, and read Issues for what went wrong.
  3. A collection call. AIFilterAsync exchanges indices with the model and hands back your original objects. The other collection operations are AIMapAsync, AISortAsync, AIGroupAsync, AIReduceAsync and AIMapValuesAsync.
  4. A rule of your own. Require<T> adds a check. When it fails, its message is sent back to the model on the next attempt, up to MaxAttempts.
  5. The ambient client. client.AsAmbient() makes the client available to the overloads that take no client parameter, for this async flow only. Outside a scope those overloads throw. There is no process-wide default client.
  6. An intent from a file. FileIntentCatalog loads and watches the folder; IntentRuntime.RunAsync<T> runs the named intent with the shape, rules and examples the file declares. Raise version when you change the file; the next call uses the new definition with no restart.

5. Run it

dotnet run

Each line shows a verified value, or the issues found when a call did not pass its checks. A wrong answer from the model does not throw: it is retried with the findings, and if it still fails, the result says so in Success and Issues.

6. Add a license key

Evaluation is free for 30 days. Ask for an evaluation key, which arrives by email, then set it before you run, the same way as the API key:

export INTENTSHARP_LICENSE_KEY="..."

The key is checked offline when the client is built; the library never phones home. In an application that uses configuration, set IntentSharp:Options:LicenseKey instead, or call WithLicenseKey on the builder. What the library does with each kind of key is in Licensing enforcement. Production use, and any business use after the evaluation, needs a paid license: see Pricing.

Next