Meeucg.FitFlow.AIServices 1.1.2

dotnet add package Meeucg.FitFlow.AIServices --version 1.1.2
                    
NuGet\Install-Package Meeucg.FitFlow.AIServices -Version 1.1.2
                    
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="Meeucg.FitFlow.AIServices" Version="1.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Meeucg.FitFlow.AIServices" Version="1.1.2" />
                    
Directory.Packages.props
<PackageReference Include="Meeucg.FitFlow.AIServices" />
                    
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 Meeucg.FitFlow.AIServices --version 1.1.2
                    
#r "nuget: Meeucg.FitFlow.AIServices, 1.1.2"
                    
#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 Meeucg.FitFlow.AIServices@1.1.2
                    
#: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=Meeucg.FitFlow.AIServices&version=1.1.2
                    
Install as a Cake Addin
#tool nuget:?package=Meeucg.FitFlow.AIServices&version=1.1.2
                    
Install as a Cake Tool

Meeucg.FitFlow.AIServices

Meeucg.FitFlow.AIServices is a .NET library for FitFlow that extends the OpenAI SDK with a more comfortable service layer for text AI workflows, typed outputs, and embeddings.

It provides dependency-injection registration, configurable text and embedding models, one-shot prompts, chat-context completions, JSON-schema typed responses, retry handling, embeddings, and optional validators for AI responses.

Installation

dotnet add package Meeucg.FitFlow.AIServices --version 1.0.9

Quick Start

1. Add Configuration

Add text AI, text model, and embedding model configuration to your application settings.

{
  "TextAI": {
    "ApiKey": "<your-api-key>",
    "ApiEndpoint": "https://api.openai.com/v1",
    "RetryAfter": "00:03:00",
    "RetryCount": 3
  },
  "EmbeddingAI": {
    "ApiKey": "<your-api-key>",
    "ApiEndpoint": "https://api.openai.com/v1",
    "RetryAfter": "00:03:00",
    "RetryCount": 3
  },
  "TextAIModels": {
    "DefaultModel": {
      "ModelAlias": "default",
      "ModelName": "<openai-chat-model>",
      "SupportsJsonOutput": true,
      "SupportsFunctionCalling": false,
      "RequestBodyExtensions": {}
    },
    "AlternativeModels": [
      {
        "ModelAlias": "fast",
        "ModelName": "<openai-chat-model>",
        "SupportsJsonOutput": true,
        "SupportsFunctionCalling": false,
        "RequestBodyExtensions": {}
      }
    ]
  },
  "EmbeddingAIModels": {
    "DefaultModel": {
      "ModelAlias": "embedding",
      "ModelName": "<openai-embedding-model>"
    },
    "AlternativeModels": [
      {
        "ModelAlias": "small-embedding",
        "ModelName": "<openai-embedding-model>"
      }
    ]
  }
}

2. Register Services

Register AIServices in your DI container.

using AIServices.ServiceBuilders;

builder.Services.AddAIServices(
    builder.Configuration.GetSection("TextAI"),
    builder.Configuration.GetSection("TextAIModels"),
    builder.Configuration.GetSection("EmbeddingAI"),
    builder.Configuration.GetSection("EmbeddingAIModels"));

AddAIServices registers only the AI services that have model configuration. If TextAIModels is missing, ITextAI is not registered. If EmbeddingAI/EmbeddingAIModels are missing, IEmbeddingAI is not registered. At least one model configuration section must be present.

3. Send a One-Shot Prompt

Use ITextAI for a single prompt without chat history.

using AIServices.Abstractions;

app.MapPost("/ask", async (ITextAI textAI, string prompt) =>
{
    var result = await textAI.OneShotResponse(prompt);

    return result.IsSuccess
        ? Results.Ok(result.Response)
        : Results.Problem("The AI request failed.");
});

Chat Context

Use Chat when the response should consider previous messages or an initial system prompt.

using AIServices.Abstractions;
using AIServices.Entities;
using AIServices.Models;

var chat = new Chat(
    chatInitialState: new ChatInitialState
    {
        SystemPrompt = "You are a concise fitness assistant."
    });

var requestText = "Give me a short warm-up for leg day.";

var result = await textAI.CompleteChat(new TextAIRequest
{
    ChatContext = chat,
    RequestText = requestText
});

if (result is { IsSuccess: true, Response: not null })
{
    chat.AddChatPair(new ChatPair
    {
        Request = requestText,
        Response = result.Response
    });
}

CompleteChat and CompleteChatTyped<T> do not add messages to the chat automatically. Add a ChatPair yourself after a successful response.

Embeddings

Use IEmbeddingAI to generate an embedding vector for a single text input.

using AIServices.Abstractions;

app.MapPost("/embed", async (IEmbeddingAI embeddingAI, string text) =>
{
    var result = await embeddingAI.EmbedText(text);

    return result.IsSuccess
        ? Results.Ok(result.Embedding.ToArray())
        : Results.Problem("The embedding request failed.");
});

EmbeddingAI has independent API connection and retry options. If every retry attempt fails, it logs the failure and returns EmbeddingAIResponse.NullResponse.

Typed Responses

Use OneShotResponseTyped<T> or CompleteChatTyped<T> when you want a strongly typed response. The selected model must have SupportsJsonOutput set to true.

using System.ComponentModel;
using AIServices.Abstractions;

public sealed record WorkoutSuggestion
{
    [Description("A short title for the workout.")]
    public required string Title { get; init; }

    [Description("A list of exercises to perform.")]
    public required IReadOnlyList<string> Exercises { get; init; }

    [Description("Estimated workout duration in minutes.")]
    public required int DurationMinutes { get; init; }
}

var result = await textAI.OneShotResponseTyped<WorkoutSuggestion>(
    "Create a beginner-friendly full-body workout.");

if (result is { IsSuccess: true, Response: not null })
{
    var workout = result.Response;
    Console.WriteLine($"{workout.Title}: {workout.DurationMinutes} minutes");
}

DescriptionAttribute values are included in generated JSON schemas by the default JSON configuration.

Generated schemas are cached per response type. The cache stores both the JSON string and BinaryData representation, so repeated typed calls for the same T reuse the same schema payload.

Validators

Register validators when typed AI responses need extra application-level validation. If a validator is registered for T, TextAI runs it before returning a successful typed result.

using AIServices.Abstractions;

public sealed class WorkoutSuggestionValidator : IValidatorForAI<WorkoutSuggestion>
{
    public Type GetValidatorType() => typeof(WorkoutSuggestion);

    public ValueTask<bool> ValidateAsync(
        WorkoutSuggestion? request,
        CancellationToken cancellationToken)
    {
        var isValid = request is not null
            && request.DurationMinutes > 0
            && request.Exercises.Count > 0;

        return ValueTask.FromResult(isValid);
    }
}

Register the validator as the non-generic IValidatorForAI abstraction so the validator manager can discover it.

using AIServices.Abstractions;

builder.Services.AddSingleton<IValidatorForAI, WorkoutSuggestionValidator>();

Keyed Models

Every configured text model alias is registered as a keyed ITextAI. Use this when you want to choose a specific configured text model.

using AIServices.Abstractions;
using Microsoft.Extensions.DependencyInjection;

var fastAI = serviceProvider.GetRequiredKeyedService<ITextAI>("fast");

var result = await fastAI.OneShotResponse("Summarize today's workout plan.");

The unkeyed ITextAI uses TextAIModels:DefaultModel.

Every configured embedding model alias is registered as a keyed IEmbeddingAI. The unkeyed IEmbeddingAI uses EmbeddingAIModels:DefaultModel.

using AIServices.Abstractions;
using Microsoft.Extensions.DependencyInjection;

var embeddingAI = serviceProvider.GetRequiredKeyedService<IEmbeddingAI>("small-embedding");

var result = await embeddingAI.EmbedText("Embed this text.");

Additional Request JSON

Some OpenAI-compatible providers expose request fields before the SDK has first-class options for them. Add those fields to RequestBodyExtensions on the model configuration. The value can be a nested JSON object or array, not only flat key-value pairs.

{
  "TextAIModels": {
    "DefaultModel": {
      "ModelAlias": "GPT-OSS 120B",
      "ModelName": "openai/gpt-oss-120b",
      "SupportsJsonOutput": true,
      "SupportsFunctionCalling": true,
      "RequestBodyExtensions": {
        "reasoning_effort": "minimal",
        "metadata": {
          "source": "fitflow",
          "features": ["typed-output", "interview"]
        }
      }
    }
  }
}

TextAI builds requests through the OpenAI SDK protocol method so these extra fields are merged into the final JSON body while SDK models still serialize chat messages and typed JSON response formats.

JSON Options

AddAIServices uses default JSON options that enable camel-case property names, enum strings, relaxed JSON escaping, and schema descriptions from DescriptionAttribute.

You can replace the JSON and schema options during registration.

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Schema;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using AIServices.Models.Options;
using AIServices.ServiceBuilders;

builder.Services.AddAIServices(
    builder.Configuration.GetSection("TextAI"),
    builder.Configuration.GetSection("TextAIModels"),
    builder.Configuration.GetSection("EmbeddingAI"),
    builder.Configuration.GetSection("EmbeddingAIModels"),
    options =>
    {
        options.JsonSerializerOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
            WriteIndented = true,
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };

        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());

        options.JsonSchemaExporterOptions = new JsonSchemaExporterOptions
        {
            TreatNullObliviousAsNonNullable = true
        };
    });

API Documentation

See DOCUMENTATION.md for the generated API documentation based on XML summaries.

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
1.1.2 80 5/21/2026
1.1.0 52 5/7/2026
1.0.9 74 5/3/2026
1.0.8 64 4/22/2026