Skip to content

Commit 1db1ece

Browse files
authored
Merge pull request #23 from braintrustdata/ark/remote-evals
Initial Support for Remote Evals Devserver
2 parents 0495e7e + c515ef0 commit 1db1ece

File tree

17 files changed

+2624
-7
lines changed

17 files changed

+2624
-7
lines changed

examples/build.gradle

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,18 @@ task runSpringAI(type: JavaExec) {
141141
suspend = false
142142
}
143143
}
144+
145+
146+
task runRemoteEval(type: JavaExec) {
147+
group = 'Braintrust SDK Examples'
148+
description = 'Run the remote eval example'
149+
classpath = sourceSets.main.runtimeClasspath
150+
mainClass = 'dev.braintrust.examples.RemoteEvalExample'
151+
systemProperty 'org.slf4j.simpleLogger.log.dev.braintrust', braintrustLogLevel
152+
debugOptions {
153+
enabled = true
154+
port = 5566
155+
server = true
156+
suspend = false
157+
}
158+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package dev.braintrust.examples;
2+
3+
import com.openai.client.okhttp.OpenAIOkHttpClient;
4+
import com.openai.models.ChatModel;
5+
import com.openai.models.chat.completions.ChatCompletionCreateParams;
6+
import dev.braintrust.Braintrust;
7+
import dev.braintrust.devserver.Devserver;
8+
import dev.braintrust.devserver.RemoteEval;
9+
import dev.braintrust.eval.Scorer;
10+
import dev.braintrust.instrumentation.openai.BraintrustOpenAI;
11+
import java.util.List;
12+
13+
/** Simple Dev Server for Remote Evals */
14+
public class RemoteEvalExample {
15+
public static void main(String[] args) throws Exception {
16+
var braintrust = Braintrust.get();
17+
var openTelemetry = braintrust.openTelemetryCreate();
18+
var openAIClient = BraintrustOpenAI.wrapOpenAI(openTelemetry, OpenAIOkHttpClient.fromEnv());
19+
20+
RemoteEval<String, String> foodTypeEval =
21+
RemoteEval.<String, String>builder()
22+
.name("food-type-classifier")
23+
.taskFunction(
24+
food -> {
25+
var request =
26+
ChatCompletionCreateParams.builder()
27+
.model(ChatModel.GPT_4O_MINI)
28+
.addSystemMessage("Return a one word answer")
29+
.addUserMessage(
30+
"What kind of food is " + food + "?")
31+
.maxTokens(50L)
32+
.temperature(0.0)
33+
.build();
34+
var response =
35+
openAIClient.chat().completions().create(request);
36+
return response.choices()
37+
.get(0)
38+
.message()
39+
.content()
40+
.orElse("")
41+
.toLowerCase();
42+
})
43+
.scorers(
44+
List.of(
45+
Scorer.of("static_scorer", (expected, result) -> 0.7),
46+
Scorer.of(
47+
"close_enough_match",
48+
(expected, result) ->
49+
expected.trim()
50+
.equalsIgnoreCase(
51+
result.trim())
52+
? 1.0
53+
: 0.0)))
54+
.build();
55+
56+
Devserver devserver =
57+
Devserver.builder()
58+
.config(braintrust.config())
59+
.registerEval(foodTypeEval)
60+
.host("localhost") // set to 0.0.0.0 to bind all interfaces
61+
.port(8301)
62+
.build();
63+
64+
Runtime.getRuntime()
65+
.addShutdownHook(
66+
new Thread(
67+
() -> {
68+
System.out.println("Shutting down...");
69+
devserver.stop();
70+
System.out.flush();
71+
System.err.flush();
72+
}));
73+
System.out.println("Starting Braintrust dev server");
74+
devserver.start();
75+
}
76+
}

src/main/java/dev/braintrust/BraintrustUtils.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import dev.braintrust.api.BraintrustApiClient;
44
import java.net.URI;
55
import java.net.URISyntaxException;
6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.List;
69
import javax.annotation.Nonnull;
710

811
public class BraintrustUtils {
@@ -28,7 +31,7 @@ public static URI createProjectURI(
2831
}
2932
}
3033

31-
static Parent parseParent(@Nonnull String parentStr) {
34+
public static Parent parseParent(@Nonnull String parentStr) {
3235
String[] parts = parentStr.split(":");
3336
if (parts.length != 2) {
3437
throw new IllegalArgumentException("Invalid parent format: " + parentStr);
@@ -42,4 +45,18 @@ public String toParentValue() {
4245
return type + ":" + id;
4346
}
4447
}
48+
49+
public static List<String> parseCsv(String csv) {
50+
if (csv == null || csv.isBlank()) {
51+
return List.of();
52+
}
53+
54+
return Arrays.stream(csv.split("\\s*,\\s*")).toList();
55+
}
56+
57+
public static <T> List<T> append(List<T> list, T value) {
58+
List<T> result = new ArrayList<>(list);
59+
result.add(value);
60+
return result;
61+
}
4562
}

src/main/java/dev/braintrust/api/BraintrustApiClient.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
* {@link dev.braintrust.eval.Eval} or {@link dev.braintrust.trace.BraintrustTracing}
2727
*/
2828
public interface BraintrustApiClient {
29+
/**
30+
* Attempt Braintrust login
31+
*
32+
* @return LoginResponse containing organization info
33+
* @throws LoginException if login fails due to invalid credentials or network errors
34+
*/
35+
LoginResponse login() throws LoginException;
36+
2937
/** Creates or gets a project by name. */
3038
Project getOrCreateProject(String projectName);
3139

@@ -117,15 +125,16 @@ public Experiment getOrCreateExperiment(CreateExperimentRequest request) {
117125
}
118126
}
119127

120-
private LoginResponse login() {
128+
@Override
129+
public LoginResponse login() throws LoginException {
121130
try {
122131
return postAsync(
123132
"/api/apikey/login",
124133
new LoginRequest(config.apiKey()),
125134
LoginResponse.class)
126135
.get();
127136
} catch (InterruptedException | ExecutionException e) {
128-
throw new RuntimeException(e);
137+
throw new LoginException("Failed to login to Braintrust", e);
129138
}
130139
}
131140

@@ -403,6 +412,12 @@ public InMemoryImpl(
403412
this.prompts.addAll(prompts);
404413
}
405414

415+
@Override
416+
public LoginResponse login() {
417+
return new LoginResponse(
418+
organizationAndProjectInfos.stream().map(o -> o.orgInfo).toList());
419+
}
420+
406421
@Override
407422
public Project getOrCreateProject(String projectName) {
408423
// Find existing project by name
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dev.braintrust.api;
2+
3+
import javax.annotation.Nullable;
4+
5+
/**
6+
* Exception thrown when login to Braintrust fails.
7+
*
8+
* <p>This is a RuntimeException so it doesn't require explicit handling, but callers can catch it
9+
* specifically if they want to handle login failures differently from other errors.
10+
*/
11+
public class LoginException extends RuntimeException {
12+
public LoginException(String message) {
13+
super(message);
14+
}
15+
16+
public LoginException(String message, @Nullable Throwable cause) {
17+
super(message, cause);
18+
}
19+
20+
public LoginException(Throwable cause) {
21+
super(cause);
22+
}
23+
}

src/main/java/dev/braintrust/config/BraintrustConfig.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ public final class BraintrustConfig extends BaseConfig {
5151
private final boolean exportSpansInMemoryForUnitTest =
5252
getConfig("BRAINTRUST_JAVA_EXPORT_SPANS_IN_MEMORY_FOR_UNIT_TEST", false);
5353

54+
/** CORS origins to allow when running remote eval devserver */
55+
private final String devserverCorsOriginWhitelistCsv =
56+
getConfig(
57+
"BRAINTRUST_DEVSERVER_CORS_ORIGIN_WHITELIST_CSV",
58+
"https://www.braintrust.dev,https://www.braintrustdata.com,http://localhost:3000");
59+
5460
public static BraintrustConfig fromEnvironment() {
5561
return of();
5662
}
@@ -192,8 +198,8 @@ Builder experimentalOtelLogs(boolean value) {
192198
return this;
193199
}
194200

195-
// hiding visibility. only used for testing
196-
Builder exportSpansInMemoryForUnitTest(boolean value) {
201+
// only used for testing
202+
public Builder exportSpansInMemoryForUnitTest(boolean value) {
197203
envOverrides.put(
198204
"BRAINTRUST_JAVA_EXPORT_SPANS_IN_MEMORY_FOR_UNIT_TEST", String.valueOf(value));
199205
return this;
@@ -209,6 +215,11 @@ public Builder x509TrustManager(X509TrustManager value) {
209215
return this;
210216
}
211217

218+
public Builder devserverCorsOriginWhitelistCsv(String csv) {
219+
envOverrides.put("BRAINTRUST_DEVSERVER_CORS_ORIGIN_WHITELIST_CSV", csv);
220+
return this;
221+
}
222+
212223
public BraintrustConfig build() {
213224
return new BraintrustConfig(envOverrides, sslContext, x509TrustManager);
214225
}

0 commit comments

Comments
 (0)