objects) {}
+
+ /**
+ * Request body for invoking a function. The input field wraps the function arguments.
+ *
+ * For remote Python/TypeScript scorers, the scorer handler parameters (input, output,
+ * expected, metadata) must be wrapped in the outer input field.
+ */
+ record FunctionInvokeRequest(@Nullable Object input, @Nullable String version) {
+
+ /** Create a simple invoke request with just input */
+ public static FunctionInvokeRequest of(Object input) {
+ return new FunctionInvokeRequest(input, null);
+ }
+
+ /** Create a simple invoke request with input and version */
+ public static FunctionInvokeRequest of(Object input, @Nullable String version) {
+ return new FunctionInvokeRequest(input, version);
+ }
+
+ /**
+ * Create an invoke request for a scorer with input, output, expected, and metadata. This
+ * maps to the standard scorer handler signature: handler(input, output, expected, metadata)
+ *
+ *
The scorer args are wrapped in the outer input field as required by the invoke API.
+ */
+ public static FunctionInvokeRequest forScorer(
+ Object input, Object output, Object expected, Object metadata) {
+ return forScorer(input, output, expected, metadata, null);
+ }
+
+ /**
+ * Create an invoke request for a scorer with input, output, expected, metadata, and
+ * version. This maps to the standard scorer handler signature: handler(input, output,
+ * expected, metadata)
+ *
+ *
The scorer args are wrapped in the outer input field as required by the invoke API.
+ */
+ public static FunctionInvokeRequest forScorer(
+ Object input,
+ Object output,
+ Object expected,
+ Object metadata,
+ @Nullable String version) {
+ // Wrap scorer args in an inner map that becomes the outer "input" field
+ var scorerArgs = new java.util.LinkedHashMap();
+ scorerArgs.put("input", input);
+ scorerArgs.put("output", output);
+ scorerArgs.put("expected", expected);
+ scorerArgs.put("metadata", metadata);
+ return new FunctionInvokeRequest(scorerArgs, version);
+ }
+ }
}
diff --git a/src/main/java/dev/braintrust/devserver/Devserver.java b/src/main/java/dev/braintrust/devserver/Devserver.java
index c3b2071..be89be8 100644
--- a/src/main/java/dev/braintrust/devserver/Devserver.java
+++ b/src/main/java/dev/braintrust/devserver/Devserver.java
@@ -288,7 +288,18 @@ private void handleEval(HttpExchange exchange) throws IOException {
return;
}
- // TODO: support remote scorers
+ // Resolve remote scorers from the request
+ List> remoteScorers = new ArrayList<>();
+ if (request.getScores() != null) {
+ var apiClient = context.getBraintrust().apiClient();
+ for (var remoteScorer : request.getScores()) {
+ remoteScorers.add(resolveRemoteScorer(remoteScorer, apiClient));
+ }
+ log.debug(
+ "Resolved {} remote scorer(s): {}",
+ remoteScorers.size(),
+ remoteScorers.stream().map(Scorer::getName).toList());
+ }
String datasetDescription =
hasInlineData
@@ -308,7 +319,7 @@ private void handleEval(HttpExchange exchange) throws IOException {
if (isStreaming) {
// SSE streaming response - errors handled inside
log.debug("Starting streaming evaluation for '{}'", request.getName());
- handleStreamingEval(exchange, eval, request, context);
+ handleStreamingEval(exchange, eval, request, context, remoteScorers);
} else {
throw new NotSupportedYetException("non-streaming responses");
}
@@ -325,7 +336,11 @@ private void handleEval(HttpExchange exchange) throws IOException {
@SuppressWarnings({"unchecked", "rawtypes"})
private void handleStreamingEval(
- HttpExchange exchange, RemoteEval eval, EvalRequest request, RequestContext context)
+ HttpExchange exchange,
+ RemoteEval eval,
+ EvalRequest request,
+ RequestContext context,
+ List> remoteScorers)
throws Exception {
// Set SSE headers
exchange.getResponseHeaders().set("Content-Type", "text/event-stream");
@@ -423,7 +438,12 @@ private void handleStreamingEval(
taskResult);
}
// run scorers - one score span per scorer
- for (var scorer : (List>) eval.getScorers()) {
+ // Combine local scorers from RemoteEval with remote scorers
+ // from request
+ List> allScorers =
+ new ArrayList<>(eval.getScorers());
+ allScorers.addAll(remoteScorers);
+ for (var scorer : allScorers) {
var scoreSpan = tracer.spanBuilder("score").startSpan();
try (var unused =
Context.current()
@@ -1037,6 +1057,30 @@ private static ParentInfo extractParentInfo(EvalRequest request) {
}
}
+ /**
+ * Resolve a remote scorer from the eval request into a Scorer instance.
+ *
+ * @param remoteScorer the remote scorer specification from the request
+ * @param apiClient the API client to use for invoking the scorer function
+ * @return a Scorer that invokes the remote function
+ * @throws IllegalArgumentException if the function_id is missing
+ */
+ private static Scorer resolveRemoteScorer(
+ EvalRequest.RemoteScorer remoteScorer, BraintrustApiClient apiClient) {
+ var functionIdSpec = remoteScorer.getFunctionId();
+
+ if (functionIdSpec == null || functionIdSpec.getFunctionId() == null) {
+ throw new IllegalArgumentException(
+ "Remote scorer '" + remoteScorer.getName() + "' missing function_id");
+ }
+
+ return new ScorerBrainstoreImpl<>(
+ apiClient,
+ functionIdSpec.getFunctionId(),
+ remoteScorer.getName(),
+ functionIdSpec.getVersion());
+ }
+
public static class Builder {
private @Nullable BraintrustConfig config = null;
private String host = "localhost";
diff --git a/src/main/java/dev/braintrust/eval/Scorer.java b/src/main/java/dev/braintrust/eval/Scorer.java
index 2ea8c9a..f6dea50 100644
--- a/src/main/java/dev/braintrust/eval/Scorer.java
+++ b/src/main/java/dev/braintrust/eval/Scorer.java
@@ -1,8 +1,10 @@
package dev.braintrust.eval;
+import dev.braintrust.api.BraintrustApiClient;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
+import javax.annotation.Nullable;
/**
* A scorer evaluates the result of a test case with a score between 0 (inclusive) and 1
@@ -49,4 +51,33 @@ public List score(TaskResult taskResult) {
}
};
}
+
+ /**
+ * Fetch a scorer from Braintrust by project name and slug.
+ *
+ * @param apiClient the API client to use
+ * @param projectName the name of the project containing the scorer
+ * @param scorerSlug the unique slug identifier for the scorer
+ * @param version optional version of the scorer to fetch
+ * @return a Scorer that invokes the remote function
+ * @throws RuntimeException if the scorer is not found
+ */
+ static Scorer fetchFromBraintrust(
+ BraintrustApiClient apiClient,
+ String projectName,
+ String scorerSlug,
+ @Nullable String version) {
+ var function =
+ apiClient
+ .getFunction(projectName, scorerSlug, version)
+ .orElseThrow(
+ () ->
+ new RuntimeException(
+ "Scorer not found: project="
+ + projectName
+ + ", slug="
+ + scorerSlug));
+
+ return new ScorerBrainstoreImpl<>(apiClient, function.id(), function.name(), version);
+ }
}
diff --git a/src/main/java/dev/braintrust/eval/ScorerBrainstoreImpl.java b/src/main/java/dev/braintrust/eval/ScorerBrainstoreImpl.java
new file mode 100644
index 0000000..88d32d3
--- /dev/null
+++ b/src/main/java/dev/braintrust/eval/ScorerBrainstoreImpl.java
@@ -0,0 +1,156 @@
+package dev.braintrust.eval;
+
+import dev.braintrust.api.BraintrustApiClient;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * A scorer that invokes a remote Braintrust function to compute scores.
+ *
+ * This implementation fetches a scorer function from Braintrust and invokes it via the API for
+ * each task result. The remote function receives the input, output, expected, and metadata as
+ * arguments.
+ */
+public class ScorerBrainstoreImpl implements Scorer {
+ private final BraintrustApiClient apiClient;
+ private final String functionId;
+ private final String scorerName;
+ private final @Nullable String version;
+
+ /**
+ * Create a new remote scorer.
+ *
+ * @param apiClient the API client to use for invoking the function
+ * @param functionId the ID of the function to invoke
+ * @param scorerName the name of the scorer (used as default score name)
+ * @param version optional version of the function to invoke. null always invokes latest
+ * version.
+ */
+ public ScorerBrainstoreImpl(
+ BraintrustApiClient apiClient,
+ String functionId,
+ String scorerName,
+ @Nullable String version) {
+ this.apiClient = apiClient;
+ this.functionId = functionId;
+ this.scorerName = scorerName;
+ this.version = version;
+ }
+
+ @Override
+ public String getName() {
+ return scorerName;
+ }
+
+ @Override
+ public List score(TaskResult taskResult) {
+ var request =
+ BraintrustApiClient.FunctionInvokeRequest.forScorer(
+ taskResult.datasetCase().input(),
+ taskResult.result(),
+ taskResult.datasetCase().expected(),
+ taskResult.datasetCase().metadata(),
+ version);
+
+ Object result = apiClient.invokeFunction(functionId, request);
+ return parseScoreResult(result);
+ }
+
+ /**
+ * Parse the result from the function invocation into a list of scores.
+ *
+ * Handles various response formats:
+ *
+ *
+ * A single number (0.0-1.0) - converted to a Score with the scorer's name
+ * A Score object: {"score": 0.8, "name": "score_name", "metadata": {...}}
+ * An LLM judge response: {"name": "judge-name", "score": null, "metadata": {"choice":
+ * "0.9", ...}}
+ * A list of Score objects
+ * null - returns empty list (score skipped)
+ *
+ */
+ @SuppressWarnings("unchecked")
+ private List parseScoreResult(Object result) {
+ if (result == null) {
+ // Scorer returned null to skip scoring
+ return List.of();
+ }
+
+ // Handle a single number
+ if (result instanceof Number number) {
+ return List.of(new Score(scorerName, number.doubleValue()));
+ }
+
+ // Handle a list of scores
+ if (result instanceof List> list) {
+ List scores = new ArrayList<>();
+ for (Object item : list) {
+ scores.addAll(parseScoreResult(item));
+ }
+ return scores;
+ }
+
+ // Handle a score object (Map)
+ if (result instanceof Map, ?> map) {
+ Map scoreMap = (Map) map;
+
+ // Extract name (use scorer name as fallback)
+ String name = scorerName;
+ Object nameValue = scoreMap.get("name");
+ if (nameValue instanceof String s) {
+ name = s;
+ }
+
+ // Extract score value
+ Object scoreValue = scoreMap.get("score");
+
+ // If score is null, check for LLM judge response with metadata.choice
+ if (scoreValue == null) {
+ Object metadataObj = scoreMap.get("metadata");
+ if (metadataObj instanceof Map, ?> metadata) {
+ Object choiceValue = ((Map) metadata).get("choice");
+ if (choiceValue != null) {
+ double score = parseNumericValue(choiceValue);
+ return List.of(new Score(name, score));
+ }
+ }
+ // No score field and no choice in metadata - skip
+ return List.of();
+ }
+
+ double score = parseNumericValue(scoreValue);
+ return List.of(new Score(name, score));
+ }
+
+ throw new IllegalArgumentException(
+ "Unexpected score result type: "
+ + result.getClass()
+ + ". Expected Number, Map, or List.");
+ }
+
+ /**
+ * Parse a numeric value from either a Number or a String.
+ *
+ * @param value the value to parse
+ * @return the double value
+ * @throws IllegalArgumentException if the value cannot be parsed as a number
+ */
+ private double parseNumericValue(Object value) {
+ if (value instanceof Number number) {
+ return number.doubleValue();
+ }
+ if (value instanceof String str) {
+ try {
+ return Double.parseDouble(str);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "Cannot parse score value as number: '" + str + "'", e);
+ }
+ }
+ throw new IllegalArgumentException(
+ "Score value must be a number or numeric string, got: " + value.getClass());
+ }
+}
diff --git a/src/test/java/dev/braintrust/ForbiddenTextCheckingTransformer.java b/src/test/java/dev/braintrust/ForbiddenTextCheckingTransformer.java
new file mode 100644
index 0000000..f04d3ec
--- /dev/null
+++ b/src/test/java/dev/braintrust/ForbiddenTextCheckingTransformer.java
@@ -0,0 +1,55 @@
+package dev.braintrust;
+
+import com.github.tomakehurst.wiremock.common.FileSource;
+import com.github.tomakehurst.wiremock.common.Json;
+import com.github.tomakehurst.wiremock.extension.Parameters;
+import com.github.tomakehurst.wiremock.extension.StubMappingTransformer;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import java.util.List;
+
+/**
+ * WireMock transformer that checks stub mappings for forbidden text (e.g., API keys) before they
+ * are written to disk. Throws an exception immediately if any forbidden text is found, preventing
+ * accidental commit of secrets.
+ */
+public class ForbiddenTextCheckingTransformer extends StubMappingTransformer {
+
+ public static final String NAME = "forbidden-text-checking-transformer";
+
+ private final List forbiddenTexts;
+
+ public ForbiddenTextCheckingTransformer(List forbiddenTexts) {
+ // Filter out null/empty strings
+ this.forbiddenTexts =
+ forbiddenTexts.stream().filter(s -> s != null && !s.isEmpty()).toList();
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public StubMapping transform(StubMapping stubMapping, FileSource files, Parameters parameters) {
+ if (forbiddenTexts.isEmpty()) {
+ return stubMapping;
+ }
+
+ // Serialize the stub mapping to JSON to check the full content
+ String json = Json.write(stubMapping);
+
+ for (String forbidden : forbiddenTexts) {
+ if (json.contains(forbidden)) {
+ throw new IllegalStateException(
+ "SECURITY: Recording contains forbidden text (likely an API key). "
+ + "URL: "
+ + stubMapping.getRequest().getUrl()
+ + ". "
+ + "This recording should not be saved. "
+ + "Check that sensitive data is being properly redacted.");
+ }
+ }
+
+ return stubMapping;
+ }
+}
diff --git a/src/test/java/dev/braintrust/LoginBodyRedactingTransformer.java b/src/test/java/dev/braintrust/LoginBodyRedactingTransformer.java
new file mode 100644
index 0000000..f77344a
--- /dev/null
+++ b/src/test/java/dev/braintrust/LoginBodyRedactingTransformer.java
@@ -0,0 +1,36 @@
+package dev.braintrust;
+
+import com.github.tomakehurst.wiremock.common.FileSource;
+import com.github.tomakehurst.wiremock.extension.Parameters;
+import com.github.tomakehurst.wiremock.extension.StubMappingTransformer;
+import com.github.tomakehurst.wiremock.matching.RequestPattern;
+import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+
+/**
+ * WireMock transformer that removes request body patterns from login endpoint recordings. This
+ * prevents API keys from being stored in cassette files, since the login endpoint sends the token
+ * in the request body.
+ */
+public class LoginBodyRedactingTransformer extends StubMappingTransformer {
+
+ public static final String NAME = "login-body-redacting-transformer";
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public StubMapping transform(StubMapping stubMapping, FileSource files, Parameters parameters) {
+ RequestPattern request = stubMapping.getRequest();
+ String url = request.getUrl();
+ if (url != null && url.contains("/api/apikey/login")) {
+ // Create a new request pattern without body patterns to avoid storing API keys
+ RequestPattern newRequest =
+ new RequestPatternBuilder(request.getMethod(), request.getUrlMatcher()).build();
+ stubMapping.setRequest(newRequest);
+ }
+ return stubMapping;
+ }
+}
diff --git a/src/test/java/dev/braintrust/TestHarness.java b/src/test/java/dev/braintrust/TestHarness.java
index 87a3235..613363c 100644
--- a/src/test/java/dev/braintrust/TestHarness.java
+++ b/src/test/java/dev/braintrust/TestHarness.java
@@ -28,12 +28,22 @@ public class TestHarness {
private static final VCR vcr;
static {
+ // Collect all API keys that should never appear in recorded cassettes
+ List apiKeysToNeverRecord =
+ List.of(
+ getEnv("OPENAI_API_KEY", ""),
+ getEnv("ANTHROPIC_API_KEY", ""),
+ getEnv("GOOGLE_API_KEY", getEnv("GEMINI_API_KEY", "")),
+ getEnv("BRAINTRUST_API_KEY", ""));
+
vcr =
new VCR(
java.util.Map.of(
"https://api.openai.com/v1", "openai",
"https://api.anthropic.com", "anthropic",
- "https://generativelanguage.googleapis.com", "google"));
+ "https://generativelanguage.googleapis.com", "google",
+ "https://api.braintrust.dev", "braintrust"),
+ apiKeysToNeverRecord);
vcr.start();
Runtime.getRuntime().addShutdownHook(new Thread(vcr::stop));
}
@@ -61,19 +71,19 @@ public static synchronized TestHarness setup(BraintrustConfig config) {
@Getter
@Accessors(fluent = true)
- private static final String defaultProjectId = "01234";
+ private static final String defaultProjectId = "6ae68365-7620-4630-921b-bac416634fc8";
@Getter
@Accessors(fluent = true)
- private static final String defaultProjectName = "Unit Test";
+ private static final String defaultProjectName = "java-unit-test";
@Getter
@Accessors(fluent = true)
- private static final String defaultOrgId = "567890";
+ private static final String defaultOrgId = "5d7c97d7-fef1-4cb7-bda6-7e3756a0ca8e";
@Getter
@Accessors(fluent = true)
- private static final String defaultOrgName = "Test Org";
+ private static final String defaultOrgName = "braintrustdata.com";
private static final AtomicReference INSTANCE = new AtomicReference<>();
@@ -135,6 +145,14 @@ public String googleApiKey() {
return getEnv("GOOGLE_API_KEY", getEnv("GEMINI_API_KEY", "test-key"));
}
+ public String braintrustApiBaseUrl() {
+ return vcr.getUrlForTargetBase("https://api.braintrust.dev");
+ }
+
+ public String braintrustApiKey() {
+ return getEnv("BRAINTRUST_API_KEY", "test-key");
+ }
+
/** flush all pending spans and return all spans which have been exported so far */
public List awaitExportedSpans() {
assertTrue(
diff --git a/src/test/java/dev/braintrust/VCR.java b/src/test/java/dev/braintrust/VCR.java
index 880fffb..bb43eab 100644
--- a/src/test/java/dev/braintrust/VCR.java
+++ b/src/test/java/dev/braintrust/VCR.java
@@ -4,13 +4,16 @@
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.recording.RecordSpecBuilder;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
+import java.util.stream.Stream;
+import javax.annotation.concurrent.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
-import opennlp.tools.commons.ThreadSafe;
/** VCR (Video Cassette Recorder) for recording and replaying HTTP interactions. */
@Slf4j
@@ -30,17 +33,29 @@ public enum VcrMode {
private final Map proxyMap;
private final VcrMode mode;
private final Map targetUrlToMappingsDir;
+ private final List textToNeverRecord;
private boolean recordingStarted = false;
public VCR(Map targetUrlToCassettesDir) {
+ this(targetUrlToCassettesDir, List.of());
+ }
+
+ public VCR(Map targetUrlToCassettesDir, List textToNeverRecord) {
this(
VcrMode.valueOf(System.getenv().getOrDefault("VCR_MODE", "replay").toUpperCase()),
- targetUrlToCassettesDir);
+ targetUrlToCassettesDir,
+ textToNeverRecord);
}
- private VCR(VcrMode mode, Map targetUrlToCassettesDir) {
+ private VCR(
+ VcrMode mode,
+ Map targetUrlToCassettesDir,
+ List textToNeverRecord) {
this.mode = mode;
this.targetUrlToMappingsDir = Map.copyOf(targetUrlToCassettesDir);
+ // Filter out null/empty strings
+ this.textToNeverRecord =
+ textToNeverRecord.stream().filter(s -> s != null && !s.isEmpty()).toList();
// Create a WireMockServer for each provider
this.proxyMap = new LinkedHashMap<>();
@@ -53,7 +68,13 @@ private VCR(VcrMode mode, Map targetUrlToCassettesDir) {
WireMockServer wireMock =
new WireMockServer(
- wireMockConfig().dynamicPort().usingFilesUnderDirectory(cassettesDir));
+ wireMockConfig()
+ .dynamicPort()
+ .usingFilesUnderDirectory(cassettesDir)
+ .extensions(
+ new LoginBodyRedactingTransformer(),
+ new ForbiddenTextCheckingTransformer(
+ this.textToNeverRecord)));
proxyMap.put(targetUrl, wireMock);
}
}
@@ -141,7 +162,12 @@ private void startRecording(String targetBaseUrl) {
// Use JSON matching:
// - ignoreArrayOrder=true
// - ignoreExtraElements=false
- .matchRequestBodyWithEqualToJson(true, false);
+ .matchRequestBodyWithEqualToJson(true, false)
+ // Remove API keys from login endpoint recordings, then check for forbidden
+ // text
+ .transformers(
+ LoginBodyRedactingTransformer.NAME,
+ ForbiddenTextCheckingTransformer.NAME);
wireMock.startRecording(recordSpec);
}
@@ -255,8 +281,10 @@ private void stopRecording() {
if (mode == VcrMode.RECORD && recordingStarted) {
for (Map.Entry entry : proxyMap.entrySet()) {
String targetUrl = entry.getKey();
+ String mappingsDir = targetUrlToMappingsDir.get(targetUrl);
WireMockServer wireMock = entry.getValue();
wireMock.stopRecording();
+ validateNoForbiddenText(mappingsDir);
log.info("Recording saved for {}", targetUrl);
}
recordingStarted = false;
@@ -265,6 +293,47 @@ private void stopRecording() {
// The servers will be stopped when the JVM shuts down via the shutdown hook.
}
+ /**
+ * Validate that no forbidden text (e.g., API keys) appears in recorded cassettes. Throws an
+ * exception if any forbidden text is found, preventing accidental commit of secrets.
+ */
+ private void validateNoForbiddenText(String mappingsDir) {
+ if (textToNeverRecord.isEmpty()) {
+ return;
+ }
+
+ Path cassettesPath = Paths.get(CASSETTES_ROOT, mappingsDir);
+ if (!Files.exists(cassettesPath)) {
+ return;
+ }
+
+ try (Stream files = Files.walk(cassettesPath)) {
+ files.filter(Files::isRegularFile)
+ .filter(p -> p.toString().endsWith(".json"))
+ .forEach(this::validateFileContainsNoForbiddenText);
+ } catch (IOException e) {
+ log.warn("Failed to validate cassettes for forbidden text: {}", e.getMessage());
+ }
+ }
+
+ private void validateFileContainsNoForbiddenText(Path file) {
+ try {
+ String content = Files.readString(file);
+ for (String forbidden : textToNeverRecord) {
+ if (content.contains(forbidden)) {
+ throw new IllegalStateException(
+ "SECURITY: Cassette file contains forbidden text (likely an API key). "
+ + "File: "
+ + file
+ + ". This cassette should not be committed. Please delete it"
+ + " and ensure the LoginBodyRedactingTransformer is working.");
+ }
+ }
+ } catch (IOException e) {
+ log.warn("Failed to read cassette file {}: {}", file, e.getMessage());
+ }
+ }
+
private void assertStarted() {
for (WireMockServer wireMock : proxyMap.values()) {
if (!wireMock.isRunning()) {
diff --git a/src/test/java/dev/braintrust/devserver/DevserverTest.java b/src/test/java/dev/braintrust/devserver/DevserverTest.java
index b1a4065..3490534 100644
--- a/src/test/java/dev/braintrust/devserver/DevserverTest.java
+++ b/src/test/java/dev/braintrust/devserver/DevserverTest.java
@@ -4,16 +4,14 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.sun.net.httpserver.HttpServer;
import dev.braintrust.TestHarness;
+import dev.braintrust.TestUtils;
import dev.braintrust.config.BraintrustConfig;
import dev.braintrust.eval.Scorer;
import io.opentelemetry.sdk.trace.data.SpanData;
import java.io.BufferedReader;
import java.io.InputStreamReader;
-import java.io.OutputStream;
import java.net.HttpURLConnection;
-import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -28,88 +26,26 @@ class DevserverTest {
private static Devserver server;
private static Thread serverThread;
private static TestHarness testHarness;
- private static HttpServer mockApiServer;
- private static final int TEST_PORT = 18300;
- private static final int MOCK_API_PORT = 18301;
+ private static final int TEST_PORT = TestUtils.getRandomOpenPort();
private static final String TEST_URL = "http://localhost:" + TEST_PORT;
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+ // Remote scorer from java-unit-test project (returns 1.0 for exact match, 0.0 otherwise)
+ private static final String REMOTE_SCORER_FUNCTION_ID = "efa5f9c3-6ece-4726-a9d6-4ba792980b3f";
+ private static final String REMOTE_SCORER_NAME = "typescript_exact_match";
+
@BeforeAll
static void setUp() throws Exception {
- // Set up mock Braintrust API server
- mockApiServer = HttpServer.create(new InetSocketAddress("localhost", MOCK_API_PORT), 0);
-
- // Mock /v1/project endpoint
- mockApiServer.createContext(
- "/v1/project",
- exchange -> {
- String response =
- JSON_MAPPER.writeValueAsString(
- Map.of(
- "id", "test-project-id",
- "name", "test-project",
- "org_id", "test-org-id",
- "created", "2023-01-01T00:00:00Z",
- "updated", "2023-01-01T00:00:00Z"));
- exchange.getResponseHeaders().set("Content-Type", "application/json");
- exchange.sendResponseHeaders(
- 200, response.getBytes(StandardCharsets.UTF_8).length);
- try (OutputStream os = exchange.getResponseBody()) {
- os.write(response.getBytes(StandardCharsets.UTF_8));
- }
- });
-
- // Mock /v1/org endpoint
- mockApiServer.createContext(
- "/v1/org",
- exchange -> {
- String response =
- JSON_MAPPER.writeValueAsString(
- Map.of(
- "results",
- List.of(
- Map.of(
- "id", "test-org-id",
- "name", "test-org"))));
- exchange.getResponseHeaders().set("Content-Type", "application/json");
- exchange.sendResponseHeaders(
- 200, response.getBytes(StandardCharsets.UTF_8).length);
- try (OutputStream os = exchange.getResponseBody()) {
- os.write(response.getBytes(StandardCharsets.UTF_8));
- }
- });
-
- // Mock /api/apikey/login endpoint (using snake_case as per API client naming strategy)
- mockApiServer.createContext(
- "/api/apikey/login",
- exchange -> {
- String response =
- JSON_MAPPER.writeValueAsString(
- Map.of(
- "org_info",
- List.of(
- Map.of(
- "id", "test-org-id",
- "name", "test-org"))));
- exchange.getResponseHeaders().set("Content-Type", "application/json");
- exchange.sendResponseHeaders(
- 200, response.getBytes(StandardCharsets.UTF_8).length);
- try (OutputStream os = exchange.getResponseBody()) {
- os.write(response.getBytes(StandardCharsets.UTF_8));
- }
- });
-
- mockApiServer.start();
-
- // Set up test harness with config pointing to mock API
+ // Set up test harness with VCR (records/replays HTTP interactions)
+ testHarness = TestHarness.setup();
+
+ // Create config pointing to VCR-proxied Braintrust API
BraintrustConfig testConfig =
BraintrustConfig.of(
- "BRAINTRUST_API_KEY", "test-key",
- "BRAINTRUST_API_URL", "http://localhost:" + MOCK_API_PORT,
- "BRAINTRUST_APP_URL", "http://localhost:3000",
- "BRAINTRUST_DEFAULT_PROJECT_NAME", "test-project",
+ "BRAINTRUST_API_KEY", testHarness.braintrustApiKey(),
+ "BRAINTRUST_API_URL", testHarness.braintrustApiBaseUrl(),
+ "BRAINTRUST_DEFAULT_PROJECT_NAME", TestHarness.defaultProjectName(),
"BRAINTRUST_JAVA_EXPORT_SPANS_IN_MEMORY_FOR_UNIT_TEST", "true");
- testHarness = TestHarness.setup(testConfig);
// Create a shared eval for all tests
RemoteEval testEval =
@@ -135,7 +71,7 @@ static void setUp() throws Exception {
server =
Devserver.builder()
- .config(testHarness.braintrust().config())
+ .config(testConfig)
.registerEval(testEval)
.host("localhost")
.port(TEST_PORT)
@@ -169,9 +105,6 @@ static void tearDown() {
if (serverThread != null) {
serverThread.interrupt();
}
- if (mockApiServer != null) {
- mockApiServer.stop(0);
- }
}
@Test
@@ -218,6 +151,14 @@ void testStreamingEval() throws Exception {
Map.of("span_attributes", Map.of("generation", "test-gen-1")));
evalRequest.setParent(parentSpec);
+ // Add remote scorer from Braintrust
+ EvalRequest.RemoteScorer remoteScorer = new EvalRequest.RemoteScorer();
+ remoteScorer.setName(REMOTE_SCORER_NAME);
+ EvalRequest.FunctionId functionId = new EvalRequest.FunctionId();
+ functionId.setFunctionId(REMOTE_SCORER_FUNCTION_ID);
+ remoteScorer.setFunctionId(functionId);
+ evalRequest.setScores(List.of(remoteScorer));
+
String requestBody = JSON_MAPPER.writeValueAsString(evalRequest);
// Make POST request to /eval with auth headers
@@ -225,9 +166,9 @@ void testStreamingEval() throws Exception {
(HttpURLConnection) new URI(TEST_URL + "/eval").toURL().openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
- conn.setRequestProperty("x-bt-auth-token", "test-token-123");
- conn.setRequestProperty("x-bt-project-id", "test-project-id");
- conn.setRequestProperty("x-bt-org-name", "test-org");
+ conn.setRequestProperty("x-bt-auth-token", testHarness.braintrustApiKey());
+ conn.setRequestProperty("x-bt-project-id", TestHarness.defaultProjectId());
+ conn.setRequestProperty("x-bt-org-name", TestHarness.defaultOrgName());
conn.setDoOutput(true);
// Write request body
@@ -299,29 +240,39 @@ void testStreamingEval() throws Exception {
Map summaryEvent = summaryEvents.get(0);
JsonNode summaryData = JSON_MAPPER.readTree(summaryEvent.get("data"));
- assertEquals("test-project", summaryData.get("projectName").asText());
+ assertEquals(TestHarness.defaultProjectName(), summaryData.get("projectName").asText());
assertTrue(summaryData.has("projectId"));
assertEquals("food-type-classifier", summaryData.get("experimentName").asText());
// Verify scores in summary
assertTrue(summaryData.has("scores"));
JsonNode scores = summaryData.get("scores");
- assertTrue(scores.has("simple_scorer"));
+
+ // Verify local scorer
+ assertTrue(scores.has("simple_scorer"), "Summary should have simple_scorer");
JsonNode simpleScorer = scores.get("simple_scorer");
assertEquals("simple_scorer", simpleScorer.get("name").asText());
assertEquals(0.7, simpleScorer.get("score").asDouble(), 0.001);
+
+ // Verify remote scorer (returns 0.0 because output "java-fruit" != expected
+ // "fruit"/"vegetable")
+ assertTrue(scores.has(REMOTE_SCORER_NAME), "Summary should have remote scorer");
+ JsonNode remoteScorerResult = scores.get(REMOTE_SCORER_NAME);
+ assertEquals(REMOTE_SCORER_NAME, remoteScorerResult.get("name").asText());
+ assertEquals(0.0, remoteScorerResult.get("score").asDouble(), 0.001);
}
// Get exported spans from test harness (since devserver uses global tracer)
List exportedSpans = testHarness.awaitExportedSpans();
assertFalse(exportedSpans.isEmpty(), "Should have exported spans");
- // We should have 2 eval traces (one per dataset case), each with task, score, and custom
+ // We should have 2 eval traces (one per dataset case), each with task, scores, and custom
// spans
- // Each trace has: 1 eval span, 1 task span, 1 score span, 1 custom-task-span = 4 spans
+ // Each trace has: 1 eval span, 1 task span, 2 score spans (local + remote), 1
+ // custom-task-span = 5 spans
// per case
- // Total: 2 cases * 4 spans = 8 spans
- assertEquals(8, exportedSpans.size(), "Should have 8 spans (4 per dataset case)");
+ // Total: 2 cases * 5 spans = 10 spans
+ assertEquals(10, exportedSpans.size(), "Should have 10 spans (5 per dataset case)");
// Verify span types
var evalSpans = exportedSpans.stream().filter(s -> s.getName().equals("eval")).toList();
@@ -332,7 +283,7 @@ void testStreamingEval() throws Exception {
assertEquals(2, evalSpans.size(), "Should have 2 eval spans");
assertEquals(2, taskSpans.size(), "Should have 2 task spans");
- assertEquals(2, scoreSpans.size(), "Should have 2 score spans");
+ assertEquals(4, scoreSpans.size(), "Should have 4 score spans (2 scorers x 2 cases)");
assertEquals(2, customSpans.size(), "Should have 2 custom-task-span spans");
// Verify eval spans have all required attributes
@@ -446,9 +397,14 @@ void testStreamingEval() throws Exception {
assertNotNull(spanAttrsJson, "Score span should have span_attributes");
JsonNode spanAttrs = JSON_MAPPER.readTree(spanAttrsJson);
assertEquals("score", spanAttrs.get("type").asText());
- assertEquals("simple_scorer", spanAttrs.get("name").asText());
assertEquals("test-gen-1", spanAttrs.get("generation").asText());
+ // Scorer name should be either simple_scorer or the remote scorer
+ String scorerName = spanAttrs.get("name").asText();
+ assertTrue(
+ scorerName.equals("simple_scorer") || scorerName.equals(REMOTE_SCORER_NAME),
+ "Score span name should be simple_scorer or " + REMOTE_SCORER_NAME);
+
// Verify braintrust.output_json contains scores
String outputJson =
scoreSpan
@@ -458,8 +414,17 @@ void testStreamingEval() throws Exception {
"braintrust.output_json"));
assertNotNull(outputJson, "Score span should have output_json");
JsonNode output = JSON_MAPPER.readTree(outputJson);
- assertTrue(output.has("simple_scorer"), "Output should contain scorer results");
- assertEquals(0.7, output.get("simple_scorer").asDouble(), 0.001);
+
+ if (scorerName.equals("simple_scorer")) {
+ assertTrue(
+ output.has("simple_scorer"), "Output should contain simple_scorer results");
+ assertEquals(0.7, output.get("simple_scorer").asDouble(), 0.001);
+ } else {
+ assertTrue(
+ output.has(REMOTE_SCORER_NAME),
+ "Output should contain remote scorer results");
+ assertEquals(0.0, output.get(REMOTE_SCORER_NAME).asDouble(), 0.001);
+ }
}
for (SpanData customSpan : customSpans) {
@@ -506,9 +471,9 @@ void testEvaluatorNotFound() throws Exception {
.uri(URI.create(TEST_URL + "/eval"))
.POST(HttpRequest.BodyPublishers.ofString(requestJson))
.header("Content-Type", "application/json")
- .header("x-bt-auth-token", "test-token")
- .header("x-bt-project-id", "test-project-id")
- .header("x-bt-org-name", "test-org")
+ .header("x-bt-auth-token", testHarness.braintrustApiKey())
+ .header("x-bt-project-id", TestHarness.defaultProjectId())
+ .header("x-bt-org-name", TestHarness.defaultOrgName())
.build();
HttpResponse response =
@@ -536,9 +501,9 @@ void testListEndpoint() throws Exception {
HttpRequest.newBuilder()
.uri(URI.create(TEST_URL + "/list"))
.GET()
- .header("x-bt-auth-token", "test-token")
- .header("x-bt-project-id", "test-project-id")
- .header("x-bt-org-name", "test-org")
+ .header("x-bt-auth-token", testHarness.braintrustApiKey())
+ .header("x-bt-project-id", TestHarness.defaultProjectId())
+ .header("x-bt-org-name", TestHarness.defaultOrgName())
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
@@ -585,9 +550,9 @@ void testListEndpointWithCors() throws Exception {
.uri(URI.create(TEST_URL + "/list"))
.GET()
.header("Origin", "https://www.braintrust.dev")
- .header("x-bt-auth-token", "test-token")
- .header("x-bt-project-id", "test-project-id")
- .header("x-bt-org-name", "test-org")
+ .header("x-bt-auth-token", testHarness.braintrustApiKey())
+ .header("x-bt-project-id", TestHarness.defaultProjectId())
+ .header("x-bt-org-name", TestHarness.defaultOrgName())
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
diff --git a/src/test/java/dev/braintrust/eval/ScorerBrainstoreImplTest.java b/src/test/java/dev/braintrust/eval/ScorerBrainstoreImplTest.java
new file mode 100644
index 0000000..ffbc35c
--- /dev/null
+++ b/src/test/java/dev/braintrust/eval/ScorerBrainstoreImplTest.java
@@ -0,0 +1,135 @@
+package dev.braintrust.eval;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import dev.braintrust.TestHarness;
+import dev.braintrust.api.BraintrustApiClient;
+import dev.braintrust.config.BraintrustConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class ScorerBrainstoreImplTest {
+ // NOTE: the remote scorers under test are standard boilerplate
+ // TODO: test is VCR'd so it's fine, but would be nice to have logic to (re)create the score
+ // objects if they are absent
+
+ // returns 1.0 for an exact match, 0.0 otherwise
+ private static final String SCORER_SLUG = "typescriptexactmatch-9e44";
+
+ // LLM judge scorer that returns {"name":"close-enough-judge","metadata":{"choice":"0.9",...}}
+ private static final String LLM_JUDGE_SLUG = "close-enough-judge-d31b";
+
+ private TestHarness testHarness;
+ private BraintrustApiClient apiClient;
+
+ @BeforeEach
+ void beforeEach() {
+ testHarness = TestHarness.setup();
+
+ var config =
+ BraintrustConfig.builder()
+ .apiKey(testHarness.braintrustApiKey())
+ .apiUrl(testHarness.braintrustApiBaseUrl())
+ .build();
+
+ apiClient = BraintrustApiClient.of(config);
+ }
+
+ @Test
+ void testScorerReturnsOneForExactMatch() {
+ Scorer scorer =
+ Scorer.fetchFromBraintrust(
+ apiClient,
+ testHarness.braintrust().config().defaultProjectName().orElseThrow(),
+ SCORER_SLUG,
+ null);
+ assertNotNull(scorer);
+ assertNotNull(scorer.getName());
+
+ var datasetCase = DatasetCase.of("test input", "hello world");
+ var taskResult = new TaskResult<>("hello world", datasetCase);
+
+ var scores = scorer.score(taskResult);
+
+ assertFalse(scores.isEmpty(), "Expected scores but got empty list");
+ assertEquals(1.0, scores.get(0).value(), 0.001, "Exact match should return 1.0");
+ }
+
+ @Test
+ void testScorerReturnsZeroForMismatch() {
+ Scorer scorer =
+ Scorer.fetchFromBraintrust(
+ apiClient,
+ testHarness.braintrust().config().defaultProjectName().orElseThrow(),
+ SCORER_SLUG,
+ null);
+ assertNotNull(scorer);
+ assertNotNull(scorer.getName());
+
+ var datasetCase = DatasetCase.of("test input", "expected");
+ var taskResult = new TaskResult<>("different", datasetCase);
+
+ var scores = scorer.score(taskResult);
+
+ assertFalse(scores.isEmpty(), "Expected scores but got empty list");
+ assertEquals(0.0, scores.get(0).value(), 0.001, "Mismatch should return 0.0");
+ }
+
+ @Test
+ void testScorerOldVersion() {
+ // Version 485dbf64e486ab3a of the exact match scorer always returns 0, even for exact
+ // matches
+ String oldVersion = "485dbf64e486ab3a";
+ Scorer scorer =
+ Scorer.fetchFromBraintrust(
+ apiClient,
+ testHarness.braintrust().config().defaultProjectName().orElseThrow(),
+ SCORER_SLUG,
+ oldVersion);
+ assertNotNull(scorer);
+ assertNotNull(scorer.getName());
+
+ var datasetCase = DatasetCase.of("test input", "hello world");
+ var taskResult = new TaskResult<>("hello world", datasetCase);
+
+ var scores = scorer.score(taskResult);
+
+ assertFalse(scores.isEmpty(), "Expected scores but got empty list");
+ assertEquals(
+ 0.0,
+ scores.get(0).value(),
+ 0.001,
+ "Old version %s should always return 0.0, even for exact match"
+ .formatted(oldVersion));
+ }
+
+ @Test
+ void testLlmJudgeScorerReturnsScoreFromMetadataChoice() {
+ Scorer scorer =
+ Scorer.fetchFromBraintrust(
+ apiClient,
+ testHarness.braintrust().config().defaultProjectName().orElseThrow(),
+ LLM_JUDGE_SLUG,
+ null);
+ assertNotNull(scorer);
+ assertNotNull(scorer.getName());
+
+ // LLM judge evaluates whether output is "close enough" to expected
+ var datasetCase = DatasetCase.of("What is 2+2?", "4");
+ var taskResult = new TaskResult<>("four", datasetCase);
+
+ var scores = scorer.score(taskResult);
+
+ assertFalse(scores.isEmpty(), "Expected scores but got empty list");
+ // LLM judge returns score in metadata.choice field
+ // The score should be between 0.0 and 1.0
+ assertTrue(
+ scores.get(0).value() >= 0.0 && scores.get(0).value() <= 1.0,
+ "LLM judge score should be between 0.0 and 1.0, got: " + scores.get(0).value());
+ // Verify the scorer name comes from the response
+ assertEquals(
+ "close-enough-judge",
+ scores.get(0).name(),
+ "Scorer name should come from the LLM judge response");
+ }
+}
diff --git a/src/test/resources/cassettes/anthropic/__files/v1_messages-4c99ad9a-3bc5-4f73-bd08-84f892478c4d.json b/src/test/resources/cassettes/anthropic/__files/v1_messages-10bd174c-61dd-42e0-b078-0f899387c789.json
similarity index 70%
rename from src/test/resources/cassettes/anthropic/__files/v1_messages-4c99ad9a-3bc5-4f73-bd08-84f892478c4d.json
rename to src/test/resources/cassettes/anthropic/__files/v1_messages-10bd174c-61dd-42e0-b078-0f899387c789.json
index 4481d62..f08a839 100644
--- a/src/test/resources/cassettes/anthropic/__files/v1_messages-4c99ad9a-3bc5-4f73-bd08-84f892478c4d.json
+++ b/src/test/resources/cassettes/anthropic/__files/v1_messages-10bd174c-61dd-42e0-b078-0f899387c789.json
@@ -1 +1 @@
-{"model":"claude-3-5-haiku-20241022","id":"msg_01MhSBYGdz56JjxtCkyqLNMi","type":"message","role":"assistant","content":[{"type":"text","text":"The capital of France is Paris."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":19,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":10,"service_tier":"standard"}}
\ No newline at end of file
+{"model":"claude-3-5-haiku-20241022","id":"msg_012gishQVgVX9SbBiEo4dCp8","type":"message","role":"assistant","content":[{"type":"text","text":"The capital of France is Paris."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":19,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":10,"service_tier":"standard"}}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/anthropic/__files/v1_messages-68182ecb-89f2-4d6d-9d6a-49cb3f036357.txt b/src/test/resources/cassettes/anthropic/__files/v1_messages-8120a850-75ab-4fe2-8c4b-f0367beada3a.txt
similarity index 73%
rename from src/test/resources/cassettes/anthropic/__files/v1_messages-68182ecb-89f2-4d6d-9d6a-49cb3f036357.txt
rename to src/test/resources/cassettes/anthropic/__files/v1_messages-8120a850-75ab-4fe2-8c4b-f0367beada3a.txt
index 65b3b61..63ec779 100644
--- a/src/test/resources/cassettes/anthropic/__files/v1_messages-68182ecb-89f2-4d6d-9d6a-49cb3f036357.txt
+++ b/src/test/resources/cassettes/anthropic/__files/v1_messages-8120a850-75ab-4fe2-8c4b-f0367beada3a.txt
@@ -1,24 +1,24 @@
event: message_start
-data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01YTLrex9iVq8MvNEBcvw3cG","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":19,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":5,"service_tier":"standard"}} }
+data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_012tS1cYEUH4trpwrzTXPaif","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":19,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":5,"service_tier":"standard"}} }
event: content_block_start
-data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} }
+data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} }
event: ping
data: {"type": "ping"}
event: content_block_delta
-data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"The capital of France is"} }
+data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"The capital of France is"} }
event: content_block_delta
-data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Paris."} }
+data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Paris."}}
event: content_block_stop
-data: {"type":"content_block_stop","index":0}
+data: {"type":"content_block_stop","index":0 }
event: message_delta
-data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":19,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10} }
+data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":19,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10} }
event: message_stop
-data: {"type":"message_stop" }
+data: {"type":"message_stop" }
diff --git a/src/test/resources/cassettes/anthropic/mappings/v1_messages-4c99ad9a-3bc5-4f73-bd08-84f892478c4d.json b/src/test/resources/cassettes/anthropic/mappings/v1_messages-10bd174c-61dd-42e0-b078-0f899387c789.json
similarity index 68%
rename from src/test/resources/cassettes/anthropic/mappings/v1_messages-4c99ad9a-3bc5-4f73-bd08-84f892478c4d.json
rename to src/test/resources/cassettes/anthropic/mappings/v1_messages-10bd174c-61dd-42e0-b078-0f899387c789.json
index 7ad4058..4f85bba 100644
--- a/src/test/resources/cassettes/anthropic/mappings/v1_messages-4c99ad9a-3bc5-4f73-bd08-84f892478c4d.json
+++ b/src/test/resources/cassettes/anthropic/mappings/v1_messages-10bd174c-61dd-42e0-b078-0f899387c789.json
@@ -1,5 +1,5 @@
{
- "id" : "4c99ad9a-3bc5-4f73-bd08-84f892478c4d",
+ "id" : "10bd174c-61dd-42e0-b078-0f899387c789",
"name" : "v1_messages",
"request" : {
"url" : "/v1/messages",
@@ -17,33 +17,33 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "v1_messages-4c99ad9a-3bc5-4f73-bd08-84f892478c4d.json",
+ "bodyFileName" : "v1_messages-10bd174c-61dd-42e0-b078-0f899387c789.json",
"headers" : {
- "Date" : "Tue, 13 Jan 2026 18:55:40 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:11 GMT",
"Content-Type" : "application/json",
"anthropic-ratelimit-requests-limit" : "10000",
"anthropic-ratelimit-requests-remaining" : "9999",
- "anthropic-ratelimit-requests-reset" : "2026-01-13T18:55:39Z",
+ "anthropic-ratelimit-requests-reset" : "2026-01-23T05:36:11Z",
"anthropic-ratelimit-input-tokens-limit" : "5000000",
"anthropic-ratelimit-input-tokens-remaining" : "5000000",
- "anthropic-ratelimit-input-tokens-reset" : "2026-01-13T18:55:39Z",
+ "anthropic-ratelimit-input-tokens-reset" : "2026-01-23T05:36:11Z",
"anthropic-ratelimit-output-tokens-limit" : "1000000",
"anthropic-ratelimit-output-tokens-remaining" : "1000000",
- "anthropic-ratelimit-output-tokens-reset" : "2026-01-13T18:55:40Z",
+ "anthropic-ratelimit-output-tokens-reset" : "2026-01-23T05:36:11Z",
"anthropic-ratelimit-tokens-limit" : "6000000",
"anthropic-ratelimit-tokens-remaining" : "6000000",
- "anthropic-ratelimit-tokens-reset" : "2026-01-13T18:55:39Z",
- "request-id" : "req_011CX5tMzY85axeEQMUkdDni",
+ "anthropic-ratelimit-tokens-reset" : "2026-01-23T05:36:11Z",
+ "X-Robots-Tag" : "none",
+ "request-id" : "req_011CXPmVCSBbJwY2Ysqo5QAg",
"strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
"anthropic-organization-id" : "27796668-7351-40ac-acc4-024aee8995a5",
"Server" : "cloudflare",
- "x-envoy-upstream-service-time" : "538",
+ "x-envoy-upstream-service-time" : "455",
"cf-cache-status" : "DYNAMIC",
- "X-Robots-Tag" : "none",
- "CF-RAY" : "9bd71befef2c76b2-SEA"
+ "CF-RAY" : "9c24ee95dd89db33-SEA"
}
},
- "uuid" : "4c99ad9a-3bc5-4f73-bd08-84f892478c4d",
+ "uuid" : "10bd174c-61dd-42e0-b078-0f899387c789",
"persistent" : true,
"insertionIndex" : 1
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/anthropic/mappings/v1_messages-68182ecb-89f2-4d6d-9d6a-49cb3f036357.json b/src/test/resources/cassettes/anthropic/mappings/v1_messages-8120a850-75ab-4fe2-8c4b-f0367beada3a.json
similarity index 69%
rename from src/test/resources/cassettes/anthropic/mappings/v1_messages-68182ecb-89f2-4d6d-9d6a-49cb3f036357.json
rename to src/test/resources/cassettes/anthropic/mappings/v1_messages-8120a850-75ab-4fe2-8c4b-f0367beada3a.json
index 9e01635..f482886 100644
--- a/src/test/resources/cassettes/anthropic/mappings/v1_messages-68182ecb-89f2-4d6d-9d6a-49cb3f036357.json
+++ b/src/test/resources/cassettes/anthropic/mappings/v1_messages-8120a850-75ab-4fe2-8c4b-f0367beada3a.json
@@ -1,5 +1,5 @@
{
- "id" : "68182ecb-89f2-4d6d-9d6a-49cb3f036357",
+ "id" : "8120a850-75ab-4fe2-8c4b-f0367beada3a",
"name" : "v1_messages",
"request" : {
"url" : "/v1/messages",
@@ -17,34 +17,34 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "v1_messages-68182ecb-89f2-4d6d-9d6a-49cb3f036357.txt",
+ "bodyFileName" : "v1_messages-8120a850-75ab-4fe2-8c4b-f0367beada3a.txt",
"headers" : {
- "Date" : "Tue, 13 Jan 2026 18:55:41 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:12 GMT",
"Content-Type" : "text/event-stream; charset=utf-8",
"Cache-Control" : "no-cache",
"anthropic-ratelimit-requests-limit" : "10000",
"anthropic-ratelimit-requests-remaining" : "9999",
- "anthropic-ratelimit-requests-reset" : "2026-01-13T18:55:40Z",
+ "anthropic-ratelimit-requests-reset" : "2026-01-23T05:36:11Z",
"anthropic-ratelimit-input-tokens-limit" : "5000000",
"anthropic-ratelimit-input-tokens-remaining" : "5000000",
- "anthropic-ratelimit-input-tokens-reset" : "2026-01-13T18:55:40Z",
+ "anthropic-ratelimit-input-tokens-reset" : "2026-01-23T05:36:11Z",
"anthropic-ratelimit-output-tokens-limit" : "1000000",
"anthropic-ratelimit-output-tokens-remaining" : "1000000",
- "anthropic-ratelimit-output-tokens-reset" : "2026-01-13T18:55:40Z",
+ "anthropic-ratelimit-output-tokens-reset" : "2026-01-23T05:36:11Z",
"anthropic-ratelimit-tokens-limit" : "6000000",
"anthropic-ratelimit-tokens-remaining" : "6000000",
- "anthropic-ratelimit-tokens-reset" : "2026-01-13T18:55:40Z",
- "request-id" : "req_011CX5tN5bEBPFm9bcjPAmud",
+ "anthropic-ratelimit-tokens-reset" : "2026-01-23T05:36:11Z",
+ "request-id" : "req_011CXPmVFdRQeFhPG37DhYW6",
"strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
"anthropic-organization-id" : "27796668-7351-40ac-acc4-024aee8995a5",
"Server" : "cloudflare",
- "x-envoy-upstream-service-time" : "371",
+ "x-envoy-upstream-service-time" : "346",
"cf-cache-status" : "DYNAMIC",
"X-Robots-Tag" : "none",
- "CF-RAY" : "9bd71bf7499f347d-SEA"
+ "CF-RAY" : "9c24ee9a8aadec98-SEA"
}
},
- "uuid" : "68182ecb-89f2-4d6d-9d6a-49cb3f036357",
+ "uuid" : "8120a850-75ab-4fe2-8c4b-f0367beada3a",
"persistent" : true,
"insertionIndex" : 2
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/api_apikey_login-68a49a40-dfe4-41d9-a6a7-5023c431254b.json b/src/test/resources/cassettes/braintrust/__files/api_apikey_login-68a49a40-dfe4-41d9-a6a7-5023c431254b.json
new file mode 100644
index 0000000..9a18a48
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/api_apikey_login-68a49a40-dfe4-41d9-a6a7-5023c431254b.json
@@ -0,0 +1 @@
+{"org_info":[{"id":"5d7c97d7-fef1-4cb7-bda6-7e3756a0ca8e","name":"braintrustdata.com","api_url":"https://staging-api.braintrust.dev","git_metadata":{"fields":["commit","branch","tag","author_name","author_email","commit_message","commit_time","dirty"],"collect":"some"},"is_universal_api":true,"proxy_url":"https://staging-api.braintrust.dev","realtime_url":"wss://realtime.braintrustapi.com"}]}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/api_apikey_login-720807f6-1acb-4361-8f82-8fd8c71d6a99.json b/src/test/resources/cassettes/braintrust/__files/api_apikey_login-720807f6-1acb-4361-8f82-8fd8c71d6a99.json
new file mode 100644
index 0000000..9a18a48
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/api_apikey_login-720807f6-1acb-4361-8f82-8fd8c71d6a99.json
@@ -0,0 +1 @@
+{"org_info":[{"id":"5d7c97d7-fef1-4cb7-bda6-7e3756a0ca8e","name":"braintrustdata.com","api_url":"https://staging-api.braintrust.dev","git_metadata":{"fields":["commit","branch","tag","author_name","author_email","commit_message","commit_time","dirty"],"collect":"some"},"is_universal_api":true,"proxy_url":"https://staging-api.braintrust.dev","realtime_url":"wss://realtime.braintrustapi.com"}]}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function-32c90375-557c-4ef8-9263-383a8386366a.json b/src/test/resources/cassettes/braintrust/__files/v1_function-32c90375-557c-4ef8-9263-383a8386366a.json
new file mode 100644
index 0000000..b0a4be5
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function-32c90375-557c-4ef8-9263-383a8386366a.json
@@ -0,0 +1 @@
+{"objects":[{"id":"efa5f9c3-6ece-4726-a9d6-4ba792980b3f","_xact_id":"1000196534222689290","project_id":"6ae68365-7620-4630-921b-bac416634fc8","log_id":"p","org_id":"5d7c97d7-fef1-4cb7-bda6-7e3756a0ca8e","name":"typescript_exact_match","slug":"typescriptexactmatch-9e44","description":null,"created":"2026-01-23T02:07:39.293Z","prompt_data":null,"tags":null,"metadata":null,"function_type":"scorer","function_data":{"data":{"code":"// Enter handler function that returns a numeric score between 0 and 1,\n// or null to skip scoring\nfunction handler({\n input,\n output,\n expected,\n metadata,\n}: {\n input: any;\n output: any;\n expected: any;\n metadata: Record;\n}): number | null {\n if (expected === null) return null;\n return output === expected ? 0 : 0;\n}","type":"inline","runtime_context":{"runtime":"node","version":"22"}},"type":"code"},"origin":null,"function_schema":null}]}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function-a40fed81-8f60-44a2-819c-afa1c6941fe1.json b/src/test/resources/cassettes/braintrust/__files/v1_function-a40fed81-8f60-44a2-819c-afa1c6941fe1.json
new file mode 100644
index 0000000..5dd6d39
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function-a40fed81-8f60-44a2-819c-afa1c6941fe1.json
@@ -0,0 +1 @@
+{"objects":[{"id":"5dd8a26d-3be8-4ecd-af5b-df2a6a592277","_xact_id":"1000196533616093054","project_id":"6ae68365-7620-4630-921b-bac416634fc8","log_id":"p","org_id":"5d7c97d7-fef1-4cb7-bda6-7e3756a0ca8e","name":"close-enough-judge","slug":"close-enough-judge-d31b","description":null,"created":"2026-01-22T23:33:23.674Z","prompt_data":{"parser":{"type":"llm_classifier","use_cot":true,"choice_scores":{"":0}},"prompt":{"type":"chat","messages":[{"role":"system","content":"you are an LLM eval scorer. you will evaluate output compared to expected results and return a value between 0.0 and 1.0 (0 is worst 1 is best)"},{"role":"user","content":"how closely does my eval case output: `{{output}}` match the expected result of `{{expected}}`"}]},"options":{"model":"gpt-5-mini","params":{"use_cache":true,"temperature":0},"position":"0|hzzzzz:"}},"tags":null,"metadata":null,"function_type":"scorer","function_data":{"type":"prompt"},"origin":null,"function_schema":null}]}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function-ce46228b-9279-4285-9695-5d3ab0ebb857.json b/src/test/resources/cassettes/braintrust/__files/v1_function-ce46228b-9279-4285-9695-5d3ab0ebb857.json
new file mode 100644
index 0000000..69c0e3c
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function-ce46228b-9279-4285-9695-5d3ab0ebb857.json
@@ -0,0 +1 @@
+{"objects":[{"id":"efa5f9c3-6ece-4726-a9d6-4ba792980b3f","_xact_id":"1000196534223020329","project_id":"6ae68365-7620-4630-921b-bac416634fc8","log_id":"p","org_id":"5d7c97d7-fef1-4cb7-bda6-7e3756a0ca8e","name":"typescript_exact_match","slug":"typescriptexactmatch-9e44","description":null,"created":"2026-01-23T02:07:44.212Z","prompt_data":null,"tags":null,"metadata":null,"function_type":"scorer","function_data":{"data":{"code":"// Enter handler function that returns a numeric score between 0 and 1,\n// or null to skip scoring\nfunction handler({\n input,\n output,\n expected,\n metadata,\n}: {\n input: any;\n output: any;\n expected: any;\n metadata: Record;\n}): number | null {\n if (expected === null) return null;\n return output === expected ? 1 : 0;\n}","type":"inline","runtime_context":{"runtime":"node","version":"22"}},"type":"code"},"origin":null,"function_schema":null}]}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function-ec6186f4-a80d-4a8c-81fb-ad828ba038b8.json b/src/test/resources/cassettes/braintrust/__files/v1_function-ec6186f4-a80d-4a8c-81fb-ad828ba038b8.json
new file mode 100644
index 0000000..69c0e3c
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function-ec6186f4-a80d-4a8c-81fb-ad828ba038b8.json
@@ -0,0 +1 @@
+{"objects":[{"id":"efa5f9c3-6ece-4726-a9d6-4ba792980b3f","_xact_id":"1000196534223020329","project_id":"6ae68365-7620-4630-921b-bac416634fc8","log_id":"p","org_id":"5d7c97d7-fef1-4cb7-bda6-7e3756a0ca8e","name":"typescript_exact_match","slug":"typescriptexactmatch-9e44","description":null,"created":"2026-01-23T02:07:44.212Z","prompt_data":null,"tags":null,"metadata":null,"function_type":"scorer","function_data":{"data":{"code":"// Enter handler function that returns a numeric score between 0 and 1,\n// or null to skip scoring\nfunction handler({\n input,\n output,\n expected,\n metadata,\n}: {\n input: any;\n output: any;\n expected: any;\n metadata: Record;\n}): number | null {\n if (expected === null) return null;\n return output === expected ? 1 : 0;\n}","type":"inline","runtime_context":{"runtime":"node","version":"22"}},"type":"code"},"origin":null,"function_schema":null}]}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function_5dd8a26d-3be8-4ecd-af5b-df2a6a592277_invoke-c67cd10d-7d05-4e66-a861-360e14dace92.json b/src/test/resources/cassettes/braintrust/__files/v1_function_5dd8a26d-3be8-4ecd-af5b-df2a6a592277_invoke-c67cd10d-7d05-4e66-a861-360e14dace92.json
new file mode 100644
index 0000000..e35193e
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function_5dd8a26d-3be8-4ecd-af5b-df2a6a592277_invoke-c67cd10d-7d05-4e66-a861-360e14dace92.json
@@ -0,0 +1 @@
+{"name":"close-enough-judge","score":null,"metadata":{"rationale":"1) Identify expected: the expected result is the numeral '4', which represents the integer 4.\n2) Identify output: the candidate output is the word 'four', which is the English lexical representation of the same integer.\n3) Semantic comparison: 'four' and '4' denote the same value; under semantic or normalized numeric evaluation they are equivalent.\n4) Token/string exact-match consideration: as raw strings they differ ('four' != '4'), so under a strict exact-byte/string-match metric they would score 0.\n5) Typical evaluation practice: most evaluation for numeric answers either normalize number words and digits or compare numeric value; in that common case they match perfectly.\nConclusion: under a sensible normalization/numeric equivalence metric the match is perfect, so score 1.0; under strict raw string match it would be 0.0. I choose the normalized/numeric-equivalence interpretation.","choice":"1.0"}}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-69d15cfb-87c7-41a1-a99a-df653a67ec0a.json b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-69d15cfb-87c7-41a1-a99a-df653a67ec0a.json
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-69d15cfb-87c7-41a1-a99a-df653a67ec0a.json
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a5548413-7bea-4a3b-8dd6-575e0594ca85.json b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a5548413-7bea-4a3b-8dd6-575e0594ca85.json
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a5548413-7bea-4a3b-8dd6-575e0594ca85.json
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a599c78a-4aec-4bf0-b64e-e9b183b45c57.json b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a599c78a-4aec-4bf0-b64e-e9b183b45c57.json
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a599c78a-4aec-4bf0-b64e-e9b183b45c57.json
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-d2eef3e5-3348-4b4d-8ea5-08d7da45f22b.json b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-d2eef3e5-3348-4b4d-8ea5-08d7da45f22b.json
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-d2eef3e5-3348-4b4d-8ea5-08d7da45f22b.json
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0.json b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0.json
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0.json
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/__files/v1_project_6ae68365-7620-4630-921b-bac416634fc8-3e4fd9ae-b027-4c4e-a714-5ba10db840dd.json b/src/test/resources/cassettes/braintrust/__files/v1_project_6ae68365-7620-4630-921b-bac416634fc8-3e4fd9ae-b027-4c4e-a714-5ba10db840dd.json
new file mode 100644
index 0000000..f2e7801
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/__files/v1_project_6ae68365-7620-4630-921b-bac416634fc8-3e4fd9ae-b027-4c4e-a714-5ba10db840dd.json
@@ -0,0 +1 @@
+{"id":"6ae68365-7620-4630-921b-bac416634fc8","org_id":"5d7c97d7-fef1-4cb7-bda6-7e3756a0ca8e","name":"java-unit-test","description":null,"created":"2026-01-21T01:32:52.137Z","deleted_at":null,"user_id":"a5ca7f9c-bf20-40c4-a82b-5c992f6a38f5","settings":{"remote_eval_sources":[{"url":"http://localhost:8301","name":"java-devserver","description":null}]}}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/api_apikey_login-68a49a40-dfe4-41d9-a6a7-5023c431254b.json b/src/test/resources/cassettes/braintrust/mappings/api_apikey_login-68a49a40-dfe4-41d9-a6a7-5023c431254b.json
new file mode 100644
index 0000000..8b8aeb2
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/api_apikey_login-68a49a40-dfe4-41d9-a6a7-5023c431254b.json
@@ -0,0 +1,35 @@
+{
+ "id" : "68a49a40-dfe4-41d9-a6a7-5023c431254b",
+ "name" : "api_apikey_login",
+ "request" : {
+ "url" : "/api/apikey/login",
+ "method" : "POST"
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "api_apikey_login-68a49a40-dfe4-41d9-a6a7-5023c431254b.json",
+ "headers" : {
+ "Content-Type" : "application/json; charset=utf-8",
+ "X-Amz-Cf-Pop" : [ "SEA900-P2", "SEA900-P10" ],
+ "Date" : "Fri, 23 Jan 2026 05:36:05 GMT",
+ "access-control-allow-credentials" : "true",
+ "x-amzn-RequestId" : "23f528d7-ebf7-41e2-9f0b-ef2bb770438b",
+ "x-amzn-Remapped-content-length" : "395",
+ "x-bt-internal-trace-id" : "697308c50000000068d808765efbc7e1",
+ "x-amz-apigw-id" : "Xn5O5HVWIAMEZvA=",
+ "vary" : "Origin, Accept-Encoding",
+ "etag" : "W/\"18b-OPCBBHzVVuCPaglXVbFjmsFzOoE\"",
+ "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan",
+ "X-Amzn-Trace-Id" : "Root=1-697308c5-09266ed92479ae524fed16c7;Parent=569846c52114e196;Sampled=0;Lineage=1:24be3d11:0",
+ "Via" : "1.1 5f438acc4bc4bcf2bce0445584c013cc.cloudfront.net (CloudFront), 1.1 0df7f27a01014ab815259ca2d88193c6.cloudfront.net (CloudFront)",
+ "X-Cache" : "Miss from cloudfront",
+ "X-Amz-Cf-Id" : "q2fthlL0f-de6l-0TpHMQBwTLJEaA3fth2klx168qpqNVdklUPXP4Q=="
+ }
+ },
+ "uuid" : "68a49a40-dfe4-41d9-a6a7-5023c431254b",
+ "persistent" : true,
+ "scenarioName" : "scenario-1-api-apikey-login",
+ "requiredScenarioState" : "Started",
+ "newScenarioState" : "scenario-1-api-apikey-login-2",
+ "insertionIndex" : 1
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/api_apikey_login-720807f6-1acb-4361-8f82-8fd8c71d6a99.json b/src/test/resources/cassettes/braintrust/mappings/api_apikey_login-720807f6-1acb-4361-8f82-8fd8c71d6a99.json
new file mode 100644
index 0000000..791cefc
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/api_apikey_login-720807f6-1acb-4361-8f82-8fd8c71d6a99.json
@@ -0,0 +1,34 @@
+{
+ "id" : "720807f6-1acb-4361-8f82-8fd8c71d6a99",
+ "name" : "api_apikey_login",
+ "request" : {
+ "url" : "/api/apikey/login",
+ "method" : "POST"
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "api_apikey_login-720807f6-1acb-4361-8f82-8fd8c71d6a99.json",
+ "headers" : {
+ "Content-Type" : "application/json; charset=utf-8",
+ "X-Amz-Cf-Pop" : [ "SEA900-P2", "SEA900-P10" ],
+ "Date" : "Fri, 23 Jan 2026 05:36:06 GMT",
+ "access-control-allow-credentials" : "true",
+ "x-amzn-RequestId" : "9714cf06-3569-4d6c-a9a2-fb3fe1b886e8",
+ "x-amzn-Remapped-content-length" : "395",
+ "x-bt-internal-trace-id" : "697308c60000000064f3337c94881869",
+ "x-amz-apigw-id" : "Xn5PBGhPIAMEZNA=",
+ "vary" : "Origin, Accept-Encoding",
+ "etag" : "W/\"18b-OPCBBHzVVuCPaglXVbFjmsFzOoE\"",
+ "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan",
+ "X-Amzn-Trace-Id" : "Root=1-697308c6-1b2bc45629f791de2a39b5b5;Parent=5c1a0861db4a7a74;Sampled=0;Lineage=1:24be3d11:0",
+ "Via" : "1.1 d178790752746ce7e53fab1b13e75448.cloudfront.net (CloudFront), 1.1 74e8c76139b8c7f9b11d5e4441c2a7a2.cloudfront.net (CloudFront)",
+ "X-Cache" : "Miss from cloudfront",
+ "X-Amz-Cf-Id" : "8v0fRj1prIwPHYubr8dcUNM_L5XiFvlYG4U7mq97jOFy9VaDLpQ87A=="
+ }
+ },
+ "uuid" : "720807f6-1acb-4361-8f82-8fd8c71d6a99",
+ "persistent" : true,
+ "scenarioName" : "scenario-1-api-apikey-login",
+ "requiredScenarioState" : "scenario-1-api-apikey-login-2",
+ "insertionIndex" : 3
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function-32c90375-557c-4ef8-9263-383a8386366a.json b/src/test/resources/cassettes/braintrust/mappings/v1_function-32c90375-557c-4ef8-9263-383a8386366a.json
new file mode 100644
index 0000000..a76b203
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function-32c90375-557c-4ef8-9263-383a8386366a.json
@@ -0,0 +1,32 @@
+{
+ "id" : "32c90375-557c-4ef8-9263-383a8386366a",
+ "name" : "v1_function",
+ "request" : {
+ "url" : "/v1/function?slug=typescriptexactmatch-9e44&project_name=java-unit-test&version=485dbf64e486ab3a",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function-32c90375-557c-4ef8-9263-383a8386366a.json",
+ "headers" : {
+ "Content-Type" : "application/json; charset=utf-8",
+ "X-Amz-Cf-Pop" : [ "SEA900-P2", "SEA900-P10" ],
+ "Date" : "Fri, 23 Jan 2026 05:36:10 GMT",
+ "access-control-allow-credentials" : "true",
+ "x-amzn-RequestId" : "91343f02-6a2d-49e1-9f20-1f8451a31cff",
+ "x-amzn-Remapped-content-length" : "913",
+ "x-bt-internal-trace-id" : "697308ca000000002493f4d436c47279",
+ "x-amz-apigw-id" : "Xn5PpExmIAMElFA=",
+ "vary" : "Origin, Accept-Encoding",
+ "etag" : "W/\"391-e2sQ3WLqbgDUML3AlteZzSuvEIE\"",
+ "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan",
+ "X-Amzn-Trace-Id" : "Root=1-697308ca-5fd10a1b6da42bcc783c927e;Parent=3e4f51cc37168d91;Sampled=0;Lineage=1:24be3d11:0",
+ "Via" : "1.1 0c387c49a68a0638a07ea76c5853a28e.cloudfront.net (CloudFront), 1.1 dbfd9bcc806d4c322e72b461b2458112.cloudfront.net (CloudFront)",
+ "X-Cache" : "Miss from cloudfront",
+ "X-Amz-Cf-Id" : "M9QwhiYeDHthLfHd0s3JH-VI1WMdF0La0k4wOdV9QD9t7oV7534xNg=="
+ }
+ },
+ "uuid" : "32c90375-557c-4ef8-9263-383a8386366a",
+ "persistent" : true,
+ "insertionIndex" : 12
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function-a40fed81-8f60-44a2-819c-afa1c6941fe1.json b/src/test/resources/cassettes/braintrust/mappings/v1_function-a40fed81-8f60-44a2-819c-afa1c6941fe1.json
new file mode 100644
index 0000000..b76eda1
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function-a40fed81-8f60-44a2-819c-afa1c6941fe1.json
@@ -0,0 +1,32 @@
+{
+ "id" : "a40fed81-8f60-44a2-819c-afa1c6941fe1",
+ "name" : "v1_function",
+ "request" : {
+ "url" : "/v1/function?slug=close-enough-judge-d31b&project_name=java-unit-test",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function-a40fed81-8f60-44a2-819c-afa1c6941fe1.json",
+ "headers" : {
+ "Content-Type" : "application/json; charset=utf-8",
+ "X-Amz-Cf-Pop" : [ "SEA900-P2", "SEA900-P10" ],
+ "Date" : "Fri, 23 Jan 2026 05:36:09 GMT",
+ "access-control-allow-credentials" : "true",
+ "x-amzn-RequestId" : "832d2cf0-bb5e-4fc9-bb74-9dc9c0c84b88",
+ "x-amzn-Remapped-content-length" : "970",
+ "x-bt-internal-trace-id" : "697308c9000000002bc838a48543b28d",
+ "x-amz-apigw-id" : "Xn5PjHfBIAMEOGQ=",
+ "vary" : "Origin, Accept-Encoding",
+ "etag" : "W/\"3ca-U7pLamkO1Ncw3QGMXz5hnQvgnvk\"",
+ "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan",
+ "X-Amzn-Trace-Id" : "Root=1-697308c9-1459d1e630f5ca357924cbc9;Parent=2a38a45e854d66b2;Sampled=0;Lineage=1:24be3d11:0",
+ "Via" : "1.1 aae9b7724185120bb8d23ea2cb4efe0c.cloudfront.net (CloudFront), 1.1 da32b45f2cc22dc818960285c4e91b66.cloudfront.net (CloudFront)",
+ "X-Cache" : "Miss from cloudfront",
+ "X-Amz-Cf-Id" : "dzrdY7PjXY0KMewRRMFmwZC4voyAuGScJqUsv_-UB1G9EYr73afWmg=="
+ }
+ },
+ "uuid" : "a40fed81-8f60-44a2-819c-afa1c6941fe1",
+ "persistent" : true,
+ "insertionIndex" : 10
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function-ce46228b-9279-4285-9695-5d3ab0ebb857.json b/src/test/resources/cassettes/braintrust/mappings/v1_function-ce46228b-9279-4285-9695-5d3ab0ebb857.json
new file mode 100644
index 0000000..cc0859f
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function-ce46228b-9279-4285-9695-5d3ab0ebb857.json
@@ -0,0 +1,34 @@
+{
+ "id" : "ce46228b-9279-4285-9695-5d3ab0ebb857",
+ "name" : "v1_function",
+ "request" : {
+ "url" : "/v1/function?slug=typescriptexactmatch-9e44&project_name=java-unit-test",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function-ce46228b-9279-4285-9695-5d3ab0ebb857.json",
+ "headers" : {
+ "Content-Type" : "application/json; charset=utf-8",
+ "X-Amz-Cf-Pop" : [ "SEA900-P2", "SEA900-P10" ],
+ "Date" : "Fri, 23 Jan 2026 05:36:08 GMT",
+ "access-control-allow-credentials" : "true",
+ "x-amzn-RequestId" : "ee2d4de1-4414-4c32-af31-cfb92621a650",
+ "x-amzn-Remapped-content-length" : "913",
+ "x-bt-internal-trace-id" : "697308c800000000320afa6b6d4db01a",
+ "x-amz-apigw-id" : "Xn5PZF7boAMETbw=",
+ "vary" : "Origin, Accept-Encoding",
+ "etag" : "W/\"391-iVW1u69I859gEDeC7XzXAO2kbf4\"",
+ "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan",
+ "X-Amzn-Trace-Id" : "Root=1-697308c8-64ccd42e3bd115736333a8c8;Parent=28a1de37d9e32ef7;Sampled=0;Lineage=1:24be3d11:0",
+ "Via" : "1.1 b542d7019a03585dbf3c5588bc1da03a.cloudfront.net (CloudFront), 1.1 e6b2537b87653726af8a79e6da505188.cloudfront.net (CloudFront)",
+ "X-Cache" : "Miss from cloudfront",
+ "X-Amz-Cf-Id" : "8f6jXBYwrKBtvL6dlOwjelCeJlyQQbVkStpx2LFr5-x7K0mLslSvJQ=="
+ }
+ },
+ "uuid" : "ce46228b-9279-4285-9695-5d3ab0ebb857",
+ "persistent" : true,
+ "scenarioName" : "scenario-2-v1-function",
+ "requiredScenarioState" : "scenario-2-v1-function-2",
+ "insertionIndex" : 8
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function-ec6186f4-a80d-4a8c-81fb-ad828ba038b8.json b/src/test/resources/cassettes/braintrust/mappings/v1_function-ec6186f4-a80d-4a8c-81fb-ad828ba038b8.json
new file mode 100644
index 0000000..6f4c2c5
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function-ec6186f4-a80d-4a8c-81fb-ad828ba038b8.json
@@ -0,0 +1,35 @@
+{
+ "id" : "ec6186f4-a80d-4a8c-81fb-ad828ba038b8",
+ "name" : "v1_function",
+ "request" : {
+ "url" : "/v1/function?slug=typescriptexactmatch-9e44&project_name=java-unit-test",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function-ec6186f4-a80d-4a8c-81fb-ad828ba038b8.json",
+ "headers" : {
+ "Content-Type" : "application/json; charset=utf-8",
+ "X-Amz-Cf-Pop" : [ "SEA900-P2", "SEA900-P10" ],
+ "Date" : "Fri, 23 Jan 2026 05:36:07 GMT",
+ "access-control-allow-credentials" : "true",
+ "x-amzn-RequestId" : "7ae431e0-39e6-4e0a-a5ed-16c7c8e33268",
+ "x-amzn-Remapped-content-length" : "913",
+ "x-bt-internal-trace-id" : "697308c70000000000288f5631c3d9db",
+ "x-amz-apigw-id" : "Xn5PREqQoAMEC6g=",
+ "vary" : "Origin, Accept-Encoding",
+ "etag" : "W/\"391-iVW1u69I859gEDeC7XzXAO2kbf4\"",
+ "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan",
+ "X-Amzn-Trace-Id" : "Root=1-697308c7-761424a613fddcae70423c3f;Parent=21d49aded2c44837;Sampled=0;Lineage=1:24be3d11:0",
+ "Via" : "1.1 09a1a54fdbf13d19927a903f4a7d5186.cloudfront.net (CloudFront), 1.1 ffe9646b2ea911744e2d51fc0715cedc.cloudfront.net (CloudFront)",
+ "X-Cache" : "Miss from cloudfront",
+ "X-Amz-Cf-Id" : "dKsTT6vOCgxlJsdHHKpOY_yKKxMe8TwWJ4IodLwzpp4ZJDJdW_JJuQ=="
+ }
+ },
+ "uuid" : "ec6186f4-a80d-4a8c-81fb-ad828ba038b8",
+ "persistent" : true,
+ "scenarioName" : "scenario-2-v1-function",
+ "requiredScenarioState" : "Started",
+ "newScenarioState" : "scenario-2-v1-function-2",
+ "insertionIndex" : 6
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function_5dd8a26d-3be8-4ecd-af5b-df2a6a592277_invoke-c67cd10d-7d05-4e66-a861-360e14dace92.json b/src/test/resources/cassettes/braintrust/mappings/v1_function_5dd8a26d-3be8-4ecd-af5b-df2a6a592277_invoke-c67cd10d-7d05-4e66-a861-360e14dace92.json
new file mode 100644
index 0000000..b34699b
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function_5dd8a26d-3be8-4ecd-af5b-df2a6a592277_invoke-c67cd10d-7d05-4e66-a861-360e14dace92.json
@@ -0,0 +1,59 @@
+{
+ "id" : "c67cd10d-7d05-4e66-a861-360e14dace92",
+ "name" : "v1_function_5dd8a26d-3be8-4ecd-af5b-df2a6a592277_invoke",
+ "request" : {
+ "url" : "/v1/function/5dd8a26d-3be8-4ecd-af5b-df2a6a592277/invoke",
+ "method" : "POST",
+ "headers" : {
+ "Content-Type" : {
+ "equalTo" : "application/json"
+ }
+ },
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"input\":{\"input\":\"What is 2+2?\",\"output\":\"four\",\"expected\":\"4\",\"metadata\":{}}}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function_5dd8a26d-3be8-4ecd-af5b-df2a6a592277_invoke-c67cd10d-7d05-4e66-a861-360e14dace92.json",
+ "headers" : {
+ "Content-Type" : "application/json",
+ "Date" : "Fri, 23 Jan 2026 05:36:09 GMT",
+ "x-bt-used-endpoint" : "OPENAI_API_KEY",
+ "x-amzn-RequestId" : "46e7b13d-1760-4604-b100-59262ce4a2c5",
+ "cf-ray" : "9c2301680d22b4ba-IAD",
+ "x-ratelimit-remaining-requests" : "29999",
+ "openai-processing-ms" : "7267",
+ "cf-cache-status" : "DYNAMIC",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-bt-cached" : "HIT",
+ "x-openai-proxy-wasm" : "v0.1",
+ "x-ratelimit-reset-requests" : "2ms",
+ "x-envoy-upstream-service-time" : "7402",
+ "openai-organization" : "braintrust-data",
+ "x-request-id" : "req_e0eec56f2a354da0ad52cca9f8fb1aa5",
+ "set-cookie" : "_cfuvid=Wfvzi.WlyOXcpGIt_lOzuCyfYKl5KGyA9p61vfKm8.g-1769126378024-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None",
+ "cache-control" : "max-age=604800",
+ "x-ratelimit-limit-tokens" : "180000000",
+ "x-content-type-options" : "nosniff",
+ "x-ratelimit-remaining-tokens" : "179999943",
+ "x-bt-function-creds-cached" : "HIT",
+ "X-Amzn-Trace-Id" : "Root=1-697308c9-62f383997faeb1d40789390a;Parent=1d54fe41e5502c92;Sampled=0;Lineage=1:8be8f50d:0",
+ "x-bt-function-meta-cached" : "HIT",
+ "x-ratelimit-reset-tokens" : "0s",
+ "openai-version" : "2020-10-01",
+ "x-ratelimit-limit-requests" : "30000",
+ "openai-project" : "proj_wMRY6YpEiASXMxPIIcI9nQRi",
+ "X-Cache" : "Miss from cloudfront",
+ "Via" : "1.1 b669d9add7767f73665f1f8b7e8cd802.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Pop" : "SEA900-P10",
+ "X-Amz-Cf-Id" : "AZnkOMP4ybJrHTO6ilSDGF-yLqzLAFHC4WWqa1pUwW2FR8NTnNxhxw==",
+ "Age" : "20189"
+ }
+ },
+ "uuid" : "c67cd10d-7d05-4e66-a861-360e14dace92",
+ "persistent" : true,
+ "insertionIndex" : 11
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-69d15cfb-87c7-41a1-a99a-df653a67ec0a.json b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-69d15cfb-87c7-41a1-a99a-df653a67ec0a.json
new file mode 100644
index 0000000..43c6112
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-69d15cfb-87c7-41a1-a99a-df653a67ec0a.json
@@ -0,0 +1,37 @@
+{
+ "id" : "69d15cfb-87c7-41a1-a99a-df653a67ec0a",
+ "name" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke",
+ "request" : {
+ "url" : "/v1/function/efa5f9c3-6ece-4726-a9d6-4ba792980b3f/invoke",
+ "method" : "POST",
+ "headers" : {
+ "Content-Type" : {
+ "equalTo" : "application/json"
+ }
+ },
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"input\":{\"input\":\"carrot\",\"output\":\"java-fruit\",\"expected\":\"vegetable\",\"metadata\":{}}}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-69d15cfb-87c7-41a1-a99a-df653a67ec0a.json",
+ "headers" : {
+ "Content-Type" : "application/json",
+ "Date" : "Fri, 23 Jan 2026 05:36:07 GMT",
+ "X-Amzn-Trace-Id" : "Root=1-697308c7-7b5db7f70a2cde7f55dc3d37;Parent=5b5163543a97cc2d;Sampled=0;Lineage=1:8be8f50d:0",
+ "x-bt-function-meta-cached" : "HIT",
+ "x-amzn-RequestId" : "d90b3df5-3157-4e1c-bce5-b948f859eb72",
+ "x-bt-function-creds-cached" : "HIT",
+ "X-Cache" : "Miss from cloudfront",
+ "Via" : "1.1 f8731007efc5ab360d90cee573a1e916.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Pop" : "SEA900-P10",
+ "X-Amz-Cf-Id" : "FHz-qaF3V-vDYJN3ao-7VkQT5W2lIdgx25TbM_-yo6J0XE_b1jHF2w=="
+ }
+ },
+ "uuid" : "69d15cfb-87c7-41a1-a99a-df653a67ec0a",
+ "persistent" : true,
+ "insertionIndex" : 5
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a5548413-7bea-4a3b-8dd6-575e0594ca85.json b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a5548413-7bea-4a3b-8dd6-575e0594ca85.json
new file mode 100644
index 0000000..1dd1140
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a5548413-7bea-4a3b-8dd6-575e0594ca85.json
@@ -0,0 +1,37 @@
+{
+ "id" : "a5548413-7bea-4a3b-8dd6-575e0594ca85",
+ "name" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke",
+ "request" : {
+ "url" : "/v1/function/efa5f9c3-6ece-4726-a9d6-4ba792980b3f/invoke",
+ "method" : "POST",
+ "headers" : {
+ "Content-Type" : {
+ "equalTo" : "application/json"
+ }
+ },
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"input\":{\"input\":\"test input\",\"output\":\"hello world\",\"expected\":\"hello world\",\"metadata\":{}}}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a5548413-7bea-4a3b-8dd6-575e0594ca85.json",
+ "headers" : {
+ "Content-Type" : "application/json",
+ "Date" : "Fri, 23 Jan 2026 05:36:09 GMT",
+ "X-Amzn-Trace-Id" : "Root=1-697308c9-400c4ada2b683df94fee5133;Parent=4fe28c055c2e986f;Sampled=0;Lineage=1:8be8f50d:0",
+ "x-bt-function-meta-cached" : "HIT",
+ "x-amzn-RequestId" : "9cb21a50-2965-4088-ac88-c8154e5a5a2a",
+ "x-bt-function-creds-cached" : "HIT",
+ "X-Cache" : "Miss from cloudfront",
+ "Via" : "1.1 d525041695bdb6325f78ebba5c11b8a2.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Pop" : "SEA900-P10",
+ "X-Amz-Cf-Id" : "hs6E1WkHWi7zdZ6-9xXQ98nPuCZQpFYWMq6voCMmz83LmAuoI1CfdA=="
+ }
+ },
+ "uuid" : "a5548413-7bea-4a3b-8dd6-575e0594ca85",
+ "persistent" : true,
+ "insertionIndex" : 9
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a599c78a-4aec-4bf0-b64e-e9b183b45c57.json b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a599c78a-4aec-4bf0-b64e-e9b183b45c57.json
new file mode 100644
index 0000000..be03d5b
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a599c78a-4aec-4bf0-b64e-e9b183b45c57.json
@@ -0,0 +1,37 @@
+{
+ "id" : "a599c78a-4aec-4bf0-b64e-e9b183b45c57",
+ "name" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke",
+ "request" : {
+ "url" : "/v1/function/efa5f9c3-6ece-4726-a9d6-4ba792980b3f/invoke",
+ "method" : "POST",
+ "headers" : {
+ "Content-Type" : {
+ "equalTo" : "application/json"
+ }
+ },
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"input\":{\"input\":\"apple\",\"output\":\"java-fruit\",\"expected\":\"fruit\",\"metadata\":{}}}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-a599c78a-4aec-4bf0-b64e-e9b183b45c57.json",
+ "headers" : {
+ "Content-Type" : "application/json",
+ "Date" : "Fri, 23 Jan 2026 05:36:06 GMT",
+ "X-Amzn-Trace-Id" : "Root=1-697308c6-2d7a2814638a885665cbbf0c;Parent=6f2db2d141c9837d;Sampled=0;Lineage=1:8be8f50d:0",
+ "x-bt-function-meta-cached" : "HIT",
+ "x-amzn-RequestId" : "2619c576-7c27-4ea8-8bb3-2594fb9c5231",
+ "x-bt-function-creds-cached" : "HIT",
+ "X-Cache" : "Miss from cloudfront",
+ "Via" : "1.1 b669d9add7767f73665f1f8b7e8cd802.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Pop" : "SEA900-P10",
+ "X-Amz-Cf-Id" : "cVWp2u0rg5QNTANlInRhVfQ3qWVDRPObVqv9u2JzyKdANNMfE-8uXg=="
+ }
+ },
+ "uuid" : "a599c78a-4aec-4bf0-b64e-e9b183b45c57",
+ "persistent" : true,
+ "insertionIndex" : 4
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-d2eef3e5-3348-4b4d-8ea5-08d7da45f22b.json b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-d2eef3e5-3348-4b4d-8ea5-08d7da45f22b.json
new file mode 100644
index 0000000..b64fba6
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-d2eef3e5-3348-4b4d-8ea5-08d7da45f22b.json
@@ -0,0 +1,37 @@
+{
+ "id" : "d2eef3e5-3348-4b4d-8ea5-08d7da45f22b",
+ "name" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke",
+ "request" : {
+ "url" : "/v1/function/efa5f9c3-6ece-4726-a9d6-4ba792980b3f/invoke",
+ "method" : "POST",
+ "headers" : {
+ "Content-Type" : {
+ "equalTo" : "application/json"
+ }
+ },
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"input\":{\"input\":\"test input\",\"output\":\"hello world\",\"expected\":\"hello world\",\"metadata\":{}},\"version\":\"485dbf64e486ab3a\"}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-d2eef3e5-3348-4b4d-8ea5-08d7da45f22b.json",
+ "headers" : {
+ "Content-Type" : "application/json",
+ "Date" : "Fri, 23 Jan 2026 05:36:10 GMT",
+ "X-Amzn-Trace-Id" : "Root=1-697308ca-0550de0169605a955c9369ea;Parent=2824946edb1ecfdd;Sampled=0;Lineage=1:8be8f50d:0",
+ "x-bt-function-meta-cached" : "HIT",
+ "x-amzn-RequestId" : "d3c5512b-29ee-4e90-bd0d-5d499be5b39a",
+ "x-bt-function-creds-cached" : "HIT",
+ "X-Cache" : "Miss from cloudfront",
+ "Via" : "1.1 74e8c76139b8c7f9b11d5e4441c2a7a2.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Pop" : "SEA900-P10",
+ "X-Amz-Cf-Id" : "QEYzusVCnA1b_6QzcDlZyjIYx0Iw18uK-fwBXeS5PNh2UWuUDAMO4g=="
+ }
+ },
+ "uuid" : "d2eef3e5-3348-4b4d-8ea5-08d7da45f22b",
+ "persistent" : true,
+ "insertionIndex" : 13
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0.json b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0.json
new file mode 100644
index 0000000..e1ab6c3
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0.json
@@ -0,0 +1,37 @@
+{
+ "id" : "f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0",
+ "name" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke",
+ "request" : {
+ "url" : "/v1/function/efa5f9c3-6ece-4726-a9d6-4ba792980b3f/invoke",
+ "method" : "POST",
+ "headers" : {
+ "Content-Type" : {
+ "equalTo" : "application/json"
+ }
+ },
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"input\":{\"input\":\"test input\",\"output\":\"different\",\"expected\":\"expected\",\"metadata\":{}}}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_function_efa5f9c3-6ece-4726-a9d6-4ba792980b3f_invoke-f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0.json",
+ "headers" : {
+ "Content-Type" : "application/json",
+ "Date" : "Fri, 23 Jan 2026 05:36:08 GMT",
+ "X-Amzn-Trace-Id" : "Root=1-697308c8-1a7890d73a86eb5a551f2af3;Parent=033072685970f626;Sampled=0;Lineage=1:8be8f50d:0",
+ "x-bt-function-meta-cached" : "HIT",
+ "x-amzn-RequestId" : "e46331ea-d1a9-4811-8bdd-3d3e571222f8",
+ "x-bt-function-creds-cached" : "HIT",
+ "X-Cache" : "Miss from cloudfront",
+ "Via" : "1.1 87247d9a9b2f9e51b0c72b364948aefa.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Pop" : "SEA900-P10",
+ "X-Amz-Cf-Id" : "rLbEuM9iZpq8FYhFPaxiPDahQm5k7Pt3NohzD5oLJzunIX4P2U715Q=="
+ }
+ },
+ "uuid" : "f8a0cc9c-fc09-4c58-bb90-f7e3c4de7cc0",
+ "persistent" : true,
+ "insertionIndex" : 7
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/braintrust/mappings/v1_project_6ae68365-7620-4630-921b-bac416634fc8-3e4fd9ae-b027-4c4e-a714-5ba10db840dd.json b/src/test/resources/cassettes/braintrust/mappings/v1_project_6ae68365-7620-4630-921b-bac416634fc8-3e4fd9ae-b027-4c4e-a714-5ba10db840dd.json
new file mode 100644
index 0000000..11d9cf0
--- /dev/null
+++ b/src/test/resources/cassettes/braintrust/mappings/v1_project_6ae68365-7620-4630-921b-bac416634fc8-3e4fd9ae-b027-4c4e-a714-5ba10db840dd.json
@@ -0,0 +1,32 @@
+{
+ "id" : "3e4fd9ae-b027-4c4e-a714-5ba10db840dd",
+ "name" : "v1_project_6ae68365-7620-4630-921b-bac416634fc8",
+ "request" : {
+ "url" : "/v1/project/6ae68365-7620-4630-921b-bac416634fc8",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "bodyFileName" : "v1_project_6ae68365-7620-4630-921b-bac416634fc8-3e4fd9ae-b027-4c4e-a714-5ba10db840dd.json",
+ "headers" : {
+ "Content-Type" : "application/json; charset=utf-8",
+ "X-Amz-Cf-Pop" : [ "SEA900-P2", "SEA900-P10" ],
+ "Date" : "Fri, 23 Jan 2026 05:36:05 GMT",
+ "access-control-allow-credentials" : "true",
+ "x-amzn-RequestId" : "715743ae-8eb9-4235-a28c-fbf74121ac58",
+ "x-amzn-Remapped-content-length" : "352",
+ "x-bt-internal-trace-id" : "697308c5000000000f234b78b61b494f",
+ "x-amz-apigw-id" : "Xn5O-Hi-IAMEsWQ=",
+ "vary" : "Origin, Accept-Encoding",
+ "etag" : "W/\"160-RUdtZR434qrXxI13debDoTK1a2s\"",
+ "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan",
+ "X-Amzn-Trace-Id" : "Root=1-697308c5-10ae7bd9215b2deb252c8ca1;Parent=77f8a8291cf951c4;Sampled=0;Lineage=1:24be3d11:0",
+ "Via" : "1.1 09a1a54fdbf13d19927a903f4a7d5186.cloudfront.net (CloudFront), 1.1 b669d9add7767f73665f1f8b7e8cd802.cloudfront.net (CloudFront)",
+ "X-Cache" : "Miss from cloudfront",
+ "X-Amz-Cf-Id" : "cbuFSM1PKiaoTqAB6KkzUk86-V4shoX_eS44HzxuiTLJPlnKWOXang=="
+ }
+ },
+ "uuid" : "3e4fd9ae-b027-4c4e-a714-5ba10db840dd",
+ "persistent" : true,
+ "insertionIndex" : 2
+}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-f1d892bb-4783-475f-9320-1a5800d4f293.json b/src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07.json
similarity index 87%
rename from src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-f1d892bb-4783-475f-9320-1a5800d4f293.json
rename to src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07.json
index 8a7e387..d729731 100644
--- a/src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-f1d892bb-4783-475f-9320-1a5800d4f293.json
+++ b/src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07.json
@@ -10,7 +10,7 @@
"role": "model"
},
"finishReason": "STOP",
- "avgLogprobs": -0.050756204873323441
+ "avgLogprobs": -0.054626878350973129
}
],
"usageMetadata": {
@@ -31,5 +31,5 @@
]
},
"modelVersion": "gemini-2.0-flash-lite",
- "responseId": "LZVmadvKL7O6mtkPxbjguAg"
+ "responseId": "zQhzabOJBLuhz7IPvoi5sAc"
}
diff --git a/src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-47e92aee-8b68-4d7a-bd8a-f07c4ce231c2.json b/src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-bbe54c72-90d7-4770-ac32-ee2b1bdcfc27.json
similarity index 87%
rename from src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-47e92aee-8b68-4d7a-bd8a-f07c4ce231c2.json
rename to src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-bbe54c72-90d7-4770-ac32-ee2b1bdcfc27.json
index 40afc75..6bf76ee 100644
--- a/src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-47e92aee-8b68-4d7a-bd8a-f07c4ce231c2.json
+++ b/src/test/resources/cassettes/google/__files/v1beta_models_gemini-20-flash-litegeneratecontent-bbe54c72-90d7-4770-ac32-ee2b1bdcfc27.json
@@ -10,7 +10,7 @@
"role": "model"
},
"finishReason": "STOP",
- "avgLogprobs": -0.0095387622714042664
+ "avgLogprobs": -0.054125471247567072
}
],
"usageMetadata": {
@@ -31,5 +31,5 @@
]
},
"modelVersion": "gemini-2.0-flash-lite",
- "responseId": "L5VmaYSPFpmdz7IP2PWQmAc"
+ "responseId": "zghzaffCAr2dz7IPjd6imQM"
}
diff --git a/src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-f1d892bb-4783-475f-9320-1a5800d4f293.json b/src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07.json
similarity index 81%
rename from src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-f1d892bb-4783-475f-9320-1a5800d4f293.json
rename to src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07.json
index 4c5b0c8..62e8fca 100644
--- a/src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-f1d892bb-4783-475f-9320-1a5800d4f293.json
+++ b/src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07.json
@@ -1,5 +1,5 @@
{
- "id" : "f1d892bb-4783-475f-9320-1a5800d4f293",
+ "id" : "a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07",
"name" : "v1beta_models_gemini-20-flash-litegeneratecontent",
"request" : {
"url" : "/v1beta/models/gemini-2.0-flash-lite:generateContent",
@@ -17,20 +17,20 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "v1beta_models_gemini-20-flash-litegeneratecontent-f1d892bb-4783-475f-9320-1a5800d4f293.json",
+ "bodyFileName" : "v1beta_models_gemini-20-flash-litegeneratecontent-a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07.json",
"headers" : {
"Content-Type" : "application/json; charset=UTF-8",
"Vary" : [ "Origin", "X-Origin", "Referer" ],
- "Date" : "Tue, 13 Jan 2026 18:55:43 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:13 GMT",
"Server" : "scaffolding on HTTPServer2",
"X-XSS-Protection" : "0",
"X-Frame-Options" : "SAMEORIGIN",
"X-Content-Type-Options" : "nosniff",
- "Server-Timing" : "gfet4t7; dur=1344",
+ "Server-Timing" : "gfet4t7; dur=380",
"Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"
}
},
- "uuid" : "f1d892bb-4783-475f-9320-1a5800d4f293",
+ "uuid" : "a96a8ad5-2ea3-4afe-a2cb-409bf8e0bf07",
"persistent" : true,
"insertionIndex" : 1
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-47e92aee-8b68-4d7a-bd8a-f07c4ce231c2.json b/src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-bbe54c72-90d7-4770-ac32-ee2b1bdcfc27.json
similarity index 81%
rename from src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-47e92aee-8b68-4d7a-bd8a-f07c4ce231c2.json
rename to src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-bbe54c72-90d7-4770-ac32-ee2b1bdcfc27.json
index 60e578d..01f0437 100644
--- a/src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-47e92aee-8b68-4d7a-bd8a-f07c4ce231c2.json
+++ b/src/test/resources/cassettes/google/mappings/v1beta_models_gemini-20-flash-litegeneratecontent-bbe54c72-90d7-4770-ac32-ee2b1bdcfc27.json
@@ -1,5 +1,5 @@
{
- "id" : "47e92aee-8b68-4d7a-bd8a-f07c4ce231c2",
+ "id" : "bbe54c72-90d7-4770-ac32-ee2b1bdcfc27",
"name" : "v1beta_models_gemini-20-flash-litegeneratecontent",
"request" : {
"url" : "/v1beta/models/gemini-2.0-flash-lite:generateContent",
@@ -17,20 +17,20 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "v1beta_models_gemini-20-flash-litegeneratecontent-47e92aee-8b68-4d7a-bd8a-f07c4ce231c2.json",
+ "bodyFileName" : "v1beta_models_gemini-20-flash-litegeneratecontent-bbe54c72-90d7-4770-ac32-ee2b1bdcfc27.json",
"headers" : {
"Content-Type" : "application/json; charset=UTF-8",
"Vary" : [ "Origin", "X-Origin", "Referer" ],
- "Date" : "Tue, 13 Jan 2026 18:55:43 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:14 GMT",
"Server" : "scaffolding on HTTPServer2",
"X-XSS-Protection" : "0",
"X-Frame-Options" : "SAMEORIGIN",
"X-Content-Type-Options" : "nosniff",
- "Server-Timing" : "gfet4t7; dur=490",
+ "Server-Timing" : "gfet4t7; dur=340",
"Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"
}
},
- "uuid" : "47e92aee-8b68-4d7a-bd8a-f07c4ce231c2",
+ "uuid" : "bbe54c72-90d7-4770-ac32-ee2b1bdcfc27",
"persistent" : true,
"insertionIndex" : 2
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/openai/__files/chat_completions-01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4.txt b/src/test/resources/cassettes/openai/__files/chat_completions-01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4.txt
new file mode 100644
index 0000000..e6803a4
--- /dev/null
+++ b/src/test/resources/cassettes/openai/__files/chat_completions-01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4.txt
@@ -0,0 +1,22 @@
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"jS8NXSKZe"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Js3TqhAu"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{"content":" capital"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Jz2"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"AnZ1V706"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{"content":" France"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"TS27"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{"content":" is"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"TIi3l6Qr"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{"content":" Paris"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"wW2vg"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lcsLFUWS2L"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"aayiy"}
+
+data: {"id":"chatcmpl-D1487Z7gvsZSSqonER8ySm2HNyhKC","object":"chat.completion.chunk","created":1769146575,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_c4585b5b9c","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":7,"total_tokens":21,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"5caYMQAl22o"}
+
+data: [DONE]
+
diff --git a/src/test/resources/cassettes/openai/__files/chat_completions-541b9306-6880-41df-bb44-69ea82f417f8.txt b/src/test/resources/cassettes/openai/__files/chat_completions-0663776c-5dfc-4fb8-9904-6a415fca9550.txt
similarity index 50%
rename from src/test/resources/cassettes/openai/__files/chat_completions-541b9306-6880-41df-bb44-69ea82f417f8.txt
rename to src/test/resources/cassettes/openai/__files/chat_completions-0663776c-5dfc-4fb8-9904-6a415fca9550.txt
index 32380a2..f3ec1f6 100644
--- a/src/test/resources/cassettes/openai/__files/chat_completions-541b9306-6880-41df-bb44-69ea82f417f8.txt
+++ b/src/test/resources/cassettes/openai/__files/chat_completions-0663776c-5dfc-4fb8-9904-6a415fca9550.txt
@@ -1,22 +1,22 @@
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"i0DEsxe0d"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"QMPctD3MA"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"f9lVHuPX"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZDH7bwkm"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" capital"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LJo"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" capital"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6EM"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"4Ush7Uov"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ck78DMdc"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" France"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"w29M"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" France"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"W7tM"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" is"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"oGMJtqrv"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" is"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Gf9inOSg"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" Paris"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"oulmu"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" Paris"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"sf7Cx"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Q7jCyF6KeB"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"PDZSCfW8AE"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"Hy3sv"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"EqWDj"}
-data: {"id":"chatcmpl-CxdqO7Gy8Lx4oY0QTk40yFak8aXlQ","object":"chat.completion.chunk","created":1768330548,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[],"usage":{"prompt_tokens":23,"completion_tokens":7,"total_tokens":30,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"8yIDij1lDhf"}
+data: {"id":"chatcmpl-D148CFcgUHTYaD1ZdHpPl9Tk0yUE2","object":"chat.completion.chunk","created":1769146580,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[],"usage":{"prompt_tokens":23,"completion_tokens":7,"total_tokens":30,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"7DzqE0n111t"}
data: [DONE]
diff --git a/src/test/resources/cassettes/openai/__files/chat_completions-13e69bf3-e90d-497f-bb66-47b448dc86bd.json b/src/test/resources/cassettes/openai/__files/chat_completions-2446f774-b17e-4abf-811c-6a10681dbf94.json
similarity index 86%
rename from src/test/resources/cassettes/openai/__files/chat_completions-13e69bf3-e90d-497f-bb66-47b448dc86bd.json
rename to src/test/resources/cassettes/openai/__files/chat_completions-2446f774-b17e-4abf-811c-6a10681dbf94.json
index 1c89a82..16156c7 100644
--- a/src/test/resources/cassettes/openai/__files/chat_completions-13e69bf3-e90d-497f-bb66-47b448dc86bd.json
+++ b/src/test/resources/cassettes/openai/__files/chat_completions-2446f774-b17e-4abf-811c-6a10681dbf94.json
@@ -1,7 +1,7 @@
{
- "id": "chatcmpl-CxdqPl3mdEMREYg8YcrJrp2tmqRTJ",
+ "id": "chatcmpl-D148DkNpJKm9pxT5Afq9fO3Tw0pos",
"object": "chat.completion",
- "created": 1768330549,
+ "created": 1769146581,
"model": "gpt-4o-mini-2024-07-18",
"choices": [
{
@@ -32,5 +32,5 @@
}
},
"service_tier": "default",
- "system_fingerprint": "fp_c4585b5b9c"
+ "system_fingerprint": "fp_29330a9688"
}
diff --git a/src/test/resources/cassettes/openai/__files/chat_completions-d6eda521-a7dd-4216-911e-4a916e45fca1.json b/src/test/resources/cassettes/openai/__files/chat_completions-80cbaecd-6aec-4d80-a719-c572a45d9d82.json
similarity index 70%
rename from src/test/resources/cassettes/openai/__files/chat_completions-d6eda521-a7dd-4216-911e-4a916e45fca1.json
rename to src/test/resources/cassettes/openai/__files/chat_completions-80cbaecd-6aec-4d80-a719-c572a45d9d82.json
index 148499c..f28c5f6 100644
--- a/src/test/resources/cassettes/openai/__files/chat_completions-d6eda521-a7dd-4216-911e-4a916e45fca1.json
+++ b/src/test/resources/cassettes/openai/__files/chat_completions-80cbaecd-6aec-4d80-a719-c572a45d9d82.json
@@ -1,14 +1,14 @@
{
- "id": "chatcmpl-CxdqMP3DDF5hyKQhHBRTrBCTtd0BE",
+ "id": "chatcmpl-D148A41Uq23LlobqzgVVmSYJM9bL6",
"object": "chat.completion",
- "created": 1768330546,
+ "created": 1769146578,
"model": "gpt-4o-mini-2024-07-18",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
- "content": "The current temperature is the same in both Paris and New York, at 72°F, and both cities are experiencing sunny weather.",
+ "content": "The current temperature in both Paris and New York is 72°F, so it is equally hot in both cities right now.",
"refusal": null,
"annotations": []
},
@@ -18,8 +18,8 @@
],
"usage": {
"prompt_tokens": 177,
- "completion_tokens": 27,
- "total_tokens": 204,
+ "completion_tokens": 26,
+ "total_tokens": 203,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
diff --git a/src/test/resources/cassettes/openai/__files/chat_completions-9016066f-1bbd-49ec-a403-4311b29fe2af.txt b/src/test/resources/cassettes/openai/__files/chat_completions-9016066f-1bbd-49ec-a403-4311b29fe2af.txt
deleted file mode 100644
index 40cd446..0000000
--- a/src/test/resources/cassettes/openai/__files/chat_completions-9016066f-1bbd-49ec-a403-4311b29fe2af.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zhm3yj623"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"AhGJkfcK"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" capital"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5JD"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9E3BimxD"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" France"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"I0ue"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" is"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zhZ6meZ6"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":" Paris"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"RtrPa"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0SIqKB167Z"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"wtORO"}
-
-data: {"id":"chatcmpl-CxdqKsEuotLHjXL9yh6KxVGkZFeSq","object":"chat.completion.chunk","created":1768330544,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_29330a9688","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":7,"total_tokens":21,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"yNAPCwHY0Pf"}
-
-data: [DONE]
-
diff --git a/src/test/resources/cassettes/openai/__files/chat_completions-d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b.json b/src/test/resources/cassettes/openai/__files/chat_completions-c881e7aa-85d0-4de6-92ec-da5dbed72027.json
similarity index 86%
rename from src/test/resources/cassettes/openai/__files/chat_completions-d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b.json
rename to src/test/resources/cassettes/openai/__files/chat_completions-c881e7aa-85d0-4de6-92ec-da5dbed72027.json
index e9fca78..1f97dcf 100644
--- a/src/test/resources/cassettes/openai/__files/chat_completions-d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b.json
+++ b/src/test/resources/cassettes/openai/__files/chat_completions-c881e7aa-85d0-4de6-92ec-da5dbed72027.json
@@ -1,7 +1,7 @@
{
- "id": "chatcmpl-CxdqLHL6kBkvILQ1S5QOt1qeWw3b5",
+ "id": "chatcmpl-D1488Eu9ZBPsuAAbqFou0rdKplKq6",
"object": "chat.completion",
- "created": 1768330545,
+ "created": 1769146576,
"model": "gpt-4o-mini-2024-07-18",
"choices": [
{
@@ -11,7 +11,7 @@
"content": null,
"tool_calls": [
{
- "id": "call_O0vPA17lB2sSTvNui5KkMr7E",
+ "id": "call_YANyIGtCtTJowRa5Emu3rNQp",
"type": "function",
"function": {
"name": "getWeather",
@@ -19,7 +19,7 @@
}
},
{
- "id": "call_gVz1cYvAomaPcTrheam72S8H",
+ "id": "call_qTfvi4D3JjATbUYoQd84Hbnf",
"type": "function",
"function": {
"name": "getWeather",
diff --git a/src/test/resources/cassettes/openai/__files/chat_completions-4706df90-aa52-4bd3-97c0-3448a4e0005f.json b/src/test/resources/cassettes/openai/__files/chat_completions-d0bef16c-03e9-44da-ab7e-a72110eb2028.json
similarity index 91%
rename from src/test/resources/cassettes/openai/__files/chat_completions-4706df90-aa52-4bd3-97c0-3448a4e0005f.json
rename to src/test/resources/cassettes/openai/__files/chat_completions-d0bef16c-03e9-44da-ab7e-a72110eb2028.json
index 1cc12fa..9228df0 100644
--- a/src/test/resources/cassettes/openai/__files/chat_completions-4706df90-aa52-4bd3-97c0-3448a4e0005f.json
+++ b/src/test/resources/cassettes/openai/__files/chat_completions-d0bef16c-03e9-44da-ab7e-a72110eb2028.json
@@ -1,7 +1,7 @@
{
- "id": "chatcmpl-CxdqNcT26jSeKfx2QAWkScbokWofM",
+ "id": "chatcmpl-D148BsNl5OIpVvrqmECQ4fLoxjJUv",
"object": "chat.completion",
- "created": 1768330547,
+ "created": 1769146579,
"model": "gpt-4o-mini-2024-07-18",
"choices": [
{
diff --git a/src/test/resources/cassettes/openai/mappings/chat_completions-9016066f-1bbd-49ec-a403-4311b29fe2af.json b/src/test/resources/cassettes/openai/mappings/chat_completions-01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4.json
similarity index 66%
rename from src/test/resources/cassettes/openai/mappings/chat_completions-9016066f-1bbd-49ec-a403-4311b29fe2af.json
rename to src/test/resources/cassettes/openai/mappings/chat_completions-01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4.json
index d7ff5b9..b4bb002 100644
--- a/src/test/resources/cassettes/openai/mappings/chat_completions-9016066f-1bbd-49ec-a403-4311b29fe2af.json
+++ b/src/test/resources/cassettes/openai/mappings/chat_completions-01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4.json
@@ -1,5 +1,5 @@
{
- "id" : "9016066f-1bbd-49ec-a403-4311b29fe2af",
+ "id" : "01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4",
"name" : "chat_completions",
"request" : {
"url" : "/chat/completions",
@@ -17,34 +17,34 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "chat_completions-9016066f-1bbd-49ec-a403-4311b29fe2af.txt",
+ "bodyFileName" : "chat_completions-01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4.txt",
"headers" : {
- "Date" : "Tue, 13 Jan 2026 18:55:44 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:15 GMT",
"Content-Type" : "text/event-stream; charset=utf-8",
"access-control-expose-headers" : "X-Request-ID",
"openai-organization" : "braintrust-data",
- "openai-processing-ms" : "163",
+ "openai-processing-ms" : "262",
"openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9",
"openai-version" : "2020-10-01",
- "x-envoy-upstream-service-time" : "295",
+ "x-envoy-upstream-service-time" : "375",
"x-ratelimit-limit-requests" : "30000",
"x-ratelimit-limit-tokens" : "150000000",
"x-ratelimit-remaining-requests" : "29999",
"x-ratelimit-remaining-tokens" : "149999990",
"x-ratelimit-reset-requests" : "2ms",
"x-ratelimit-reset-tokens" : "0s",
- "x-request-id" : "req_54de4d24315b4956916c34f431127db5",
+ "x-request-id" : "req_3b6df98ab842452ba801ae44d01fb3e2",
"x-openai-proxy-wasm" : "v0.1",
"cf-cache-status" : "DYNAMIC",
- "Set-Cookie" : [ "__cf_bm=_UjMnl_QDwQVpxokprrHLdnoEskB5r7JY8twdegieGo-1768330544-1.0.1.1-J0MsKAdxFAJi7gXyTdIY5xGElzghGXqWT3jZB7J_idg15FnZdRUgy.ZylsUo74UEwCL2AQDf4nu0oRUBxH7c8.DgsbIiU8E4QPM84OyZgrE; path=/; expires=Tue, 13-Jan-26 19:25:44 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=yPo_7QFyEr5D0p4gVCxgxZusm3_C.Ms7TeOjEsGEjZM-1768330544555-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
+ "Set-Cookie" : [ "__cf_bm=2X0RC1RS9O3oPKF5RMJ4HK8nCbsE1ELw9sKno3wraUY-1769146575-1.0.1.1-qa6h7i46YVaDkApDd_dB5nXg37Kjis81F1KLj_TntBl4r7sN8AH9D4zRGSpU2hgtq69l35IVJIrDrIk28HAX.Jm6NHnnhsC6Xtxn4tIDjeo; path=/; expires=Fri, 23-Jan-26 06:06:15 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=RCbXCG2kNzL2y4V2kRg4reQXbnvNOHX.sty8iEMCicg-1769146575546-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
"Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload",
"X-Content-Type-Options" : "nosniff",
"Server" : "cloudflare",
- "CF-RAY" : "9bd71c0d1f2f7538-SEA",
+ "CF-RAY" : "9c24eeaca80e7209-SEA",
"alt-svc" : "h3=\":443\"; ma=86400"
}
},
- "uuid" : "9016066f-1bbd-49ec-a403-4311b29fe2af",
+ "uuid" : "01f57d6d-f85b-4093-8dcf-2aa7a75c2ab4",
"persistent" : true,
"insertionIndex" : 1
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/openai/mappings/chat_completions-541b9306-6880-41df-bb44-69ea82f417f8.json b/src/test/resources/cassettes/openai/mappings/chat_completions-0663776c-5dfc-4fb8-9904-6a415fca9550.json
similarity index 66%
rename from src/test/resources/cassettes/openai/mappings/chat_completions-541b9306-6880-41df-bb44-69ea82f417f8.json
rename to src/test/resources/cassettes/openai/mappings/chat_completions-0663776c-5dfc-4fb8-9904-6a415fca9550.json
index 4a08405..96ad058 100644
--- a/src/test/resources/cassettes/openai/mappings/chat_completions-541b9306-6880-41df-bb44-69ea82f417f8.json
+++ b/src/test/resources/cassettes/openai/mappings/chat_completions-0663776c-5dfc-4fb8-9904-6a415fca9550.json
@@ -1,5 +1,5 @@
{
- "id" : "541b9306-6880-41df-bb44-69ea82f417f8",
+ "id" : "0663776c-5dfc-4fb8-9904-6a415fca9550",
"name" : "chat_completions",
"request" : {
"url" : "/chat/completions",
@@ -17,34 +17,34 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "chat_completions-541b9306-6880-41df-bb44-69ea82f417f8.txt",
+ "bodyFileName" : "chat_completions-0663776c-5dfc-4fb8-9904-6a415fca9550.txt",
"headers" : {
- "Date" : "Tue, 13 Jan 2026 18:55:48 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:20 GMT",
"Content-Type" : "text/event-stream; charset=utf-8",
"access-control-expose-headers" : "X-Request-ID",
"openai-organization" : "braintrust-data",
- "openai-processing-ms" : "183",
+ "openai-processing-ms" : "250",
"openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9",
"openai-version" : "2020-10-01",
- "x-envoy-upstream-service-time" : "196",
+ "x-envoy-upstream-service-time" : "354",
"x-ratelimit-limit-requests" : "30000",
"x-ratelimit-limit-tokens" : "150000000",
"x-ratelimit-remaining-requests" : "29999",
"x-ratelimit-remaining-tokens" : "149999982",
"x-ratelimit-reset-requests" : "2ms",
"x-ratelimit-reset-tokens" : "0s",
- "x-request-id" : "req_a5315e01514f4b9298b1727d17a7a63e",
+ "x-request-id" : "req_0b00db9feb0e4ef58a01bb7ac876bf4f",
"x-openai-proxy-wasm" : "v0.1",
"cf-cache-status" : "DYNAMIC",
- "Set-Cookie" : [ "__cf_bm=JXEJvLpTetK2tdCv6rCOsWm9XsCh35xX80RYSdbKlOQ-1768330548-1.0.1.1-LU.AUqqeHIJMar9S7gWLrEIivCcP4MJUEnfNDdc0mSsxnUNyUxhElI0Hk9_JklQpx0ceQrZ0v8T5VBY5twFxgLeJ9Rws3dzYTIx0I2L2Y_U; path=/; expires=Tue, 13-Jan-26 19:25:48 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=.9AGf0FnX2Yz1lpFeCSgRDTEkbpoZxJQOmHuxZaOYiU-1768330548584-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
+ "Set-Cookie" : [ "__cf_bm=bYIPyEKfuTm409VPkQCtLzpmMg2nxTeFyykwHWGgA_U-1769146580-1.0.1.1-ZCCSd64AQcFy.CRADYK_IVDPXnmNu2C29QtbMdjQJri.6VVxE054Zig.XR4WT6pKbmLQ7pVUaeDFxZz12wNcgNs86ZhgjFV0FWwVoMKtowU; path=/; expires=Fri, 23-Jan-26 06:06:20 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=FxzNO4rIHjSTd7qpLrk6jpjSoK0E50RPpOkalVDhcmY-1769146580890-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
"Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload",
"X-Content-Type-Options" : "nosniff",
"Server" : "cloudflare",
- "CF-RAY" : "9bd71c270a48d7dd-SEA",
+ "CF-RAY" : "9c24eecfec977688-SEA",
"alt-svc" : "h3=\":443\"; ma=86400"
}
},
- "uuid" : "541b9306-6880-41df-bb44-69ea82f417f8",
+ "uuid" : "0663776c-5dfc-4fb8-9904-6a415fca9550",
"persistent" : true,
"insertionIndex" : 5
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/openai/mappings/chat_completions-13e69bf3-e90d-497f-bb66-47b448dc86bd.json b/src/test/resources/cassettes/openai/mappings/chat_completions-2446f774-b17e-4abf-811c-6a10681dbf94.json
similarity index 65%
rename from src/test/resources/cassettes/openai/mappings/chat_completions-13e69bf3-e90d-497f-bb66-47b448dc86bd.json
rename to src/test/resources/cassettes/openai/mappings/chat_completions-2446f774-b17e-4abf-811c-6a10681dbf94.json
index 3041718..b5687b6 100644
--- a/src/test/resources/cassettes/openai/mappings/chat_completions-13e69bf3-e90d-497f-bb66-47b448dc86bd.json
+++ b/src/test/resources/cassettes/openai/mappings/chat_completions-2446f774-b17e-4abf-811c-6a10681dbf94.json
@@ -1,5 +1,5 @@
{
- "id" : "13e69bf3-e90d-497f-bb66-47b448dc86bd",
+ "id" : "2446f774-b17e-4abf-811c-6a10681dbf94",
"name" : "chat_completions",
"request" : {
"url" : "/chat/completions",
@@ -17,34 +17,34 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "chat_completions-13e69bf3-e90d-497f-bb66-47b448dc86bd.json",
+ "bodyFileName" : "chat_completions-2446f774-b17e-4abf-811c-6a10681dbf94.json",
"headers" : {
- "Date" : "Tue, 13 Jan 2026 18:55:49 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:22 GMT",
"Content-Type" : "application/json",
"access-control-expose-headers" : "X-Request-ID",
"openai-organization" : "braintrust-data",
- "openai-processing-ms" : "414",
+ "openai-processing-ms" : "306",
"openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9",
"openai-version" : "2020-10-01",
- "x-envoy-upstream-service-time" : "553",
+ "x-envoy-upstream-service-time" : "449",
"x-ratelimit-limit-requests" : "30000",
"x-ratelimit-limit-tokens" : "150000000",
"x-ratelimit-remaining-requests" : "29999",
"x-ratelimit-remaining-tokens" : "149999982",
"x-ratelimit-reset-requests" : "2ms",
"x-ratelimit-reset-tokens" : "0s",
- "x-request-id" : "req_bc56bf172e1b41cda76cb06a5ca73ed0",
+ "x-request-id" : "req_ff8b6d5e346e4ed885fa0898a92f6a0a",
"x-openai-proxy-wasm" : "v0.1",
"cf-cache-status" : "DYNAMIC",
- "Set-Cookie" : [ "__cf_bm=KnxsdeOSd50kaFk76pJ9ETxarjO99vDibkW9gcRSZF0-1768330549-1.0.1.1-WjfuxAjZSzqtvv3lnyq9bnx3pxzRUbCjfXzeXwBJKRzcvutTMQfCl6z4L1pE42fFq93ODxppcSpBpRt.84GY4apv7AufWbLfugBz5mnyZp0; path=/; expires=Tue, 13-Jan-26 19:25:49 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=ACDXQn3j61tpIi9gPK9jM.jFHQdh0jpS7JKxLogWKpY-1768330549707-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
+ "Set-Cookie" : [ "__cf_bm=kjb4Pwx.qIHTEO4OoD_3xTOpJaGW5fSOiU9zwXB7Wv0-1769146582-1.0.1.1-_giyGbaRzHz_9IYno8kYmmpV5GhMPud1LXIByQIEgG8APuGOgEUkvEDSKj2dRkBlXJpEJzExftWl1TuKPsAZjOdWnpKG6J5A2neSGHGcMdg; path=/; expires=Fri, 23-Jan-26 06:06:22 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=v9TvVXzQKYp5xe.IdnuOzXZvKFmYXxiIw85Hif87nPc-1769146582202-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
"Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload",
"X-Content-Type-Options" : "nosniff",
"Server" : "cloudflare",
- "CF-RAY" : "9bd71c2bbc74b9c4-SEA",
+ "CF-RAY" : "9c24eed52936a366-SEA",
"alt-svc" : "h3=\":443\"; ma=86400"
}
},
- "uuid" : "13e69bf3-e90d-497f-bb66-47b448dc86bd",
+ "uuid" : "2446f774-b17e-4abf-811c-6a10681dbf94",
"persistent" : true,
"insertionIndex" : 6
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/openai/mappings/chat_completions-d6eda521-a7dd-4216-911e-4a916e45fca1.json b/src/test/resources/cassettes/openai/mappings/chat_completions-80cbaecd-6aec-4d80-a719-c572a45d9d82.json
similarity index 72%
rename from src/test/resources/cassettes/openai/mappings/chat_completions-d6eda521-a7dd-4216-911e-4a916e45fca1.json
rename to src/test/resources/cassettes/openai/mappings/chat_completions-80cbaecd-6aec-4d80-a719-c572a45d9d82.json
index c745847..8f2b465 100644
--- a/src/test/resources/cassettes/openai/mappings/chat_completions-d6eda521-a7dd-4216-911e-4a916e45fca1.json
+++ b/src/test/resources/cassettes/openai/mappings/chat_completions-80cbaecd-6aec-4d80-a719-c572a45d9d82.json
@@ -1,5 +1,5 @@
{
- "id" : "d6eda521-a7dd-4216-911e-4a916e45fca1",
+ "id" : "80cbaecd-6aec-4d80-a719-c572a45d9d82",
"name" : "chat_completions",
"request" : {
"url" : "/chat/completions",
@@ -10,41 +10,41 @@
}
},
"bodyPatterns" : [ {
- "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"messages\" : [ {\n \"role\" : \"user\",\n \"content\" : \"is it hotter in Paris or New York right now?\"\n }, {\n \"role\" : \"assistant\",\n \"tool_calls\" : [ {\n \"id\" : \"call_O0vPA17lB2sSTvNui5KkMr7E\",\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"getWeather\",\n \"arguments\" : \"{\\\"arg0\\\": \\\"Paris\\\"}\"\n }\n }, {\n \"id\" : \"call_gVz1cYvAomaPcTrheam72S8H\",\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"getWeather\",\n \"arguments\" : \"{\\\"arg0\\\": \\\"New York\\\"}\"\n }\n } ]\n }, {\n \"role\" : \"tool\",\n \"tool_call_id\" : \"call_O0vPA17lB2sSTvNui5KkMr7E\",\n \"content\" : \"The weather in Paris is sunny with 72°F temperature.\"\n }, {\n \"role\" : \"tool\",\n \"tool_call_id\" : \"call_gVz1cYvAomaPcTrheam72S8H\",\n \"content\" : \"The weather in New York is sunny with 72°F temperature.\"\n } ],\n \"temperature\" : 0.0,\n \"stream\" : false,\n \"tools\" : [ {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"getForecast\",\n \"description\" : \"Get weather forecast for next N days\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"arg0\" : {\n \"type\" : \"string\"\n },\n \"arg1\" : {\n \"type\" : \"integer\"\n }\n },\n \"required\" : [ \"arg0\", \"arg1\" ]\n }\n }\n }, {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"getWeather\",\n \"description\" : \"Get current weather for a location\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"arg0\" : {\n \"type\" : \"string\"\n }\n },\n \"required\" : [ \"arg0\" ]\n }\n }\n } ]\n}",
+ "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"messages\" : [ {\n \"role\" : \"user\",\n \"content\" : \"is it hotter in Paris or New York right now?\"\n }, {\n \"role\" : \"assistant\",\n \"tool_calls\" : [ {\n \"id\" : \"call_YANyIGtCtTJowRa5Emu3rNQp\",\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"getWeather\",\n \"arguments\" : \"{\\\"arg0\\\": \\\"Paris\\\"}\"\n }\n }, {\n \"id\" : \"call_qTfvi4D3JjATbUYoQd84Hbnf\",\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"getWeather\",\n \"arguments\" : \"{\\\"arg0\\\": \\\"New York\\\"}\"\n }\n } ]\n }, {\n \"role\" : \"tool\",\n \"tool_call_id\" : \"call_YANyIGtCtTJowRa5Emu3rNQp\",\n \"content\" : \"The weather in Paris is sunny with 72°F temperature.\"\n }, {\n \"role\" : \"tool\",\n \"tool_call_id\" : \"call_qTfvi4D3JjATbUYoQd84Hbnf\",\n \"content\" : \"The weather in New York is sunny with 72°F temperature.\"\n } ],\n \"temperature\" : 0.0,\n \"stream\" : false,\n \"tools\" : [ {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"getForecast\",\n \"description\" : \"Get weather forecast for next N days\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"arg0\" : {\n \"type\" : \"string\"\n },\n \"arg1\" : {\n \"type\" : \"integer\"\n }\n },\n \"required\" : [ \"arg0\", \"arg1\" ]\n }\n }\n }, {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"getWeather\",\n \"description\" : \"Get current weather for a location\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"arg0\" : {\n \"type\" : \"string\"\n }\n },\n \"required\" : [ \"arg0\" ]\n }\n }\n } ]\n}",
"ignoreArrayOrder" : true,
"ignoreExtraElements" : false
} ]
},
"response" : {
"status" : 200,
- "bodyFileName" : "chat_completions-d6eda521-a7dd-4216-911e-4a916e45fca1.json",
+ "bodyFileName" : "chat_completions-80cbaecd-6aec-4d80-a719-c572a45d9d82.json",
"headers" : {
- "Date" : "Tue, 13 Jan 2026 18:55:47 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:19 GMT",
"Content-Type" : "application/json",
"access-control-expose-headers" : "X-Request-ID",
"openai-organization" : "braintrust-data",
- "openai-processing-ms" : "725",
+ "openai-processing-ms" : "803",
"openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9",
"openai-version" : "2020-10-01",
- "x-envoy-upstream-service-time" : "782",
+ "x-envoy-upstream-service-time" : "935",
"x-ratelimit-limit-requests" : "30000",
"x-ratelimit-limit-tokens" : "150000000",
"x-ratelimit-remaining-requests" : "29999",
"x-ratelimit-remaining-tokens" : "149999955",
"x-ratelimit-reset-requests" : "2ms",
"x-ratelimit-reset-tokens" : "0s",
- "x-request-id" : "req_3fd434fe36e5457387641e91bddd872e",
+ "x-request-id" : "req_e102c2cce7804951971407ff595137bb",
"x-openai-proxy-wasm" : "v0.1",
"cf-cache-status" : "DYNAMIC",
- "Set-Cookie" : [ "__cf_bm=GqbfmxZoOlS1Ff1BF3Pbb7tWbU6gizh68j2IIgjH.CE-1768330547-1.0.1.1-X3.JfC8E_qdMD_um.SLG7_uLhhxfj3letN8jGjFgDK2ZPDWk.lQYUWqkOUoshTf8UIxpi7UBmB2_C68nkATEom3o5SMBkYQ6I9bBYW32Y00; path=/; expires=Tue, 13-Jan-26 19:25:47 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=bEAnkV1ZOc_LuPfi3M1oNKr3OyHkhoGrgkd6IxqpeK0-1768330547136-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
+ "Set-Cookie" : [ "__cf_bm=YNdj5EqdOkbf3kW7UwY85FeszzF4Hqdrrd7SgsnlrWA-1769146579-1.0.1.1-tBNepI43o2C3UdWbmqNH3UsvsJwtA4ulBudNkwhcodCsKIVzlRJbL_8O1nu1llUXs3KSfyqyc5QSClpxxZsTLiNlne.M7kR1OfoJSUcs4Kk; path=/; expires=Fri, 23-Jan-26 06:06:19 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=2J72JctaoJwYevB_t8pgB89EJH0tGa.ZQyc2GiCdy4k-1769146579229-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
"Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload",
"X-Content-Type-Options" : "nosniff",
"Server" : "cloudflare",
- "CF-RAY" : "9bd71c1a3ce5d44f-SEA",
+ "CF-RAY" : "9c24eebfcd07ad72-SEA",
"alt-svc" : "h3=\":443\"; ma=86400"
}
},
- "uuid" : "d6eda521-a7dd-4216-911e-4a916e45fca1",
+ "uuid" : "80cbaecd-6aec-4d80-a719-c572a45d9d82",
"persistent" : true,
"insertionIndex" : 3
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/openai/mappings/chat_completions-d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b.json b/src/test/resources/cassettes/openai/mappings/chat_completions-c881e7aa-85d0-4de6-92ec-da5dbed72027.json
similarity index 74%
rename from src/test/resources/cassettes/openai/mappings/chat_completions-d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b.json
rename to src/test/resources/cassettes/openai/mappings/chat_completions-c881e7aa-85d0-4de6-92ec-da5dbed72027.json
index 29775d6..25e3f4f 100644
--- a/src/test/resources/cassettes/openai/mappings/chat_completions-d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b.json
+++ b/src/test/resources/cassettes/openai/mappings/chat_completions-c881e7aa-85d0-4de6-92ec-da5dbed72027.json
@@ -1,5 +1,5 @@
{
- "id" : "d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b",
+ "id" : "c881e7aa-85d0-4de6-92ec-da5dbed72027",
"name" : "chat_completions",
"request" : {
"url" : "/chat/completions",
@@ -17,34 +17,34 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "chat_completions-d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b.json",
+ "bodyFileName" : "chat_completions-c881e7aa-85d0-4de6-92ec-da5dbed72027.json",
"headers" : {
- "Date" : "Tue, 13 Jan 2026 18:55:46 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:17 GMT",
"Content-Type" : "application/json",
"access-control-expose-headers" : "X-Request-ID",
"openai-organization" : "braintrust-data",
- "openai-processing-ms" : "1118",
+ "openai-processing-ms" : "1236",
"openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9",
"openai-version" : "2020-10-01",
- "x-envoy-upstream-service-time" : "1134",
+ "x-envoy-upstream-service-time" : "1377",
"x-ratelimit-limit-requests" : "30000",
"x-ratelimit-limit-tokens" : "150000000",
"x-ratelimit-remaining-requests" : "29999",
"x-ratelimit-remaining-tokens" : "149999987",
"x-ratelimit-reset-requests" : "2ms",
"x-ratelimit-reset-tokens" : "0s",
- "x-request-id" : "req_986e2d3165064709ac5ed23dc003dbd7",
+ "x-request-id" : "req_45a7f0c5ca6949b0bf20035b8f02624c",
"x-openai-proxy-wasm" : "v0.1",
"cf-cache-status" : "DYNAMIC",
- "Set-Cookie" : [ "__cf_bm=BUHB2FaXynubGP92D6G2IvABiHu2eEGLvn8wFbWPmSo-1768330546-1.0.1.1-T6ZEZ.yw9bAhRfralFWrawOZlupeGnKzWk2CEzY7ok6AOXGdnWY5SL1.mewnFFHj6vLfMOIn0CPdULLrBjhYGOfb02DSZ4XJoHpYosO3KUU; path=/; expires=Tue, 13-Jan-26 19:25:46 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=xY.tpG.J8nfj5Le3HLJpq.g736HU9coaVEmWUc98mx4-1768330546132-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
+ "Set-Cookie" : [ "__cf_bm=jwB44RgFskbI4cQEbTE4psDX8Bq7z9G4PeMDMVeabPY-1769146577-1.0.1.1-BEkhAl3uAVtQ7rCMUL33pBAwd_xCsE9R5LsuuZ0tfGKrKKVns6CWIE6tWgyzvQg1gEGEqr5sByC.2GNfPOokiDdjLwAbQSWE95Bc1yGCB5k; path=/; expires=Fri, 23-Jan-26 06:06:17 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=QFttWNtqPpE05MC54d0ecFgzddWNtjmW6TCvrc1wKCI-1769146577730-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
"Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload",
"X-Content-Type-Options" : "nosniff",
"Server" : "cloudflare",
- "CF-RAY" : "9bd71c11cdf8c39a-SEA",
+ "CF-RAY" : "9c24eeb3685db9c2-SEA",
"alt-svc" : "h3=\":443\"; ma=86400"
}
},
- "uuid" : "d0ddbec9-910b-4f8b-a0b8-a605e6a23b4b",
+ "uuid" : "c881e7aa-85d0-4de6-92ec-da5dbed72027",
"persistent" : true,
"insertionIndex" : 2
}
\ No newline at end of file
diff --git a/src/test/resources/cassettes/openai/mappings/chat_completions-4706df90-aa52-4bd3-97c0-3448a4e0005f.json b/src/test/resources/cassettes/openai/mappings/chat_completions-d0bef16c-03e9-44da-ab7e-a72110eb2028.json
similarity index 65%
rename from src/test/resources/cassettes/openai/mappings/chat_completions-4706df90-aa52-4bd3-97c0-3448a4e0005f.json
rename to src/test/resources/cassettes/openai/mappings/chat_completions-d0bef16c-03e9-44da-ab7e-a72110eb2028.json
index f67468f..21a14ed 100644
--- a/src/test/resources/cassettes/openai/mappings/chat_completions-4706df90-aa52-4bd3-97c0-3448a4e0005f.json
+++ b/src/test/resources/cassettes/openai/mappings/chat_completions-d0bef16c-03e9-44da-ab7e-a72110eb2028.json
@@ -1,5 +1,5 @@
{
- "id" : "4706df90-aa52-4bd3-97c0-3448a4e0005f",
+ "id" : "d0bef16c-03e9-44da-ab7e-a72110eb2028",
"name" : "chat_completions",
"request" : {
"url" : "/chat/completions",
@@ -17,34 +17,34 @@
},
"response" : {
"status" : 200,
- "bodyFileName" : "chat_completions-4706df90-aa52-4bd3-97c0-3448a4e0005f.json",
+ "bodyFileName" : "chat_completions-d0bef16c-03e9-44da-ab7e-a72110eb2028.json",
"headers" : {
- "Date" : "Tue, 13 Jan 2026 18:55:47 GMT",
+ "Date" : "Fri, 23 Jan 2026 05:36:20 GMT",
"Content-Type" : "application/json",
"access-control-expose-headers" : "X-Request-ID",
"openai-organization" : "braintrust-data",
- "openai-processing-ms" : "403",
+ "openai-processing-ms" : "297",
"openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9",
"openai-version" : "2020-10-01",
- "x-envoy-upstream-service-time" : "421",
+ "x-envoy-upstream-service-time" : "460",
"x-ratelimit-limit-requests" : "30000",
"x-ratelimit-limit-tokens" : "150000000",
"x-ratelimit-remaining-requests" : "29999",
"x-ratelimit-remaining-tokens" : "149999990",
"x-ratelimit-reset-requests" : "2ms",
"x-ratelimit-reset-tokens" : "0s",
- "x-request-id" : "req_e6ad6e08adaf4bafbb57a63d4f06081f",
+ "x-request-id" : "req_af046753669347ffb931da8968d60d3b",
"x-openai-proxy-wasm" : "v0.1",
"cf-cache-status" : "DYNAMIC",
- "Set-Cookie" : [ "__cf_bm=Kf0PbpZlmshlH.OMcmGSPEKIneXZrbLpMkYluWZ53rM-1768330547-1.0.1.1-dyEibCy4JMqWjO47uv.WMSRPxMrD4E.v7QDrQ9e9D1qyPfRzJoKmz2v5zMtvapNpQ5Xv.RYHD0MIgE2GXDk6vYpwO_sTmRvtYdbtEIiEcuU; path=/; expires=Tue, 13-Jan-26 19:25:47 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=a2Iqf.uw0VrBcSHQkzxsmfmy9XzkFde5gjk5LB7Tpb8-1768330547777-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
+ "Set-Cookie" : [ "__cf_bm=z3G4hl.rYDWjZse9PNgVi0tZLHDR5O04WdLkkXpMVj4-1769146580-1.0.1.1-OHy5BXW1875sGlXQoo9Hqe_L9qiG0bWF3MEs5p7v8r8lugfjzZ77qfXVl3V0UTnQdI0SBO55T5uO8snJgq8ybHHhVne9KfoWIUhEQiUVjl8; path=/; expires=Fri, 23-Jan-26 06:06:20 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None", "_cfuvid=pzSfHK3yGW2i0qHi7.SbpjutNAXNegUcggHRzVxYzWQ-1769146580232-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None" ],
"Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload",
"X-Content-Type-Options" : "nosniff",
"Server" : "cloudflare",
- "CF-RAY" : "9bd71c206d2f280d-SEA",
+ "CF-RAY" : "9c24eec918c9dede-SEA",
"alt-svc" : "h3=\":443\"; ma=86400"
}
},
- "uuid" : "4706df90-aa52-4bd3-97c0-3448a4e0005f",
+ "uuid" : "d0bef16c-03e9-44da-ab7e-a72110eb2028",
"persistent" : true,
"insertionIndex" : 4
}
\ No newline at end of file