Ozakboy.Ai.Abstractions 0.1.0

dotnet add package Ozakboy.Ai.Abstractions --version 0.1.0
                    
NuGet\Install-Package Ozakboy.Ai.Abstractions -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ozakboy.Ai.Abstractions" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ozakboy.Ai.Abstractions" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Ozakboy.Ai.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ozakboy.Ai.Abstractions --version 0.1.0
                    
#r "nuget: Ozakboy.Ai.Abstractions, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ozakboy.Ai.Abstractions@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ozakboy.Ai.Abstractions&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Ozakboy.Ai.Abstractions&version=0.1.0
                    
Install as a Cake Tool

Ozakboy.Ai.Abstractions

Vendor-neutral abstractions for asking a language model a question and getting a typed object back.

English | 繁體中文

dotnet add package Ozakboy.Ai.Abstractions

Requires .NET 10. Depends on nothing but the .NET BCL and Ozakboy.Core.Abstractions.


Why this exists

An application that consults a language model should not be written against one particular way of reaching it. A local CLI subprocess today, an HTTP API tomorrow, a scripted fake in the tests — the calling code should be the same in all three.

So the contract is deliberately narrow:

Task<Result<AiCompletion<T>>> CompleteAsync<T>(AiRequest request, CancellationToken ct) where T : class;

Plain text in. A typed object out. Every expected failure — timeout, backend unavailable, output that does not validate — comes back as a Result, never as an exception.

Only the shape of the output is guaranteed, never its correctness. What comes back deserialises into T and passes the validator you supplied. Whether the values in it are sensible, or safe to act on, is still yours to decide.


Quick start

using Ozakboy.Ai.Abstractions;

internal sealed class Summary
{
    [JsonPropertyName("headline")] public required string Headline { get; init; }
    [JsonPropertyName("score")]    public required decimal Score { get; init; }
}

var request = new AiRequest
{
    SystemPrompt = "You summarise trading journals. Be terse.",
    UserPrompt = journalText,
    Timeout = TimeSpan.FromMinutes(3),
    MaxRepairAttempts = 1,
}.WithValidator<Summary>(summary => summary.Score is >= 0m and <= 1m
    ? Result.Success()
    : Result.Failure("score.range", "score must be between 0 and 1"));

var result = await client.CompleteAsync<Summary>(request, cancellationToken);

if (result.IsFailure)
{
    // AiErrorCodes.Timeout / Unavailable / InvalidOutput / Cancelled / RateLimited
    logger.LogWarning("AI unavailable: {Code}", result.Error.Code);
    return FallbackPlan();
}

var completion = result.GetValueOrThrow();
logger.LogInformation(
    "{Model} {In}/{Out} tokens in {Duration}, {Attempts} attempt(s)",
    completion.Usage.Model, completion.Usage.InputTokens, completion.Usage.OutputTokens,
    completion.Usage.Duration, completion.Usage.Attempts);

How JSON Schema validation actually works here

This is the part worth reading before you trust the output.

There is no JSON Schema validator in this package, and that is deliberate. The .NET BCL does not ship one, and the dependency policy of this package family forbids third-party libraries. So "validation" here is two checks made by the type system rather than one check made by a schema engine:

  1. Strict deserialisation (AiStructuredOutput.StrictOptions).

    • UnmappedMemberHandling.Disallow — a property the schema did not define fails the parse. Silently dropping an invented "confidence_note" would leave no trace that the model went off contract, and next time the invented name may be one that matters.
    • PropertyNameCaseInsensitive = false"Symbol" does not quietly match symbol. Case-insensitive matching would turn the names in the schema into suggestions.
    • required members are enforced by System.Text.Json itself: a missing one throws, which becomes a failed Result.
    • JSON comments are tolerated (ReadCommentHandling.Skip). Models like to annotate their JSON, and spending a repair round trip on a // line is waste.

    Between them these cover the type, required, and no-additional-properties families of schema constraint — which is most of what a schema actually says about an object.

  2. Your semantic validator (AiRequest.WithValidator<T>). Numeric ranges, enum allow-lists, cross-field consistency. Nothing is lost by doing these here, because JSON Schema does not enforce minimum / maximum in a structured-output setting anyway. If a value must not exceed 2%, that is a check you write, in either design.

The generated schema (AiStructuredOutput.CreateSchema<T>(), derived by the BCL's JsonSchemaExporter) therefore has two jobs: it is appended to the prompt so the model knows what shape to answer with, and it can be handed to a backend that enforces a format natively. It is never walked clause by clause against the returned JSON.

The repair round

When either check fails, the implementation hands the failure message back to the model and asks again, up to MaxRepairAttempts times (default 1, maximum 3). AiUsage.Attempts reports how many sends it actually took.

When the budget runs out, the result is an ai.invalid_output failure whose Error.Data carries the final validation message and the first 500 characters of the output (AiErrorDataKeys). That data is for a person — it is the difference between "the prompt was underspecified" and "the schema asks for too much" — and should not be branched on.


Why the model is given no tools

The contract is text in, JSON out, and nothing else. Implementations of IAiCompletionClient are expected to run the model with no tool access at all: no file reads, no shell, no network fetches of its own.

  • Everything the model is allowed to see is in the prompt. The caller assembles that context and can therefore redact it. A model that can read files decides for itself what to look at, and the caller loses the ability to say what is off limits.
  • The output is a recommendation, not an action. A request produces one JSON object, which the caller is free to reject. Tools would let the model change the world on the way to answering, and the caller's validation would then be arriving after the fact.
  • It makes the call reproducible. The same prompt gives a comparable answer regardless of what happens to be in the working directory.
  • It is faster and cheaper. No tool round trips, no directory scanning before the first token.

If a caller needs the model to see a file, the caller reads the file and puts it in UserPrompt.


What is in the package

Type Purpose
IAiCompletionClient The one-method contract.
AiRequest Prompts, model and timeout overrides, repair budget, optional semantic validator. Non-generic, so one request can be reused for different output types.
AiCompletion<T> The validated value, the raw output text, and the metering.
AiUsage Model actually used, token counts, cache tokens, decimal cost, duration, attempts.
AiStructuredOutput Schema generation, prompt instructions, fence stripping, strict deserialisation.
AiErrorCodes / AiErrors / AiErrorDataKeys The error contract, with one place that maps a code to a category.

Notes on two design choices

AiRequest is a class, not a record. A record's generated ToString() prints every property, and UserPrompt in practice holds account and performance data — logging the request object would log all of it.

The validator is stored as a Delegate. The expected output type belongs to the call, not to the request, so AiRequest carries no generic parameter. WithValidator<T> and GetValidator<T> are the typed way in and out; using a request built with WithValidator<A> on CompleteAsync<B> throws, because dropping the validation silently would leave the caller with no way to notice.


Implementations

  • Ozakboy.Ai.ClaudeCli — runs the locally installed Claude Code CLI as a subprocess, with no tools and no session persistence.

License

MIT. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 91 9/13/2026

0.1.0 — 首次發佈。First release.