Compendium.Adapters.OpenAI
1.1.0-preview.6
dotnet add package Compendium.Adapters.OpenAI --version 1.1.0-preview.6
NuGet\Install-Package Compendium.Adapters.OpenAI -Version 1.1.0-preview.6
<PackageReference Include="Compendium.Adapters.OpenAI" Version="1.1.0-preview.6" />
<PackageVersion Include="Compendium.Adapters.OpenAI" Version="1.1.0-preview.6" />
<PackageReference Include="Compendium.Adapters.OpenAI" />
paket add Compendium.Adapters.OpenAI --version 1.1.0-preview.6
#r "nuget: Compendium.Adapters.OpenAI, 1.1.0-preview.6"
#:package Compendium.Adapters.OpenAI@1.1.0-preview.6
#addin nuget:?package=Compendium.Adapters.OpenAI&version=1.1.0-preview.6&prerelease
#tool nuget:?package=Compendium.Adapters.OpenAI&version=1.1.0-preview.6&prerelease
Compendium.Adapters.OpenAI
Direct adapter for the OpenAI REST API (https://api.openai.com/v1) that implements IAIProvider from Compendium.Abstractions.AI.
| What | Value |
|---|---|
| Target framework | net9.0;net10.0 |
| Abstractions pin | Compendium.Abstractions.AI 1.0.1 |
| HTTP stack | Typed HttpClient + Microsoft.Extensions.Http.Resilience (standard pipeline) + System.Text.Json |
| Auth | Authorization: Bearer <ApiKey>, optional OpenAI-Organization / OpenAI-Project headers |
| Surface | Chat completions (sync + SSE streaming), embeddings (batched), tool calling, structured outputs |
| Tests | xUnit 2.9 + FluentAssertions + NSubstitute + RichardSzalay.MockHttp |
Sits alongside Compendium.Adapters.OpenRouter (gateway routing). Use this adapter when you want to talk directly to OpenAI — for latency, billing isolation, or OpenAI-only models (e.g. text-embedding-3-large).
Installation
dotnet add package Compendium.Adapters.OpenAI
Configuration
Register at startup:
services.AddCompendiumOpenAI(opt =>
{
opt.ApiKey = builder.Configuration["OpenAI:ApiKey"]!;
opt.DefaultModel = "gpt-4o-mini";
opt.DefaultEmbeddingModel = "text-embedding-3-small";
});
…or bind from IConfiguration (section OpenAI):
services.AddCompendiumOpenAI(builder.Configuration);
{
"OpenAI": {
"ApiKey": "sk-...",
"DefaultModel": "gpt-4o-mini",
"DefaultEmbeddingModel": "text-embedding-3-small",
"Organization": "org-...", // optional
"Project": "proj_...", // optional
"TimeoutSeconds": 120,
"MaxEmbeddingsBatchSize": 2048
}
}
The standard resilience pipeline (AddStandardResilienceHandler) is applied automatically; tune retry/timeout behaviour by configuring OpenAIOptions.TimeoutSeconds and RetryAttempts.
Usage
Chat (sync)
var provider = sp.GetRequiredService<IAIProvider>();
var result = await provider.CompleteAsync(new CompletionRequest
{
Model = "gpt-4o-mini",
SystemPrompt = "Be concise.",
Messages = new List<Message> { Message.User("Pick a colour.") },
MaxTokens = 64
});
if (result.IsSuccess) Console.WriteLine(result.Value.Content);
Chat (streaming)
await foreach (var chunk in provider.StreamCompleteAsync(request))
{
if (chunk.IsSuccess) Console.Write(chunk.Value.ContentDelta);
}
The final chunk carries IsFinal=true, FinishReason, and Usage (token counts).
Embeddings (batched)
EmbedAsync chunks the input list into requests no larger than MaxEmbeddingsBatchSize (default 2048, OpenAI's hard limit) and aggregates the embeddings, re-mapping indices to the original input order.
var result = await provider.EmbedAsync(new EmbeddingRequest
{
Model = "text-embedding-3-small",
Inputs = manyTexts
});
Tool calling
using Compendium.Adapters.OpenAI.Tools;
using Compendium.Abstractions.AI.Agents.Models;
var tools = new List<AgentTool>
{
new(
Name: "get_weather",
Description: "Returns the current weather for the named city.",
InputSchemaJson: """{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}""")
};
var request = new CompletionRequest
{
Model = "gpt-4o-mini",
Messages = new List<Message> { Message.User("What's the weather in Paris?") }
}.WithTools(tools, toolChoice: "auto");
var result = await provider.CompleteAsync(request);
foreach (var call in result.Value.GetToolCalls())
{
Console.WriteLine($"{call.ToolName}({call.ArgumentsJson})");
}
Tool definitions are serialised into OpenAI's tools[] array; the assistant's tool_calls are surfaced as IReadOnlyList<AgentToolInvocation> via CompletionResponse.GetToolCalls(). ResultText / Latency on each invocation are left empty/zero — your tool registry fills them in when it executes the call.
Structured outputs
using Compendium.Adapters.OpenAI.StructuredOutputs;
var schema = """
{
"type":"object",
"properties":{ "city": {"type":"string"}, "tempC": {"type":"number"} },
"required":["city","tempC"],
"additionalProperties": false
}
""";
var request = new CompletionRequest
{
Model = "gpt-4o-mini",
Messages = new List<Message> { Message.User("Weather in Paris as JSON.") }
}.WithStructuredOutput(schema, schemaName: "Weather");
// or: plain json_object mode (no schema)
request = request.WithJsonMode();
Globally opt every request into response_format: { type: "json_object" } with OpenAIOptions.UseStructuredOutputsByDefault = true.
The provider-agnostic way is CompletionRequest.ResponseSchema (new ResponseSchema { Name = "Weather", SchemaJson = schema }):
the same response_format.json_schema here, the same request on every adapter, and IAIProvider.SupportsJsonSchema(model) to ask first. It
takes precedence over the keys above. See the repository README, "Structured output".
Cancellation
Every async surface accepts a CancellationToken. The adapter:
- re-throws
OperationCanceledExceptionwhen the caller cancelled, so callers canawaitcleanly; - maps non-caller cancellations (timeouts) into
Result.Failure(AIErrors.Timeout(...)); - aborts streaming loops on cancellation between SSE chunks.
Error mapping
| HTTP | Error.Code |
|---|---|
| 401 | AI.InvalidApiKey |
| 402 | AI.InsufficientCredits |
| 404 | AI.ModelNotFound |
| 429 | AI.RateLimitExceeded |
| 5xx / other | AI.ProviderError |
| Timeout (non-cancellation) | AI.Timeout |
Observability
OpenAIOptions.EnableLogging = true emits the raw request/response bodies at Debug level via the injected ILogger<OpenAIHttpClient>. Keep it off in production — payloads can contain user PII.
Sample
samples/01-chat-with-tool wires up a 1-tool agent and dumps the model's tool-call decision. Run with OPENAI_API_KEY=sk-... dotnet run --project samples/01-chat-with-tool.
Integration tests
Live tests live in tests/Integration/Compendium.Adapters.OpenAI.IntegrationTests. They:
- skip automatically when
OPENAI_API_KEYis unset; - are excluded from the default CI run via
--filter "FullyQualifiedName!~IntegrationTests"; - hit
gpt-4o-mini/text-embedding-3-smallso they cost cents per full run.
export OPENAI_API_KEY=sk-...
dotnet test -c Release --filter "FullyQualifiedName~IntegrationTests"
SDK choice (ADR)
Decision: hand-rolled HttpClient + System.Text.Json, no OpenAI official SDK dependency.
Reasons:
- Avoids the heavy transitive graph and rapid version churn of the official SDK.
- Lets the framework's pinned
Microsoft.Extensions.Http.Resiliencepipeline handle retries / circuit breaking. - Mirrors the proven
Compendium.Adapters.OpenRouterpattern (98.7 % coverage). - The wire format is small and stable — chat / embeddings / tools /
response_format.
If the official SDK stabilises (sane transitive graph, stable type surface), we may pivot — document any switch with an updated ADR in docs/.
Out of scope (first preview)
- Realtime API, Assistants API, fine-tuning, batch API.
- Image generation / vision — separate
Compendium.Adapters.OpenAI.Imagespackage if/when needed. - Azure OpenAI — separate adapter (different auth and URL shape).
License
MIT — same as Compendium itself.
| 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 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. |
-
net10.0
- Compendium.Abstractions.AI (>= 1.1.0-preview.6)
- Compendium.Core (>= 1.0.5-preview.5)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.13)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.13)
- Microsoft.Extensions.DependencyInjection (>= 10.0.13)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.13)
- Microsoft.Extensions.Http (>= 10.0.13)
- Microsoft.Extensions.Http.Resilience (>= 10.6.0)
- Microsoft.Extensions.Logging (>= 10.0.13)
- Microsoft.Extensions.Options (>= 10.0.13)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.13)
-
net9.0
- Compendium.Abstractions.AI (>= 1.1.0-preview.6)
- Compendium.Core (>= 1.0.5-preview.5)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.20)
- Microsoft.Extensions.Configuration.Binder (>= 9.0.20)
- Microsoft.Extensions.DependencyInjection (>= 9.0.20)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.20)
- Microsoft.Extensions.Http (>= 9.0.20)
- Microsoft.Extensions.Http.Resilience (>= 10.6.0)
- Microsoft.Extensions.Logging (>= 9.0.20)
- Microsoft.Extensions.Options (>= 9.0.20)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.20)
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.0-preview.6 | 572 | 10/31/2026 |
| 1.0.0-preview.0 | 119 | 5/17/2026 |