Documentation
Table of Contents

Microsoft.Extensions.AI

The IntentSharp.Extensions.AI package connects IntentSharp to Microsoft.Extensions.AI in two places. Your IChatClient can be a provider, so a backend you have already configured (Azure OpenAI, OpenAI, Ollama, or any other with an IChatClient) serves IntentSharp too. And catalog intents can be tools for an agent, so an agent's model proposes and the intent's rules decide whether the answer stands.

The package references Microsoft.Extensions.AI.Abstractions, IntentSharp and IntentSharp.Catalog. The IntentSharp package itself does not depend on Microsoft.Extensions.AI.

Any IChatClient can be a provider

UseChatClient makes a chat client the primary provider, and UseChatClientAsFallback makes one the fallback:

var client = new IntentClientBuilder()
    .WithLoggerFactory(loggers)
    .UseChatClient(azureOpenAI, name: "AzureOpenAI", logger: loggers.CreateLogger("IntentSharp.AzureOpenAI"))
    .UseChatClientAsFallback(ollama, name: "Ollama", model: "llama3.1")
    .Build();

Both take the chat client and, optionally:

Argument What it does
embeddingGenerator An IEmbeddingGenerator<string, Embedding<float>>. Without one, this provider has no embeddings.
name The provider's name in results, logs, rate limiting and AIRequestOptions.Provider. Default: the provider name in the chat client's metadata, or ChatClient.
model The model used when a call names none. Default: the chat client's default model from its metadata; with neither, the chat client chooses.
logger Receives, at Debug, each chat client exception that becomes an IntentSharp error.

The primary and the fallback need different names, compared ignoring case, and two chat clients for one vendor usually report the same metadata name, so pass name to both. The builder's WithLoggerFactory does not reach a chat client provider; pass logger to see its failures.

You own the chat client: disposing the IntentSharp client does not dispose it, or the embedding generator.

Mix it with a built-in provider

A chat client provider is an ordinary provider (ChatClientProvider), so it can sit beside OpenAI or Anthropic, or be passed to UseProvider and UseFallbackProvider. Here Anthropic is the primary, and an Azure OpenAI chat client is the fallback that also supplies embeddings:

var client = new IntentClientBuilder()
    .UseAnthropic(anthropicKey)
    .UseChatClientAsFallback(azureOpenAI, embeddingGenerator: embeddings, name: "AzureOpenAI")
    .Build();

What the chat client receives

Each attempt is one GetResponseAsync call:

From the request To the chat client
The system prompt, when there is one the first message, with ChatRole.System
Each message, in order a ChatMessage with that message's role
The model, or the provider's default model ChatOptions.ModelId, unset when both are empty
The output token limit ChatOptions.MaxOutputTokens
The temperature ChatOptions.Temperature
A typed result's schema ChatOptions.ResponseFormat = ChatResponseFormat.ForJsonSchema(...)
Any JSON result without a schema ChatOptions.ResponseFormat = ChatResponseFormat.Json
A text result no response format

Request tags are never sent. Whether a backend enforces the JSON schema depends on the backend. The checks do not: a reply that is not JSON, or does not match the result type, fails its attempt and is retried with the problem, whatever the backend did with the format.

From the reply, ChatResponse.Text is the answer and Usage the token counts. A FinishReason of ChatFinishReason.Length marks the reply as cut off, which fails that attempt. ChatFinishReason.ContentFilter marks it as a refusal, which ends the call's attempts.

A chat client failure ends the call with an issue

A chat client provider does not retry. Put retries in the chat client's own pipeline, or rely on the fallback. Every failure of the chat client becomes an IntentSharp provider error, with the original exception as its InnerException, so it ends the call with a Provider issue, or falls back, exactly as for the built-in providers (Providers):

The chat client's failure Becomes Falls back
HTTP status 401 or 403 AIAuthenticationException no
429 AIRateLimitException, with no RetryAfter yes
400 or 422 AIValidationException no
408 AIProviderException, transient yes
5xx AIProviderInternalException yes
any other status AIProviderException no
a transport error with no status AIProviderException, transient yes
a timeout inside the chat client, or a TimeoutException AITimeoutException yes
any other exception, or no response at all AIProviderException no

The status comes from HttpRequestException.StatusCode, or from a Status or StatusCode property on the exception or one of its inner exceptions, which is where the OpenAI and Azure SDKs put it. Two things pass through unchanged: an exception that is already an IntentSharp exception, and a cancellation of your own token.

Catalog intents can be an agent's tools

AsAIFunction and AsAIFunctions turn intents from a catalog into Microsoft.Extensions.AI tools. Pass them to anything that takes such tools: ChatOptions.Tools for a chat client with tool calling, as here, or the tool list of an Agent Framework agent.

// One tool per named intent: Orders.ClassifyUrgency becomes the tool Orders_ClassifyUrgency.
var tools = intents.AsAIFunctions(logger, "Orders.ClassifyUrgency", "Invoice.Extract");

using var agent = new ChatClientBuilder(model).UseFunctionInvocation().Build();
var reply = await agent.GetResponseAsync(inbox, new ChatOptions { Tools = [.. tools] });
Call Returns
runtime.AsAIFunction(name, toolName, logger) one tool for that intent
runtime.AsAIFunctions(names), runtime.AsAIFunctions(logger, names) one tool per named intent, in the order given
runtime.AsAIFunctions(), runtime.AsAIFunctions(logger) one tool per enabled intent in the catalog, ordered by name
// Every enabled intent in the catalog, ordered by name.
var all = intents.AsAIFunctions();

// One intent, under a tool name of your choice.
var urgency = intents.AsAIFunction("Orders.ClassifyUrgency", toolName: "rate_order_urgency");

Tool names

A tool is named after its intent, with each character outside A-Z a-z 0-9 _ - replaced by _, because OpenAI and Anthropic refuse dots in tool names: Orders.ClassifyUrgency becomes Orders_ClassifyUrgency. IntentToolExtensions.ToToolName does the same conversion for your own use. A name longer than 64 characters throws ArgumentException rather than being cut; pass toolName to choose a shorter one. Two intents whose tool names would be the same also throw. Each tool keeps its intent's name in AdditionalProperties, under IntentToolExtensions.IntentNameProperty (intentsharp.intent).

What a tool takes and answers

Each tool takes one argument, input: text, or an object for an intent whose shape is a .NET type. It is described to the agent from the intent's description, its wording and its result shape. It runs the intent with its shape, rules, named checks, examples, limits and audit tags, and answers JSON:

  • a result that passed its checks: {"success": true, "intent": "<intent name>", "value": <result>};
  • anything else: {"success": false, "intent": "<intent name>", "error": "..."}. That covers a result that failed its checks (the error lists the problems, for example value is 9; the maximum is 5.), a call with no input, an input that cannot be read as text, and any exception from the run.

So a failure never breaks the agent's loop: the agent sees what went wrong and can try a better input, ask the user, or move on. Only a cancellation of your own token propagates. An exception the tool answers with success: false would otherwise reach no one, so a tool created with a logger logs it at Warning.

Tools follow the catalog

Each tool call reads the intent's current definition, so a saved intent file applies on the next call. The tool's name, description and input are fixed when the tool is created, and AsAIFunctions() lists the intents enabled when you call it, so call it again after a reload that adds an intent, re-enables one or changes a description.

A tool created for a named intent that is, or later becomes, disabled answers success: false with the IntentDisabledException message, and makes no model call.

Another intent runner can be exposed the same way: implement IIntentInvoker (run, describe, list the enabled intents), and the same AsAIFunction and AsAIFunctions methods apply to it.

Limits

  • A chat client provider does not retry; transport resilience belongs to the chat client's pipeline.
  • A 429 from a chat client carries no RetryAfter.
  • Each tool takes a single input argument.
  • The judge, when on, uses the primary provider, which may be a chat client.