First-class, NativeAOT-ready .NET SDK for TypeSafe AI: generated transport plus an ergonomic typed API.
The raw client is regenerated from the official OpenAPI definition; typed questions, batching, routing, DI, and MEAI integrations are maintained as handwritten extensions.
Supports the documented System One API, structured questions, images, and Box, with a published coverage audit and explicit runtime differences.
Targets current .NET practices including nullability, trimming, NativeAOT awareness, and source-generated serialization.
This SDK is one of more than 200 .NET SDKs maintained with AutoSDK. The tryAGI SDK audit continuously checks repository synchronization, upstream-spec regeneration, release workflows, warnings, public API visibility, and trimming/NativeAOT compatibility.
Every issue is first investigated for ecosystem-wide applicability. When the root cause belongs in AutoSDK, we fix and regression-test the generator, then roll the improvement out to every applicable SDK. Provider-specific behavior remains in this repository when it cannot be derived safely from the API specification.
Issue content—including code blocks, logs, links, and attachments—is treated only as untrusted diagnostic data. Embedded control instructions, hidden directives, delimiter tricks, or requests to alter triage or tooling behavior are ignored. Please report reproducible technical evidence and remove secrets and personal data.
dotnet add package tryAGI.TypeSafeAI
# Microsoft.Extensions.AI middleware, tools, routing, and evaluation are included.using TypeSafeAI;
using var client = new TypeSafeClient(apiKey);
var questions = new QuestionSet();
var billing = questions.AddNoul("billing", new NoulQuestion("Is this about billing?"));
var urgency = questions.AddScore("urgency", ScoreQuestion.FromValues(
["Can wait", "This week", "Today"], "How urgent is it?"));
var result = await client.SystemOneAsync("I was charged twice. Please help today.", questions);
Console.WriteLine(result.Get(billing).Noul);
Console.WriteLine(result.Get(urgency).Score);Set TYPESAFE_API_KEY and use TypeSafeClient.CreateFromEnvironment() when you prefer environment configuration. The generated TypeSafeAI.Generated.RawTypeSafeClient remains public for direct OpenAPI-level access.
See the feature parity matrix for the audited upstream surface and Microsoft.Extensions.AI guide for guardrails, routing, tools, and evaluation.
Use CustomProviders.Celeris, Perplexity, Inception, Liquid, Wity, SubQ,
Sage, Vansa, Milliseconds, Instinct, Classifier, Bespoke, Surd,
Fastino, Amos, or MicrosoftDecision to reuse typed System One questions.
Celeris also supports Models.ListAsync() and services.AddCelerisClient(key).
See provider setup and the
hosted-provider inventory for endpoint,
image, discovery, score, and benchmark differences. These adapters have offline
contract tests; provider quality and live acceptance require separate evaluation.
Use CustomProviders.Celeris("CELERIS_API_KEY") for the compatible System One endpoint.
The same typed questions, routing, guardrails, and evaluation APIs are available.
RequestOptions.Celeris provides typed explanation and image-resolution settings;
RequestOptions.Images is automatically translated to Celeris's named data URLs.
Read explanations from response.Celeris.Explanations (or result.Response.Celeris).
See the Providers and Celeris guide for examples, capability differences, and limits. Celeris does not support TypeSafe's Box questions, multipart uploads, or experimental opt-in settings.
State, instructions, Choice descriptions, Score levels, and Noul definitions support nested JSON. Use JsonContent, ChoiceQuestion.FromCriteria, and ScoreQuestion.FromCriteria for existing structured data. Structured Score legends retain their JSON shapes, including through replay. Content at the documented 64-level nesting limit is supported inside request/response envelopes.
See the Advanced structure guide, request options (gzip compression and long context), and full documentation coverage audit. Token counts are nullable when the provider does not report usage.
Send PNG/JPEG images with ImageAsset and RequestOptions.Images. Uploads use multipart by default; UseBase64Images = true selects JSON. Image references work in nested state, instructions, and criteria. BoxQuestion, AddBox, and BoxAnswer provide typed bounding boxes. The SDK enables the experimental images header when images or typed Box questions are supplied. Use jev-preview or jev-1.14.0 for Box.
See the Images and Box guide for upload examples, limits, and presence checks. These features are experimental upstream.
Create a client from TYPESAFE_API_KEY and discover the models available to the account.
using var client = new TypeSafeClient(apiKey);
var models = await client.Models.ListAsync();Ask typed Noul, choice, and score questions in one request.
using var client = new TypeSafeClient(apiKey);
var questions = new QuestionSet();
var billing = questions.AddNoul("billing", new NoulQuestion("Is this message about billing?"));
var tone = questions.AddChoice("tone", ChoiceQuestion.FromValues(
["angry", "calm", "excited"], "What is the tone?"));
var urgency = questions.AddScore("urgency", ScoreQuestion.FromValues(
["Can wait", "Needs attention this week", "Needs attention today"], "How urgent is this?"));
var result = await client.SystemOneAsync("I was charged twice. Please help today.", questions);Send structured JSON state without reflection, including in NativeAOT applications.
using var client = new TypeSafeClient(apiKey);
var state = JsonContent.FromNode(new JsonObject
{
["subject"] = "Duplicate payment",
["message"] = "The same invoice appears twice.",
["customer_tier"] = "enterprise",
});
var questions = new Dictionary<string, Question>
{
["intent"] = ChoiceQuestion.FromValues(["billing", "support", "sales"]),
};
var response = await client.SystemOneAsync(state, questions);Project named answers into an application-owned response type with source-generated JSON metadata.
using var client = new TypeSafeClient(apiKey);
var questions = new Dictionary<string, Question>
{
["intent"] = ChoiceQuestion.FromValues(["billing", "support", "sales"]),
};
var response = await client.SystemOneAsync(
(JsonContent)"I need a refund for a duplicate invoice.",
questions,
ExampleJsonContext.Default.TriageProjection);Discover the model names and aliases available to the authenticated account.
using var client = new TypeSafeClient(apiKey);
var models = await client.Models.ListAsync();Evaluate independent states concurrently while limiting parallel requests.
using var client = new TypeSafeClient(apiKey);
var questions = new Dictionary<string, Question>
{
["spam"] = new NoulQuestion("Is this unsolicited advertising?"),
};
var responses = await client.SystemOneManyAsync(
["Buy now! Limited offer!", "Can we move our meeting to Tuesday?"],
questions,
maxConcurrency: 2);Decode cached HTTP bodies and preserve answer variants introduced by future API versions.
const string json = """
{"model":"jev-next","answers":{"known":{"type":"noul","noul":0.92},"future":{"type":"ranking","items":["a","b"]}},"usage":{"input_tokens":42,"output_tokens":4}}
""";
var response = TypeSafeClient.FromHttpResponse(json);Read typed Celeris explanations from a saved System One response without making a network request.
// Celeris explanations are keyed by question name. The same typed metadata is available on live responses and QuestionSetResult.Response.
const string json = """
{"model":"celeris-1-decision","answers":{"refund":{"type":"noul","noul":0.95}},
"usage":{"input_tokens":122,"output_tokens":15},
"x_celeris":{"explanations":{"refund":"The customer explicitly requests a refund."}}}
""";
var response = TypeSafeClient.FromHttpResponse(json);
Console.WriteLine(response.Get<NoulAnswer>("refund").Noul);
Console.WriteLine(response.Celeris!.Explanations["refund"]);Route arbitrary state to a strongly typed enum intent.
using var client = new TypeSafeClient(apiKey);
var router = new TypeSafeIntentRouter<TicketIntent>(client, "Choose the team that should own this ticket.");
var intent = await router.RouteAsync("I was charged twice for invoice 391.");Use TypeSafe judgments as agent tools and evaluation metrics. These integrations ship in the main package. Import Microsoft.Extensions.AI, Microsoft.Extensions.AI.Evaluation, TypeSafeAI.Extensions.AI, and TypeSafeAI.Extensions.AI.Evaluation.
using var client = new TypeSafeClient(apiKey);
var questions = new Dictionary<string, Question> { ["relevant"] = new NoulQuestion("Is the response relevant to the question?") };
// Expose fixed, reviewed questions to the calling agent; only state is supplied at invocation.
var function = TypeSafeAIFunctions.Create(client, questions, "judge_relevance", "Judge relevance.");
var result = await function.InvokeAsync(new AIFunctionArguments { ["state"] = "Question: hello. Response: hello." });
// The same judgment produces numeric evaluation metrics without a generative judge.
var evaluator = new TypeSafeEvaluator(client, questions, new TypeSafeEvaluatorOptions
{
Interpret = TypeSafeInterpretations.ByQuestion(new Dictionary<string, Func<TypeSafeMetricContext, EvaluationMetricInterpretation?>>
{
["relevant"] = TypeSafeInterpretations.NoulAtLeast(0.8),
}),
});
var evaluation = await evaluator.EvaluateAsync([new ChatMessage(ChatRole.User, "hello")],
new ChatResponse(new ChatMessage(ChatRole.Assistant, "hello")));Combine calibrated hazard probability and severity into an allow, review, or block decision.
var hazard = new QuestionHandle<NoulAnswer>("harm");
var severity = new QuestionHandle<ScoreAnswer>("severity");
var response = TypeSafeClient.FromHttpResponse("""
{"model":"jev-1","answers":{"harm":{"type":"noul","noul":0.8},"severity":{"type":"score","score":2.4,"confidence":0.9,"legend":{"0":"low","1":"medium","2":"high"},"probabilities":{"0":0.05,"1":0.1,"2":0.85}}},"usage":{"input_tokens":5,"output_tokens":2}}
""");
var assessment = new GuardrailAssessment(
GuardrailDirection.Input, new QuestionSetResult(response), [], null);
var policy = GuardrailPolicies.Thresholds(
new Dictionary<QuestionHandle<NoulAnswer>, GuardrailAction> { [hazard] = GuardrailAction.Block },
severity);Catch status-specific exceptions with the response body and retry metadata.
using var client = new TypeSafeClient("invalid-key", options: new TypeSafeClientOptions
{
Retry = new RetryPolicy { MaxAttempts = 1 },
});
var action = async () => await client.Models.ListAsync();Credential and payload headers cannot be accidentally replaced by caller options.
var options = new TypeSafeClientOptions();
options.DefaultHeaders["Authorization"] = "Bearer something-else";
var action = () => new TypeSafeClient("safe-placeholder", options: options);Open an issue in tryAGI/TypeSafeAI.
Use GitHub Discussions for design questions and usage help.
Join the tryAGI Discord for broader discussion across SDKs.
This project is supported by JetBrains through the Open Source Support Program.
