Skip to content

TypeSafeAI

First-class, NativeAOT-ready .NET SDK for TypeSafe AI: generated transport plus an ergonomic typed API.

Nuget package dotnet License: MIT Discord

Generated transport, designed API

The raw client is regenerated from the official OpenAPI definition; typed questions, batching, routing, DI, and MEAI integrations are maintained as handwritten extensions.

Complete ecosystem parity

Includes the official JavaScript/Python examples plus the strongest .NET features from TypeSafeAI.Net, typesafe-sdk-dotnet, and Jev.Net.

Modern .NET

Targets current .NET practices including nullability, trimming, NativeAOT awareness, and source-generated serialization.

Docs from examples

Examples stay in sync between the README, MkDocs site, and integration tests through the AutoSDK docs pipeline.

Ecosystem maintenance

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.

Usage

1
2
dotnet add package tryAGI.TypeSafeAI
# Microsoft.Extensions.AI middleware, tools, routing, and evaluation are included.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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.

Generate

Create a client from TYPESAFE_API_KEY and discover the models available to the account.

1
2
3
using var client = new TypeSafeClient(apiKey);

var models = await client.Models.ListAsync();

Mixed questions

Ask typed Noul, choice, and score questions in one request.

1
2
3
4
5
6
7
8
9
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);

Structured state

Send structured JSON state without reflection, including in NativeAOT applications.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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);

Custom response projection

Project named answers into an application-owned response type with source-generated JSON metadata.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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);

List models

Discover the model names and aliases available to the authenticated account.

1
2
3
using var client = new TypeSafeClient(apiKey);

var models = await client.Models.ListAsync();

Bounded batch

Evaluate independent states concurrently while limiting parallel requests.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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);

Replay and forward compatibility

Decode cached HTTP bodies and preserve answer variants introduced by future API versions.

1
2
3
4
5
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);

Typed intent routing

Route arbitrary state to a strongly typed enum intent.

1
2
3
4
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.");

AI functions and evaluation

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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")));

Guardrail policy

Combine calibrated hazard probability and severity into an allow, review, or block decision.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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);

Typed API errors

Catch status-specific exceptions with the response body and retry metadata.

1
2
3
4
5
6
using var client = new TypeSafeClient("invalid-key", options: new TypeSafeClientOptions
{
    Retry = new RetryPolicy { MaxAttempts = 1 },
});

var action = async () => await client.Models.ListAsync();

Protected headers

Credential and payload headers cannot be accidentally replaced by caller options.

1
2
3
4
var options = new TypeSafeClientOptions();
options.DefaultHeaders["Authorization"] = "Bearer something-else";

var action = () => new TypeSafeClient("safe-placeholder", options: options);

Support

Bugs

Open an issue in tryAGI/TypeSafeAI.

Ideas and questions

Use GitHub Discussions for design questions and usage help.

Community

Join the tryAGI Discord for broader discussion across SDKs.

Acknowledgments

JetBrains logo

This project is supported by JetBrains through the Open Source Support Program.