Run-time schemas

The fields are not known until the application runs.

When each customer defines the fields it wants extracted, a compiled class per customer means a release for every new field. With IntentSharp the fields are a JSON schema read at run time: one call asks the model for JSON in that shape, and your validators check each answer.

What it costs today

A typed C# class is the right tool when the shape is known at compile time. When customers configure their own fields, the list of classes grows with the list of customers, a switch picks the class for each one, and a new field for one customer is a code change, a build and a deployment.

The usual way out is a dictionary of strings with a hand-written check for each field, which loses the types and still needs the parsing, the checks and the retries around every call.

How IntentSharp handles it

Two customers keep their fields as JSON schemas in settings, each with fields of its own. One TransformAsync(input, intent, schema, options) call per customer asks for JSON in that customer's shape. Your validator, SchemaRules, checks each schema's required fields and enum lists: the first customer's first answer writes the currency as a symbol, and the retry with the finding fixes it.

Program.cs: each customer's fields
// Each customer's fields, as a JSON schema kept in your settings
// or database. A new field is an edit, not a build.
var customerFields = new Dictionary<string, string>
{
    ["acme"] = """
        {"type": "object",
         "properties": {
           "vendor": {"type": "string"},
           "total": {"type": "number"},
           "currency": {"type": "string", "enum": ["EUR", "GBP"]}},
         "required": ["vendor", "total", "currency"]}
        """,
    ["globex"] = """
        {"type": "object",
         "properties": {
           "supplier": {"type": "string"},
           "invoiceNumber": {"type": "string"},
           "dueDate": {"type": "string"}},
         "required": ["supplier", "invoiceNumber", "dueDate"]}
        """,
};
Program.cs: one call per customer
var email = "Invoice INV-3307 from Fabrikam Tools: 12 torque wrenches, total 1,860.00 EUR, due by 2026-12-01."; // The core checks that each reply is JSON and not null. The// schema's own rules are for your validator: this one checks its// required fields and its enum lists.static IEnumerable<string> SchemaRules(JsonNode schema, JsonNode value){    foreach (var field in schema["required"]!.AsArray())        if (value[(string)field!] is null)            yield return $"{field} is required.";    foreach (var (name, rule) in schema["properties"]!.AsObject())        if (rule?["enum"] is JsonArray list && value[name] is { } given            && !list.Any(a => JsonNode.DeepEquals(a, given)))            yield return $"{name} is {given}; allowed: "                + $"{string.Join(", ", list)}.";} var intent = "Extract these fields from the invoice email.";foreach (var (customer, fieldsJson) in customerFields){    var schema = JsonNode.Parse(fieldsJson)!;    var options = new AIRequestOptions()        .Validate<JsonNode>(value => SchemaRules(schema, value));    results[customer] = await client.TransformAsync(        email, intent, schema, options);}
Output: two customers, two shapes
acme, fields: vendor, total, currency  [Validator] attempt 1: currency is €; allowed: EUR, GBP.  Success: true, after 2 attempts  Value: {"vendor":"Fabrikam Tools","total":1860.00,"currency":"EUR"}globex, fields: supplier, invoiceNumber, dueDate  Success: true, after 1 attempt  Value: {"supplier":"Fabrikam Tools","invoiceNumber":"INV-3307","dueDate":"2026-12-01"}

Compiled and run with every build of this site; the model's answers are scripted, and a pass means the answer met the checks, not that it is right.

What stays in your control

The schemas are data you own, and so are the checks that hold an answer to them.

  • What the core checks. For a schema chosen at run time, IntentSharp checks only that the reply is JSON and not null. Constraint keywords such as required, enum, minimum or pattern are checked by your validators, as SchemaRules does here, or, for an intent with a schema shape, by the catalog (next point).
  • When the catalog checks the whole schema. An intent with a schema shape, in an intent file or defined in code with InMemoryIntentCatalog, is checked against its whole schema on every reply, limits such as minimum and pattern included.
  • Your validator's findings go back. A failed check is retried with the previous reply and the findings, within the attempts you allow, so keep secrets and personal data out of a validator's messages.
  • Fields the input may not give. The model must fill every schema property, so give one the input may leave out a type that lists null, or the model invents it.
  • What you get back. An AIResult<JsonNode>: Success, the JSON value, every finding in Issues and every attempt in Attempts.
  • A pass is not a proof. A pass means the reply is JSON, is not null and met your validators, not that the values in it are right.

Let one customer define its own fields.

dotnet add package IntentSharp --prerelease