AddSystemOneDecider

Answers every train's decisions through a typed decision model that speaks the System One request format (Jev, or another server that accepts it), by registering a SystemOneDecider as the IDecider, or, with a name, as a keyed SystemOneDecider only. For Nimble, use AddNimbleDecider, which fills in its model name and limits. See Typed decision models.

Signature

public static TBuilder AddSystemOneDecider<TBuilder>(
    this TBuilder configurationBuilder,
    Action<SystemOneOptions> configure
)
    where TBuilder : TraxEffectBuilder
 
public static TBuilder AddSystemOneDecider<TBuilder>(
    this TBuilder configurationBuilder,
    string name,
    Action<SystemOneOptions> configure
)
    where TBuilder : TraxEffectBuilder

SystemOneOptions

PropertyTypeDefaultDescription
EndpointUri?requiredThe model's endpoint, such as https://api.typesafe.ai/v1/systemone for Jev, or the URL of a server you run. Requests go to it and nowhere else: redirects are not followed. Must be an http or https URL, and HTTPS unless it is a loopback address.
Modelstring?requiredThe model pinned to a version, such as jev-1.13.0. A floating alias (ending latest, or with no version digits) is refused.
AllowFloatingModelboolfalseAccepts a floating alias.
ApiKeystring?nullSent as a bearer token. A blank or whitespace key counts as none and sends no Authorization header. Never logged.
AttemptTimeoutTimeSpan10 secondsHow long one attempt may take before it is abandoned and retried.
MaxAttemptsint3Attempts per request, counting the first.
RetryDelayTimeSpan500 msThe wait before the first retry, doubling after each, with jitter, unless the model sends Retry-After. The doubling stops at MaxRetryDelay.
MaxRetryDelayTimeSpan30 secondsThe longest wait before a retry, at most a day. A Retry-After asking for longer is not retried: the decision fails as Transient.
MaxOptionsint255The most options or levels one question may offer, from 2 to 255. A question with more is refused at startup.
MaxQuestionsint64The most questions one request may carry. A Decide asking more is refused at startup.
MaxConcurrentRequestsint?nullRequests in flight at once, or no limit. A request over the limit waits for a slot, which does not count against its AttemptTimeout.

Returns

TBuilder, the same builder type that was passed in.

Example

services.AddTrax(trax => trax
    .AddEffects(effects => effects
        .UsePostgres(connectionString)
        .AddDecisionRecording()
        .AddSystemOneDecider(o =>
        {
            o.Endpoint = new Uri("https://api.typesafe.ai/v1/systemone");
            o.Model = "jev-1.13.0";
            o.ApiKey = configuration["Jev:ApiKey"];
        })
    )
);

Failures

What the model didRetriedRun fails withFailure class
408, 429 or 5xx other than 501 and 505 (including Nimble's 529, busy), no answer in time, a connection refused, reset or timed out, or a 200 whose body is not a System One response, including one with no answers objectyes, until MaxAttemptsDecisionServiceExceptionTransient
A Retry-After longer than MaxRetryDelaynoDecisionServiceExceptionTransient
501, 505 or any other 4xx (bad criteria, a bad key, 402 for no credit left)noDecisionServiceExceptionPermanent
A 3xx. Redirects are not followed, including one handed back by an HttpClient you pass in; the endpoint is the one configured.noDecisionServiceExceptionPermanent
An endpoint that cannot be reached as configured: its name does not resolve, its TLS handshake fails, or a proxy refuses the credentialsnoDecisionServiceExceptionPermanent
A request that cannot be sent as it is: a state written as JSON null, or one that cannot be serializednoDecisionServiceExceptionPermanent
An answer it cannot readnothe train's unanswered-question failureTransient

SystemOneDecider implements IVetsQuestions, so what the model refuses whatever the state stops the host at startup instead of failing every run: more questions in one Decide than MaxQuestions, a question with blank instructions, fewer than two or more than MaxOptions options or levels, an option named twice, a kind of question the format cannot ask, and a state type JSON writes as a bare number, true or false. Each question is sent under its question key, the type's name without its namespace or the Key set on [Asks].

A choice or score answer without a confidence, which the format allows, takes the probability of the chosen option or the nearest level instead; one with neither is left out. A score whose probabilities are not keyed by every level from 0 is left out rather than shifted onto the wrong levels.

A retry waits for what Retry-After asks, plus a little jitter so a fleet of callers does not return at once, or else the doubling RetryDelay, jittered and capped at MaxRetryDelay. Cancelling the run cancels the request and is not retried. A failure's message gives the status, a few words on what it means, and the provider's request id from the x-typesafe-request-id response header when there is one; the request id is what the provider asks for when a failure is reported. The response body is never read into the message: it can echo the request, which carries the train's state, and the message is stored as the run's failure reason.

Every answer carries the model the response names. That is the name the request asked for, echoed back, not a version the server confirms, so pin the version where the model is deployed.

Remarks

  • The options are checked when this is called, so unusable settings stop the host from starting.
  • The unnamed form registers the decider as SystemOneDecider and as IDecider, and may be used once across this method and AddNimbleDecider; a second unnamed registration fails at startup instead of replacing the first.
  • The named form registers a keyed SystemOneDecider under name and nothing else, so several models can sit side by side. Resolve each with GetRequiredKeyedService<SystemOneDecider>(name) and compose them, for example into a CascadingDecider registered as the IDecider; see Putting the model in front of a larger one. DecidedBy<TDecider>() resolves by type, so it cannot name a keyed decider.
  • The decider is built on first use and owned by the container, which disposes its HTTP client when the host stops. A decision in flight when the decider is disposed keeps the outcome of its request.
  • A SystemOneDecider built with its own HTTP client does not follow redirects. One built with an HttpClient you pass in uses that client, and a 3xx it hands back still fails the decision.

Package

dotnet add package Trax.Effect.Decisions.SystemOne