> ## Documentation Index
> Fetch the complete documentation index at: https://braintrust.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# API reference

> Functions, classes, and configuration options found in the Braintrust .NET SDK.

<Note>The .NET SDK is in beta. APIs can change between minor versions.</Note>

This page covers the key APIs in the Braintrust .NET SDK. For setup, see the [Quickstart](/sdks/csharp/quickstart). For the complete API, browse the [source on GitHub](https://github.com/braintrustdata/braintrust-sdk-dotnet). The .NET SDK does not support prompts.

## 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](#wrappers)), and you trace your own application code with the `ActivitySource` 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](#configuration)).

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk;

var braintrust = Braintrust.Sdk.Braintrust.Get();
```

Returns: `Braintrust`

Overloads:

* **`Get()`**: reads `BraintrustConfig.FromEnvironment()` and auto-manages OpenTelemetry.
* **`Get(BraintrustConfig config, bool autoManageOpenTelemetry = true)`**: uses the supplied config. Set `autoManageOpenTelemetry` to `false` to 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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
var activitySource = braintrust.GetActivitySource();
```

Returns: `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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
var activitySource = braintrust.GetActivitySource();

using (var activity = activitySource.StartActivity("process-request"))
{
    activity?.SetTag("user.id", userId);
    var output = await AnswerQuestion("Write a haiku about evals");
    activity?.SetTag("app.output", output);
}
```

### `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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
var uri = await braintrust.GetProjectUriAsync();
```

Returns: `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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk.Trace;

BraintrustTracing.ForceFlush();
```

Returns: `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 to `10000`.

## 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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk.Eval;

var eval = await braintrust
    .EvalBuilder<string, string>()
    .Name("food-classifier")
    .Tags("dotnet-sdk")
    .Metadata(new Dictionary<string, object> { { "model", "gpt-4o-mini" } })
    .Cases(
        DatasetCase.Of("strawberry", "fruit"),
        DatasetCase.Of("asparagus", "vegetable")
    )
    .TaskFunction(GetFoodType)
    .Scorers(new FunctionScorer<string, string>("exact_match",
        (expected, actual) => expected == actual ? 1.0 : 0.0))
    .BuildAsync();

var result = await eval.RunAsync();
Console.WriteLine(result.CreateReportString());
```

Returns: `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 or `Dataset()`.
* **`Dataset(IDataset)`** → `Builder`: run against an in-memory dataset instead of inline `Cases`.
* **`TaskFunction(Func<TInput, TOutput>)`** → `Builder` (required): the task that produces the output to score. An async overload taking `Func<TInput, Task<TOutput>>` is also available.
* **`Scorers(params IScorer[])`** → `Builder`: scorers to apply to each case. Required unless `Classifiers` is set.
* **`Classifiers(params IClassifier[])`** → `Builder`: classifiers to apply to each case. Required unless `Scorers` is 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.

Run the built eval with `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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sealed class ExactMatchScorer : IScorer<string, string>
{
    public string Name => "exact_match";

    public Task<IReadOnlyList<Score>> Score(TaskResult<string, string> taskResult)
    {
        var isMatch = taskResult.DatasetCase.Expected == taskResult.Result;
        IReadOnlyList<Score> scores = [new Score(Name, isMatch ? 1.0 : 0.0)];
        return Task.FromResult(scores);
    }
}
```

### `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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
var scorer = new FunctionScorer<string, string>(
    "accuracy",
    (expected, actual) => actual.Contains(expected) ? 1.0 : 0.0);
```

Constructors:

* **`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 with `DatasetCase.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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
DatasetCase.Of("strawberry", "fruit");

DatasetCase.Of(
    "banana",
    "fruit",
    new List<string> { "tropical" },
    new Dictionary<string, object> { { "category", "tropical-fruit" } });
```

Returns: `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[])`.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk.Eval;

var dataset = Dataset.Of(
    DatasetCase.Of("strawberry", "fruit"),
    DatasetCase.Of("asparagus", "vegetable"));
```

Returns: `IDataset<TInput, TOutput>`

## Wrappers

The .NET SDK instruments AI provider clients by wrapping them. Each wrapper takes the `ActivitySource` 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](/sdks/csharp/sdk-integrations).

### `BraintrustOpenAI.WrapOpenAI`

Creates an instrumented `OpenAIClient`. Every call on the returned client is traced.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk.OpenAI;

var client = BraintrustOpenAI.WrapOpenAI(activitySource, apiKey);
```

Returns: `OpenAIClient`

Parameters:

* **`activitySource`** (`ActivitySource`, required): source from `GetActivitySource()`.
* **`openAIApiKey`** (`string`, required): OpenAI API key.
* **`options`** (`OpenAIClientOptions`): optional OpenAI client options. Defaults to `null`.

### `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.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk.Anthropic;

var client = new AnthropicClient().WithBraintrust();
```

Returns: `IAnthropicClient`

Overloads:

* **`WithBraintrust(bool captureMessageContent = true)`**: uses the global Braintrust instance.
* **`WithBraintrust(ActivitySource activitySource, bool captureMessageContent = true)`**: uses the supplied `ActivitySource`.

Set `captureMessageContent` to `false` to omit message content from spans.

### `BraintrustAzureOpenAI.WrapAzureOpenAI`

Creates an instrumented `AzureOpenAIClient`, reusing the OpenAI instrumentation pipeline.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk.AzureOpenAI;

var client = BraintrustAzureOpenAI.WrapAzureOpenAI(activitySource, endpoint, apiKey);
```

Returns: `AzureOpenAIClient`

Overloads:

* **`WrapAzureOpenAI(ActivitySource activitySource, Uri endpoint, string apiKey, ...)`**: API key authentication.
* **`WrapAzureOpenAI(ActivitySource activitySource, Uri endpoint, TokenCredential credential, ...)`**: Microsoft Entra ID authentication.

You can also call `.WithBraintrust(activitySource)` on a client you already created.

### Agent Framework tracing

Traces Microsoft Agent Framework agents. Add the middleware to a `ChatClientBuilder`, then wrap your agent.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk.AgentFramework;

var chatClient = builder.UseBraintrustTracing(activitySource).Build();
var agent = new ChatClientAgent(chatClient, ...).WithBraintrustAgentTracing(activitySource);
```

Methods:

* **`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.

The tracing methods accept a `captureMessageContent` flag, and function tracing accepts a `captureToolArguments` flag, to turn off content capture.

## Configuration

Configure the SDK with environment variables, or programmatically with `BraintrustConfig.Of`, then pass the config to `Braintrust.Get(config)`.

```csharp #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
using Braintrust.Sdk;
using Braintrust.Sdk.Config;

var config = BraintrustConfig.Of(
    ("BRAINTRUST_API_KEY", "your-api-key"),
    ("BRAINTRUST_DEFAULT_PROJECT_NAME", "My project"));

var braintrust = Braintrust.Sdk.Braintrust.Get(config);
```

### Environment variables

* **`BRAINTRUST_API_KEY`** (required): Braintrust API key. Also discoverable from a `.env.braintrust` file.
* **`BRAINTRUST_DEFAULT_PROJECT_NAME`**: project that traced spans route to. Defaults to `default-dotnet-project`.
* **`BRAINTRUST_DEFAULT_PROJECT_ID`**: project UUID. Takes precedence over the project name.
* **`BRAINTRUST_API_URL`**: Braintrust API URL. Defaults to `https://api.braintrust.dev`.
* **`BRAINTRUST_APP_URL`**: Braintrust app URL, used for permalinks. Defaults to `https://www.braintrust.dev`.
* **`BRAINTRUST_DEBUG`**: enable debug logging. Defaults to `false`.
* **`BRAINTRUST_ENABLE_TRACE_CONSOLE_LOG`**: print spans to the console. Defaults to `false`.
* **`BRAINTRUST_REQUEST_TIMEOUT`**: request timeout in seconds. Defaults to `30`.
* **`BRAINTRUST_TRACES_PATH`**: OpenTelemetry traces endpoint path. Defaults to `/otel/v1/traces`.
* **`BRAINTRUST_LOGS_PATH`**: OpenTelemetry logs endpoint path. Defaults to `/otel/v1/logs`.
