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" />
<PackageReference Include="PromptForge.Testing" />
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
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#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
#tool nuget:?package=PromptForge.Testing&version=0.2.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
PromptForge
Strongly-typed LLM prompt construction, response parsing, validation, retry, and multi-provider management for .NET 9+
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 | Versions 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.
-
net9.0
- PromptForge.Core (>= 0.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.