Documentation
Table of Contents

Provider file

The provider file names each provider, its kind, its model, where its key comes from and where its calls go, and maps the roles to them. It is intentsharp.providers.json, or its .yaml or .yml spelling, in a document store, and it starts with intentsharp: providers/1. Its keys are on Provider secrets: secret references, a resolver of your own, endpoints and plain http, and the edits flagged as widening.

{
  "intentsharp": "providers/1",
  "providers": [
    { "name": "main", "kind": "anthropic", "secret": "env:ANTHROPIC_API_KEY", "model": "<model id>" },
    { "name": "backup", "kind": "openai", "secret": "env:OPENAI_API_KEY", "model": "<model id>", "embeddingModel": "<embedding model id>" },
    { "name": "local", "kind": "openai", "endpoint": "http://localhost:11434/v1/", "model": "<model id>" }
  ],
  "roles": {
    "answer": ["main", "backup"],
    "judge": "backup",
    "embedding": "backup",
    "maintenance": "local"
  },
  "aliases": {
    "quick": { "provider": "main", "model": "<smaller model id>" }
  }
}
// "policy" holds intentsharp.providers.json. WithDocumentStore names the same store, so an edit submitted
// through the client is checked by the provider file's validator before it is saved.
using var store = new FolderDocumentStore("policy");
using var client = new IntentClientBuilder()
    .UseProviderOptions(store)
    .WithDocumentStore(store)
    .Build();

Under dependency injection the client reads the provider file of the registered document store (Providers). The same options can come from a configuration section, with ConfigurationProviderOptionsSource, or from an IProviderOptionsSource of your own; providers added in code with UseProvider need no entry.

Entries

Key What it says
name The provider's name, unique ignoring case: in results, logs, rate limiting, the roles and AIRequestOptions.Provider.
kind openai, anthropic, chat-client (with IntentSharp.Extensions.AI) or a kind of your own (AddProviderFactory).
model The model the provider asks for when the call and the role name none.
secret Where the key is read from, never the key itself (Provider secrets). An entry with no secret sends no key.
endpoint The address its requests go to, when it is not the vendor's (Provider secrets).
embeddingModel, fallbackModel The model for embeddings, and the model when the entry answers as a fallback.
capabilities What the model accepts, such as { "temperature": true }.
key For the chat-client kind, the service key of the chat client that receives the calls.
allowInsecureHttp true lets an entry send its key over plain http (Provider secrets). false when absent.

A field that holds a key itself, such as apiKey, does not load (Provider secrets).

Roles and chains

A role names one provider or an ordered chain of them. answer serves the calls; judge serves the judge, and defaults to the first provider of answer; embedding serves embedding calls; maintenance serves the library's own calls, such as compiling a rule written as intent, and also defaults to the first provider of answer. A role of another name serves the calls that name it in AIRequestOptions.Role. A link of a chain is a provider, such as "main", a provider and a model, such as "main@<model id>", or an alias, such as "@quick". A call moves to the next link of the chain after a failure that falls back, or when a rule or setting refused the link before it (Providers).

Model aliases

aliases gives a short name to a provider and a model. A call that names an alias as its model goes to that provider with that model; an alias the file does not define throws ArgumentException:

// "@quick" is an alias of the provider file: it chooses the provider main and the model the alias names.
var options = new AIRequestOptions { Model = "@quick" };
var summary = await email.AIAsync(client, "Summarize this email in one sentence.", options);

IntentSharp ships no model id: a call whose provider has no model in the call, the alias, the role or the entry throws ProviderConfigurationException, naming the provider and the role, and nothing is sent.

The hosts a call may reach

A rule on request.host, at the Request checkpoint, pins the hosts a call may reach, whatever the provider file says:

intentsharp: rules/1
set: hosts
checkpoint: request
rules:
  - id: known-hosts
    subject: request.host
    allow: [api.anthropic.com, api.openai.com, localhost]
    message: Calls go only to the hosts this application uses.
// hosts.rules.yaml, listed in the store's intentsharp.guardrails.json, is checked before every model call.
var client = new IntentClientBuilder()
    .UseProviderOptions(store)
    .WithDocumentStore(store)
    .WithGuardrailFiles(store, "hosts")
    .Build();

The host is in lower case, with no port; an international name is in its xn-- form, and an IPv6 address has no brackets. For the openai and anthropic kinds, and any provider built on HttpProviderBase, it is the host of the address the provider sends its requests to. A chat client reports the host its metadata names, which IntentSharp cannot check against where the client sends its requests. Under a rule on request.host, a provider that reports no host is refused.

The output ends with two calls the set hosts refuses: one to a host the set does not name, and one whose provider reports no host. It starts with the refusal and the edits that Provider secrets explains:

The entry gateway: plain http, a secret and no allowInsecureHttp:
  intentsharp.providers.json:4: providers[0].endpoint is a plain http address of a host that is not loopback, so the entry's secret would be sent unencrypted; use https, or set allowInsecureHttp: true on the entry to accept that.
An edit of the model of main:
  Status: Applied, WidensSecurity: false
An edit of the endpoint and the secret of backup:
  Status: Applied, WidensSecurity: true
  providers.backup.endpoint
  providers.backup.secret
A call to gateway.example.net, under the set hosts:
  Success: false
  [Guardrail] attempt 1: The answer call was refused at the Request checkpoint: Calls go only to the hosts this application uses (rule known-hosts in guardrail set "hosts").
A call whose provider reports no host, under the set hosts:
  Success: false
  [Guardrail] attempt 1: The answer call was refused at the Request checkpoint: Calls go only to the hosts this application uses (rule known-hosts in guardrail set "hosts").

The chat-client kind

With IntentSharp.Extensions.AI, under dependency injection, an entry of the kind chat-client names a chat client you register under a service key, its key, once AddChatClientProvider() has added the kind (Microsoft.Extensions.AI). The chat client holds its own configuration, so the entry has no secret; its key decides which client receives the calls, so a change of it is flagged as widening.

Without a file on disk

The openai and anthropic kinds are made only from provider entries; no builder method creates one in code. With no files on disk, keep the provider file in an InMemoryDocumentStore. It is read as a file is, its secret stays a reference, and a new version applies to the next call:

// No file on disk: the provider file is a document of an in-memory store, read as a file would be.
var store = new InMemoryDocumentStore();
await store.SaveAsync(new DocumentSave
{
    Id = new DocumentId("intentsharp.providers.json"),
    Text = """
        {
          "intentsharp": "providers/1",
          "providers": [ { "name": "main", "kind": "openai", "secret": "env:OPENAI_API_KEY", "model": "<model id>" } ],
          "roles": { "answer": "main" }
        }
        """,
    Author = new EditAuthor { Kind = AuthorKind.Developer, Label = "startup" },
});

var client = new IntentClientBuilder()
    .UseProviderOptions(store)
    .Build();