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:
- The client.
UseProvidersFromEnvironmentregisters the providers from the two environment variables, and throwsInvalidOperationExceptionwhen neither is set. The license key is read fromINTENTSHARP_LICENSE_KEY; without one the client still works, and logs a license warning to the logger you give it withWithLoggerFactory(see section 6). - A typed call.
AIAsync<T>sends the intent and the serialized input. The answer must be JSON that matches a strict schema generated fromPurchaseRequest; it is parsed and checked before it is returned. Every call returns anAIResult<T>: checkSuccessbefore readingValue, and readIssuesfor what went wrong. - A collection call.
AIFilterAsyncexchanges indices with the model and hands back your original objects. The other collection operations areAIMapAsync,AISortAsync,AIGroupAsync,AIReduceAsyncandAIMapValuesAsync. - 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 toMaxAttempts. - 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. - An intent from a file.
FileIntentCatalogloads and watches the folder;IntentRuntime.RunAsync<T>runs the named intent with the shape, rules and examples the file declares. Raiseversionwhen 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
- Architecture overview and the request pipeline.
- Intent catalog: the full file format, shapes, rules and hot
reload, and registering the catalog with dependency injection (
AddIntentCatalog,AddIntentRuntime). - Microsoft.Extensions.AI and Agent Framework: running on any
IChatClient, and catalog intents as agent tools. - Database runtime: natural-language queries with a dry run first.