SystemOne.Extensions.OpenAI
1.0.0
dotnet add package SystemOne.Extensions.OpenAI --version 1.0.0
NuGet\Install-Package SystemOne.Extensions.OpenAI -Version 1.0.0
<PackageReference Include="SystemOne.Extensions.OpenAI" Version="1.0.0" />
<PackageVersion Include="SystemOne.Extensions.OpenAI" Version="1.0.0" />
<PackageReference Include="SystemOne.Extensions.OpenAI" />
paket add SystemOne.Extensions.OpenAI --version 1.0.0
#r "nuget: SystemOne.Extensions.OpenAI, 1.0.0"
#:package SystemOne.Extensions.OpenAI@1.0.0
#addin nuget:?package=SystemOne.Extensions.OpenAI&version=1.0.0
#tool nuget:?package=SystemOne.Extensions.OpenAI&version=1.0.0
SystemOne.Extensions.OpenAI
A modern .NET 11 client for the System One API, with integration with OpenAIClient and .NET Aspire hosting. Send a state and a set of named questions to receive choice, yes/no (noul), or score answers.
Key Features
- 🎯 Three question types:
SystemOneChoiceQuestion(pick one of named criteria),SystemOneNoulQuestion(yes/no answer), andSystemOneScoreQuestion(score on an ordered scale). - 🧩 Flexible state: send text,
JsonObject,JsonArray, astring[], or any serializable object as the shared state. - ⚙️ Rich options:
KeepAlive(durations or seconds), base64Images, and provider-specificAdditionalPropertiesmerged into the request body. - 🔗 OpenAI & Aspire integration: reuse an
OpenAIClientpipeline or registerISystemOneClientin .NET Aspire viaAddSystemOneClient(). - 📊 Typed answers: polymorphic
SystemOneChoiceAnswer,SystemOneNoulAnswer, andSystemOneScoreAnswerwith probabilities, confidence, legend, and token usage. - ⚠️ Consistent errors: all HTTP and parsing failures are surfaced as
SystemOneExceptionwith the status code and endpoint message.
Installation
Install the NuGet package in your application project:
dotnet add package SystemOne.Extensions.OpenAI
The package targets .NET 11 and depends on OpenAI and Aspire.OpenAI.
Quick Start & Usage
1. .NET Aspire integration
Register an OpenAI client with the decision key in the AppHost and chain .AddSystemOneClient():
var decision = builder.AddKeyedOpenAIClient("decision")
.AddSystemOneClient();
Configure the decision connection string with the endpoint, credentials, and model settings supported by Aspire.OpenAI. The extension uses the OpenAIClient registered for that key and registers an ISystemOneClient with the same key, plus an unkeyed ISystemOneClient registration. The model is read from Model= or Deployment= in the connection string. You can also specify it explicitly:
builder.AddKeyedOpenAIClient("decision")
.AddSystemOneClient(model: "my-model");
In the application service, inject the client with the same key — or without a key, using the unkeyed registration:
using SystemOne.Extensions.OpenAI;
using Microsoft.Extensions.DependencyInjection;
public sealed class DecisionService(
[FromKeyedServices("decision")] ISystemOneClient client)
{
public Task<SystemOneResponse> DecideAsync(CancellationToken cancellationToken) =>
client.DecideAsync(
state: "Does the proposal meet the criteria?",
questions: new Dictionary<string, SystemOneQuestion>
{
["approved"] = new SystemOneNoulQuestion
{
Instructions = "Determine whether the proposal should be approved.",
Criteria = new SystemOneNoulCriteria
{
Yes = "Meets the defined criteria.",
No = "Does not meet the defined criteria."
}
}
},
cancellationToken: cancellationToken);
}
The extension also provides AddKeyedSystemOneClient to register the System One client under a key different from the OpenAIClient key:
builder.AddKeyedOpenAIClient("decision")
.AddKeyedSystemOneClient("systemone");
The OpenAIClient key is used to resolve the OpenAI client; ISystemOneClient is registered under systemone.
2. Creating a client without Aspire
You can also create the client directly:
using SystemOne.Extensions.OpenAI;
using var client = new SystemOneClient(
model: "my-model",
endpoint: new Uri("http://localhost:11434"),
apiKey: "optional-key");
SystemOneResponse response = await client.DecideAsync(
state: "Content to analyze",
questions: new Dictionary<string, SystemOneQuestion>
{
["category"] = new SystemOneChoiceQuestion
{
Instructions = "Choose the most appropriate category.",
Criteria = new Dictionary<string, string?>
{
["support"] = "Support request",
["sales"] = "Sales request"
}
}
},
cancellationToken: cancellationToken);
The client sends a POST to v1/systemone, appended to the endpoint unless the address already ends with that path (an address ending in v1 is normalized as well).
You can also create a System One client from an existing OpenAIClient, reusing its pipeline and endpoint:
using OpenAI;
using SystemOne.Extensions.OpenAI;
ISystemOneClient client = openAiClient.GetSystemOneClient("my-model");
3. State
DecideAsync accepts a SystemOneState, which is implicitly created from:
- A text string:
client.DecideAsync("plain text state", questions) - A
JsonObjectorJsonArray - A
string[](converted to a JSON array of strings) - Any serializable object via
SystemOneState.FromObject(value)
4. Options
Use SystemOneOptions to send additional parameters, images, or the KeepAlive option:
var options = new SystemOneOptions
{
KeepAlive = "10m", // or seconds (int), a TimeSpan, or 10m-style durations
Images = ["<base64-encoded image>"],
AdditionalProperties = new Dictionary<string, object?>
{
["temperature"] = 0.2
}
};
KeepAlive specifies how long the model stays loaded after the request and is serialized as keep_alive. Images is serialized as images. AdditionalProperties merges provider-specific properties into the request body at the root level.
5. Question types
SystemOneChoiceQuestion: selects one item from named criteria (2 to 255 criteria).SystemOneNoulQuestion: produces a yes/no answer. The criteria are optional and describe thefalseandtrueoutcomes.SystemOneScoreQuestion: scores the state using an ordered scale of criteria (2 to 10 criteria).
var questions = new Dictionary<string, SystemOneQuestion>
{
["label"] = new SystemOneChoiceQuestion
{
Instructions = "Which label fits this ticket?",
Criteria = new Dictionary<string, string?>
{
["billing"] = "Payments and refunds",
["bug"] = "Software errors",
["account"] = "Login and account access"
}
},
["urgent"] = new SystemOneNoulQuestion
{
Instructions = "Does this ticket need attention within the hour?",
Criteria = new SystemOneNoulCriteria
{
No = "No attention is needed now",
Yes = "Needs attention within the hour"
}
},
["urgency"] = new SystemOneScoreQuestion
{
Instructions = "How urgently does this ticket need a response?",
Criteria =
[
"Routine: no time pressure",
"Soon: a customer is inconvenienced",
"Immediate: a critical service is unavailable"
]
}
};
6. Handling the response
DecideAsync returns a SystemOneResponse with Model, Answers keyed by question name, and Usage. Each answer is deserialized according to the question type, so cast it to the matching answer class to read its data:
SystemOneResponse response = await client.DecideAsync(state, questions);
// Choice answer: the selected option, its probabilities, and the model's confidence.
var category = (SystemOneChoiceAnswer)response.Answers["category"];
Console.WriteLine(category.Choice); // "support"
Console.WriteLine(category.Confidence); // 0.8906
Console.WriteLine(category.Probabilities["bug"]); // 0.9781
// Noul answer: the yes/no answer as a numeric value.
var urgent = (SystemOneNoulAnswer)response.Answers["urgent"];
Console.WriteLine(urgent.Noul); // 0.959
// Score answer: the score, the legend describing each level, and probabilities.
var urgency = (SystemOneScoreAnswer)response.Answers["urgency"];
Console.WriteLine(urgency.Score); // 0.8308
Console.WriteLine(urgency.Legend["2"]); // "Immediate: a critical service is unavailable"
// Token usage of the generation operation.
Console.WriteLine(response.Usage.InputTokens); // 174
Console.WriteLine(response.Usage.OutputTokens); // 1
response.Model contains the model that generated the response, as reported by the endpoint.
Error handling
Failed requests throw a SystemOneException carrying the HTTP status code and the error message returned by the endpoint. Responses with an empty or unparsable body are surfaced the same way.
License
This project is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net11.0 is compatible. |
-
net11.0
- Aspire.OpenAI (>= 13.6.4-preview.1.26523.11)
- OpenAI (>= 2.15.0)
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.0.0 | 52 | 11/11/2026 |