Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions examples/EvalExample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System;
using System.Linq;
using Braintrust.Sdk;
using Braintrust.Sdk.Eval;
using Braintrust.Sdk.Instrumentation.OpenAI;
using OpenAI;
Expand All @@ -10,7 +7,7 @@ namespace Braintrust.Sdk.Examples.EvalExample;

class Program
{
static void Main(string[] args)
static async Task Main(string[] args)
{
var openAIApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
if (string.IsNullOrEmpty(openAIApiKey))
Expand Down Expand Up @@ -44,23 +41,23 @@ string GetFoodType(string food)
}

// Create and run the evaluation
var eval = braintrust
var eval = await braintrust
.EvalBuilder<string, string>()
.Name($"dotnet-eval-x-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}")
.Cases(
DatasetCase<string, string>.Of("strawberry", "fruit"),
DatasetCase<string, string>.Of("asparagus", "vegetable"),
DatasetCase<string, string>.Of("apple", "fruit"),
DatasetCase<string, string>.Of("banana", "fruit")
DatasetCase.Of("strawberry", "fruit"),
DatasetCase.Of("asparagus", "vegetable"),
DatasetCase.Of("apple", "fruit"),
DatasetCase.Of("banana", "fruit")
)
.TaskFunction(GetFoodType)
.Scorers(
Scorer<string, string>.Of("exact_match", (expected, actual) => expected == actual ? 1.0 : 0.0),
Scorer<string, string>.Of("close_enough_match", (expected, actual) => expected.Trim().ToLowerInvariant() == actual.Trim().ToLowerInvariant() ? 1.0 : 0.0)
new FunctionScorer<string, string>("exact_match", (expected, actual) => expected == actual ? 1.0 : 0.0),
new FunctionScorer<string, string>("close_enough_match", (expected, actual) => expected.Trim().ToLowerInvariant() == actual.Trim().ToLowerInvariant() ? 1.0 : 0.0)
)
.Build();
.BuildAsync();

var result = eval.Run();
var result = await eval.RunAsync();
Console.WriteLine($"\n\n{result.CreateReportString()}");
}
}
2 changes: 1 addition & 1 deletion examples/OpenAIInstrumentation/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ static async Task Main(string[] args)
if (rootActivity != null)
{
await ChatCompletionsExample(instrumentedClient);
var url = braintrust.ProjectUri()
var url = await braintrust.GetProjectUriAsync()
+ $"/logs?r={rootActivity.TraceId}&s={rootActivity.SpanId}";
Console.WriteLine($"\n\n Example complete! View your data in Braintrust: {url}\n");
}
Expand Down
8 changes: 2 additions & 6 deletions examples/SimpleOpenTelemetry/Program.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
using System;
using System.Threading;
using Braintrust.Sdk;

namespace Braintrust.Sdk.Examples.SimpleOpenTelemetry;

class Program
{
static void Main(string[] args)
static async Task Main(string[] args)
{
var braintrust = Braintrust.Get();
var activitySource = braintrust.GetActivitySource();
Expand All @@ -17,7 +13,7 @@ static void Main(string[] args)
ArgumentNullException.ThrowIfNull(activity);
Console.WriteLine("Performing simple operation...");
activity.SetTag("some boolean attribute", true);
url = braintrust.ProjectUri() + $"/logs?r={activity.TraceId}&s={activity.SpanId}";
url = await braintrust.GetProjectUriAsync() + $"/logs?r={activity.TraceId}&s={activity.SpanId}";
}
Console.WriteLine($"\n\n Example complete! View your data in Braintrust: {url}");
}
Expand Down
18 changes: 18 additions & 0 deletions src/Braintrust.Sdk/Api/ApiException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Braintrust.Sdk.Api;

/// <summary>
/// Exception thrown when an API request fails.
/// </summary>
public class ApiException : Exception
{
public int? StatusCode { get; }

public ApiException(string message) : base(message) { }

public ApiException(string message, Exception innerException) : base(message, innerException) { }

public ApiException(int statusCode, string message) : base(message)
{
StatusCode = statusCode;
}
}
84 changes: 29 additions & 55 deletions src/Braintrust.Sdk/Api/BraintrustApiClient.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Braintrust.Sdk.Config;

namespace Braintrust.Sdk.Api;

/// <summary>
/// Exception thrown when an API request fails.
/// </summary>
public class ApiException : Exception
{
public int? StatusCode { get; }

public ApiException(string message) : base(message) { }

public ApiException(string message, Exception innerException) : base(message, innerException) { }

public ApiException(int statusCode, string message) : base(message)
{
StatusCode = statusCode;
}
}

/// <summary>
/// Implementation of Braintrust API client.
/// </summary>
Expand Down Expand Up @@ -64,12 +41,11 @@ internal BraintrustApiClient(BraintrustConfig config, HttpClient? httpClient = n

private static HttpClient CreateDefaultHttpClient(BraintrustConfig config)
{
var client = new HttpClient
return new HttpClient
{
BaseAddress = new Uri(config.ApiUrl),
Timeout = config.RequestTimeout
};
return client;
}

private static JsonSerializerOptions CreateJsonOptions()
Expand All @@ -82,57 +58,55 @@ private static JsonSerializerOptions CreateJsonOptions()
};
}

public Project GetOrCreateProject(string projectName)
public async Task<Project> GetOrCreateProject(string projectName)
{
var request = new CreateProjectRequest(projectName);
return PostAsync<CreateProjectRequest, Project>("/v1/project", request, default)
.ConfigureAwait(false).GetAwaiter().GetResult();
return await PostAsync<CreateProjectRequest, Project>("/v1/project", request).ConfigureAwait(false);
}

public Project? GetProject(string projectId)
public async Task<Project?> GetProject(string projectId)
{
try
{
return GetAsync<Project>($"/v1/project/{projectId}", default)
.ConfigureAwait(false).GetAwaiter().GetResult();
return await GetAsync<Project>($"/v1/project/{projectId}").ConfigureAwait(false);
}
catch (ApiException ex) when (ex.StatusCode == 404)
{
return null;
}
}

public Experiment GetOrCreateExperiment(CreateExperimentRequest request)
public async Task<Experiment> GetOrCreateExperiment(CreateExperimentRequest request)
{
return PostAsync<CreateExperimentRequest, Experiment>("/v1/experiment", request, default)
.ConfigureAwait(false).GetAwaiter().GetResult();
return await PostAsync<CreateExperimentRequest, Experiment>("/v1/experiment", request)
.ConfigureAwait(false);
}

public OrganizationAndProjectInfo? GetProjectAndOrgInfo()
public async Task<OrganizationAndProjectInfo?> GetProjectAndOrgInfo()
{
if (_config.DefaultProjectId != null)
{
return GetProjectAndOrgInfo(_config.DefaultProjectId);
return await GetProjectAndOrgInfo(_config.DefaultProjectId).ConfigureAwait(false);
}

if (_config.DefaultProjectName != null)
{
var project = GetOrCreateProject(_config.DefaultProjectName);
return GetProjectAndOrgInfo(project.Id);
var project = await GetOrCreateProject(_config.DefaultProjectName).ConfigureAwait(false);
return await GetProjectAndOrgInfo(project.Id).ConfigureAwait(false);
}

return null;
}

public OrganizationAndProjectInfo? GetProjectAndOrgInfo(string projectId)
public async Task<OrganizationAndProjectInfo?> GetProjectAndOrgInfo(string projectId)
{
var project = GetProject(projectId);
var project = await GetProject(projectId).ConfigureAwait(false);
if (project == null)
{
return null;
}

var loginResponse = Login();
var loginResponse = await Login().ConfigureAwait(false);
var orgInfo = loginResponse.OrgInfo.FirstOrDefault(org =>
string.Equals(org.Id, project.OrgId, StringComparison.OrdinalIgnoreCase));

Expand All @@ -144,13 +118,13 @@ public Experiment GetOrCreateExperiment(CreateExperimentRequest request)
return new OrganizationAndProjectInfo(orgInfo, project);
}

public OrganizationAndProjectInfo GetOrCreateProjectAndOrgInfo()
public async Task<OrganizationAndProjectInfo> GetOrCreateProjectAndOrgInfo()
{
Project project;

if (_config.DefaultProjectId != null)
{
var existingProject = GetProject(_config.DefaultProjectId);
var existingProject = await GetProject(_config.DefaultProjectId).ConfigureAwait(false);
if (existingProject == null)
{
throw new ApiException($"Project with ID {_config.DefaultProjectId} not found");
Expand All @@ -159,14 +133,14 @@ public OrganizationAndProjectInfo GetOrCreateProjectAndOrgInfo()
}
else if (_config.DefaultProjectName != null)
{
project = GetOrCreateProject(_config.DefaultProjectName);
project = await GetOrCreateProject(_config.DefaultProjectName).ConfigureAwait(false);
}
else
{
throw new InvalidOperationException("Either DefaultProjectId or DefaultProjectName must be set in config");
}

var loginResponse = Login();
var loginResponse = await Login().ConfigureAwait(false);
var orgInfo = loginResponse.OrgInfo.FirstOrDefault(org =>
string.Equals(org.Id, project.OrgId, StringComparison.OrdinalIgnoreCase));

Expand All @@ -178,11 +152,11 @@ public OrganizationAndProjectInfo GetOrCreateProjectAndOrgInfo()
return new OrganizationAndProjectInfo(orgInfo, project);
}

private LoginResponse Login()
private async Task<LoginResponse> Login()
{
var request = new LoginRequest(_config.ApiKey);
return PostAsync<LoginRequest, LoginResponse>("/api/apikey/login", request, default)
.ConfigureAwait(false).GetAwaiter().GetResult();
return await PostAsync<LoginRequest, LoginResponse>("/api/apikey/login", request)
.ConfigureAwait(false);
}

private async Task<TResponse> GetAsync<TResponse>(string path, CancellationToken cancellationToken = default)
Expand All @@ -191,8 +165,8 @@ private async Task<TResponse> GetAsync<TResponse>(string path, CancellationToken
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _config.ApiKey);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

using var response = await _httpClient.SendAsync(request, cancellationToken);
return await HandleResponseAsync<TResponse>(response, cancellationToken);
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
return await HandleResponseAsync<TResponse>(response, cancellationToken).ConfigureAwait(false);
}

private async Task<TResponse> PostAsync<TRequest, TResponse>(
Expand All @@ -205,15 +179,15 @@ private async Task<TResponse> PostAsync<TRequest, TResponse>(
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = JsonContent.Create(body, options: _jsonOptions);

using var response = await _httpClient.SendAsync(request, cancellationToken);
return await HandleResponseAsync<TResponse>(response, cancellationToken);
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
return await HandleResponseAsync<TResponse>(response, cancellationToken).ConfigureAwait(false);
}

private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken)
{
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
var result = JsonSerializer.Deserialize<T>(content, _jsonOptions);

if (result == null)
Expand All @@ -225,7 +199,7 @@ private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response, Cance
}
else
{
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
throw new ApiException(
(int)response.StatusCode,
$"API request failed with status {(int)response.StatusCode}: {content}");
Expand All @@ -236,7 +210,7 @@ public void Dispose()
{
if (_ownsHttpClient)
{
_httpClient?.Dispose();
_httpClient.Dispose();
}
}
}
12 changes: 6 additions & 6 deletions src/Braintrust.Sdk/Api/IBraintrustApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@ public interface IBraintrustApiClient
/// <summary>
/// Get or create a project by name.
/// </summary>
Project GetOrCreateProject(string projectName);
Task<Project> GetOrCreateProject(string projectName);

/// <summary>
/// Get a project by ID.
/// </summary>
Project? GetProject(string projectId);
Task<Project?> GetProject(string projectId);

/// <summary>
/// Get or create an experiment.
/// </summary>
Experiment GetOrCreateExperiment(CreateExperimentRequest request);
Task<Experiment> GetOrCreateExperiment(CreateExperimentRequest request);

/// <summary>
/// Get project and organization information using the default project from config.
/// </summary>
OrganizationAndProjectInfo? GetProjectAndOrgInfo();
Task<OrganizationAndProjectInfo?> GetProjectAndOrgInfo();

/// <summary>
/// Get project and organization information for a specific project ID.
/// </summary>
OrganizationAndProjectInfo? GetProjectAndOrgInfo(string projectId);
Task<OrganizationAndProjectInfo?> GetProjectAndOrgInfo(string projectId);

/// <summary>
/// Get or create project and organization information from config.
/// </summary>
OrganizationAndProjectInfo GetOrCreateProjectAndOrgInfo();
Task<OrganizationAndProjectInfo> GetOrCreateProjectAndOrgInfo();
}
2 changes: 0 additions & 2 deletions src/Braintrust.Sdk/Api/Models.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Braintrust.Sdk.Api;
Expand Down
Loading
Loading