How it works
Every IntentSharp call keeps one contract: on a value, a collection, a method marked [AIIntent], an intent from a
file or a database question. The Overview draws it.
What you give
An intent in plain words, in code or in an intent file; any .NET value, sent in
full and never cut ([JsonIgnore] keeps a member's value out, not its name); the type you want back, or a JSON
schema chosen at run time; and, optionally, rules, examples, instructions, limits, the judge and the provider, on
AIRequestOptions or in the file.
What you get back
Every call returns an AIResult<T>. Read Success before Value.
| Member | What it holds |
|---|---|
Success |
True when an answer passed every check and the judge, when it ran, did not reject it. An unreadable verdict does not reject. |
Value |
The checked value on success; the type's default otherwise. |
Issues |
Every problem, with its Source, its attempt and its message. |
Attempts |
One record per attempt that finished its checks: provider, model, tokens, time, reply and issues. A cached answer's record shows the tokens it first cost. |
Usage |
Tokens for this call's model calls, judge calls included; a cached answer adds none, though its judge call does. A reply that a limit or a provider error stopped is counted only in the client's TotalUsage. |
FromCache |
True when the answer came from the request cache. A judge, when on, still made one model call. |
The client's TotalUsage and TotalModelCalls add up every model reply since it was built, judge replies and
failed attempts included, but no cached answer; TotalModelCalls also counts EmbedAsync.
What is checked
Every answer is checked in your process before your code sees it.
- Your type: required members present, no null your type does not allow, enum values by name. A refusal, a reply cut off at the output token limit and an empty reply fail.
- Your rules:
Require<T>,Validate<T>, and an intent file's rules and named validators. Their messages, and the message of a check that throws, go to the model on the next attempt, so keep secrets out of them. - The judge, when on: one more model call per answer that passed, set by
JudgeMode(Never,OnRequest, the default, orAlways). The default judge asks the primary provider, even when the request names another, and never falls back. An unreadable verdict accepts the answer and logs aWarning; a judge call that fails fails the call.
Most result types reach the provider as a strict schema; a dictionary, object, a JSON type, a type that contains
itself, or a type with a member of one of these (not marked [JsonIgnore]) does not, and then only these checks
hold the answer to your type. Ranges, lengths and patterns are never in the schema: check them in your rules. The
model must fill every member and every schema property, so make one the input may not give nullable (DateOnly?,
or a schema type that lists null), or the model invents it. For a schema chosen at run time, IntentSharp checks
only that the reply is JSON and not null.
A failed check is retried with the previous reply and the findings; a refusal is not retried. A cached answer is checked against the current rules, and one that fails is replaced. A pass means the checks passed, not that the answer is right.
What leaves your process
Your intent, input, type descriptions, instructions, output description, examples and, on a retry, the previous
reply (up to 6,000 characters) and the findings go to the provider you chose and to no one else: the primary; the
fallback only after a transient error; or the provider a request or an intent file names, with no fallback. With
the default judge on, the intent, the input and each passing answer also go to the primary. When AddIntentSharp
or UseProvidersFromEnvironment finds both API keys, the second vendor becomes the fallback, so leave one unset to
keep your data with one vendor. EmbedAsync, and a memory with an embedding provider, send their text to that
provider; an agent tool's description and answers go to your agent's model. Tags never leave through the built-in
or IChatClient providers; a provider of your own receives them. Your license key goes nowhere: it is checked in
your process. Nothing goes to C2Consultants, and the rows a database question returns stay with you.
The client keeps each call in an in-process memory of its last 10,000 calls (the intent, the first 2,000 characters of the input, the reply and the tags), and writes nothing to disk unless you choose a SQLite memory (Memory, cache and rate limiting).
Limits
| Limit | Default | What it counts | When it is reached |
|---|---|---|---|
MaxAttempts |
2 | Attempts: one request and its checks | Success false, with the findings |
MaxModelCalls |
6 | Attempts and judge calls; not cached answers, provider retries or the fallback | Success false, Guardrail issue |
MaxOutputTokens |
4,096 | Tokens of each reply | The reply is cut off and fails |
Timeout |
none | The whole call | Success false, Guardrail issue |
MaxInputChars |
200,000 | Characters of the serialized input | ArgumentException, no model call |
Provider MaxRetries, Timeout |
3, 120 seconds | Resends after a 429, a 5xx or a transport error; each send | Provider issue, or the fallback |
| Rate limiter wait | 30 seconds | A model call's wait for its turn | Like a 429 |
Set MaxAttempts, MaxModelCalls, MaxOutputTokens and the time budget (DefaultTimeout) on
IntentClientOptions, and override them for one call on AIRequestOptions (Timeout for the time budget).
MaxInputChars is set on the client only, the provider's retries and send timeout on HttpProviderSettings
(Providers), and the rate limiter's 30-second wait is fixed. Judge
replies are capped at 1,024 tokens. Limits count calls, tokens and time, not money.
When a call fails
A wrong answer, a refusal, a provider error and a reached limit do not throw: Success is false and Issues says
why. A call throws only for a mistake in the call, before any model call:
| Exception | When |
|---|---|
ArgumentException |
An empty intent, input over MaxInputChars, or an unknown provider name. |
ArgumentOutOfRangeException |
A limit out of range. |
InvalidOperationException |
A method without a client parameter, outside client.AsAmbient(). |
IntentNotFoundException, IntentDisabledException, IntentShapeMismatchException, IntentSharpException |
An intent file: unknown name, enabled: false, a shape that does not fit T, or an unregistered validator or type. |
Your own CancellationToken throws OperationCanceledException, and an exception from your own provider that is
not an IntentSharp provider exception reaches your code. QueryAsync and UpsertAsync throw
SemanticDatabaseException when no safe statement results (Database queries).