Sigil extends OpenTelemetry-style instrumentation with normalized AI generation records. The .NET SDK follows the same generation-first contract and provider parity target as the Go SDK.
Grafana.Sigil: core runtime (SigilClient, generation/tool recorders, generation export)Grafana.Sigil.OpenAI: OpenAI Responses + Chat Completions + Embeddings wrappers and mappersGrafana.Sigil.Anthropic: Anthropic Messages wrappers and mappersGrafana.Sigil.Gemini: Gemini GenerateContent + EmbedContent wrappers and mappers
Package docs:
- Core:
src/Grafana.Sigil/README.md - OpenAI:
src/Grafana.Sigil.OpenAI/README.md - Anthropic:
src/Grafana.Sigil.Anthropic/README.md - Gemini:
src/Grafana.Sigil.Gemini/README.md
- Core package:
net8.0,netstandard2.0 - Provider packages:
net8.0
dotnet add package Grafana.Sigil
dotnet add package Grafana.Sigil.OpenAI
# or: Grafana.Sigil.Anthropic / Grafana.Sigil.Geminiusing Grafana.Sigil;
using Grafana.Sigil.OpenAI;
using OpenAI.Responses;
var sigil = new SigilClient(new SigilClientConfig
{
GenerationExport = new GenerationExportConfig
{
Protocol = GenerationExportProtocol.Grpc,
Endpoint = "localhost:4317",
Auth = new AuthConfig
{
Mode = ExportAuthMode.Tenant,
TenantId = "dev-tenant",
},
BatchSize = 100,
FlushInterval = TimeSpan.FromSeconds(1),
QueueSize = 2000,
},
Api = new ApiConfig
{
Endpoint = "http://localhost:8080",
},
});
// Configure OTel exporters (traces/metrics) separately in your application's OTel setup.
using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
.AddSource("github.com/grafana/sigil/sdks/dotnet")
.AddOtlpExporter()
.Build();
using var meterProvider = OpenTelemetry.Sdk.CreateMeterProviderBuilder()
.AddMeter("github.com/grafana/sigil/sdks/dotnet")
.AddOtlpExporter()
.Build();
var openAI = new ResponsesClient(
"gpt-5",
Environment.GetEnvironmentVariable("OPENAI_API_KEY")!
);
var inputItems = new List<ResponseItem>
{
ResponseItem.CreateUserMessageItem("Give me a short weather summary for Paris."),
};
var requestOptions = new CreateResponseOptions
{
Instructions = "You are concise.",
MaxOutputTokenCount = 320,
};
var response = await OpenAIRecorder.CreateResponseAsync(
sigil,
openAI,
inputItems,
requestOptions: requestOptions,
options: new OpenAISigilOptions
{
ConversationId = "conv-9b2f",
AgentName = "assistant-core",
AgentVersion = "1.0.0",
},
cancellationToken: CancellationToken.None
);
await sigil.ShutdownAsync(CancellationToken.None);StartGeneration(...)for non-stream operations (SYNC)StartStreamingGeneration(...)for stream operations (STREAM)StartEmbedding(...)for embedding operations (embeddings)StartToolExecution(...)for tool spans/eventsFlushAsync(...)for explicit export flush pointsShutdownAsync(...)for graceful shutdown
Recorder behavior is explicit and idempotent:
GenerationRecorder.SetResult(...)andSetCallError(...)ToolExecutionRecorder.SetResult(...)andSetExecutionError(...)End()is safe to call once infinally- recorder
Erroronly reports local instrumentation/export-queue errors
Generation export transport protocols:
GenerationExportProtocol.GrpcGenerationExportProtocol.HttpGenerationExportProtocol.None(instrumentation-only; no generation transport)
Use StartEmbedding(...) for manual embedding instrumentation. Embedding recording emits OTel spans and SDK metrics only, and does not enqueue generation export payloads.
var recorder = client.StartEmbedding(new EmbeddingStart
{
AgentName = "retrieval-worker",
AgentVersion = "1.0.0",
Model = new ModelRef
{
Provider = "openai",
Name = "text-embedding-3-small",
},
});
try
{
var response = await embeddingClient.GenerateEmbeddingsAsync(new[] { "hello", "world" });
recorder.SetResult(new EmbeddingResult
{
InputCount = 2,
InputTokens = response.Value.Usage.InputTokenCount,
InputTexts = new List<string> { "hello", "world" }, // captured only when EmbeddingCapture.CaptureInput=true
ResponseModel = response.Value.Model,
});
}
catch (Exception ex)
{
recorder.SetCallError(ex);
throw;
}
finally
{
recorder.End();
}Provider helpers:
OpenAIRecorder.GenerateEmbeddingsAsync(...)GeminiRecorder.EmbedContentAsync(...)- Anthropic .NET SDK does not currently expose an embeddings API surface in this repository.
Input text capture is opt-in:
var config = new SigilClientConfig
{
EmbeddingCapture = new EmbeddingCaptureConfig
{
CaptureInput = true,
MaxInputItems = 20,
MaxTextLength = 1024,
},
};CaptureInput can expose PII/document content in spans. Keep it disabled by default and enable only for scoped diagnostics.
TraceQL examples:
traces{gen_ai.operation.name="embeddings"}traces{gen_ai.operation.name="embeddings" && gen_ai.request.model="text-embedding-3-small"}traces{gen_ai.operation.name="embeddings" && error.type!=""}
SigilContext uses async-local scopes:
SigilContext.WithConversationId(...)SigilContext.WithAgentName(...)SigilContext.WithAgentVersion(...)
These defaults are used when a start payload omits those fields.
Use the SDK helper to submit user-facing ratings:
var result = await client.SubmitConversationRatingAsync(
"conv-123",
new SubmitConversationRatingRequest
{
RatingId = "rat-123",
Rating = ConversationRatingValue.Bad,
Comment = "Answer ignored user context",
Metadata = new Dictionary<string, object?>
{
["channel"] = "assistant-ui",
},
Source = "sdk-dotnet",
},
CancellationToken.None
);
Console.WriteLine($"{result.Rating.Rating} hasBad={result.Summary.HasBadRating}");SubmitConversationRatingAsync(...) sends requests to SigilClientConfig.Api.Endpoint (default http://localhost:8080) and uses the same generation-export auth headers (tenant or bearer) already configured on the SDK client.
- Create one long-lived
SigilClientper process (for example as a singleton in DI). - Always call
ShutdownAsync(...)during process shutdown. - Keep provider request/response payloads normalized; enable raw artifacts only for debug sessions.
- Use explicit generation export auth config instead of sharing ad-hoc headers.
var sigil = new SigilClient(new SigilClientConfig
{
GenerationExport = new GenerationExportConfig
{
Protocol = GenerationExportProtocol.None,
},
});The SDK emits these OTel histograms through your configured OTel meter provider:
gen_ai.client.operation.durationgen_ai.client.token.usagegen_ai.client.time_to_first_tokengen_ai.client.tool_calls_per_operation
Run from repository root:
mise run test:cs:sdk-coremise run test:cs:sdk-openaimise run test:cs:sdk-anthropicmise run test:cs:sdk-gemini