The .NET SDK is in beta. APIs can change between minor versions.
Tracing
Tracing records what your application does as spans you can inspect in Braintrust. The .NET SDK is built on OpenTelemetry: you instrument AI calls by wrapping your provider clients (see Wrappers), and you trace your own application code with theActivitySource Braintrust provides. The APIs below set up that pipeline and let you flush and link to your traces.
Braintrust.Get
Gets the global Braintrust instance, creating it on the first call and returning that same instance afterward. This is your entry point to the SDK: it reads configuration and sets up the OpenTelemetry pipeline that exports spans to Braintrust.
Call Get() with no arguments to configure from environment variables, which is the most common setup. To configure it in code, build a BraintrustConfig and pass it to Get(config) (see Configuration).
#skip-compile
Braintrust
Overloads:
Get(): readsBraintrustConfig.FromEnvironment()and auto-manages OpenTelemetry.Get(BraintrustConfig config, bool autoManageOpenTelemetry = true): uses the supplied config. SetautoManageOpenTelemetrytofalseto attach Braintrust to an OpenTelemetry pipeline you manage yourself.Of(BraintrustConfig config, bool autoManageOpenTelemetry = true): creates a new, non-global instance.
Braintrust.GetActivitySource
Returns the System.Diagnostics.ActivitySource Braintrust uses to create spans. Pass it to the provider wrap methods, and use it to wrap your own application code so traced AI calls nest under your operations.
#skip-compile
System.Diagnostics.ActivitySource
Braintrust traces wrapped provider calls, but not the code around them, so wrapping that work in a span shows its structure in the trace and nests any traced AI calls underneath.
#skip-compile
Braintrust.GetProjectUriAsync
Builds the Braintrust UI URL for the configured organization and project. Use it to link from your own app or logs straight to the project in Braintrust.
#skip-compile
Task<Uri>
BraintrustTracing.ForceFlush
Forces any pending spans to be sent to Braintrust, returning once they’re flushed or the timeout elapses.
You usually don’t need this. When the SDK manages OpenTelemetry (the default), it registers a process-exit hook that flushes on termination, so even short-lived processes export correctly with no extra work.
Call ForceFlush only when you opt out of auto-management (autoManageOpenTelemetry: false) and wire Braintrust into your own tracer provider, where that hook isn’t registered.
#skip-compile
bool (true when the flush completes within the timeout, or when no tracer provider exists)
Parameters:
timeoutMilliseconds(int): maximum time to wait for the flush. Defaults to10000.
Evaluations
An evaluation runs your task over a set of cases, scores each output, and reports the results, which is how you measure quality and catch regressions as you change prompts or models. These APIs build and run evaluations from your C# code.Braintrust.EvalBuilder
Defines an evaluation in code for input type TInput and output type TOutput: you give it cases, a task function, and one or more scorers, then call BuildAsync() to get an Eval. Call RunAsync() on it to run the task over every case, score the outputs, and log an experiment.
#skip-compile
Eval<TInput, TOutput>.Builder
Builder methods (each returns the Builder for chaining, except BuildAsync()):
Name(string)→Builder: experiment name.Cases(params DatasetCase[])→Builder: inline evaluation cases. Provide this orDataset().Dataset(IDataset)→Builder: run against an in-memory dataset instead of inlineCases.TaskFunction(Func<TInput, TOutput>)→Builder(required): the task that produces the output to score. An async overload takingFunc<TInput, Task<TOutput>>is also available.Scorers(params IScorer[])→Builder: scorers to apply to each case. Required unlessClassifiersis set.Classifiers(params IClassifier[])→Builder: classifiers to apply to each case. Required unlessScorersis set.Tags(params string[])→Builder: experiment-level tags.Metadata(IDictionary<string, object>)→Builder: experiment-level metadata.MaxConcurrency(int?)→Builder: cap on how many cases run in parallel.BuildAsync()→Task<Eval<TInput, TOutput>>: builds the eval.
eval.RunAsync(), which returns an EvalResult. Call result.CreateReportString() for a human-readable CLI summary.
IScorer<TInput, TOutput>
Measures how good your task’s output is. Implement Score, which receives a TaskResult and returns one or more Score values. Implement the interface when you need full control over scoring.
#skip-compile
FunctionScorer<TInput, TOutput>
Creates a scorer from a function, without defining a class. Reach for it when your scoring logic is simple, such as comparing the output to the expected value.
#skip-compile
FunctionScorer(string name, Func<TOutput, TOutput, double> scorerFn): synchronous scorer, scoring from(expected, actual).FunctionScorer(string name, Func<TOutput, TOutput, Task<double>> scorerFn): asynchronous scorer.
IClassifier<TInput, TOutput> and FunctionClassifier<TInput, TOutput>
Categorize a task’s output instead of scoring it numerically. A classifier returns structured Classification items (an id, plus optional label and metadata) rather than a number, which is useful for labeling outputs by topic, intent, or failure type. Implement IClassifier’s Classify method, or create one from a function with FunctionClassifier.
Datasets
A dataset is the set of cases an evaluation runs against. The .NET SDK supports in-memory datasets: define cases inline withDatasetCase.Of, and optionally group them into a dataset with Dataset.Of.
DatasetCase.Of
A dataset case is a single example: an input paired with the output you expect. Use Of(input, expected) for the common case, or the longer overloads to attach case-level tags and metadata.
#skip-compile
DatasetCase<TInput, TOutput>
Overloads:
DatasetCase.Of(TInput input, TOutput expected): input and expected output.DatasetCase.Of(TInput input, TOutput expected, IReadOnlyList<string> tags): adds case-level tags.DatasetCase.Of(TInput input, TOutput expected, IReadOnlyList<string> tags, IReadOnlyDictionary<string, object> metadata): adds case-level tags and metadata.
Dataset.Of
Groups several cases into an in-memory dataset you can hand to an evaluation with the builder’s Dataset(...) method, as an alternative to passing cases inline. Build one with Dataset.Of(params DatasetCase[]).
#skip-compile
IDataset<TInput, TOutput>
Wrappers
The .NET SDK instruments AI provider clients by wrapping them. Each wrapper takes theActivitySource from GetActivitySource() and returns an instrumented client whose calls are traced to Braintrust. For setup and the full list of supported integrations, see .NET SDK integrations.
BraintrustOpenAI.WrapOpenAI
Creates an instrumented OpenAIClient. Every call on the returned client is traced.
#skip-compile
OpenAIClient
Parameters:
activitySource(ActivitySource, required): source fromGetActivitySource().openAIApiKey(string, required): OpenAI API key.options(OpenAIClientOptions): optional OpenAI client options. Defaults tonull.
WithBraintrust (Anthropic)
Wraps an Anthropic client so every Messages.Create call (including streaming) emits a span. It’s an extension method, so you call it on the client itself.
#skip-compile
IAnthropicClient
Overloads:
WithBraintrust(bool captureMessageContent = true): uses the global Braintrust instance.WithBraintrust(ActivitySource activitySource, bool captureMessageContent = true): uses the suppliedActivitySource.
captureMessageContent to false to omit message content from spans.
BraintrustAzureOpenAI.WrapAzureOpenAI
Creates an instrumented AzureOpenAIClient, reusing the OpenAI instrumentation pipeline.
#skip-compile
AzureOpenAIClient
Overloads:
WrapAzureOpenAI(ActivitySource activitySource, Uri endpoint, string apiKey, ...): API key authentication.WrapAzureOpenAI(ActivitySource activitySource, Uri endpoint, TokenCredential credential, ...): Microsoft Entra ID authentication.
.WithBraintrust(activitySource) on a client you already created.
Agent Framework tracing
Traces Microsoft Agent Framework agents. Add the middleware to aChatClientBuilder, then wrap your agent.
#skip-compile
UseBraintrustTracing(activitySource)→ChatClientBuilder: traces both LLM calls and function calls.UseBraintrustLLMTracing(activitySource)→ChatClientBuilder: traces LLM calls only.UseBraintrustFunctionTracing(activitySource)→ChatClientBuilder: traces function (tool) calls only.WithBraintrustAgentTracing(activitySource)→AIAgent: wraps an agent so each run is captured as a span.
captureMessageContent flag, and function tracing accepts a captureToolArguments flag, to turn off content capture.
Configuration
Configure the SDK with environment variables, or programmatically withBraintrustConfig.Of, then pass the config to Braintrust.Get(config).
#skip-compile
Environment variables
BRAINTRUST_API_KEY(required): Braintrust API key. Also discoverable from a.env.braintrustfile.BRAINTRUST_DEFAULT_PROJECT_NAME: project that traced spans route to. Defaults todefault-dotnet-project.BRAINTRUST_DEFAULT_PROJECT_ID: project UUID. Takes precedence over the project name.BRAINTRUST_API_URL: Braintrust API URL. Defaults tohttps://api.braintrust.dev.BRAINTRUST_APP_URL: Braintrust app URL, used for permalinks. Defaults tohttps://www.braintrust.dev.BRAINTRUST_DEBUG: enable debug logging. Defaults tofalse.BRAINTRUST_ENABLE_TRACE_CONSOLE_LOG: print spans to the console. Defaults tofalse.BRAINTRUST_REQUEST_TIMEOUT: request timeout in seconds. Defaults to30.BRAINTRUST_TRACES_PATH: OpenTelemetry traces endpoint path. Defaults to/otel/v1/traces.BRAINTRUST_LOGS_PATH: OpenTelemetry logs endpoint path. Defaults to/otel/v1/logs.