PromptForge.Testing 0.2.0

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

PromptForge

Strongly-typed LLM prompt construction, response parsing, validation, retry, and multi-provider management for .NET 9+

NuGet License: MIT


Why PromptForge?

Challenge PromptForge answer
LLM returns JSON but parsing fails Automatic retry with corrective feedback
Prompts drift out of sync with code Compile-time {{Variable}} validation (PF0001)
Response model needs validation First-class DataAnnotations + IValidatableObject support
Supporting multiple providers Single ILlmClient interface, swap providers in config
Observability OpenTelemetry GenAI semantic convention spans

Packages

Package Purpose
PromptForge.Core Abstractions, builder chain, retry, parsing, validation, DI, telemetry
PromptForge.OpenAI OpenAI Chat Completions + SSE streaming
PromptForge.Anthropic Anthropic Messages API + SSE streaming
PromptForge.Ollama Ollama local models + streaming
PromptForge.SourceGen Roslyn source generator for compile-time safety
PromptForge.Testing FakeLlmProvider for unit tests without network calls

Quick Start

dotnet add package PromptForge.Core
dotnet add package PromptForge.OpenAI

Program.cs (minimal API or console)

using Microsoft.Extensions.DependencyInjection;
using PromptForge.Core.Abstractions;
using PromptForge.Core.Builder;
using PromptForge.Core.DependencyInjection;
using PromptForge.OpenAI;
using System.ComponentModel.DataAnnotations;

var services = new ServiceCollection();
services.AddLlmKit(options =>
{
    options.UseOpenAI(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
    options.Defaults.Temperature(0.2).MaxTokens(512);
});

var sp = services.BuildServiceProvider();
var llm = sp.GetRequiredService<ILlmClient>();

// Build a typed prompt
var prompt = Prompt.For<ReviewRequest>()
    .System("You are a product review analyst. Respond in JSON.")
    .User("Analyze: {{ReviewText}}")
    .Build(new ReviewRequest { ReviewText = "Amazing headphones!" });

// Execute with retry and typed parsing
ReviewAnalysis result = await llm
    .Complete(prompt)
    .ParseAs<ReviewAnalysis>()
    .WithRetry(maxAttempts: 3);

Console.WriteLine($"Sentiment: {result.Sentiment} ({result.Confidence:P0})");

// --- Models ---
record ReviewRequest(string ReviewText);

class ReviewAnalysis
{
    [AllowedValues("positive", "negative", "neutral", "mixed")]
    public string Sentiment { get; set; } = string.Empty;

    [Range(0.0, 1.0)]
    public double Confidence { get; set; }
}

Multi-Turn Conversations

using PromptForge.Core.Conversation;

var conversation = new ConversationSession()
    .WithSystemMessage("You are a helpful assistant.")
    .WithTruncationStrategy(TruncationStrategy.TrimOldest(maxMessages: 20));

while (true)
{
    var userInput = Console.ReadLine()!;
    var reply = await llm.Continue(conversation)
        .WithUserMessage(userInput)
        .AsText();
    Console.WriteLine(reply);
    // conversation.Messages is automatically updated
}

Source Generator

using PromptForge.Core.Attributes;
using System.ComponentModel.DataAnnotations;

[LlmResponse]
public partial class InvoiceData
{
    [Required]
    public string InvoiceNumber { get; set; } = string.Empty;

    [Range(0, 1_000_000)]
    public decimal TotalAmount { get; set; }
}
// Generated: InvoiceData.JsonSchema, InvoiceData.Parse(string), InvoiceData.GetSchemaFragment()

Template validation:

[PromptTemplate]
public partial class ExtractInvoicePrompt : PromptTemplate<InvoiceInput, InvoiceData>
{
    public override string System => "Extract invoice fields from raw OCR text.";
    public override string User => "Invoice text:\n\n{{RawText}}";
    // {{UnknownField}} → compile error PF0001
}

DI Configuration

services.AddLlmKit(options =>
{
    // Primary provider
    options.UseOpenAI(apiKey, model: "gpt-4o");

    // Fallback chain (tries Anthropic if OpenAI fails with 429/503)
    // options.UseProviderChain(chain => chain
    //     .Primary(openAiProvider)
    //     .FallbackTo(anthropicProvider), modelName: "gpt-4o");

    // Named clients for different quality tiers
    options.AddAnthropicClient("quality", anthropicKey, "claude-sonnet-4-6");

    // Middleware pipeline
    options.UseLogging();
    options.UseCaching(o => o.Duration = TimeSpan.FromMinutes(10));

    // Defaults applied to every call
    options.Defaults
        .Temperature(0.2)
        .MaxTokens(1024)
        .MaxRetries(3);

    // Cost tracking
    options.OnUsage((usage, ctx) =>
        metrics.RecordTokens(ctx.ProviderName, usage.TotalTokens));

    // OpenTelemetry
    options.EnableOpenTelemetry();
});

Testing

using PromptForge.Testing;

var fake = new FakeLlmProvider()
    .Returns("{\"sentiment\":\"positive\",\"confidence\":0.9}")
    .ThenThrows(new PromptForgeRateLimitException("fake"));

var services = new ServiceCollection();
services.AddLlmKit(o => o.UseProvider(fake, "fake-model"));
var llm = services.BuildServiceProvider().GetRequiredService<ILlmClient>();

// ... run your logic ...

fake.Verify(calls => calls.Count == 1);

Exception Hierarchy

PromptForgeException
├── PromptForgeParseException       — JSON deserialization failed
├── PromptForgeValidationException  — DataAnnotations validation failed
├── PromptForgeTemplateException    — Unresolved {{Variable}} at runtime
├── PromptForgeProviderException    — HTTP 4xx/5xx from provider
│   └── PromptForgeRateLimitException — HTTP 429
├── PromptForgeTimeoutException     — Request exceeded configured timeout
└── PromptForgeRetryExhaustedException — All attempts exhausted

OperationCanceledException is never caught and always propagates.


License

MIT — see LICENSE

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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.2.0 102 3/20/2026
0.1.0 99 3/20/2026