diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/build.gradle b/braintrust-sdk/instrumentation/langchain_1_14_0/build.gradle new file mode 100644 index 00000000..7b90ba08 --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/build.gradle @@ -0,0 +1,69 @@ +// Java plugin, toolchain (Java 17 / Adoptium), options.release, and repositories +// are inherited from the parent's subprojects {} block. + +// Minimum langchain4j version that ships the OpenAI Responses API +// (OpenAiResponsesChatModel / OpenAiResponsesStreamingChatModel, first released in 1.14.0). +def langchainVersion = '1.14.0' +// Test against a recent release to exercise forward compatibility (and match the version +// used to record btx cassettes). +def langchainTestVersion = '1.19.0' + +muzzle { + pass { + group = 'dev.langchain4j' + module = 'langchain4j' + versions = "[${langchainVersion},)" + extraDependency 'dev.langchain4j:langchain4j-http-client' + extraDependency 'dev.langchain4j:langchain4j-open-ai' + extraDependency 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + extraDependency 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' + } + // The Responses API classes this module targets did not exist before 1.14.0, so it must + // not apply to older releases (langchain_1_8_0 covers [1.8.0,1.14.0)). + fail { + group = 'dev.langchain4j' + module = 'langchain4j' + pinVersions '1.13.0' + extraDependency 'dev.langchain4j:langchain4j-http-client' + extraDependency 'dev.langchain4j:langchain4j-open-ai' + extraDependency 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + extraDependency 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' + } +} + +dependencies { + compileOnly project(':braintrust-java-agent:instrumenter') + implementation "io.opentelemetry:opentelemetry-api:${otelVersion}" + implementation 'com.google.code.findbugs:jsr305:3.0.2' // for @Nullable annotations + implementation "org.slf4j:slf4j-api:${slf4jVersion}" + implementation project(':braintrust-sdk') + + // ByteBuddy for ElementMatcher types used in instrumentation definitions + compileOnly 'net.bytebuddy:byte-buddy:1.17.5' + + // Target libraries — compileOnly because they will be on the app classpath at runtime + compileOnly "dev.langchain4j:langchain4j:${langchainVersion}" + compileOnly "dev.langchain4j:langchain4j-http-client:${langchainVersion}" + compileOnly "dev.langchain4j:langchain4j-open-ai:${langchainVersion}" + + // Test dependencies + testImplementation(testFixtures(project(":test-harness"))) + testImplementation project(':braintrust-java-agent:instrumenter') + testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}" + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'net.bytebuddy:byte-buddy-agent:1.17.5' + testRuntimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" + testImplementation "dev.langchain4j:langchain4j:${langchainTestVersion}" + testImplementation "dev.langchain4j:langchain4j-http-client:${langchainTestVersion}" + testImplementation "dev.langchain4j:langchain4j-open-ai:${langchainTestVersion}" +} + +test { + useJUnitPlatform() + workingDir = rootProject.projectDir + testLogging { + events "passed", "skipped", "failed" + showStandardStreams = true + exceptionFormat "full" + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/BraintrustLangchain.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/BraintrustLangchain.java new file mode 100644 index 00000000..9caa393c --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/BraintrustLangchain.java @@ -0,0 +1,247 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0; + +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesStreamingChatModel; +import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import dev.langchain4j.service.AiServiceContext; +import dev.langchain4j.service.AiServices; +import dev.langchain4j.service.tool.ToolExecutor; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** Braintrust LangChain4j client instrumentation. */ +@Slf4j +public final class BraintrustLangchain { + + private static final String INSTRUMENTATION_NAME = "braintrust-langchain4j"; + private static final ThreadLocal AI_SERVICES_RECURSION_GUARD = + ThreadLocal.withInitial(() -> false); + + @SuppressWarnings("unchecked") + public static T wrap(OpenTelemetry openTelemetry, AiServices aiServices) { + if (AI_SERVICES_RECURSION_GUARD.get()) { + // already wrapped + return null; + } + AI_SERVICES_RECURSION_GUARD.set(true); + try { + AiServiceContext context = getPrivateField(aiServices, "context"); + Tracer tracer = openTelemetry.getTracer(INSTRUMENTATION_NAME); + + // ////// CREATE A LLM SPAN FOR EACH CALL TO AI PROVIDER + var chatModel = context.chatModel; + var streamingChatModel = context.streamingChatModel; + if (chatModel != null) { + if (chatModel instanceof OpenAiChatModel oaiModel) { + aiServices.chatModel(wrap(openTelemetry, oaiModel)); + } else if (chatModel instanceof OpenAiResponsesChatModel responsesModel) { + aiServices.chatModel(wrap(openTelemetry, responsesModel)); + } else { + log.warn( + "unsupported model: {}. LLM calls will not be instrumented", + chatModel.getClass().getName()); + } + // intentional fall-through + } else if (streamingChatModel != null) { + if (streamingChatModel instanceof OpenAiStreamingChatModel oaiModel) { + aiServices.streamingChatModel(wrap(openTelemetry, oaiModel)); + } else if (streamingChatModel + instanceof OpenAiResponsesStreamingChatModel responsesModel) { + aiServices.streamingChatModel(wrap(openTelemetry, responsesModel)); + } else { + log.warn( + "unsupported model: {}. LLM calls will not be instrumented", + streamingChatModel.getClass().getName()); + } + // intentional fall-through + } else { + // langchain is going to fail to build. don't apply instrumentation. + throw new RuntimeException("model or chat model must be set"); + } + + if (context.toolService != null) { + // ////// CREATE A SPAN FOR EACH TOOL CALL + for (Map.Entry entry : + context.toolService.toolExecutors().entrySet()) { + String toolName = entry.getKey(); + ToolExecutor original = entry.getValue(); + entry.setValue(new TracingToolExecutor(original, toolName, tracer)); + } + + // ////// LINK SPANS ACROSS CONCURRENT TOOL CALLS + var underlyingExecutor = context.toolService.executor(); + if (underlyingExecutor != null) { + aiServices.executeToolsConcurrently( + new OtelContextPassingExecutor(underlyingExecutor)); + } + } + + // ////// CREATE A SPAN ON SERVICE METHOD INVOKE + T service = aiServices.build(); + Class serviceInterface = (Class) context.aiServiceClass; + return TracingProxy.create(serviceInterface, service, tracer); + } catch (Exception e) { + log.warn("failed to apply langchain AI services instrumentation", e); + return aiServices.build(); + } finally { + AI_SERVICES_RECURSION_GUARD.set(false); + } + } + + /** Instrument langchain openai chat model with braintrust traces */ + public static OpenAiChatModel wrap( + OpenTelemetry otel, OpenAiChatModel.OpenAiChatModelBuilder builder) { + return wrap(otel, builder.build()); + } + + public static OpenAiChatModel wrap(OpenTelemetry otel, OpenAiChatModel model) { + try { + // Get the internal OpenAiClient from the chat model + Object internalClient = getPrivateField(model, "client"); + + // Get the HttpClient from the internal client + dev.langchain4j.http.client.HttpClient httpClient = + getPrivateField(internalClient, "httpClient"); + + if (httpClient instanceof WrappedHttpClient) { + log.debug("model already instrumented. skipping: {}", httpClient.getClass()); + return model; + } + + // Wrap the HttpClient with our instrumented version + dev.langchain4j.http.client.HttpClient wrappedHttpClient = + new WrappedHttpClient(otel, httpClient, new Options("openai")); + + setPrivateField(internalClient, "httpClient", wrappedHttpClient); + + return model; + } catch (Exception e) { + log.warn("failed to instrument OpenAiChatModel", e); + return model; + } + } + + /** Instrument langchain openai chat model with braintrust traces */ + public static OpenAiStreamingChatModel wrap( + OpenTelemetry otel, OpenAiStreamingChatModel.OpenAiStreamingChatModelBuilder builder) { + return wrap(otel, builder.build()); + } + + public static OpenAiStreamingChatModel wrap( + OpenTelemetry otel, OpenAiStreamingChatModel model) { + try { + // Get the internal OpenAiClient from the streaming chat model + Object internalClient = getPrivateField(model, "client"); + + // Get the HttpClient from the internal client + dev.langchain4j.http.client.HttpClient httpClient = + getPrivateField(internalClient, "httpClient"); + + if (httpClient instanceof WrappedHttpClient) { + log.debug("model already instrumented. skipping: {}", httpClient.getClass()); + return model; + } + + // Wrap the HttpClient with our instrumented version + dev.langchain4j.http.client.HttpClient wrappedHttpClient = + new WrappedHttpClient(otel, httpClient, new Options("openai")); + + setPrivateField(internalClient, "httpClient", wrappedHttpClient); + + return model; + } catch (Exception e) { + log.warn("failed to instrument OpenAiStreamingChatModel", e); + return model; + } + } + + /** Instrument a langchain openai responses model with braintrust traces. */ + public static OpenAiResponsesChatModel wrap( + OpenTelemetry otel, OpenAiResponsesChatModel.Builder builder) { + return wrap(otel, builder.build()); + } + + public static OpenAiResponsesChatModel wrap( + OpenTelemetry otel, OpenAiResponsesChatModel model) { + wrapResponsesHttpClient(otel, model, "OpenAiResponsesChatModel"); + return model; + } + + /** Instrument a langchain openai streaming responses model with braintrust traces. */ + public static OpenAiResponsesStreamingChatModel wrap( + OpenTelemetry otel, OpenAiResponsesStreamingChatModel.Builder builder) { + return wrap(otel, builder.build()); + } + + public static OpenAiResponsesStreamingChatModel wrap( + OpenTelemetry otel, OpenAiResponsesStreamingChatModel model) { + wrapResponsesHttpClient(otel, model, "OpenAiResponsesStreamingChatModel"); + return model; + } + + /** + * Swaps the {@code httpClient} inside a responses model's internal {@code + * OpenAiResponsesClient} for an instrumented {@link WrappedHttpClient}. Both {@link + * OpenAiResponsesChatModel} and {@link OpenAiResponsesStreamingChatModel} hold an {@code + * OpenAiResponsesClient client} field with the same {@code + * dev.langchain4j.http.client.HttpClient httpClient} field as the regular chat models, so the + * tracing strategy is identical — the client POSTs to {@code /v1/responses} and {@code + * InstrumentationSemConv} tags the responses payload. + */ + private static void wrapResponsesHttpClient( + OpenTelemetry otel, Object model, String modelName) { + try { + Object internalClient = getPrivateField(model, "client"); + dev.langchain4j.http.client.HttpClient httpClient = + getPrivateField(internalClient, "httpClient"); + + if (httpClient instanceof WrappedHttpClient) { + log.debug("model already instrumented. skipping: {}", httpClient.getClass()); + return; + } + + dev.langchain4j.http.client.HttpClient wrappedHttpClient = + new WrappedHttpClient(otel, httpClient, new Options("openai")); + setPrivateField(internalClient, "httpClient", wrappedHttpClient); + } catch (Exception e) { + log.warn("failed to instrument {}", modelName, e); + } + } + + public record Options(String providerName) {} + + @SuppressWarnings("unchecked") + private static T getPrivateField(Object obj, String fieldName) + throws ReflectiveOperationException { + Class clazz = obj.getClass(); + while (clazz != null) { + try { + java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(obj); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + private static void setPrivateField(Object obj, String fieldName, Object value) + throws ReflectiveOperationException { + Class clazz = obj.getClass(); + while (clazz != null) { + try { + java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(obj, value); + return; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/OtelContextPassingExecutor.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/OtelContextPassingExecutor.java new file mode 100644 index 00000000..a36018c6 --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/OtelContextPassingExecutor.java @@ -0,0 +1,29 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0; + +import io.opentelemetry.context.Context; +import java.util.concurrent.Executor; + +/** + * An executor that links open telemetry spans across threads. + * + *

Any tasks submitted to the executor will point to the parent context that was present at the + * time of task submission. + */ +class OtelContextPassingExecutor implements Executor { + private final Executor underlying; + + public OtelContextPassingExecutor(Executor executor) { + this.underlying = executor; + } + + @Override + public void execute(Runnable command) { + var context = Context.current(); + underlying.execute( + () -> { + try (var ignored = context.makeCurrent()) { + command.run(); + } + }); + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingProxy.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingProxy.java new file mode 100644 index 00000000..4269b2e4 --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingProxy.java @@ -0,0 +1,51 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; + +class TracingProxy { + /** + * Use a java {@link Proxy} to wrap a service interface methods with spans. + * + *

Each interface method will create a span named {@code .} (e.g. {@code + * Assistant.chat}), so agents sharing a method name stay distinguishable on a trace. + */ + @SuppressWarnings("unchecked") + public static T create(Class serviceInterface, T service, Tracer tracer) { + return (T) + Proxy.newProxyInstance( + serviceInterface.getClassLoader(), + new Class[] {serviceInterface}, + (proxy, method, args) -> { + // Skip Object methods (equals, hashCode, toString) + if (method.getDeclaringClass() == Object.class) { + return method.invoke(service, args); + } + + String spanName = + serviceInterface.getSimpleName() + "." + method.getName(); + Span span = tracer.spanBuilder(spanName).startSpan(); + try (Scope ignored = span.makeCurrent()) { + method.setAccessible(true); + return method.invoke(service, args); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + span.setStatus(StatusCode.ERROR, cause.getMessage()); + span.recordException(cause); + throw cause; + } catch (Exception e) { + span.setStatus(StatusCode.ERROR, e.getMessage()); + span.recordException(e); + throw e; + } finally { + span.end(); + } + }); + } + + private TracingProxy() {} +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingToolExecutor.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingToolExecutor.java new file mode 100644 index 00000000..97cdadec --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingToolExecutor.java @@ -0,0 +1,78 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0; + +import dev.langchain4j.agent.tool.ToolExecutionRequest; +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.service.tool.ToolExecutionResult; +import dev.langchain4j.service.tool.ToolExecutor; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; + +/** A ToolExecutor wrapper that creates a span around tool execution. */ +@Slf4j +class TracingToolExecutor implements ToolExecutor { + static final String TYPE_TOOL_JSON = "{\"type\":\"tool\"}"; + + private final ToolExecutor delegate; + private final String toolName; + private final Tracer tracer; + + TracingToolExecutor(ToolExecutor delegate, String toolName, Tracer tracer) { + this.delegate = delegate; + this.toolName = toolName; + this.tracer = tracer; + } + + @Override + public String execute(ToolExecutionRequest request, Object memoryId) { + Span span = tracer.spanBuilder(toolName).startSpan(); + try (Scope ignored = span.makeCurrent()) { + String result = delegate.execute(request, memoryId); + setSpanAttributes(span, request, result); + return result; + } catch (Exception e) { + span.setStatus(StatusCode.ERROR, e.getMessage()); + span.recordException(e); + throw e; + } finally { + span.end(); + } + } + + @Override + public ToolExecutionResult executeWithContext( + ToolExecutionRequest request, InvocationContext context) { + Span span = tracer.spanBuilder(toolName).startSpan(); + try (Scope ignored = span.makeCurrent()) { + ToolExecutionResult result = delegate.executeWithContext(request, context); + setSpanAttributes(span, request, result.resultText()); + return result; + } catch (Exception e) { + span.setStatus(StatusCode.ERROR, e.getMessage()); + span.recordException(e); + throw e; + } finally { + span.end(); + } + } + + private void setSpanAttributes( + Span span, ToolExecutionRequest request, @Nullable String toolCallResult) { + try { + span.setAttribute("braintrust.span_attributes", TYPE_TOOL_JSON); + + String args = request.arguments(); + if (args != null && !args.isEmpty()) { + span.setAttribute("braintrust.input_json", args); + } + if (toolCallResult != null) { + span.setAttribute("braintrust.output", toolCallResult); + } + } catch (Exception e) { + log.debug("Failed to set tool span attributes", e); + } + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/WrappedHttpClient.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/WrappedHttpClient.java new file mode 100644 index 00000000..a25ff58a --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/WrappedHttpClient.java @@ -0,0 +1,212 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0; + +import dev.braintrust.bootstrap.BraintrustBridge; +import dev.braintrust.instrumentation.InstrumentationSemConv; +import dev.braintrust.instrumentation.SseStreamAccumulator; +import dev.braintrust.json.BraintrustJsonMapper; +import dev.langchain4j.exception.HttpException; +import dev.langchain4j.http.client.HttpClient; +import dev.langchain4j.http.client.HttpRequest; +import dev.langchain4j.http.client.SuccessfulHttpResponse; +import dev.langchain4j.http.client.sse.ServerSentEvent; +import dev.langchain4j.http.client.sse.ServerSentEventContext; +import dev.langchain4j.http.client.sse.ServerSentEventListener; +import dev.langchain4j.http.client.sse.ServerSentEventParser; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import java.net.URI; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class WrappedHttpClient implements HttpClient { + private final Tracer tracer; + private final HttpClient underlying; + private final BraintrustLangchain.Options options; + + public WrappedHttpClient( + OpenTelemetry openTelemetry, + HttpClient underlying, + BraintrustLangchain.Options options) { + this.tracer = openTelemetry.getTracer(BraintrustBridge.INSTRUMENTATION_NAME); + this.underlying = underlying; + this.options = options; + } + + @Override + public SuccessfulHttpResponse execute(HttpRequest request) + throws HttpException, RuntimeException { + Instant llmSpanStart = Instant.now(); + Span span = + tracer.spanBuilder(InstrumentationSemConv.UNSET_LLM_SPAN_NAME) + .setSpanKind(SpanKind.CLIENT) + .setStartTimestamp(llmSpanStart) + .startSpan(); + try (Scope scope = span.makeCurrent()) { + tagRequest(span, request); + var response = underlying.execute(request); + InstrumentationSemConv.tagLLMSpanResponse( + tracer, span, options.providerName(), response.body()); + return response; + } catch (Throwable t) { + InstrumentationSemConv.tagLLMSpanResponse(span, t); + throw t; + } finally { + span.end(); + } + } + + @Override + public void execute(HttpRequest request, ServerSentEventListener listener) { + if (listener instanceof WrappedServerSentEventListener) { + underlying.execute(request, listener); + return; + } + Instant llmSpanStart = Instant.now(); + Span span = + tracer.spanBuilder(InstrumentationSemConv.UNSET_LLM_SPAN_NAME) + .setSpanKind(SpanKind.CLIENT) + .setStartTimestamp(llmSpanStart) + .startSpan(); + try (Scope ignored = span.makeCurrent()) { + tagRequest(span, request); + underlying.execute( + request, + new WrappedServerSentEventListener( + listener, span, options.providerName(), tracer)); + } catch (Throwable t) { + InstrumentationSemConv.tagLLMSpanResponse(span, t); + span.end(); + throw t; + } + } + + @Override + public void execute( + HttpRequest request, ServerSentEventParser parser, ServerSentEventListener listener) { + if (listener instanceof WrappedServerSentEventListener) { + underlying.execute(request, parser, listener); + return; + } + Instant llmSpanStart = Instant.now(); + Span span = + tracer.spanBuilder(InstrumentationSemConv.UNSET_LLM_SPAN_NAME) + .setSpanKind(SpanKind.CLIENT) + .setStartTimestamp(llmSpanStart) + .startSpan(); + try (Scope ignored = span.makeCurrent()) { + tagRequest(span, request); + underlying.execute( + request, + parser, + new WrappedServerSentEventListener( + listener, span, options.providerName(), tracer)); + } catch (Throwable t) { + InstrumentationSemConv.tagLLMSpanResponse(span, t); + span.end(); + throw t; + } + } + + private void tagRequest(Span span, HttpRequest request) { + try { + URI uri = new URI(request.url()); + String baseUrl = uri.getScheme() + "://" + uri.getAuthority(); + List pathSegments = + Arrays.stream(uri.getPath().split("/")).filter(s -> !s.isEmpty()).toList(); + InstrumentationSemConv.tagLLMSpanRequest( + span, options.providerName(), baseUrl, pathSegments, "POST", request.body()); + } catch (Exception e) { + log.debug("Failed to tag request span", e); + } + } + + static class WrappedServerSentEventListener implements ServerSentEventListener { + private final ServerSentEventListener delegate; + private final Span span; + private final String providerName; + private final Tracer tracer; + private final long startNanos = System.nanoTime(); + private final AtomicLong timeToFirstTokenNanos = new AtomicLong(); + // Handles both endpoints this module instruments: chat-completions chunk streams and + // Responses API (`/v1/responses`) event streams. + private final SseStreamAccumulator accumulator = + new SseStreamAccumulator(BraintrustJsonMapper.get()); + + WrappedServerSentEventListener( + ServerSentEventListener delegate, Span span, String providerName, Tracer tracer) { + this.delegate = delegate; + this.span = span; + this.providerName = providerName; + this.tracer = tracer; + } + + @Override + public void onOpen(SuccessfulHttpResponse response) { + try (Scope ignored = span.makeCurrent()) { + delegate.onOpen(response); + } + } + + @Override + public void onEvent(ServerSentEvent event, ServerSentEventContext context) { + try (Scope ignored = span.makeCurrent()) { + accumulateChunk(event.data()); + delegate.onEvent(event, context); + } + } + + @Override + public void onEvent(ServerSentEvent event) { + try (Scope ignored = span.makeCurrent()) { + accumulateChunk(event.data()); + delegate.onEvent(event); + } + } + + @Override + public void onError(Throwable error) { + try (Scope ignored = span.makeCurrent()) { + delegate.onError(error); + } finally { + InstrumentationSemConv.tagLLMSpanResponse(span, error); + span.end(); + } + } + + @Override + public void onClose() { + try (Scope ignored = span.makeCurrent()) { + delegate.onClose(); + } finally { + finalizeSpan(); + span.end(); + } + } + + private void accumulateChunk(String data) { + if (data == null || data.isEmpty() || "[DONE]".equals(data)) return; + if (timeToFirstTokenNanos.get() == 0L) { + timeToFirstTokenNanos.compareAndExchange(0L, System.nanoTime() - startNanos); + } + accumulator.merge(data); + } + + private void finalizeSpan() { + try { + Long ttft = timeToFirstTokenNanos.get(); + String responseBody = accumulator.build(); + InstrumentationSemConv.tagLLMSpanResponse( + tracer, span, providerName, responseBody, ttft); + } catch (Exception e) { + log.debug("Failed to finalize streaming span", e); + } + } + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/WrappedHttpClientBuilder.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/WrappedHttpClientBuilder.java new file mode 100644 index 00000000..13b1b21c --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/WrappedHttpClientBuilder.java @@ -0,0 +1,48 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0; + +import dev.langchain4j.http.client.HttpClient; +import dev.langchain4j.http.client.HttpClientBuilder; +import io.opentelemetry.api.OpenTelemetry; +import java.time.Duration; + +class WrappedHttpClientBuilder implements HttpClientBuilder { + private final OpenTelemetry openTelemetry; + private final HttpClientBuilder underlying; + private final BraintrustLangchain.Options options; + + public WrappedHttpClientBuilder( + OpenTelemetry openTelemetry, + HttpClientBuilder underlying, + BraintrustLangchain.Options options) { + this.openTelemetry = openTelemetry; + this.underlying = underlying; + this.options = options; + } + + @Override + public Duration connectTimeout() { + return underlying.connectTimeout(); + } + + @Override + public HttpClientBuilder connectTimeout(Duration timeout) { + underlying.connectTimeout(timeout); + return this; + } + + @Override + public Duration readTimeout() { + return underlying.readTimeout(); + } + + @Override + public HttpClientBuilder readTimeout(Duration timeout) { + underlying.readTimeout(timeout); + return this; + } + + @Override + public HttpClient build() { + return new WrappedHttpClient(openTelemetry, underlying.build(), options); + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/auto/LangchainInstrumentationModule.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/auto/LangchainInstrumentationModule.java new file mode 100644 index 00000000..6019bce0 --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_14_0/auto/LangchainInstrumentationModule.java @@ -0,0 +1,229 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0.auto; + +import static net.bytebuddy.matcher.ElementMatchers.*; + +import com.google.auto.service.AutoService; +import dev.braintrust.instrumentation.InstrumentationModule; +import dev.braintrust.instrumentation.TypeInstrumentation; +import dev.braintrust.instrumentation.TypeTransformer; +import dev.braintrust.instrumentation.langchain.v1_14_0.BraintrustLangchain; +import dev.braintrust.instrumentation.muzzle.ClassLoaderMatchers; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesStreamingChatModel; +import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import dev.langchain4j.service.AiServices; +import io.opentelemetry.api.GlobalOpenTelemetry; +import java.util.List; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class LangchainInstrumentationModule extends InstrumentationModule { + private static final String MANUAL_PACKAGE = + "dev.braintrust.instrumentation.langchain.v1_14_0."; + + public LangchainInstrumentationModule() { + super("langchain_1_14_0"); + } + + /** + * Gates this module to langchain4j >= 1.14.0, where the OpenAI Responses API classes ({@code + * OpenAiResponsesChatModel} et al.) first appeared. Earlier releases are covered by the {@code + * langchain_1_8_0} module, whose matcher excludes 1.14.0+ — so exactly one module applies for + * any given langchain4j version and the two never overlap. + */ + @Override + public ElementMatcher classLoaderMatcher() { + return ClassLoaderMatchers.hasClassNamed( + "dev.langchain4j.model.openai.OpenAiResponsesChatModel"); + } + + @Override + public List getHelperClassNames() { + return List.of( + MANUAL_PACKAGE + "BraintrustLangchain", + MANUAL_PACKAGE + "BraintrustLangchain$Options", + MANUAL_PACKAGE + "WrappedHttpClient", + MANUAL_PACKAGE + "WrappedHttpClient$WrappedServerSentEventListener", + MANUAL_PACKAGE + "WrappedHttpClientBuilder", + MANUAL_PACKAGE + "TracingProxy", + MANUAL_PACKAGE + "TracingToolExecutor", + MANUAL_PACKAGE + "OtelContextPassingExecutor", + "dev.braintrust.instrumentation.SseStreamAccumulator", + "dev.braintrust.instrumentation.SseResponseAccumulator", + "dev.braintrust.instrumentation.InstrumentationSemConv", + "dev.braintrust.json.BraintrustJsonMapper"); + } + + @Override + public List typeInstrumentations() { + return List.of( + new OpenAiChatModelBuilderInstrumentation(), + new OpenAiStreamingChatModelBuilderInstrumentation(), + new OpenAiResponsesChatModelBuilderInstrumentation(), + new OpenAiResponsesStreamingChatModelBuilderInstrumentation(), + new AiServicesInstrumentation()); + } + + // ------------------------------------------------------------------------- + // Intercept OpenAiChatModel.Builder.build() to wrap the HTTP client + // ------------------------------------------------------------------------- + + public static class OpenAiChatModelBuilderInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("dev.langchain4j.model.openai.OpenAiChatModel$OpenAiChatModelBuilder"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("build").and(takesArguments(0)), + LangchainInstrumentationModule.class.getName() + + "$OpenAiChatModelBuilderAdvice"); + } + } + + private static class OpenAiChatModelBuilderAdvice { + @Advice.OnMethodExit + public static void build( + @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) + Object returnedModel) { + returnedModel = + BraintrustLangchain.wrap( + GlobalOpenTelemetry.get(), (OpenAiChatModel) returnedModel); + } + } + + // ------------------------------------------------------------------------- + // Intercept OpenAiStreamingChatModel.Builder.build() to wrap the HTTP client + // ------------------------------------------------------------------------- + + public static class OpenAiStreamingChatModelBuilderInstrumentation + implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named( + "dev.langchain4j.model.openai.OpenAiStreamingChatModel$OpenAiStreamingChatModelBuilder"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("build").and(takesArguments(0)), + LangchainInstrumentationModule.class.getName() + + "$OpenAiStreamingChatModelBuilderAdvice"); + } + } + + private static class OpenAiStreamingChatModelBuilderAdvice { + @Advice.OnMethodExit + public static void build( + @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) + Object returnedModel) { + returnedModel = + BraintrustLangchain.wrap( + GlobalOpenTelemetry.get(), (OpenAiStreamingChatModel) returnedModel); + } + } + + // ------------------------------------------------------------------------- + // Intercept OpenAiResponsesChatModel.Builder.build() to wrap the HTTP client + // ------------------------------------------------------------------------- + + public static class OpenAiResponsesChatModelBuilderInstrumentation + implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("dev.langchain4j.model.openai.OpenAiResponsesChatModel$Builder"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("build").and(takesArguments(0)), + LangchainInstrumentationModule.class.getName() + + "$OpenAiResponsesChatModelBuilderAdvice"); + } + } + + private static class OpenAiResponsesChatModelBuilderAdvice { + @Advice.OnMethodExit + public static void build( + @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) + Object returnedModel) { + returnedModel = + BraintrustLangchain.wrap( + GlobalOpenTelemetry.get(), (OpenAiResponsesChatModel) returnedModel); + } + } + + // ------------------------------------------------------------------------- + // Intercept OpenAiResponsesStreamingChatModel.Builder.build() to wrap the HTTP client + // ------------------------------------------------------------------------- + + public static class OpenAiResponsesStreamingChatModelBuilderInstrumentation + implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("dev.langchain4j.model.openai.OpenAiResponsesStreamingChatModel$Builder"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("build").and(takesArguments(0)), + LangchainInstrumentationModule.class.getName() + + "$OpenAiResponsesStreamingChatModelBuilderAdvice"); + } + } + + private static class OpenAiResponsesStreamingChatModelBuilderAdvice { + @Advice.OnMethodExit + public static void build( + @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) + Object returnedModel) { + returnedModel = + BraintrustLangchain.wrap( + GlobalOpenTelemetry.get(), + (OpenAiResponsesStreamingChatModel) returnedModel); + } + } + + // ------------------------------------------------------------------------ - + // Intercept AiServices.build() to wrap with TracingProxy + TracingToolExecutor + // ------------------------------------------------------------------------- + + public static class AiServicesInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return hasSuperType(named("dev.langchain4j.service.AiServices")) + .and( + declaresMethod( + named("build").and(takesArguments(0)).and(not(isAbstract())))); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("build").and(takesArguments(0)), + LangchainInstrumentationModule.class.getName() + "$AiServicesAdvice"); + } + } + + private static class AiServicesAdvice { + @Advice.OnMethodExit + public static void build( + @Advice.This AiServices aiServices, + @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) + Object returnedService) { + var wrapped = BraintrustLangchain.wrap(GlobalOpenTelemetry.get(), aiServices); + if (wrapped != null) { + returnedService = wrapped; + } + } + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_14_0/BraintrustLangchainTest.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_14_0/BraintrustLangchainTest.java new file mode 100644 index 00000000..49b4b809 --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_14_0/BraintrustLangchainTest.java @@ -0,0 +1,875 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.braintrust.TestHarness; +import dev.braintrust.instrumentation.Instrumenter; +import dev.langchain4j.agent.tool.Tool; +import dev.langchain4j.agent.tool.ToolSpecification; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.model.chat.ChatModel; +import dev.langchain4j.model.chat.StreamingChatModel; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.request.json.JsonObjectSchema; +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesStreamingChatModel; +import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import dev.langchain4j.service.AiServices; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.SneakyThrows; +import net.bytebuddy.agent.ByteBuddyAgent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class BraintrustLangchainTest { + + private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); + + @BeforeAll + public static void beforeAll() { + var instrumentation = ByteBuddyAgent.install(); + Instrumenter.install(instrumentation, BraintrustLangchainTest.class.getClassLoader()); + } + + private TestHarness testHarness; + + @BeforeEach + void beforeEach() { + testHarness = TestHarness.setup(); + } + + @Test + @SneakyThrows + void testSyncChatCompletion() { + ChatModel model = + OpenAiChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + .modelName("gpt-4o-mini") + .temperature(0.0) + .build(); + + var message = UserMessage.from("What is the capital of France?"); + var response = model.chat(message); + + assertNotNull(response); + assertNotNull(response.aiMessage().text()); + + var spans = testHarness.awaitExportedSpans(); + assertEquals(1, spans.size(), "Expected one span for sync chat completion"); + var span = spans.get(0); + + assertEquals("Chat Completion", span.getName(), "Span name should be 'Chat Completion'"); + + var attributes = span.getAttributes(); + var braintrustSpanAttributesJson = + attributes.get(AttributeKey.stringKey("braintrust.span_attributes")); + + JsonNode spanAttributes = JSON_MAPPER.readTree(braintrustSpanAttributesJson); + assertEquals("llm", spanAttributes.get("type").asText(), "Span type should be 'llm'"); + + String metadataJson = attributes.get(AttributeKey.stringKey("braintrust.metadata")); + assertNotNull(metadataJson, "Metadata should be present"); + JsonNode metadata = JSON_MAPPER.readTree(metadataJson); + assertEquals("openai", metadata.get("provider").asText(), "Provider should be 'openai'"); + assertEquals( + "gpt-4o-mini", metadata.get("model").asText(), "Model should be 'gpt-4o-mini'"); + + String metricsJson = attributes.get(AttributeKey.stringKey("braintrust.metrics")); + assertNotNull(metricsJson, "Metrics should be present"); + JsonNode metrics = JSON_MAPPER.readTree(metricsJson); + assertTrue(metrics.get("tokens").asLong() > 0, "Total tokens should be > 0"); + assertTrue(metrics.get("prompt_tokens").asLong() > 0, "Prompt tokens should be > 0"); + assertTrue( + metrics.get("completion_tokens").asLong() > 0, "Completion tokens should be > 0"); + assertFalse( + metrics.has("time_to_first_token"), + "time_to_first_token should not be present for non-streaming"); + + String inputJson = attributes.get(AttributeKey.stringKey("braintrust.input_json")); + assertNotNull(inputJson, "Input should be present"); + JsonNode input = JSON_MAPPER.readTree(inputJson); + assertTrue(input.isArray(), "Input should be an array"); + assertTrue(input.size() > 0, "Input array should not be empty"); + assertTrue( + input.get(0).get("content").asText().contains("What is the capital of France"), + "Input should contain the user message"); + + String outputJson = attributes.get(AttributeKey.stringKey("braintrust.output_json")); + assertNotNull(outputJson, "Output should be present"); + JsonNode output = JSON_MAPPER.readTree(outputJson); + assertTrue(output.isArray(), "Output should be an array"); + assertTrue(output.size() > 0, "Output array should not be empty"); + assertNotNull( + output.get(0).get("message").get("content"), + "Output should contain assistant response content"); + + // The serialized span output should reflect the full response the client received. + assertSpanOutputReflects(response, span); + } + + /** + * Exercises the OpenAI Responses API path (OpenAiResponsesChatModel -> /v1/responses), which is + * only available on this module's langchain4j range (>= 1.14.0). Auto-instrumentation wraps the + * responses model's HTTP client on build(); a single llm span should be produced and tagged. + */ + @Test + @SneakyThrows + void testResponsesApi() { + ChatModel model = + OpenAiResponsesChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + .modelName("gpt-4o-mini") + .build(); + + var response = model.chat(UserMessage.from("What is the capital of France?")); + assertNotNull(response); + assertNotNull(response.aiMessage().text()); + + var spans = testHarness.awaitExportedSpans(); + assertEquals(1, spans.size(), "Expected one span for a responses-API call"); + var span = spans.get(0); + assertEquals("responses", span.getName(), "Span name should be 'responses'"); + + var attributes = span.getAttributes(); + JsonNode spanAttributes = + JSON_MAPPER.readTree( + attributes.get(AttributeKey.stringKey("braintrust.span_attributes"))); + assertEquals("llm", spanAttributes.get("type").asText(), "Span type should be 'llm'"); + + JsonNode metadata = + JSON_MAPPER.readTree(attributes.get(AttributeKey.stringKey("braintrust.metadata"))); + assertEquals("openai", metadata.get("provider").asText(), "Provider should be 'openai'"); + + String metricsJson = attributes.get(AttributeKey.stringKey("braintrust.metrics")); + assertNotNull(metricsJson, "Metrics should be present"); + JsonNode metrics = JSON_MAPPER.readTree(metricsJson); + assertTrue(metrics.get("tokens").asLong() > 0, "Total tokens should be > 0"); + + assertNotNull( + attributes.get(AttributeKey.stringKey("braintrust.input_json")), + "Input should be present"); + assertNotNull( + attributes.get(AttributeKey.stringKey("braintrust.output_json")), + "Output should be present"); + } + + /** + * Streaming over the Responses API (OpenAiResponsesStreamingChatModel -> /v1/responses with + * {@code stream: true}). The SSE events here are {@code response.*} events, not {@code + * chat.completion.chunk} objects, so the streamed body has to be reassembled from the terminal + * {@code response.completed} snapshot for the span to carry any output or token metrics at all. + */ + @Test + @SneakyThrows + void testStreamingResponsesApi() { + var tracer = testHarness.openTelemetry().getTracer("test-tracer"); + + StreamingChatModel model = + OpenAiResponsesStreamingChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + .modelName("gpt-4o-mini") + .temperature(0.0) + .build(); + + var future = new CompletableFuture(); + var streamedText = new StringBuilder(); + var callbackCount = new AtomicInteger(0); + + model.chat( + "What is the capital of France?", + new StreamingChatResponseHandler() { + @Override + public void onPartialResponse(String token) { + Span childSpan = + tracer.spanBuilder( + "callback-span-" + callbackCount.incrementAndGet()) + .startSpan(); + childSpan.end(); + streamedText.append(token); + } + + @Override + public void onCompleteResponse(ChatResponse response) { + future.complete(response); + } + + @Override + public void onError(Throwable error) { + future.completeExceptionally(error); + } + }); + + var response = future.get(); + assertNotNull(response); + assertFalse(streamedText.toString().isEmpty(), "Streamed text should not be empty"); + + int expectedMinSpans = 1 + callbackCount.get(); + var spans = testHarness.awaitExportedSpans(expectedMinSpans); + + var llmSpan = + spans.stream() + .filter(s -> s.getName().equals("responses")) + .findFirst() + .orElseThrow( + () -> new AssertionError("Should have a 'responses' llm span")); + var callbackSpans = + spans.stream().filter(s -> s.getName().startsWith("callback-span-")).toList(); + assertEquals( + callbackCount.get(), + callbackSpans.size(), + "Should have one callback span per onPartialResponse invocation"); + for (var callbackSpan : callbackSpans) { + assertEquals( + llmSpan.getSpanId(), + callbackSpan.getParentSpanId(), + "Callback span '" + + callbackSpan.getName() + + "' should be parented under the llm span"); + } + + var attributes = llmSpan.getAttributes(); + JsonNode spanAttributes = + JSON_MAPPER.readTree( + attributes.get(AttributeKey.stringKey("braintrust.span_attributes"))); + assertEquals("llm", spanAttributes.get("type").asText(), "Span type should be 'llm'"); + + JsonNode metadata = + JSON_MAPPER.readTree(attributes.get(AttributeKey.stringKey("braintrust.metadata"))); + assertEquals("openai", metadata.get("provider").asText(), "Provider should be 'openai'"); + + String metricsJson = attributes.get(AttributeKey.stringKey("braintrust.metrics")); + assertNotNull(metricsJson, "Metrics should be present"); + JsonNode metrics = JSON_MAPPER.readTree(metricsJson); + assertTrue( + metrics.get("time_to_first_token").asDouble() > 0, + "time_to_first_token should be set for a streaming call"); + assertTrue(metrics.get("tokens").asLong() > 0, "Total tokens should be > 0"); + assertTrue(metrics.get("prompt_tokens").asLong() > 0, "Prompt tokens should be > 0"); + assertTrue( + metrics.get("completion_tokens").asLong() > 0, "Completion tokens should be > 0"); + + assertNotNull( + attributes.get(AttributeKey.stringKey("braintrust.input_json")), + "Input should be present"); + + // The Responses API reports output as an "output" array of items, and the streamed span + // output must carry the same assistant text the caller saw. + String outputJson = attributes.get(AttributeKey.stringKey("braintrust.output_json")); + assertNotNull(outputJson, "Output should be present"); + JsonNode output = JSON_MAPPER.readTree(outputJson); + assertTrue(output.isArray(), "Output should be an array"); + assertFalse(output.isEmpty(), "Output array should not be empty"); + assertTrue( + outputJson.contains(streamedText.toString()), + "Span output should contain the streamed assistant text, got: " + outputJson); + } + + /** + * OpenAI's hosted web search over a streamed Responses call. The web_search_call item + * only ever appears in the reassembled response body, so this also guards that streamed output + * still drives the server-side tool child spans. + */ + @Test + @SneakyThrows + void testStreamingResponsesApiWithWebSearch() { + StreamingChatModel model = + OpenAiResponsesStreamingChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + // gpt-4o-mini accepts the tool but never searches + .modelName("gpt-4o") + .temperature(0.0) + .serverTools(List.of(Map.of("type", "web_search_preview"))) + .build(); + + var future = new CompletableFuture(); + model.chat( + "Do a web search for news about Moderna. What are they up to lately?", + new StreamingChatResponseHandler() { + @Override + public void onPartialResponse(String token) {} + + @Override + public void onCompleteResponse(ChatResponse response) { + future.complete(response); + } + + @Override + public void onError(Throwable error) { + future.completeExceptionally(error); + } + }); + assertNotNull(future.get()); + + var spans = testHarness.awaitExportedSpans(2); + var llmSpanIds = + spans.stream() + .filter(s -> s.getName().equals("responses")) + .map(SpanData::getSpanId) + .toList(); + assertFalse(llmSpanIds.isEmpty(), "should have at least one 'responses' llm span"); + + var webSearchSpan = + spans.stream() + .filter(s -> s.getName().equals("web_search_call")) + .findFirst() + .orElseThrow( + () -> + new AssertionError( + "no 'web_search_call' span; the streamed output was" + + " not reassembled, or the model did not" + + " run a server-side search")); + JsonNode metadata = + JSON_MAPPER.readTree( + webSearchSpan + .getAttributes() + .get(AttributeKey.stringKey("braintrust.metadata"))); + assertEquals( + "web_search_call", + metadata.get("tool_type").asText(), + "web search span should record its tool_type"); + assertTrue( + llmSpanIds.contains(webSearchSpan.getParentSpanId()), + "web search span should be a child of the streaming llm span that reported it"); + } + + @Test + @SneakyThrows + void testStreamingChatCompletion() { + var tracer = testHarness.openTelemetry().getTracer("test-tracer"); + + // Auto-instrumentation intercepts OpenAiStreamingChatModel.Builder.build() + StreamingChatModel model = + OpenAiStreamingChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + .modelName("gpt-4o-mini") + .temperature(0.0) + .build(); + + var future = new CompletableFuture(); + var responseBuilder = new StringBuilder(); + var callbackCount = new AtomicInteger(0); + + model.chat( + "What is the capital of France?", + new StreamingChatResponseHandler() { + @Override + public void onPartialResponse(String token) { + Span childSpan = + tracer.spanBuilder( + "callback-span-" + callbackCount.incrementAndGet()) + .startSpan(); + childSpan.end(); + responseBuilder.append(token); + } + + @Override + public void onCompleteResponse(ChatResponse response) { + future.complete(response); + } + + @Override + public void onError(Throwable error) { + future.completeExceptionally(error); + } + }); + + var response = future.get(); + + assertNotNull(response); + assertFalse(responseBuilder.toString().isEmpty(), "Response should not be empty"); + + int expectedMinSpans = 1 + callbackCount.get(); + var spans = testHarness.awaitExportedSpans(expectedMinSpans); + assertTrue( + spans.size() >= expectedMinSpans, + "Expected at least " + expectedMinSpans + " spans, got " + spans.size()); + + SpanData llmSpan = null; + List callbackSpans = new java.util.ArrayList<>(); + + for (var span : spans) { + if (span.getName().equals("Chat Completion")) { + llmSpan = span; + } else if (span.getName().startsWith("callback-span-")) { + callbackSpans.add(span); + } + } + + assertNotNull(llmSpan, "Should have an LLM span named 'Chat Completion'"); + assertEquals( + callbackCount.get(), + callbackSpans.size(), + "Should have one callback span per onPartialResponse invocation"); + + String llmSpanId = llmSpan.getSpanId(); + for (var callbackSpan : callbackSpans) { + assertEquals( + llmSpanId, + callbackSpan.getParentSpanId(), + "Callback span '" + + callbackSpan.getName() + + "' should be parented under LLM span"); + } + + var attributes = llmSpan.getAttributes(); + + var braintrustSpanAttributesJson = + attributes.get(AttributeKey.stringKey("braintrust.span_attributes")); + + JsonNode spanAttributes = JSON_MAPPER.readTree(braintrustSpanAttributesJson); + assertEquals("llm", spanAttributes.get("type").asText(), "Span type should be 'llm'"); + + String metadataJson = attributes.get(AttributeKey.stringKey("braintrust.metadata")); + assertNotNull(metadataJson, "Metadata should be present"); + JsonNode metadata = JSON_MAPPER.readTree(metadataJson); + assertEquals("openai", metadata.get("provider").asText(), "Provider should be 'openai'"); + assertEquals( + "gpt-4o-mini", metadata.get("model").asText(), "Model should be 'gpt-4o-mini'"); + + String metricsJson = attributes.get(AttributeKey.stringKey("braintrust.metrics")); + assertNotNull(metricsJson, "Metrics should be present"); + JsonNode metrics = JSON_MAPPER.readTree(metricsJson); + assertTrue(metrics.get("tokens").asLong() > 0, "Total tokens should be > 0"); + assertTrue(metrics.get("prompt_tokens").asLong() > 0, "Prompt tokens should be > 0"); + assertTrue( + metrics.get("completion_tokens").asLong() > 0, "Completion tokens should be > 0"); + assertTrue( + metrics.has("time_to_first_token"), + "Metrics should contain time_to_first_token for streaming"); + assertTrue( + metrics.get("time_to_first_token").isNumber(), + "time_to_first_token should be a number"); + + String inputJson = attributes.get(AttributeKey.stringKey("braintrust.input_json")); + assertNotNull(inputJson, "Input should be present"); + JsonNode input = JSON_MAPPER.readTree(inputJson); + assertTrue(input.isArray(), "Input should be an array"); + assertTrue(input.size() > 0, "Input array should not be empty"); + assertTrue( + input.get(0).get("content").asText().contains("What is the capital of France"), + "Input should contain the user message"); + + String outputJson = attributes.get(AttributeKey.stringKey("braintrust.output_json")); + assertNotNull(outputJson, "Output should be present"); + JsonNode output = JSON_MAPPER.readTree(outputJson); + assertTrue(output.isArray(), "Output should be an array"); + assertTrue(output.size() > 0, "Output array should not be empty"); + JsonNode choice = output.get(0); + assertNotNull( + choice.get("message").get("content"), + "Output should contain the complete streamed response"); + assertNotNull(choice.get("finish_reason"), "Output should have finish_reason"); + + // The reconstructed streaming span output should reflect the full response the client + // received — the instrumentation must feed every SSE event to the accumulator. + assertSpanOutputReflects(response, llmSpan); + } + + @Test + @SneakyThrows + void testStreamingChatCompletionWithTools() { + // Auto-instrumentation intercepts OpenAiStreamingChatModel.Builder.build() + StreamingChatModel model = + OpenAiStreamingChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + .modelName("gpt-4o") + .temperature(0.0) + .build(); + + var weatherTool = + ToolSpecification.builder() + .name("get_weather") + .description("Get the current weather for a location") + .parameters( + JsonObjectSchema.builder() + .addStringProperty( + "location", + "The city and state, e.g. San" + " Francisco, CA") + .required("location") + .build()) + .build(); + + var chatRequest = + ChatRequest.builder() + .messages(UserMessage.from("What is the weather in Paris, France?")) + .toolSpecifications(weatherTool) + .build(); + + var future = new CompletableFuture(); + model.chat( + chatRequest, + new StreamingChatResponseHandler() { + @Override + public void onPartialResponse(String token) {} + + @Override + public void onCompleteResponse(ChatResponse response) { + future.complete(response); + } + + @Override + public void onError(Throwable error) { + future.completeExceptionally(error); + } + }); + var response = future.get(); + + // The stream must carry tool-call deltas (merged by index) all the way to the span — the + // original bug dropped tool_calls entirely from streaming reconstruction. + assertTrue( + response.aiMessage().hasToolExecutionRequests(), + "Model should have requested a tool call"); + + var llmSpan = + testHarness.awaitExportedSpans(1).stream() + .filter(s -> s.getName().equals("Chat Completion")) + .findFirst() + .orElseThrow(() -> new AssertionError("no 'Chat Completion' llm span")); + + assertSpanOutputReflects(response, llmSpan); + } + + /** + * Asserts that the llm span's serialized output ({@code braintrust.output_json}) reflects the + * full response the langchain client received — comparing the reconstructed assistant message + * against the client's parsed {@link ChatResponse} (content, thinking, and tool calls) rather + * than hand-asserting individual fields per test. langchain decodes the same stream + * independently of our accumulator, so agreement is a meaningful end-to-end check. + */ + @SneakyThrows + private void assertSpanOutputReflects(ChatResponse clientResponse, SpanData llmSpan) { + String outputJson = + llmSpan.getAttributes().get(AttributeKey.stringKey("braintrust.output_json")); + assertNotNull(outputJson, "Span should have braintrust.output_json"); + JsonNode message = JSON_MAPPER.readTree(outputJson).get(0).get("message"); + assertNotNull(message, "Span output should contain a choice message"); + + var aiMessage = clientResponse.aiMessage(); + + if (aiMessage.text() != null) { + assertEquals( + aiMessage.text(), + message.path("content").asText(), + "Span output content should match the client's assistant text"); + } + if (aiMessage.thinking() != null) { + assertEquals( + aiMessage.thinking(), + message.path("reasoning_content").asText(), + "Span output reasoning_content should match the client's thinking"); + } + if (aiMessage.hasToolExecutionRequests()) { + JsonNode toolCalls = message.get("tool_calls"); + assertNotNull(toolCalls, "Span output should contain tool_calls"); + var requests = aiMessage.toolExecutionRequests(); + assertEquals( + requests.size(), toolCalls.size(), "tool_calls count should match the client"); + for (int i = 0; i < requests.size(); i++) { + var request = requests.get(i); + JsonNode function = toolCalls.get(i).get("function"); + assertEquals( + request.name(), function.get("name").asText(), "tool name should match"); + assertEquals( + JSON_MAPPER.readTree(request.arguments()), + JSON_MAPPER.readTree(function.get("arguments").asText()), + "tool arguments should match"); + if (request.id() != null) { + assertEquals( + request.id(), + toolCalls.get(i).get("id").asText(), + "tool id should match"); + } + } + } + } + + @Test + @SneakyThrows + void testAiServicesWithTools() { + // Auto-instrumentation intercepts both OpenAiChatModel.Builder.build() and + // AiServices.build() + Assistant assistant = + AiServices.builder(Assistant.class) + .chatModel( + OpenAiChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + .modelName("gpt-4o-mini") + .temperature(0.0) + .build()) + .tools(new WeatherTools()) + .executeToolsConcurrently() + .build(); + + var response = assistant.chat("is it hotter in Paris or New York right now?"); + + assertNotNull(response); + + var spans = testHarness.awaitExportedSpans(3); + assertTrue(spans.size() >= 3, "Expected at least 3 spans for AI Services with tools"); + + int numServiceMethodSpans = 0; + int numLLMSpans = 0; + int numToolCallSpans = 0; + + for (var span : spans) { + String spanName = span.getName(); + var attributes = span.getAttributes(); + + if (spanName.equals("Assistant.chat")) { + numServiceMethodSpans++; + } else if (spanName.equals("Chat Completion")) { + numLLMSpans++; + var spanAttributesJson = + attributes.get(AttributeKey.stringKey("braintrust.span_attributes")); + assertNotNull(spanAttributesJson, "LLM span should have span_attributes"); + JsonNode spanAttributes = JSON_MAPPER.readTree(spanAttributesJson); + assertEquals( + "llm", spanAttributes.get("type").asText(), "Span type should be 'llm'"); + } else if (spanName.equals("getWeather")) { + numToolCallSpans++; + var spanAttributesJson = + attributes.get(AttributeKey.stringKey("braintrust.span_attributes")); + assertNotNull(spanAttributesJson, "Tool span should have span_attributes"); + JsonNode spanAttributes = JSON_MAPPER.readTree(spanAttributesJson); + assertEquals( + "tool", spanAttributes.get("type").asText(), "Span type should be 'tool'"); + } + } + assertEquals(1, numServiceMethodSpans, "should be exactly one service call"); + assertTrue(numLLMSpans >= 2, "should be at least two llm spans"); + assertTrue(numToolCallSpans >= 2, "should be at least two tool call spans"); + } + + /** + * AI Services driven by the Responses API (/v1/responses) — the path the + * langchain-ai-services-responses example takes. Wrapping the AiServices builder manually must + * instrument the responses model too (it is not an OpenAiChatModel subtype), so the trace gets + * llm spans in addition to the service-method and tool spans. + */ + @Test + @SneakyThrows + void testAiServicesWithToolsOverResponsesApi() { + Assistant assistant = + BraintrustLangchain.wrap( + testHarness.openTelemetry(), + AiServices.builder(Assistant.class) + .chatModel( + OpenAiResponsesChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + .modelName("gpt-4o-mini") + .temperature(0.0) + .build()) + .tools(new WeatherTools()) + .executeToolsConcurrently()); + + var response = assistant.chat("is it hotter in Paris or New York right now?"); + assertNotNull(response); + + var spans = testHarness.awaitExportedSpans(3); + + int numServiceMethodSpans = 0; + int numLLMSpans = 0; + int numToolCallSpans = 0; + for (var span : spans) { + var attributes = span.getAttributes(); + switch (span.getName()) { + case "Assistant.chat" -> numServiceMethodSpans++; + case "responses" -> { + numLLMSpans++; + JsonNode spanAttributes = + JSON_MAPPER.readTree( + attributes.get( + AttributeKey.stringKey("braintrust.span_attributes"))); + assertEquals( + "llm", + spanAttributes.get("type").asText(), + "Span type should be 'llm'"); + } + case "getWeather" -> { + numToolCallSpans++; + JsonNode spanAttributes = + JSON_MAPPER.readTree( + attributes.get( + AttributeKey.stringKey("braintrust.span_attributes"))); + assertEquals( + "tool", + spanAttributes.get("type").asText(), + "Span type should be 'tool'"); + } + default -> {} + } + } + assertEquals(1, numServiceMethodSpans, "should be exactly one service call"); + assertTrue(numLLMSpans >= 2, "should be at least two llm spans, got " + numLLMSpans); + assertTrue( + numToolCallSpans >= 2, + "should be at least two tool call spans, got " + numToolCallSpans); + } + + /** + * Openai's hosted web search runs server side, so it never surfaces as a langchain tool + * execution — braintrust derives a {@code web_search_call} tool span from the response payload + * instead. Mirrors the langchain-ai-services example's responses agent: the server tool is + * passed as a raw {@code serverTools} map and rides in the same request {@code tools} array as + * the {@code @Tool} functions. + */ + @Test + @SneakyThrows + void testAiServicesWebSearchOverResponsesApi() { + var assistant = + BraintrustLangchain.wrap( + testHarness.openTelemetry(), + AiServices.builder(Assistant.class) + .chatModel( + OpenAiResponsesChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + // gpt-4o-mini accepts the tool but never searches + .modelName("gpt-4o") + .temperature(0.0) + .serverTools( + List.of( + Map.of( + "type", + "web_search_preview"))) + .build()) + .tools(new WeatherTools())); + + var response = + assistant.chat( + "Do a web search for news about Moderna. What are they up to lately?"); + assertNotNull(response); + + var spans = testHarness.awaitExportedSpans(3); + var llmSpanIds = + spans.stream() + .filter(s -> s.getName().equals("responses")) + .map(SpanData::getSpanId) + .toList(); + assertFalse(llmSpanIds.isEmpty(), "should have at least one 'responses' llm span"); + + var webSearchSpan = + spans.stream() + .filter(s -> s.getName().equals("web_search_call")) + .findFirst() + .orElseThrow( + () -> + new AssertionError( + "no 'web_search_call' span; the model did not run a" + + " server-side search")); + + var attributes = webSearchSpan.getAttributes(); + JsonNode spanAttributes = + JSON_MAPPER.readTree( + attributes.get(AttributeKey.stringKey("braintrust.span_attributes"))); + assertEquals( + "tool", spanAttributes.get("type").asText(), "web search span type should be tool"); + JsonNode metadata = + JSON_MAPPER.readTree(attributes.get(AttributeKey.stringKey("braintrust.metadata"))); + assertEquals( + "web_search_call", + metadata.get("tool_type").asText(), + "web search span should record its tool_type"); + assertTrue( + llmSpanIds.contains(webSearchSpan.getParentSpanId()), + "web search span should be a child of the llm span that reported it"); + } + + /** + * Guards the manual wrap path used by the examples (no java agent): {@code + * BraintrustLangchain.wrap(otel, aiServices)} must recognize a responses model, which is not an + * {@code OpenAiChatModel} subtype. Auto-instrumentation is installed for this test class and + * already wrapped the model on build(), so the wrap is undone first to isolate the AiServices + * dispatch. + */ + @Test + @SneakyThrows + void testAiServicesWrapsResponsesModel() { + var model = + OpenAiResponsesChatModel.builder() + .apiKey(testHarness.openAiApiKey()) + .baseUrl(testHarness.openAiBaseUrl()) + .modelName("gpt-4o-mini") + .build(); + unwrapHttpClient(model); + assertFalse( + httpClientOf(model) instanceof WrappedHttpClient, + "precondition: model should start uninstrumented"); + + var assistant = + BraintrustLangchain.wrap( + testHarness.openTelemetry(), + AiServices.builder(Assistant.class).chatModel(model)); + + assertNotNull(assistant, "wrap should return an instrumented service"); + assertTrue( + httpClientOf(model) instanceof WrappedHttpClient, + "AiServices wrap should instrument the responses model's http client"); + } + + /** Reads the {@code client.httpClient} a langchain model issues requests through. */ + @SneakyThrows + private static Object httpClientOf(Object model) { + return readField(readField(model, "client"), "httpClient"); + } + + /** Restores a model's original http client, reversing {@link WrappedHttpClient} wrapping. */ + @SneakyThrows + private static void unwrapHttpClient(Object model) { + Object client = readField(model, "client"); + Object httpClient = readField(client, "httpClient"); + if (!(httpClient instanceof WrappedHttpClient)) { + return; + } + var field = client.getClass().getDeclaredField("httpClient"); + field.setAccessible(true); + field.set(client, readField(httpClient, "underlying")); + } + + @SneakyThrows + private static Object readField(Object obj, String name) { + var field = obj.getClass().getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } + + /** AI Service interface for the assistant */ + interface Assistant { + String chat(String userMessage); + } + + /** Example tool class with weather-related methods */ + public static class WeatherTools { + @Tool("Get current weather for a location") + public String getWeather(String location) { + return String.format("The weather in %s is sunny with 72°F temperature.", location); + } + + @Tool("Get weather forecast for next N days") + public String getForecast(String location, int days) { + return String.format( + "The %d-day forecast for %s: Mostly sunny with temperatures between 65-75°F.", + days, location); + } + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_14_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingToolExecutorTest.java b/braintrust-sdk/instrumentation/langchain_1_14_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingToolExecutorTest.java new file mode 100644 index 00000000..b192a20f --- /dev/null +++ b/braintrust-sdk/instrumentation/langchain_1_14_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_14_0/TracingToolExecutorTest.java @@ -0,0 +1,18 @@ +package dev.braintrust.instrumentation.langchain.v1_14_0; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dev.braintrust.json.BraintrustJsonMapper; +import java.util.Map; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +public class TracingToolExecutorTest { + @Test + @SneakyThrows + void typeToolJsonCorrect() { + assertEquals( + BraintrustJsonMapper.toJson(Map.of("type", "tool")), + TracingToolExecutor.TYPE_TOOL_JSON); + } +} diff --git a/braintrust-sdk/instrumentation/langchain_1_8_0/build.gradle b/braintrust-sdk/instrumentation/langchain_1_8_0/build.gradle index 74db5342..91481c08 100644 --- a/braintrust-sdk/instrumentation/langchain_1_8_0/build.gradle +++ b/braintrust-sdk/instrumentation/langchain_1_8_0/build.gradle @@ -4,10 +4,23 @@ def langchainVersion = '1.8.0' muzzle { + // Upper-bounded below 1.14.0: from 1.14.0 on the OpenAI Responses API exists and the + // langchain_1_14_0 module takes over (it also covers chat completions). pass { group = 'dev.langchain4j' module = 'langchain4j' - versions = '[1.8.0,)' + versions = '[1.8.0,1.14.0)' + extraDependency 'dev.langchain4j:langchain4j-http-client' + extraDependency 'dev.langchain4j:langchain4j-open-ai' + extraDependency 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + extraDependency 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' + } + // Assert the classLoaderMatcher rejects 1.14.0+ so the two langchain modules never both + // instrument OpenAiChatModel on the same classloader. + fail { + group = 'dev.langchain4j' + module = 'langchain4j' + pinVersions '1.14.0' extraDependency 'dev.langchain4j:langchain4j-http-client' extraDependency 'dev.langchain4j:langchain4j-open-ai' extraDependency 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' diff --git a/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/BraintrustLangchain.java b/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/BraintrustLangchain.java index a996ec40..9f0805cd 100644 --- a/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/BraintrustLangchain.java +++ b/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/BraintrustLangchain.java @@ -4,33 +4,162 @@ import dev.langchain4j.model.openai.OpenAiStreamingChatModel; import dev.langchain4j.service.AiServices; import io.opentelemetry.api.OpenTelemetry; +import java.lang.reflect.Method; +import lombok.extern.slf4j.Slf4j; -/** Braintrust LangChain4j client instrumentation. */ +/** + * Braintrust LangChain4j client instrumentation. + * + * @deprecated use the wrapper matching your langchain4j version instead: + *

    + *
  • {@code dev.braintrust.instrumentation.langchain.v1_14_0.BraintrustLangchain} — + * langchain4j 1.14.0 and up. + *
  • {@code dev.braintrust.instrumentation.langchain.v1_8_0.BraintrustLangchain} — + * langchain4j 1.8.0 through 1.13.x. + *
+ */ +@Deprecated(forRemoval = true) +@Slf4j public final class BraintrustLangchain { - /** Instrument a LangChain4j AiServices builder with Braintrust traces. */ + /** + * Sentinel class that first appeared in langchain4j 1.14.0, alongside the Responses API. Its + * presence is what {@code LangchainInstrumentationModule.classLoaderMatcher()} keys the + * agent-side module gate on, so reusing it here keeps the manual path picking the same module + * the agent would. + */ + private static final String RESPONSES_SENTINEL = + "dev.langchain4j.model.openai.OpenAiResponsesChatModel"; + + private static final String V1_14_0_WRAPPER = + "dev.braintrust.instrumentation.langchain.v1_14_0.BraintrustLangchain"; + + /** + * The {@code v1_14_0} wrapper class, or {@code null} when this process is on langchain4j < + * 1.14.0 (or that module was stripped from the jar). Resolved once at class-init: both modules + * are embedded in the same braintrust-sdk jar, so this is a fixed property of the classpath + * rather than something to re-check per call. + */ + private static final Class V1_14_0 = resolveV1140Wrapper(); + + private static Class resolveV1140Wrapper() { + ClassLoader loader = BraintrustLangchain.class.getClassLoader(); + try { + Class.forName(RESPONSES_SENTINEL, false, loader); + } catch (ClassNotFoundException | LinkageError e) { + // langchain4j < 1.14.0: v1_8_0 is the correct module. + return null; + } + try { + return Class.forName(V1_14_0_WRAPPER, true, loader); + } catch (ClassNotFoundException | LinkageError e) { + log.warn( + "langchain4j 1.14.0+ detected but {} is missing; falling back to the" + + " langchain_1_8_0 instrumentation (chat completions only)", + V1_14_0_WRAPPER, + e); + return null; + } + } + + /** Whether calls should be forwarded to the {@code v1_14_0} wrapper. */ + private static boolean forwards() { + return V1_14_0 != null; + } + + /** + * Reflectively invokes {@code v1_14_0.BraintrustLangchain.wrap(OpenTelemetry, paramType)}. + * Reflection is required because {@code langchain_1_8_0} deliberately does not depend on the + * newer module — doing so would put langchain4j 1.14.0+ on this module's compile classpath and + * defeat compiling against the minimum supported version. + * + * @return the wrapped model, or {@code null} if the forward failed (caller falls back) + */ + private static Object forward(Class paramType, OpenTelemetry otel, Object arg) { + try { + Method wrap = V1_14_0.getMethod("wrap", OpenTelemetry.class, paramType); + return wrap.invoke(null, otel, arg); + } catch (ReflectiveOperationException | LinkageError e) { + log.warn( + "failed to forward to {}.wrap(OpenTelemetry, {}); falling back to" + + " langchain_1_8_0", + V1_14_0_WRAPPER, + paramType.getName(), + e); + return null; + } + } + + /** + * Instrument a LangChain4j AiServices builder with Braintrust traces. + * + * @deprecated see {@link BraintrustLangchain} + */ + @Deprecated(forRemoval = true) @SuppressWarnings("unchecked") public static T wrap(OpenTelemetry openTelemetry, AiServices aiServices) { + if (forwards()) { + Object wrapped = forward(AiServices.class, openTelemetry, aiServices); + if (wrapped != null) { + return (T) wrapped; + } + } return dev.braintrust.instrumentation.langchain.v1_8_0.BraintrustLangchain.wrap( openTelemetry, aiServices); } - /** Instrument langchain openai chat model with braintrust traces. */ + /** + * Instrument langchain openai chat model with braintrust traces. + * + * @deprecated see {@link BraintrustLangchain} + */ + @Deprecated(forRemoval = true) public static OpenAiChatModel wrap( OpenTelemetry otel, OpenAiChatModel.OpenAiChatModelBuilder builder) { + if (forwards()) { + Object wrapped = forward(OpenAiChatModel.OpenAiChatModelBuilder.class, otel, builder); + if (wrapped != null) { + return (OpenAiChatModel) wrapped; + } + } return dev.braintrust.instrumentation.langchain.v1_8_0.BraintrustLangchain.wrap( otel, builder); } - /** Instrument langchain openai streaming chat model with braintrust traces. */ + /** + * Instrument langchain openai streaming chat model with braintrust traces. + * + * @deprecated see {@link BraintrustLangchain} + */ + @Deprecated(forRemoval = true) public static OpenAiStreamingChatModel wrap( OpenTelemetry otel, OpenAiStreamingChatModel.OpenAiStreamingChatModelBuilder builder) { + if (forwards()) { + Object wrapped = + forward( + OpenAiStreamingChatModel.OpenAiStreamingChatModelBuilder.class, + otel, + builder); + if (wrapped != null) { + return (OpenAiStreamingChatModel) wrapped; + } + } return dev.braintrust.instrumentation.langchain.v1_8_0.BraintrustLangchain.wrap( otel, builder.build()); } + /** + * @deprecated see {@link BraintrustLangchain} + */ + @Deprecated(forRemoval = true) public static OpenAiStreamingChatModel wrap( OpenTelemetry otel, OpenAiStreamingChatModel model) { + if (forwards()) { + Object wrapped = forward(OpenAiStreamingChatModel.class, otel, model); + if (wrapped != null) { + return (OpenAiStreamingChatModel) wrapped; + } + } return dev.braintrust.instrumentation.langchain.v1_8_0.BraintrustLangchain.wrap( otel, model); } diff --git a/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/TracingProxy.java b/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/TracingProxy.java index 4248e492..36304b5b 100644 --- a/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/TracingProxy.java +++ b/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/TracingProxy.java @@ -11,7 +11,8 @@ class TracingProxy { /** * Use a java {@link Proxy} to wrap a service interface methods with spans. * - *

Each interface method will create a span with the same name as the method. + *

Each interface method will create a span named {@code .} (e.g. {@code + * Assistant.chat}), so agents sharing a method name stay distinguishable on a trace. */ @SuppressWarnings("unchecked") public static T create(Class serviceInterface, T service, Tracer tracer) { @@ -25,7 +26,9 @@ public static T create(Class serviceInterface, T service, Tracer tracer) return method.invoke(service, args); } - Span span = tracer.spanBuilder(method.getName()).startSpan(); + String spanName = + serviceInterface.getSimpleName() + "." + method.getName(); + Span span = tracer.spanBuilder(spanName).startSpan(); try (Scope ignored = span.makeCurrent()) { method.setAccessible(true); return method.invoke(service, args); diff --git a/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/auto/LangchainInstrumentationModule.java b/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/auto/LangchainInstrumentationModule.java index 63ddc6a0..80cdd172 100644 --- a/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/auto/LangchainInstrumentationModule.java +++ b/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/auto/LangchainInstrumentationModule.java @@ -7,6 +7,7 @@ import dev.braintrust.instrumentation.TypeInstrumentation; import dev.braintrust.instrumentation.TypeTransformer; import dev.braintrust.instrumentation.langchain.v1_8_0.BraintrustLangchain; +import dev.braintrust.instrumentation.muzzle.ClassLoaderMatchers; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; import dev.langchain4j.service.AiServices; @@ -25,6 +26,20 @@ public LangchainInstrumentationModule() { super("langchain_1_8_0"); } + /** + * Gates this module to langchain4j < 1.14.0. The Responses API classes ({@code + * OpenAiResponsesChatModel} et al.) first appeared in 1.14.0; from there on the {@code + * langchain_1_14_0} module takes over (chat completions + responses). Excluding classloaders + * that already have the responses classes keeps the two modules from both instrumenting {@code + * OpenAiChatModel} on 1.14.0+. + */ + @Override + public ElementMatcher classLoaderMatcher() { + return not( + ClassLoaderMatchers.hasClassNamed( + "dev.langchain4j.model.openai.OpenAiResponsesChatModel")); + } + @Override public List getHelperClassNames() { return List.of( diff --git a/braintrust-sdk/instrumentation/langchain_1_8_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_8_0/BraintrustLangchainTest.java b/braintrust-sdk/instrumentation/langchain_1_8_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_8_0/BraintrustLangchainTest.java index e5756cfc..d25da0b6 100644 --- a/braintrust-sdk/instrumentation/langchain_1_8_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_8_0/BraintrustLangchainTest.java +++ b/braintrust-sdk/instrumentation/langchain_1_8_0/src/test/java/dev/braintrust/instrumentation/langchain/v1_8_0/BraintrustLangchainTest.java @@ -403,7 +403,7 @@ void testAiServicesWithTools() { String spanName = span.getName(); var attributes = span.getAttributes(); - if (spanName.equals("chat")) { + if (spanName.equals("Assistant.chat")) { numServiceMethodSpans++; } else if (spanName.equals("Chat Completion")) { numLLMSpans++; diff --git a/braintrust-sdk/src/main/java/dev/braintrust/instrumentation/SseStreamAccumulator.java b/braintrust-sdk/src/main/java/dev/braintrust/instrumentation/SseStreamAccumulator.java new file mode 100644 index 00000000..eb5af531 --- /dev/null +++ b/braintrust-sdk/src/main/java/dev/braintrust/instrumentation/SseStreamAccumulator.java @@ -0,0 +1,132 @@ +package dev.braintrust.instrumentation; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.Nullable; +import javax.annotation.concurrent.NotThreadSafe; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +/** + * Reconstructs a full response body from an OpenAI SSE stream, whichever of the two wire shapes the + * stream uses: + * + *

    + *
  • Chat Completions ({@code /v1/chat/completions}) — {@code chat.completion.chunk} + * objects whose fragments must be concatenated. Delegated to {@link SseResponseAccumulator}. + *
  • Responses ({@code /v1/responses}) — {@code response.*} events. Handled here. + *
+ * + *

The shape is detected from the events themselves rather than from the request, so a caller + * that sees only the response stream (e.g. a wrapped HTTP client) can forward every {@code data:} + * payload without knowing which endpoint was called. Use this instead of {@link + * SseResponseAccumulator} directly wherever both endpoints are reachable — feeding Responses events + * to the chat-completions accumulator yields a body with no {@code choices}, no {@code usage} and + * no {@code output}, which tags an empty span output and drops all token metrics. + */ +@Slf4j +@NotThreadSafe +public final class SseStreamAccumulator { + private static final String RESPONSES_EVENT_PREFIX = "response."; + + private final ObjectMapper jsonMapper; + private final SseResponseAccumulator chatCompletions; + // Latest complete snapshot of the response object, from the most recent event that carried one. + @Nullable private ObjectNode responsesSnapshot; + // Output items seen individually, keyed by their "output_index" (see #mergeResponsesEvent). + private final Map responsesItemsByIndex = new LinkedHashMap<>(); + private boolean sawResponsesEvent; + + public SseStreamAccumulator(ObjectMapper jsonMapper) { + this.jsonMapper = jsonMapper; + this.chatCompletions = new SseResponseAccumulator(jsonMapper); + } + + /** + * Merge one SSE {@code data:} payload into the reconstructed response. Blank payloads, the + * {@code [DONE]} sentinel, non-JSON, and non-object chunks are ignored, so callers can forward + * every event without pre-filtering. + */ + public void merge(String jsonChunk) { + if (jsonChunk == null) return; + String data = jsonChunk.strip(); + if (data.isEmpty() || "[DONE]".equals(data)) return; + + JsonNode chunk; + try { + chunk = jsonMapper.readTree(data); + } catch (JsonProcessingException e) { + log.debug("Failed to parse SSE chunk: {}", data, e); + return; + } + if (chunk == null || !chunk.isObject()) return; + + // Once a Responses event has been seen the stream is a Responses stream; keep routing + // everything there so trailing non-"response.*" events (e.g. a terminal `error` event) + // can't leak into the chat-completions reconstruction. + if (sawResponsesEvent || isResponsesEvent(chunk)) { + sawResponsesEvent = true; + mergeResponsesEvent(chunk); + } else { + chatCompletions.merge(data); + } + } + + /** + * Serialize the reconstructed response. Safe to call once the stream is complete; leaves this + * accumulator's state untouched, so a partial build mid-stream is also valid. + */ + @SneakyThrows(JsonProcessingException.class) + public String build() { + if (!sawResponsesEvent) { + return chatCompletions.build(); + } + ObjectNode root = + responsesSnapshot == null + ? jsonMapper.createObjectNode() + : responsesSnapshot.deepCopy(); + JsonNode output = root.get("output"); + if ((output == null || output.isEmpty()) && !responsesItemsByIndex.isEmpty()) { + var items = jsonMapper.createArrayNode(); + responsesItemsByIndex.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> items.add(entry.getValue())); + root.set("output", items); + } + return jsonMapper.writeValueAsString(root); + } + + private static boolean isResponsesEvent(JsonNode chunk) { + JsonNode type = chunk.get("type"); + return type != null && type.isTextual() && type.asText().startsWith(RESPONSES_EVENT_PREFIX); + } + + /** + * Merges one {@code response.*} event. Unlike chat-completions chunks, Responses events are not + * fragments to be stitched: {@code response.created} / {@code .in_progress} / {@code + * .completed} / {@code .incomplete} / {@code .failed} each carry a complete snapshot + * of the response object, so the newest snapshot simply replaces the previous one and the + * terminal event supplies the authoritative {@code output} and {@code usage}. + * + *

{@code response.output_item.added} / {@code .done} events are also recorded per {@code + * output_index} so that a stream which closes without a terminal snapshot still reports the + * items it produced. Text/argument delta events need no handling: whatever they build up is + * repeated whole in the item's {@code .done} event. + */ + private void mergeResponsesEvent(JsonNode event) { + JsonNode snapshot = event.get("response"); + if (snapshot != null && snapshot.isObject()) { + responsesSnapshot = snapshot.deepCopy(); + return; + } + JsonNode item = event.get("item"); + JsonNode outputIndex = event.get("output_index"); + if (item != null && item.isObject() && outputIndex != null && outputIndex.isNumber()) { + responsesItemsByIndex.put(outputIndex.asInt(), item.deepCopy()); + } + } +} diff --git a/braintrust-sdk/src/test/java/dev/braintrust/instrumentation/SseStreamAccumulatorTest.java b/braintrust-sdk/src/test/java/dev/braintrust/instrumentation/SseStreamAccumulatorTest.java new file mode 100644 index 00000000..a7acc74a --- /dev/null +++ b/braintrust-sdk/src/test/java/dev/braintrust/instrumentation/SseStreamAccumulatorTest.java @@ -0,0 +1,233 @@ +package dev.braintrust.instrumentation; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import java.util.List; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +/** + * Hermetic tests for the endpoint-sniffing SSE accumulator: Responses API ({@code /v1/responses}) + * event streams reassemble into the terminal snapshot, and Chat Completions chunk streams keep + * flowing through {@link SseResponseAccumulator}. + */ +class SseStreamAccumulatorTest { + + private static final ObjectMapper JSON = new ObjectMapper(); + + @SneakyThrows + private static JsonNode reconstruct(String... chunks) { + var acc = new SseStreamAccumulator(JSON); + for (String chunk : chunks) { + acc.merge(chunk); + } + return JSON.readTree(acc.build()); + } + + /** The event sequence OpenAI sends for a plain streamed {@code /v1/responses} text answer. */ + private static String[] textResponsesStream() { + return new String[] { + "{\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"resp_1\",\"object\":\"response\",\"model\":\"gpt-4o-mini\",\"status\":\"in_progress\",\"output\":[],\"usage\":null}}", + "{\"type\":\"response.in_progress\",\"sequence_number\":1,\"response\":{\"id\":\"resp_1\",\"object\":\"response\",\"model\":\"gpt-4o-mini\",\"status\":\"in_progress\",\"output\":[],\"usage\":null}}", + "{\"type\":\"response.output_item.added\",\"sequence_number\":2,\"output_index\":0,\"item\":{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"status\":\"in_progress\",\"content\":[]}}", + "{\"type\":\"response.content_part.added\",\"sequence_number\":3,\"item_id\":\"msg_1\",\"output_index\":0,\"content_index\":0,\"part\":{\"type\":\"output_text\",\"text\":\"\",\"annotations\":[]}}", + "{\"type\":\"response.output_text.delta\",\"sequence_number\":4,\"item_id\":\"msg_1\",\"output_index\":0,\"content_index\":0,\"delta\":\"Hello\"}", + "{\"type\":\"response.output_text.delta\",\"sequence_number\":5,\"item_id\":\"msg_1\",\"output_index\":0,\"content_index\":0,\"delta\":\"" + + " world\"}", + "{\"type\":\"response.output_text.done\",\"sequence_number\":6,\"item_id\":\"msg_1\",\"output_index\":0,\"content_index\":0,\"text\":\"Hello" + + " world\"}", + "{\"type\":\"response.content_part.done\",\"sequence_number\":7,\"item_id\":\"msg_1\",\"output_index\":0,\"content_index\":0,\"part\":{\"type\":\"output_text\",\"text\":\"Hello" + + " world\",\"annotations\":[]}}", + "{\"type\":\"response.output_item.done\",\"sequence_number\":8,\"output_index\":0,\"item\":{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello" + + " world\",\"annotations\":[]}]}}", + "{\"type\":\"response.completed\",\"sequence_number\":9,\"response\":{\"id\":\"resp_1\",\"object\":\"response\",\"model\":\"gpt-4o-mini\",\"status\":\"completed\",\"output\":[{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello" + + " world\",\"annotations\":[]}]}],\"usage\":{\"input_tokens\":11,\"input_tokens_details\":{\"cached_tokens\":3},\"output_tokens\":5,\"output_tokens_details\":{\"reasoning_tokens\":2},\"total_tokens\":16}}}", + }; + } + + @Test + void reassemblesResponsesApiStreamFromTerminalSnapshot() { + var response = reconstruct(textResponsesStream()); + + // The Responses API reports output under "output", not "choices" — feeding these events to + // the chat-completions accumulator instead would leave an empty "choices" array behind. + assertFalse(response.has("choices"), "Responses output must not be shaped as choices"); + assertEquals("resp_1", response.get("id").asText()); + assertEquals("gpt-4o-mini", response.get("model").asText()); + assertEquals("completed", response.get("status").asText()); + + JsonNode output = response.get("output"); + assertNotNull(output, "output must be present"); + assertEquals(1, output.size()); + JsonNode message = output.get(0); + assertEquals("message", message.get("type").asText()); + assertEquals("Hello world", message.get("content").get(0).get("text").asText()); + + // usage passes through so the semconv tool can map the Responses field names. + JsonNode usage = response.get("usage"); + assertNotNull(usage, "usage must survive reconstruction"); + assertEquals(11, usage.get("input_tokens").asInt()); + assertEquals(5, usage.get("output_tokens").asInt()); + assertEquals(16, usage.get("total_tokens").asInt()); + assertEquals(2, usage.get("output_tokens_details").get("reasoning_tokens").asInt()); + } + + @Test + void laterResponseSnapshotReplacesRatherThanMergesWithEarlierOne() { + // response.created carries an empty output and a null usage; a naive field-wise merge would + // either keep that empty array or concatenate the two snapshots' scalars. + var response = reconstruct(textResponsesStream()); + + assertEquals(1, response.get("output").size(), "created's empty output must not survive"); + assertEquals("resp_1", response.get("id").asText(), "ids must not be concatenated"); + assertEquals( + "completed", + response.get("status").asText(), + "status must not be concatenated across snapshots"); + } + + @Test + void preservesServerSideToolCallsInOutputOrder() { + var response = + reconstruct( + "{\"type\":\"response.created\",\"response\":{\"id\":\"resp_2\",\"output\":[]}}", + "{\"type\":\"response.output_item.added\",\"output_index\":0,\"item\":{\"id\":\"ws_1\",\"type\":\"web_search_call\",\"status\":\"in_progress\"}}", + "{\"type\":\"response.web_search_call.searching\",\"item_id\":\"ws_1\",\"output_index\":0}", + "{\"type\":\"response.completed\",\"response\":{\"id\":\"resp_2\",\"output\":[{\"id\":\"ws_1\",\"type\":\"web_search_call\",\"status\":\"completed\",\"action\":{\"type\":\"search\",\"query\":\"ai" + + " news\"}},{\"id\":\"msg_2\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"News.\"}]}],\"usage\":{\"input_tokens\":300,\"output_tokens\":20,\"total_tokens\":320}}}"); + + JsonNode output = response.get("output"); + assertEquals(2, output.size()); + // Order matters: the tool call precedes the assistant message, and the server-side tool + // child spans are derived from these items. + assertEquals("web_search_call", output.get(0).get("type").asText()); + assertEquals("search", output.get(0).get("action").get("type").asText()); + assertEquals("message", output.get(1).get("type").asText()); + } + + @Test + void fallsBackToCompletedItemsWhenStreamEndsWithoutTerminalSnapshot() { + // A stream cut off after its items completed but before response.completed: the items are + // all we have, so report them rather than the empty output from response.created. + var response = + reconstruct( + "{\"type\":\"response.created\",\"response\":{\"id\":\"resp_3\",\"model\":\"gpt-4o-mini\",\"output\":[]}}", + "{\"type\":\"response.output_item.added\",\"output_index\":1,\"item\":{\"id\":\"msg_4\",\"type\":\"message\",\"content\":[]}}", + "{\"type\":\"response.output_item.done\",\"output_index\":0,\"item\":{\"id\":\"msg_3\",\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"text\":\"first\"}]}}", + "{\"type\":\"response.output_item.done\",\"output_index\":1,\"item\":{\"id\":\"msg_4\",\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"text\":\"second\"}]}}"); + + assertEquals("resp_3", response.get("id").asText()); + JsonNode output = response.get("output"); + assertEquals(2, output.size(), "both completed items should be reported"); + // Emitted in output_index order even though index 1 was announced before index 0 finished, + // and the .done item replaces the partial .added one. + assertEquals("first", output.get(0).get("content").get(0).get("text").asText()); + assertEquals("second", output.get(1).get("content").get(0).get("text").asText()); + } + + @Test + void stillReconstructsChatCompletionChunkStreams() { + var response = + reconstruct( + "{\"id\":\"cc\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-4o-mini\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Par\"}}]}", + "{\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"is\"},\"finish_reason\":\"stop\"}]}", + "{\"object\":\"chat.completion.chunk\",\"choices\":[],\"usage\":{\"prompt_tokens\":9,\"completion_tokens\":2,\"total_tokens\":11}}", + "[DONE]"); + + JsonNode choice = response.get("choices").get(0); + assertEquals("Paris", choice.get("message").get("content").asText()); + assertEquals("stop", choice.get("finish_reason").asText()); + assertEquals(11, response.get("usage").get("total_tokens").asInt()); + assertFalse(response.has("output"), "chat completions must not grow a Responses output"); + } + + @Test + void ignoresBlankNonJsonAndNonObjectPayloads() { + var acc = new SseStreamAccumulator(JSON); + acc.merge(null); + acc.merge(""); + acc.merge(" "); + acc.merge("[DONE]"); + acc.merge("not json"); + acc.merge("[1,2,3]"); + assertEquals("{\"choices\":[]}", acc.build(), "no events means nothing to reconstruct"); + } + + @Test + void trailingErrorEventDoesNotClobberResponsesReconstruction() { + // The Responses stream can end with a bare `error` event, which has no "response." type + // prefix; it must not be routed into the chat-completions accumulator. + var response = + reconstruct( + "{\"type\":\"response.completed\",\"response\":{\"id\":\"resp_4\",\"output\":[{\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"text\":\"hi\"}]}]}}", + "{\"type\":\"error\",\"code\":\"server_error\",\"message\":\"boom\"}"); + + assertEquals("resp_4", response.get("id").asText()); + assertEquals(1, response.get("output").size()); + assertFalse(response.has("choices"), "must not fall back to the chat-completions shape"); + } + + /** + * End-to-end check that a reconstructed Responses stream tags a span the way the + * instrumentation modules consume it: output from {@code output}, token metrics from the + * Responses {@code usage} field names, and a child tool span per server-side tool call. + */ + @Test + @SneakyThrows + void tagsSpanWithOutputMetricsAndToolChildSpansFromResponsesStream() { + var acc = new SseStreamAccumulator(JSON); + for (String chunk : textResponsesStream()) { + acc.merge(chunk); + } + acc.merge( + "{\"type\":\"response.completed\",\"response\":{\"id\":\"resp_1\",\"model\":\"gpt-4o-mini\",\"output\":[{\"id\":\"ws_1\",\"type\":\"web_search_call\",\"status\":\"completed\",\"action\":{\"type\":\"search\"}},{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello" + + " world\"}]}],\"usage\":{\"input_tokens\":11,\"output_tokens\":5,\"total_tokens\":16}}}"); + + var exporter = InMemorySpanExporter.create(); + try (var tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(exporter)) + .build()) { + var tracer = tracerProvider.get("test"); + var span = tracer.spanBuilder("llm").startSpan(); + InstrumentationSemConv.tagLLMSpanResponse( + tracer, + span, + InstrumentationSemConv.PROVIDER_NAME_OPENAI, + acc.build(), + 1_500_000_000L); + span.end(); + + List spans = exporter.getFinishedSpanItems(); + SpanData llmSpan = + spans.stream().filter(s -> s.getName().equals("llm")).findFirst().orElseThrow(); + + JsonNode output = + JSON.readTree( + llmSpan.getAttributes() + .get(AttributeKey.stringKey("braintrust.output_json"))); + assertEquals(2, output.size(), "span output should be the Responses output array"); + assertEquals("Hello world", output.get(1).get("content").get(0).get("text").asText()); + + JsonNode metrics = + JSON.readTree( + llmSpan.getAttributes() + .get(AttributeKey.stringKey("braintrust.metrics"))); + assertEquals(11, metrics.get("prompt_tokens").asInt()); + assertEquals(5, metrics.get("completion_tokens").asInt()); + assertEquals(16, metrics.get("tokens").asInt()); + assertEquals(1.5, metrics.get("time_to_first_token").asDouble(), 1e-9); + + assertTrue( + spans.stream().anyMatch(s -> s.getName().equals("web_search_call")), + "server-side tool calls in the streamed output should emit child tool spans"); + } + } +} diff --git a/btx/build.gradle b/btx/build.gradle index b72303de..e254c2d4 100644 --- a/btx/build.gradle +++ b/btx/build.gradle @@ -14,13 +14,21 @@ repositories { } // ---- Isolated client source sets --------------------------------------------- -// Spring AI 1.x and 2.x share Maven coordinates and package names, so they cannot -// coexist on the test classpath. The springai2 source set is compiled against 2.x -// and executed inside a child-first classloader at test runtime (see -// SpecClient.isolation() / IsolatedClientDelegate). Its runtime classpath is handed -// to the test JVM via the btx.springai2.classpath system property below. +// Some client libraries cannot share the test classpath: different major/minor versions +// of the same library reuse Maven coordinates and package names. Those clients get their +// own source set, compiled against the conflicting version and executed inside a +// child-first classloader at test runtime (see SpecClient.isolation() / +// IsolatedClientDelegate). Each source set's runtime classpath is handed to the test JVM +// via a btx..classpath system property below. +// +// springai2 - Spring AI 2.x (main test classpath has 1.x) +// langchain18 - langchain4j < 1.14.0, driving the langchain_1_8_0 instrumentation module +// (main test classpath has 1.19.0 + langchain_1_14_0). Keeps the older +// module under btx spec coverage even though the two langchain4j lines +// cannot coexist. sourceSets { springai2 + langchain18 } dependencies { @@ -35,6 +43,20 @@ dependencies { springai2Implementation "io.opentelemetry:opentelemetry-api:${rootProject.ext.otelVersion}" springai2Implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.1' springai2RuntimeOnly 'org.slf4j:slf4j-simple:2.0.17' + + // langchain4j < 1.14.0 (pre-Responses-API) against the langchain_1_8_0 module. Pinned to + // 1.9.1, the version btx exercised before langchain_1_14_0 existed; must stay < 1.14.0 or + // the module's classLoaderMatcher gate (and this source set's whole purpose) is moot. + langchain18CompileOnly sourceSets.test.output + langchain18CompileOnly(testFixtures(project(':test-harness'))) + + langchain18Implementation project(':braintrust-sdk:instrumentation:langchain_1_8_0') + langchain18Implementation 'dev.langchain4j:langchain4j:1.9.1' + langchain18Implementation 'dev.langchain4j:langchain4j-http-client:1.9.1' + langchain18Implementation 'dev.langchain4j:langchain4j-open-ai:1.9.1' + langchain18Implementation "io.opentelemetry:opentelemetry-api:${rootProject.ext.otelVersion}" + langchain18Implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.1' + langchain18RuntimeOnly 'org.slf4j:slf4j-simple:2.0.17' } dependencies { @@ -43,7 +65,7 @@ dependencies { testImplementation project(':braintrust-sdk:instrumentation:openai_2_15_0') testImplementation project(':braintrust-sdk:instrumentation:anthropic_2_2_0') testImplementation project(':braintrust-sdk:instrumentation:genai_1_18_0') - testImplementation project(':braintrust-sdk:instrumentation:langchain_1_8_0') + testImplementation project(':braintrust-sdk:instrumentation:langchain_1_14_0') testImplementation project(':braintrust-sdk:instrumentation:springai_1_0_0') testImplementation project(':braintrust-sdk:instrumentation:aws_bedrock_2_30_0') @@ -70,10 +92,11 @@ dependencies { testRuntimeOnly 'io.projectreactor.netty:reactor-netty-http:1.2.3' testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.3.1' - // LangChain4j - testImplementation 'dev.langchain4j:langchain4j:1.9.1' - testImplementation 'dev.langchain4j:langchain4j-http-client:1.9.1' - testImplementation 'dev.langchain4j:langchain4j-open-ai:1.9.1' + // LangChain4j — 1.14.0+ ships the OpenAI Responses API (OpenAiResponsesChatModel), which the + // langchain_1_14_0 module instruments. Pinned to a recent 1.x for the responses spec coverage. + testImplementation 'dev.langchain4j:langchain4j:1.19.0' + testImplementation 'dev.langchain4j:langchain4j-http-client:1.19.0' + testImplementation 'dev.langchain4j:langchain4j-open-ai:1.19.0' // OpenTelemetry testImplementation 'io.opentelemetry:opentelemetry-api:1.54.1' @@ -94,12 +117,14 @@ test { useJUnitPlatform() workingDir = rootProject.projectDir - // Hand the isolated springai2 client classpath to the test JVM (resolved lazily so - // configuration time doesn't force dependency resolution). - dependsOn tasks.named('springai2Classes') + // Hand the isolated client classpaths to the test JVM (resolved lazily so configuration + // time doesn't force dependency resolution). + dependsOn tasks.named('springai2Classes'), tasks.named('langchain18Classes') doFirst { systemProperty 'btx.springai2.classpath', (sourceSets.springai2.output.classesDirs + configurations.springai2RuntimeClasspath).asPath + systemProperty 'btx.langchain18.classpath', + (sourceSets.langchain18.output.classesDirs + configurations.langchain18RuntimeClasspath).asPath } testLogging { events "passed", "skipped", "failed" diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChainOpenAiSpecClient.java b/btx/src/langchain18/java/dev/braintrust/sdkspecimpl/langchain18/LangChain18OpenAiSpecClient.java similarity index 90% rename from btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChainOpenAiSpecClient.java rename to btx/src/langchain18/java/dev/braintrust/sdkspecimpl/langchain18/LangChain18OpenAiSpecClient.java index cf270346..fbcd0bc8 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChainOpenAiSpecClient.java +++ b/btx/src/langchain18/java/dev/braintrust/sdkspecimpl/langchain18/LangChain18OpenAiSpecClient.java @@ -1,7 +1,7 @@ -package dev.braintrust.sdkspecimpl.clients; +package dev.braintrust.sdkspecimpl.langchain18; import com.fasterxml.jackson.databind.ObjectMapper; -import dev.braintrust.instrumentation.langchain.BraintrustLangchain; +import dev.braintrust.instrumentation.langchain.v1_8_0.BraintrustLangchain; import dev.braintrust.sdkspecimpl.LlmSpanSpec; import dev.braintrust.sdkspecimpl.SpecClient; import dev.braintrust.sdkspecimpl.SpecClientContext; @@ -11,14 +11,23 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; -/** LangChain4j OpenAI client: chat completions (sync + streaming). */ -public final class LangChainOpenAiSpecClient implements SpecClient { +/** + * LangChain4j OpenAI client for langchain4j 1.8.0–1.13.x, exercising the {@code langchain_1_8_0} + * instrumentation module: chat completions (sync + streaming) only. + * + *

That module predates the OpenAI Responses API, so there is no {@code /v1/responses} coverage + * here — {@code LangChain114OpenAiSpecClient} owns that. Runs inside the isolated {@code + * langchain18} classloader (see {@code SpecClient.isolation()}) because langchain4j < 1.14.0 and + * >= 1.14.0 share Maven coordinates and package names and so cannot coexist on one classpath. Spec + * filtering lives on the registry-side {@code IsolatedClientStub}, not here. + */ +public final class LangChain18OpenAiSpecClient implements SpecClient { private static final ObjectMapper MAPPER = new ObjectMapper(); @Override public String id() { - return "langchain-openai"; + return "langchain1.8-openai"; } @Override @@ -26,14 +35,6 @@ public String provider() { return "openai"; } - @Override - public boolean supports(LlmSpanSpec spec) { - // Chat completions only: langchain4j-open-ai 1.9.x has no OpenAI Responses API - // (/v1/responses); its internal OpenAiClient exposes only chat/completion/embedding/ - // moderation/image. Responses specs are covered by the raw OpenAiSpecClient. - return "/v1/chat/completions".equals(spec.endpoint()); - } - @Override public void executeSpec(LlmSpanSpec spec, SpecClientContext ctx) throws Exception { for (Map request : spec.requests()) { diff --git a/btx/src/springai2/java/dev/braintrust/sdkspecimpl/springai2/SpringAi2OpenAiSpecClient.java b/btx/src/springai2/java/dev/braintrust/sdkspecimpl/springai2/SpringAi2OpenAiSpecClient.java index abd27d6f..8628b746 100644 --- a/btx/src/springai2/java/dev/braintrust/sdkspecimpl/springai2/SpringAi2OpenAiSpecClient.java +++ b/btx/src/springai2/java/dev/braintrust/sdkspecimpl/springai2/SpringAi2OpenAiSpecClient.java @@ -7,8 +7,10 @@ import dev.braintrust.sdkspecimpl.SpecClientContext; import java.util.ArrayList; import java.util.Base64; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.SystemMessage; @@ -58,12 +60,6 @@ private void executeChatCompletion(SpecClientContext ctx, Map re options.apiKey(ctx.openAiApiKey()); options.baseUrl(ctx.openAiBaseUrl()); options.model((String) request.get("model")); - if (request.get("temperature") instanceof Number temperature) { - options.temperature(temperature.doubleValue()); - } - if (request.get("max_tokens") instanceof Number maxTokens) { - options.maxTokens(maxTokens.intValue()); - } if (stream) { // Ask for the final usage chunk so token metrics are captured for streaming. options.streamUsage(true); @@ -71,6 +67,8 @@ private void executeChatCompletion(SpecClientContext ctx, Map re if (request.get("tools") instanceof List tools) { options.toolCallbacks(mapTools(tools)); } + // Everything else the spec sets rides through as-is; see STRUCTURAL_REQUEST_KEYS. + options.extraBody(passthroughBody(request)); var model = OpenAiChatModel.builder().options(options.build()).build(); BraintrustSpringAI.wrap(ctx.otel(), model); @@ -83,6 +81,45 @@ private void executeChatCompletion(SpecClientContext ctx, Map re } } + /** + * Spec request keys this client must express through Spring AI's own typed API rather than pass + * through as raw body properties, because the framework needs to understand them: {@code model} + * and {@code messages} drive the call itself, {@code stream} selects {@code stream()} vs {@code + * call()}, {@code stream_options} is owned by {@code streamUsage(true)}, and {@code tools} must + * become typed {@link ToolCallback}s for Spring AI to parse tool calls out of the response. + */ + private static final Set STRUCTURAL_REQUEST_KEYS = + Set.of("model", "messages", "stream", "stream_options", "tools"); + + /** + * Every other spec request field, forwarded verbatim as OpenAI request body properties. + * + *

Spring AI 1.x and the raw-SDK clients deserialize the spec's JSON straight into a request + * DTO ({@code OpenAiApi.ChatCompletionRequest}, {@code ChatCompletionCreateParams.Body}, ...), + * so a spec field they don't know about still reaches the wire. Spring AI 2.0 removed {@code + * OpenAiApi} entirely — it wraps the official {@code openai-java} client and assembles the + * request internally from {@link OpenAiChatOptions} — so there is no DTO to deserialize into + * and no seam to hand a raw request through. + * + *

Mapping fields one at a time is what made this client silently drop {@code n} (caught by + * {@code streaming_multiple_choices}) and {@code reasoning_effort} (not caught: o4-mini emits + * reasoning tokens either way, so the spec passed while the request was wrong). {@code + * extraBody} reaches openai-java's {@code additionalBodyProperties}, which is the closest thing + * to the passthrough the other clients get for free — so new spec fields now flow without a + * code change, and the maintenance burden is the small, stable exclusion list above rather than + * an open-ended inclusion list. + */ + private static Map passthroughBody(Map request) { + Map body = new LinkedHashMap<>(); + request.forEach( + (key, value) -> { + if (!STRUCTURAL_REQUEST_KEYS.contains(key) && value != null) { + body.put(key, value); + } + }); + return body; + } + /** * Maps raw OpenAI wire-format tool definitions to Spring {@link ToolCallback}s. The JSON schema * passes through untouched ({@link ToolDefinition#inputSchema()} is a raw JSON string). The diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java index 8b07ada5..d716a1f4 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; /** * Validates a list of brainstore spans against the {@code expected_brainstore_spans} structure from @@ -181,7 +182,14 @@ static void validateValue(Object actual, Object expected, String context) { } else { // scalar: null expected means "don't care" if (expected == null) return; - if (!valuesEqual(actual, expected)) { + // A message's text content is legitimately represented either as a plain string or, + // in the OpenAI Responses API, as a single text content part + // ([{type: input_text|output_text, text: "..."}]). When the spec asserts the string + // form but an SDK (e.g. langchain4j's OpenAiResponsesChatModel) emits the content-part + // form, collapse it so the two representations compare equal. + Object normalizedActual = + expected instanceof String ? collapseTextContentParts(actual) : actual; + if (!valuesEqual(normalizedActual, expected)) { fail( String.format( "%s: expected %s (%s) but got %s (%s)", @@ -194,6 +202,36 @@ static void validateValue(Object actual, Object expected, String context) { } } + /** + * The OpenAI Responses API text content-part types. Deliberately excludes the bare {@code text} + * type used by Anthropic/Bedrock content blocks (and by Chat Completions parts): collapsing + * those would silently weaken any spec that asserts a provider's block-shaped content against a + * plain string. + */ + private static final Set RESPONSES_TEXT_PART_TYPES = + Set.of("input_text", "output_text"); + + /** + * Collapses an OpenAI Responses text content-part list ({@code [{type: input_text|output_text, + * text: "..."}]}) into its concatenated text. Returns the input unchanged if it is not such a + * list. + */ + private static Object collapseTextContentParts(Object actual) { + if (!(actual instanceof List parts) || parts.isEmpty()) { + return actual; + } + StringBuilder text = new StringBuilder(); + for (Object part : parts) { + if (!(part instanceof Map map) + || !(map.get("text") instanceof String partText) + || !RESPONSES_TEXT_PART_TYPES.contains(map.get("type"))) { + return actual; + } + text.append(partText); + } + return text.toString(); + } + private static void assertMatcher(Object actual, SpecMatcher matcher, String context) { if (matcher instanceof SpecMatcher.FnMatcher) { assertFnMatcher(actual, (SpecMatcher.FnMatcher) matcher, context); diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidatorTest.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidatorTest.java new file mode 100644 index 00000000..4a45590e --- /dev/null +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidatorTest.java @@ -0,0 +1,85 @@ +package dev.braintrust.sdkspecimpl; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the spec-assertion semantics that are not themselves specified by a YAML spec — + * chiefly which actual/expected representation mismatches the validator is allowed to paper over. + */ +class SpanValidatorTest { + + private static void validate(Object actual, Object expected) { + SpanValidator.validateValue(actual, expected, "test"); + } + + @Test + void collapsesResponsesApiTextContentPartsToTheStringTheSpecAsserts() { + // langchain4j's OpenAiResponsesChatModel sends a user message's text as a content part, + // while the spec asserts the plain-string form. + assertDoesNotThrow( + () -> validate(List.of(Map.of("type", "input_text", "text", "hello")), "hello")); + assertDoesNotThrow( + () -> validate(List.of(Map.of("type", "output_text", "text", "hello")), "hello")); + // Multiple parts concatenate. + assertDoesNotThrow( + () -> + validate( + List.of( + Map.of("type", "output_text", "text", "hel"), + Map.of("type", "output_text", "text", "lo")), + "hello")); + } + + @Test + void collapsedContentStillHasToMatch() { + assertThrows( + AssertionError.class, + () -> validate(List.of(Map.of("type", "input_text", "text", "hello")), "goodbye")); + } + + @Test + void doesNotCollapseAnthropicStyleTextBlocks() { + // {type: text} is the Anthropic/Bedrock content-block shape (and the Chat Completions + // content-part shape). Collapsing it would let a spec that asserts a plain string pass + // against block-shaped content, silently weakening every such assertion. + assertThrows( + AssertionError.class, + () -> validate(List.of(Map.of("type", "text", "text", "hello")), "hello")); + } + + @Test + void leavesNonTextPartListsAlone() { + // A list carrying anything other than text parts is not a stringly-typed content value. + assertThrows( + AssertionError.class, + () -> + validate( + List.of( + Map.of("type", "input_text", "text", "describe this"), + Map.of("type", "input_image", "image_url", "data:...")), + "describe this")); + assertThrows(AssertionError.class, () -> validate(List.of("hello"), "hello")); + assertThrows(AssertionError.class, () -> validate(List.of(), "hello")); + } + + @Test + void doesNotCollapseWhenTheSpecAssertsTheContentPartForm() { + // The spec asserting the array form must still be compared structurally. + assertDoesNotThrow( + () -> + validate( + List.of(Map.of("type", "output_text", "text", "hello")), + List.of(Map.of("type", "output_text", "text", "hello")))); + assertThrows( + AssertionError.class, + () -> + validate( + List.of(Map.of("type", "output_text", "text", "hello")), + List.of(Map.of("type", "output_text", "text", "goodbye")))); + } +} diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java index 9768db00..1e7e5711 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java @@ -3,7 +3,7 @@ import dev.braintrust.sdkspecimpl.clients.AnthropicSpecClient; import dev.braintrust.sdkspecimpl.clients.BedrockSpecClient; import dev.braintrust.sdkspecimpl.clients.GoogleSpecClient; -import dev.braintrust.sdkspecimpl.clients.LangChainOpenAiSpecClient; +import dev.braintrust.sdkspecimpl.clients.LangChain114OpenAiSpecClient; import dev.braintrust.sdkspecimpl.clients.OpenAiSpecClient; import dev.braintrust.sdkspecimpl.clients.SpringAi1AnthropicSpecClient; import dev.braintrust.sdkspecimpl.clients.SpringAi1OpenAiSpecClient; @@ -52,12 +52,23 @@ public final class SpecClientRegistry { private static final List CLIENTS = Stream.of( (SpecClient) new OpenAiSpecClient(), - new LangChainOpenAiSpecClient(), + new LangChain114OpenAiSpecClient(), new SpringAi1OpenAiSpecClient(), new AnthropicSpecClient(), new SpringAi1AnthropicSpecClient(), new BedrockSpecClient(), new GoogleSpecClient(), + new IsolatedClientStub( + "langchain1.8-openai", + "openai", + // langchain_1_8_0 predates the OpenAI Responses API, so this + // client is chat-completions only; langchain1.14-openai covers + // /v1/responses. + Set.of("/v1/chat/completions"), + Set.of(), + new SpecClient.Isolation( + "btx.langchain18.classpath", + "dev.braintrust.sdkspecimpl.langchain18.LangChain18OpenAiSpecClient")), new IsolatedClientStub( "springai2-openai", "openai", diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChain114OpenAiSpecClient.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChain114OpenAiSpecClient.java new file mode 100644 index 00000000..da070889 --- /dev/null +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChain114OpenAiSpecClient.java @@ -0,0 +1,350 @@ +package dev.braintrust.sdkspecimpl.clients; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.braintrust.instrumentation.langchain.v1_14_0.BraintrustLangchain; +import dev.braintrust.sdkspecimpl.LlmSpanSpec; +import dev.braintrust.sdkspecimpl.SpecClient; +import dev.braintrust.sdkspecimpl.SpecClientContext; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesChatRequestParameters; +import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * LangChain4j OpenAI client for langchain4j >= 1.14.0, exercising the {@code langchain_1_14_0} + * instrumentation module. Covers both OpenAI endpoints the module instruments: chat completions + * (sync + streaming, via the internal {@code OpenAiClient}) and the Responses API (via {@link + * OpenAiResponsesChatModel}). + * + *

Clients are split by instrumentation module version, not by endpoint: {@code + * LangChain18OpenAiSpecClient} covers the older {@code langchain_1_8_0} module, which has no + * Responses API and is therefore chat-completions only. + */ +public final class LangChain114OpenAiSpecClient implements SpecClient { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Override + public String id() { + return "langchain1.14-openai"; + } + + @Override + public String provider() { + return "openai"; + } + + @Override + public boolean supports(LlmSpanSpec spec) { + return "/v1/chat/completions".equals(spec.endpoint()) + || "/v1/responses".equals(spec.endpoint()); + } + + @Override + public void executeSpec(LlmSpanSpec spec, SpecClientContext ctx) throws Exception { + if ("/v1/responses".equals(spec.endpoint())) { + executeResponses(spec, ctx); + return; + } + for (Map request : spec.requests()) { + executeLangChainChatCompletion(ctx, request); + } + } + + /** + * Jackson ObjectMapper for deserializing spec JSON into LangChain4j's internal {@link + * dev.langchain4j.model.openai.internal.chat.ChatCompletionRequest}. + * + *

LangChain4j's {@code Message} interface has no {@code @JsonTypeInfo}, so we register a + * custom deserializer that dispatches on the {@code role} field. + */ + private static final ObjectMapper LANGCHAIN_MAPPER = createLangChainMapper(); + + private static ObjectMapper createLangChainMapper() { + var module = new com.fasterxml.jackson.databind.module.SimpleModule(); + module.addDeserializer( + dev.langchain4j.model.openai.internal.chat.Message.class, + new com.fasterxml.jackson.databind.JsonDeserializer< + dev.langchain4j.model.openai.internal.chat.Message>() { + @Override + public dev.langchain4j.model.openai.internal.chat.Message deserialize( + com.fasterxml.jackson.core.JsonParser p, + com.fasterxml.jackson.databind.DeserializationContext ctx) + throws java.io.IOException { + com.fasterxml.jackson.databind.JsonNode node = p.getCodec().readTree(p); + String role = node.has("role") ? node.get("role").asText() : ""; + com.fasterxml.jackson.databind.ObjectMapper codec = + (com.fasterxml.jackson.databind.ObjectMapper) p.getCodec(); + return switch (role) { + case "system" -> + codec.treeToValue( + node, + dev.langchain4j.model.openai.internal.chat.SystemMessage + .class); + case "user" -> deserializeUserMessage(codec, node); + case "assistant" -> + codec.treeToValue( + node, + dev.langchain4j.model.openai.internal.chat + .AssistantMessage.class); + case "tool" -> + codec.treeToValue( + node, + dev.langchain4j.model.openai.internal.chat.ToolMessage + .class); + default -> + throw new java.io.IOException( + "Unsupported langchain message role: " + role); + }; + } + }); + return new ObjectMapper() + .disable( + com.fasterxml.jackson.databind.DeserializationFeature + .FAIL_ON_IGNORED_PROPERTIES) + .disable( + com.fasterxml.jackson.databind.DeserializationFeature + .FAIL_ON_UNKNOWN_PROPERTIES) + .registerModule(module); + } + + /** + * Deserialize a LangChain4j UserMessage from a JSON node, handling the polymorphic {@code + * content} field (string vs array of Content blocks) that the Builder can't dispatch + * automatically. + */ + private static dev.langchain4j.model.openai.internal.chat.UserMessage deserializeUserMessage( + ObjectMapper mapper, com.fasterxml.jackson.databind.JsonNode node) + throws com.fasterxml.jackson.core.JsonProcessingException { + var builder = dev.langchain4j.model.openai.internal.chat.UserMessage.builder(); + if (node.has("content")) { + var content = node.get("content"); + if (content.isTextual()) { + builder.content(content.asText()); + } else if (content.isArray()) { + List list = + mapper.convertValue( + content, + mapper.getTypeFactory() + .constructCollectionType( + List.class, + dev.langchain4j.model.openai.internal.chat.Content + .class)); + builder.content(list); + } + } + if (node.has("name")) { + builder.name(node.get("name").asText()); + } + return builder.build(); + } + + private void executeLangChainChatCompletion(SpecClientContext ctx, Map request) + throws Exception { + boolean streaming = Boolean.TRUE.equals(request.get("stream")); + + // Build a model just to get an instrumented client via BraintrustLangchain.wrap(). + dev.langchain4j.model.openai.internal.OpenAiClient langchainClient; + if (streaming) { + var modelBuilder = + OpenAiStreamingChatModel.builder() + .baseUrl(ctx.openAiBaseUrl()) + .apiKey(ctx.openAiApiKey()); + var model = BraintrustLangchain.wrap(ctx.otel(), modelBuilder); + langchainClient = getPrivateField(model, "client"); + } else { + var modelBuilder = + OpenAiChatModel.builder() + .baseUrl(ctx.openAiBaseUrl()) + .apiKey(ctx.openAiApiKey()); + OpenAiChatModel model = BraintrustLangchain.wrap(ctx.otel(), modelBuilder); + langchainClient = getPrivateField(model, "client"); + } + + // Deserialize the spec JSON directly into LangChain4j's ChatCompletionRequest. + // The LANGCHAIN_MAPPER has custom deserializers for Message (role-based dispatch) + // and UserMessage (polymorphic string/array content handling). + String json = MAPPER.writeValueAsString(request); + var chatRequest = + LANGCHAIN_MAPPER.readValue( + json, + dev.langchain4j.model.openai.internal.chat.ChatCompletionRequest.class); + + if (streaming) { + var done = new CompletableFuture(); + langchainClient + .chatCompletion(chatRequest) + .onPartialResponse(response -> {}) + .onComplete(() -> done.complete(null)) + .onError(done::completeExceptionally) + .execute(); + done.get(); + } else { + langchainClient.chatCompletion(chatRequest).execute(); + } + } + + @SuppressWarnings("unchecked") + private static T getPrivateField(Object obj, String fieldName) throws Exception { + var field = obj.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(obj); + } + + // ---- Responses API (/v1/responses) --------------------------------------------------------- + + /** Drives the Responses API through {@link OpenAiResponsesChatModel}. */ + private void executeResponses(LlmSpanSpec spec, SpecClientContext ctx) throws Exception { + // The builder requires a modelName even though each request overrides it via parameters; + // seed it from the first request. + String defaultModel = + spec.requests().isEmpty() + ? "o4-mini" + : String.valueOf(spec.requests().get(0).get("model")); + OpenAiResponsesChatModel model = + BraintrustLangchain.wrap( + ctx.otel(), + OpenAiResponsesChatModel.builder() + .baseUrl(ctx.openAiBaseUrl()) + .apiKey(ctx.openAiApiKey()) + .modelName(defaultModel) + .build()); + + // Running conversation accumulated across turns. Prior assistant turns (with their + // reasoning) live here as AiMessages and get re-serialized into each request's input. + List conversation = new ArrayList<>(); + for (Map request : spec.requests()) { + appendInputMessages(conversation, request.get("input")); + + ChatRequest chatRequest = + ChatRequest.builder() + .messages(conversation) + .parameters(buildParameters(request)) + .build(); + ChatResponse response = model.chat(chatRequest); + conversation.add(response.aiMessage()); + } + } + + /** Translates the spec request's reasoning and hosted-tool options into request parameters. */ + private static OpenAiResponsesChatRequestParameters buildParameters( + Map request) { + var params = OpenAiResponsesChatRequestParameters.builder(); + params.modelName((String) request.get("model")); + + if (request.get("reasoning") instanceof Map reasoning) { + if (reasoning.get("effort") instanceof String effort) { + params.reasoningEffort(effort); + } + if (reasoning.get("summary") instanceof String summary) { + params.reasoningSummary(summary); + } + // Ask for encrypted reasoning content so prior reasoning items can be replayed in the + // next turn's input. Only meaningful for reasoning models, so keep it scoped to + // requests that actually ask for reasoning. + params.include(List.of("reasoning.encrypted_content")); + } + + List> serverTools = hostedTools(request.get("tools")); + if (!serverTools.isEmpty()) { + // langchain4j has no typed API for the responses API's server-side tools, so the raw + // tool objects are passed through; OpenAiResponsesClient appends them to the request's + // `tools` array. NOTE: the spec's `tool_choice: {type: web_search_preview}` cannot be + // expressed — langchain4j types toolChoice as the ToolChoice enum (AUTO/REQUIRED/NONE), + // so a specific hosted tool cannot be forced. The search is therefore model-elected; + // it is reliable on gpt-4o (gpt-4o-mini accepts the tool but never searches), and the + // recorded cassette pins the exact response for replay. + params.serverTools(serverTools); + } + + // Stateless multi-turn: don't persist responses server-side. + params.store(false); + return params.build(); + } + + /** + * Extracts the spec request's hosted (server-side) tools — entries typed only by {@code type}, + * e.g. {@code {type: web_search_preview, search_context_size: low}} — from its {@code tools} + * list. Function tools, which carry a {@code name}, are not hosted tools and are excluded. + */ + @SuppressWarnings("unchecked") + private static List> hostedTools(Object tools) { + if (!(tools instanceof List items)) { + return List.of(); + } + List> hosted = new ArrayList<>(); + for (Object item : items) { + if (item instanceof Map map + && map.get("type") != null + && !map.containsKey("name")) { + hosted.add((Map) map); + } + } + return hosted; + } + + /** Appends this turn's role-tagged input items (user/system/assistant) to the conversation. */ + private static void appendInputMessages(List conversation, Object input) { + if (!(input instanceof List items)) { + return; + } + for (Object item : items) { + if (!(item instanceof Map map)) { + continue; + } + Object role = map.get("role"); + String text = inputText(map.get("content")); + if ("user".equals(role)) { + conversation.add(UserMessage.from(text)); + } else if ("system".equals(role)) { + conversation.add(SystemMessage.from(text)); + } else if ("assistant".equals(role)) { + conversation.add(AiMessage.from(text)); + } + } + } + + /** + * Reads a Responses API input item's {@code content} as text. The spec may express it either as + * a plain string or as the content-part array form ({@code [{type: input_text, text: "..."}]}); + * langchain4j's {@link ChatMessage} types only carry text, so parts are concatenated. Falls + * back to {@code toString()} only for shapes with no text parts, which would otherwise be + * dropped silently. + * + *

Package-private for unit testing: no current spec uses the content-part form for a {@code + * /v1/responses} input, so only a unit test exercises that branch. + */ + static String inputText(Object content) { + if (content == null) { + return ""; + } + if (content instanceof String s) { + return s; + } + if (content instanceof List parts) { + StringBuilder text = new StringBuilder(); + for (Object part : parts) { + if (part instanceof Map partMap + && partMap.get("text") instanceof String partText) { + text.append(partText); + } else if (part instanceof String partText) { + text.append(partText); + } + } + if (!text.isEmpty()) { + return text.toString(); + } + } + return content.toString(); + } +} diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChain114OpenAiSpecClientTest.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChain114OpenAiSpecClientTest.java new file mode 100644 index 00000000..8ccc9ffd --- /dev/null +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/LangChain114OpenAiSpecClientTest.java @@ -0,0 +1,46 @@ +package dev.braintrust.sdkspecimpl.clients; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** Unit tests for the spec-JSON translation this client does before calling langchain4j. */ +class LangChain114OpenAiSpecClientTest { + + @Test + void readsPlainStringContent() { + assertEquals("hello", LangChain114OpenAiSpecClient.inputText("hello")); + assertEquals("", LangChain114OpenAiSpecClient.inputText(null)); + } + + @Test + void readsResponsesContentPartArrays() { + // A /v1/responses input item may express its text as content parts; sending the Java map + // literal ("[{type=input_text, text=hello}]") instead would silently corrupt the request. + assertEquals( + "hello", + LangChain114OpenAiSpecClient.inputText( + List.of(Map.of("type", "input_text", "text", "hello")))); + assertEquals( + "hello there", + LangChain114OpenAiSpecClient.inputText( + List.of( + Map.of("type", "input_text", "text", "hello"), + Map.of("type", "input_text", "text", " there")))); + // Non-text parts (e.g. images) contribute nothing — langchain4j's ChatMessage types here + // only carry text. + assertEquals( + "describe this", + LangChain114OpenAiSpecClient.inputText( + List.of( + Map.of("type", "input_text", "text", "describe this"), + Map.of("type", "input_image", "image_url", "data:...")))); + } + + @Test + void readsBareStringParts() { + assertEquals("ab", LangChain114OpenAiSpecClient.inputText(List.of("a", "b"))); + } +} diff --git a/examples/langchain-ai-services/build.gradle b/examples/langchain-ai-services/build.gradle index b47a8bb9..ac734c9d 100644 --- a/examples/langchain-ai-services/build.gradle +++ b/examples/langchain-ai-services/build.gradle @@ -3,13 +3,13 @@ application { } dependencies { - implementation project(':braintrust-sdk:instrumentation:langchain_1_8_0') - implementation 'dev.langchain4j:langchain4j:1.9.1' - implementation 'dev.langchain4j:langchain4j-open-ai:1.9.1' + implementation project(':braintrust-sdk:instrumentation:langchain_1_14_0') + implementation 'dev.langchain4j:langchain4j:1.19.0' + implementation 'dev.langchain4j:langchain4j-open-ai:1.19.0' } run { - description = 'Run the LangChain4j AI Services example. NOTE: this requires OPENAI_API_KEY to be exported and will make a small call to openai, using your tokens' + description = 'Run the LangChain4j AI Services example: one agent on chat completions, one on the responses API. NOTE: this requires OPENAI_API_KEY to be exported and will make a few small calls to openai, using your tokens' debugOptions { enabled = true port = 5566 diff --git a/examples/langchain-ai-services/src/main/java/dev/braintrust/examples/LangchainAIServicesExample.java b/examples/langchain-ai-services/src/main/java/dev/braintrust/examples/LangchainAIServicesExample.java index e911b10b..5fe1abe4 100644 --- a/examples/langchain-ai-services/src/main/java/dev/braintrust/examples/LangchainAIServicesExample.java +++ b/examples/langchain-ai-services/src/main/java/dev/braintrust/examples/LangchainAIServicesExample.java @@ -1,29 +1,42 @@ package dev.braintrust.examples; import dev.braintrust.Braintrust; -import dev.braintrust.instrumentation.langchain.BraintrustLangchain; +import dev.braintrust.instrumentation.langchain.v1_14_0.BraintrustLangchain; import dev.langchain4j.agent.tool.Tool; import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiResponsesChatModel; import dev.langchain4j.service.AiServices; +import io.opentelemetry.api.OpenTelemetry; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +/** + * Two LangChain4j AI Services agents — one on the OpenAI chat completions API, one on the OpenAI + * Responses API — traced under a single root span so their subtrees can be compared side by side in + * Braintrust. {@link OpenAiResponsesChatModel} requires langchain4j >= 1.14.0. + * + *

The responses agent additionally enables openai's hosted web search tool, which the chat + * completions API cannot do: it runs server side, so it shows up as a {@code web_search_call} tool + * span that braintrust derives from the response payload, alongside the {@code @Tool} spans for + * functions this process executes itself. + */ public class LangchainAIServicesExample { + private static final String WEATHER_PROMPT = "is it hotter in Paris or New York right now?"; + private static final String WEB_SEARCH_PROMPT = + "Do a web search for news about Moderna. What are they up to lately?"; + public static void main(String[] args) throws Exception { + if (null == System.getenv("OPENAI_API_KEY")) { + System.err.println( + "\nWARNING envar OPENAI_API_KEY not found. This example will likely fail.\n"); + } var braintrust = Braintrust.get(); var openTelemetry = braintrust.openTelemetryCreate(); - Assistant assistant = - BraintrustLangchain.wrap( - openTelemetry, - AiServices.builder(Assistant.class) - .chatModel( - OpenAiChatModel.builder() - .apiKey(System.getenv("OPENAI_API_KEY")) - .modelName("gpt-4o-mini") - .temperature(0.0) - .build()) - .tools(new WeatherTools()) - .executeToolsConcurrently()); + var chatCompletionsAgent = chatCompletionsAgent(openTelemetry); + var responsesAgent = responsesAgent(openTelemetry); var rootSpan = openTelemetry @@ -31,11 +44,11 @@ public static void main(String[] args) throws Exception { .spanBuilder("langchain4j-ai-services-example") .startSpan(); try (var ignored = rootSpan.makeCurrent()) { - // response 1 should do a concurrent tool call - var response1 = assistant.chat("is it hotter in Paris or New York right now?"); - System.out.println("response1: " + response1); - var response2 = assistant.chat("what's the five day forecast for San Francisco?"); - System.out.println("response2: " + response2); + // should do a concurrent tool call + System.out.println( + "chat completions agent: " + chatCompletionsAgent.chatExample(WEATHER_PROMPT)); + // should do a server-side web search + System.out.println("responses agent: " + responsesAgent.chatExample(WEB_SEARCH_PROMPT)); } finally { rootSpan.end(); } @@ -49,23 +62,78 @@ public static void main(String[] args) throws Exception { "\n\n Example complete! View your data in Braintrust: %s\n".formatted(url)); } + /** An agent calling openai's chat completions API (/v1/chat/completions). */ + private static MyAssistant chatCompletionsAgent(OpenTelemetry openTelemetry) { + return BraintrustLangchain.wrap( + openTelemetry, + AiServices.builder(MyAssistant.class) + .chatModel( + OpenAiChatModel.builder() + .apiKey(System.getenv("OPENAI_API_KEY")) + .modelName("gpt-4o-mini") + .temperature(0.0) + .build()) + .tools(new WeatherTools()) + .executeToolsConcurrently()); + } + + /** An agent calling openai's responses API (/v1/responses), with hosted web search enabled. */ + private static MyAssistant responsesAgent(OpenTelemetry openTelemetry) { + return BraintrustLangchain.wrap( + openTelemetry, + AiServices.builder(MyAssistant.class) + .chatModel( + OpenAiResponsesChatModel.builder() + .apiKey(System.getenv("OPENAI_API_KEY")) + // NOTE: the hosted web search tool needs a model that + // supports it. gpt-4o-mini accepts the request but never + // searches: it answers that it can't browse, or falls back + // to the @Tool functions below. + .modelName("gpt-4o") + .temperature(0.0) + // langchain4j has no typed API for the responses API's + // server-side tools, so pass the raw tool object. It gets + // appended to the same request `tools` array as the @Tool + // functions, so the two kinds coexist. + .serverTools(List.of(Map.of("type", "web_search_preview"))) + .build()) + .tools(new WeatherTools()) + .executeToolsConcurrently()); + } + /** AI Service interface for the assistant */ - interface Assistant { - String chat(String userMessage); + interface MyAssistant { + String chatExample(String userMessage); } /** Example tool class with weather-related methods */ public static class WeatherTools { @Tool("Get current weather for a location") public String getWeather(String location) { + randomDelay(10, 200); return String.format("The weather in %s is sunny with 72°F temperature.", location); } @Tool("Get weather forecast for next N days") public String getForecast(String location, int days) { + randomDelay(10, 200); return String.format( "The %d-day forecast for %s: Mostly sunny with temperatures between 65-75°F.", days, location); } + + /** Fake some work so concurrent tool spans have a visible, staggered duration. */ + private static void randomDelay(int lowerBoundInclusiveMS, int upperBoundInclusiveMS) { + // ThreadLocalRandom because tools run concurrently (executeToolsConcurrently) + int millis = + ThreadLocalRandom.current() + .nextInt(lowerBoundInclusiveMS, upperBoundInclusiveMS + 1); + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("interrupted while faking work", e); + } + } } } diff --git a/examples/langchain-simple/build.gradle b/examples/langchain-simple/build.gradle index 70b214a2..f8dfeb32 100644 --- a/examples/langchain-simple/build.gradle +++ b/examples/langchain-simple/build.gradle @@ -3,9 +3,9 @@ application { } dependencies { - implementation project(':braintrust-sdk:instrumentation:langchain_1_8_0') - implementation 'dev.langchain4j:langchain4j:1.9.1' - implementation 'dev.langchain4j:langchain4j-open-ai:1.9.1' + implementation project(':braintrust-sdk:instrumentation:langchain_1_14_0') + implementation 'dev.langchain4j:langchain4j:1.19.0' + implementation 'dev.langchain4j:langchain4j-open-ai:1.19.0' } run { diff --git a/examples/langchain-simple/src/main/java/dev/braintrust/examples/LangchainSimpleExample.java b/examples/langchain-simple/src/main/java/dev/braintrust/examples/LangchainSimpleExample.java index 9d6fd2b2..e85c745b 100644 --- a/examples/langchain-simple/src/main/java/dev/braintrust/examples/LangchainSimpleExample.java +++ b/examples/langchain-simple/src/main/java/dev/braintrust/examples/LangchainSimpleExample.java @@ -1,7 +1,7 @@ package dev.braintrust.examples; import dev.braintrust.Braintrust; -import dev.braintrust.instrumentation.langchain.BraintrustLangchain; +import dev.braintrust.instrumentation.langchain.v1_14_0.BraintrustLangchain; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.openai.OpenAiChatModel; diff --git a/gradle.properties b/gradle.properties index a411a4f9..302abbc1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.daemon=true org.gradle.warning.mode=summary # braintrust-spec git ref (SHA or tag) used by btx tests -braintrustSpecRef=v0.0.11 +braintrustSpecRef=v0.0.12 # braintrust-openapi commit SHA used by braintrust-api braintrustOpenApiRef=4481f2e10e5859c930abc844483354101d10a57b diff --git a/settings.gradle b/settings.gradle index 7fadeb0b..f04daf27 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,6 +31,7 @@ include 'braintrust-sdk:instrumentation:openai_2_15_0' include 'braintrust-sdk:instrumentation:anthropic_2_2_0' include 'braintrust-sdk:instrumentation:genai_1_18_0' include 'braintrust-sdk:instrumentation:langchain_1_8_0' +include 'braintrust-sdk:instrumentation:langchain_1_14_0' include 'braintrust-sdk:instrumentation:springai_1_0_0' include 'braintrust-sdk:instrumentation:springai_2_0_0' include 'braintrust-sdk:instrumentation:aws_bedrock_2_30_0' diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-114a3fe592b5.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-114a3fe592b5.txt new file mode 100644 index 00000000..31148c00 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-114a3fe592b5.txt @@ -0,0 +1,20 @@ +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Ls4TT4cES"} + +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"OxKRrC"} + +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SrQOGuc2vB"} + +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7InC1G47J"} + +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"FmmR6z"} + +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"YUv2ILSruB"} + +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"7gknT"} + +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"WkkcJ"} + +data: {"id":"chatcmpl-EFN0SN12S7EYH251k69F9DNmhfFcB","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":4,"total_tokens":26,"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":"qqc2lRjrTlP"} + +data: [DONE] + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-1c381de89b6c.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-1c381de89b6c.txt new file mode 100644 index 00000000..6c90570c --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-1c381de89b6c.txt @@ -0,0 +1,20 @@ +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rEudEqXFN"} + +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LB0ILc"} + +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iPznrrsuz1"} + +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8dXecZ6Ws"} + +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"pRv1ll"} + +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"WsgOO0RJ0s"} + +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"qv4yN"} + +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"osG0y"} + +data: {"id":"chatcmpl-EFN0SBgvy3SD5O0PLMSmu4c0qMFrE","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":4,"total_tokens":26,"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":"zZ61gRPTVdm"} + +data: [DONE] + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-57dcb1d10cc1.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-57dcb1d10cc1.txt new file mode 100644 index 00000000..94cae36d --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-57dcb1d10cc1.txt @@ -0,0 +1,20 @@ +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CUhBPmUcy"} + +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"o6ORo6"} + +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"coIhJfci0I"} + +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"IFFE01toV"} + +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"eWbHsW"} + +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"o8Lf7r42u1"} + +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"lXejU"} + +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"FQD5F"} + +data: {"id":"chatcmpl-EFN08ov25rgg1Lg81GXC1P51lAy2K","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":4,"total_tokens":26,"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":"uKI2r1lKkLB"} + +data: [DONE] + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-60642de92108.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-60642de92108.txt new file mode 100644 index 00000000..d472f369 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-60642de92108.txt @@ -0,0 +1,88 @@ +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lCydCmc6I"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"Sure"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"IP1feBM"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"plT80lsjuL"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" Here"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xNIjv6"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" we"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"TLukkqeM"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KGqzq7xh"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":":\n\n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"RXfO8o"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"1"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"WRrQeNOeka"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NIj20BnW"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"naITW8u"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"2"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"VFr7QkW3dB"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1XoqhCrO"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1SVSz3N"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"3"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"nHCSZGtkD3"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"tUdUMyiH"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"JF29FzY"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"4"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SpP1R3NVYE"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2VX1cGh3"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NopZrSm"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"5"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8jjjwwxzDV"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"yx2bZJiC"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"oeVCUY3"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"6"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5VvxYpy7We"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"niIlIvGD"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7xgiTJt"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"7"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"hBTiSjZblm"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"bhdodBh3"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"YJRMZCq"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"8"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iurrgYIvAE"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7SucKZyV"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"K3L7XbW"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"9"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lV5WvT3oWQ"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"126oXJLM"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LUQIxHB"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"10"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xZcDd96zU"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"skdwbOse"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n\n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"hUaSl"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"Take"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ikEFM1J"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" your"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"wzT05N"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" time"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"mOXYXV"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zsfFf8buZV"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"bmJNG"} + +data: {"id":"chatcmpl-EFN09eeAPJWLHYFpAB2etyKVjYTy5","object":"chat.completion.chunk","created":1787332045,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[],"usage":{"prompt_tokens":25,"completion_tokens":40,"total_tokens":65,"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":"m7dHRxEUxJ"} + +data: [DONE] + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-640d051c9592.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-640d051c9592.txt new file mode 100644 index 00000000..3b3cfcd1 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-640d051c9592.txt @@ -0,0 +1,20 @@ +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"o33x7oNTd"} + +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Sw7kiT"} + +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"MjkZxZhG9A"} + +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Lcd73gjIz"} + +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CcU1uV"} + +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2fdTRh3xt6"} + +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"7FBRD"} + +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"HV4Ja"} + +data: {"id":"chatcmpl-EFN07ZZg74HMGNgDBgKfv4XMkbXgv","object":"chat.completion.chunk","created":1787332043,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":4,"total_tokens":26,"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":"NT9jonWnysp"} + +data: [DONE] + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7aec0dd2e530.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7aec0dd2e530.json new file mode 100644 index 00000000..0f21ce9b --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7aec0dd2e530.json @@ -0,0 +1,35 @@ +{ + "id": "chatcmpl-EFMziX8rUJjfS7sppQGlsIM5eyZJi", + "object": "chat.completion", + "created": 1787332018, + "model": "o4-mini-2025-04-16", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Each term is the product of two consecutive integers:\n\n2 = 1·2 \n6 = 2·3 \n12 = 3·4 \n20 = 4·5 \n30 = 5·6 \n\nSo if we start counting at n = 1, the nth term aₙ is\n\n aₙ = n(n + 1)\n\nEquivalently,\n\n aₙ = n² + n.", + "refusal": null, + "annotations": [] + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 41, + "completion_tokens": 1005, + "total_tokens": 1046, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 896, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": null +} diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7eca37e75602.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7eca37e75602.txt new file mode 100644 index 00000000..d0c2dcca --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7eca37e75602.txt @@ -0,0 +1,90 @@ +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"VLghwQgxe"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"Sure"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZIyiNiK"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"T74Wy0OvOw"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" Here"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1zWfBN"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" we"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XX2bAnTw"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ghJ9IMTN"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":":\n\n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6MQtCs"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"1"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"4G1g3sth8S"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"uRisCLBq"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5clz1Ny"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"2"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8Pl4jk5d8B"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3NkhHZV6"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Z4BPHop"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"3"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"HigagInjwb"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"giaTw99v"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"90ns2Tn"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"4"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vmSrNtOKIA"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"EBAbdm31"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ka1c8hH"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"5"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"DcERiAuWM0"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0IlnGb9R"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Jq8pwOG"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"6"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"YCfqp9BEEF"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fNGLDfRm"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Q7lkHwM"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"7"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"dLqZ7oALue"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"PpxLzFeb"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"cs4VCeJ"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"8"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2P55EFjGD7"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"DfUqM6Tt"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"uiT1cIk"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"9"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"n8Z7DdDNuh"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0vMvyKsE"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"im8IZEj"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"10"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"hXFp5wvgK"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"..."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"jDTcM2ki"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" \n\n"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zfAt7"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"There"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GMfUgf"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" you"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6OMCuyV"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" have"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Npp01H"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":" it"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ryLEAufU"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vy6XWdFIhe"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"nFHpC"} + +data: {"id":"chatcmpl-EFN08p4GLHdp4RWOMeFmsbynZaKqE","object":"chat.completion.chunk","created":1787332044,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_f344a168a1","choices":[],"usage":{"prompt_tokens":25,"completion_tokens":41,"total_tokens":66,"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":"Kcrf9PtCzQ"} + +data: [DONE] + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-84ab36b6457e.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-84ab36b6457e.txt new file mode 100644 index 00000000..1426844e --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-84ab36b6457e.txt @@ -0,0 +1,20 @@ +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null} + +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null} + +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null} + +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null} + +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null} + +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null} + +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null} + +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null} + +data: {"id":"chatcmpl-EFN0SoCuMHzyj2GFTHkgiuTTHpdhD","object":"chat.completion.chunk","created":1787332064,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":4,"total_tokens":26,"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}}} + +data: [DONE] + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-94957db25a8e.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-94957db25a8e.json new file mode 100644 index 00000000..a3af5778 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-94957db25a8e.json @@ -0,0 +1,35 @@ +{ + "id": "chatcmpl-EFMzddChBy4Hjq1tS7aMS9E9mP0MA", + "object": "chat.completion", + "created": 1787332013, + "model": "o4-mini-2025-04-16", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Each term is the product of two consecutive integers (sometimes called a “pronic” or “oblong” number). Equivalently you get the sequence by starting at 2 and adding successive even numbers 4, 6, 8, 10,…\n\nIn closed form, for n=1,2,3,…: \naₙ = n·(n+1) = n² + n\n\nCheck: \nn=1 → 1·2 = 2 \nn=2 → 2·3 = 6 \nn=3 → 3·4 = 12 \nn=4 → 4·5 = 20 \nn=5 → 5·6 = 30 …", + "refusal": null, + "annotations": [] + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 41, + "completion_tokens": 423, + "total_tokens": 464, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 256, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": null +} diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-b9ecbd8616dd.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-b9ecbd8616dd.json new file mode 100644 index 00000000..cb1d941e --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-b9ecbd8616dd.json @@ -0,0 +1,35 @@ +{ + "id": "chatcmpl-EFMzcfrMhRTAAc1D0ChoHiM3Mt1sN", + "object": "chat.completion", + "created": 1787332012, + "model": "o4-mini-2025-04-16", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "The terms are 2, 6, 12, 20, 30,… and their successive differences are 4, 6, 8, 10,… (i.e. the even numbers from 4 upward). Equivalently each term is the product of two consecutive integers:\n\nTerm n = n·(n + 1).\n\nCheck: \nn=1 → 1·2=2 \nn=2 → 2·3=6 \nn=3 → 3·4=12 \n… etc.", + "refusal": null, + "annotations": [] + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 41, + "completion_tokens": 579, + "total_tokens": 620, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 448, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": null +} diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-dafccec799b8.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-dafccec799b8.txt new file mode 100644 index 00000000..cc8bee09 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-dafccec799b8.txt @@ -0,0 +1,20 @@ +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KgIfEktYT"} + +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Uk5t1F"} + +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CiL8exTwim"} + +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"z5dxrxYmu"} + +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"nwxOD8"} + +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qmLLmvfqIy"} + +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"bisc4"} + +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"OcY8P"} + +data: {"id":"chatcmpl-EFN0TK1gSMVtA9lMeru7YqNRAQBZi","object":"chat.completion.chunk","created":1787332065,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_5259353f0d","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":4,"total_tokens":26,"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":"BpdgSWSKTSn"} + +data: [DONE] + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-1967c8c76482.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-1967c8c76482.txt new file mode 100644 index 00000000..5abe8e6c --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-1967c8c76482.txt @@ -0,0 +1,1128 @@ +event: response.created +data: {"type":"response.created","response":{"id":"resp_04fa46b17a39180a016a888fa20f2c87d0a86633063307bf6a","object":"response","created_at":1787334562,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":0.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + +event: response.in_progress +data: {"type":"response.in_progress","response":{"id":"resp_04fa46b17a39180a016a888fa20f2c87d0a86633063307bf6a","object":"response","created_at":1787334562,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":0.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + +event: response.output_item.added +data: {"type":"response.output_item.added","item":{"id":"ws_04fa46b17a39180a016a888fa2a81087d0a961c6a9b9866bb8","type":"web_search_call","status":"in_progress","action":{"type":"search"}},"output_index":0,"sequence_number":2} + +event: response.web_search_call.in_progress +data: {"type":"response.web_search_call.in_progress","item_id":"ws_04fa46b17a39180a016a888fa2a81087d0a961c6a9b9866bb8","output_index":0,"sequence_number":3} + +event: response.web_search_call.searching +data: {"type":"response.web_search_call.searching","item_id":"ws_04fa46b17a39180a016a888fa2a81087d0a961c6a9b9866bb8","output_index":0,"sequence_number":4} + +event: response.web_search_call.completed +data: {"type":"response.web_search_call.completed","item_id":"ws_04fa46b17a39180a016a888fa2a81087d0a961c6a9b9866bb8","output_index":0,"sequence_number":5} + +event: response.output_item.done +data: {"type":"response.output_item.done","item":{"id":"ws_04fa46b17a39180a016a888fa2a81087d0a961c6a9b9866bb8","type":"web_search_call","status":"completed","action":{"type":"search","queries":["Moderna news October 2023"],"query":"Moderna news October 2023"}},"output_index":0,"sequence_number":6} + +event: response.output_item.added +data: {"type":"response.output_item.added","item":{"id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":1,"sequence_number":7} + +event: response.content_part.added +data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":8} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"Mod","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Dc3pf1MOJnzLS","output_index":1,"sequence_number":9} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"erna","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"zY8GyaogrFC0","output_index":1,"sequence_number":10} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" has","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"fcg7ET10k7K7","output_index":1,"sequence_number":11} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" recently","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"LuLgVF5","output_index":1,"sequence_number":12} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" achieved","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"wPLKSIa","output_index":1,"sequence_number":13} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" significant","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"DcJD","output_index":1,"sequence_number":14} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" milestones","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"nBUNW","output_index":1,"sequence_number":15} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"c9bPDKJqoxViK","output_index":1,"sequence_number":16} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" both","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ZLKnLBkUA1r","output_index":1,"sequence_number":17} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" oncology","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"judqKXA","output_index":1,"sequence_number":18} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"3vrXbTXj15T1","output_index":1,"sequence_number":19} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" infectious","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"5yoBs","output_index":1,"sequence_number":20} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" disease","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"FgUz1cdn","output_index":1,"sequence_number":21} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" prevention","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Ea60q","output_index":1,"sequence_number":22} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":":","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"RRtMRAJJ1my52oi","output_index":1,"sequence_number":23} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"daGLt9kQE0wfSrQ","output_index":1,"sequence_number":24} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n**","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ugr1tVvCs36rr","output_index":1,"sequence_number":25} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"Break","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"PA1xaxoehXn","output_index":1,"sequence_number":26} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"through","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"DiQf2PwL0","output_index":1,"sequence_number":27} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"PxIAPIDS72mZ0","output_index":1,"sequence_number":28} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" m","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"KtzUOSPSphhoNC","output_index":1,"sequence_number":29} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"RNA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"FRJMc4Vpcuhsn","output_index":1,"sequence_number":30} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-Based","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"waxNzRiNsM","output_index":1,"sequence_number":31} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Cancer","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"s0VPL7eWQ","output_index":1,"sequence_number":32} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Treatment","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"tHQXso","output_index":1,"sequence_number":33} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"**\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"8nBR7Vx18PdOi","output_index":1,"sequence_number":34} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"W1vH3mLXMqeLX9D","output_index":1,"sequence_number":35} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"Mod","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"7gyQHOcbGvJah","output_index":1,"sequence_number":36} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"erna","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"IJaYRkkkt48l","output_index":1,"sequence_number":37} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Mylj3PrIiMu6tWe","output_index":1,"sequence_number":38} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"6HH4yAiIrAAJc","output_index":1,"sequence_number":39} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" collaboration","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Lm","output_index":1,"sequence_number":40} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" with","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"INjiVurJ6p2","output_index":1,"sequence_number":41} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Mer","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"49ZcfbzIe6Ld","output_index":1,"sequence_number":42} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"ck","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"kH62csueNpbmeT","output_index":1,"sequence_number":43} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"jVRFyypj7jHbdtc","output_index":1,"sequence_number":44} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" announced","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"V5QAs5","output_index":1,"sequence_number":45} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" promising","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"gPG0mF","output_index":1,"sequence_number":46} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" results","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"HSqGvF1w","output_index":1,"sequence_number":47} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" from","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"uaqOJzdajvO","output_index":1,"sequence_number":48} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"rUpzXnOw4foojj","output_index":1,"sequence_number":49} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" late","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"cSjQpJ262ur","output_index":1,"sequence_number":50} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-stage","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ePZGVQLYm7","output_index":1,"sequence_number":51} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" clinical","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"GlhnV0h","output_index":1,"sequence_number":52} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" trial","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"n7m9sF7lEx","output_index":1,"sequence_number":53} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"u8REquAn1QKd7","output_index":1,"sequence_number":54} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" their","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"yg4A9D8F8U","output_index":1,"sequence_number":55} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" personalized","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"F5g","output_index":1,"sequence_number":56} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" m","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Tg3iewTMrxDf4Z","output_index":1,"sequence_number":57} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"RNA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"pZcRHtch4qIKm","output_index":1,"sequence_number":58} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-based","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"57XyA2dgkC","output_index":1,"sequence_number":59} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" cancer","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"GJsmIi8c4","output_index":1,"sequence_number":60} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" vaccine","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"L1MoVXqn","output_index":1,"sequence_number":61} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"jn5GM0xWmEWNFf3","output_index":1,"sequence_number":62} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" int","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"w1jxKdR8Dthd","output_index":1,"sequence_number":63} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"is","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"hpkoRvz6MBypuC","output_index":1,"sequence_number":64} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"mer","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"lu6PpgeNYe5f6","output_index":1,"sequence_number":65} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"an","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"uSFv5mJHdkNAF0","output_index":1,"sequence_number":66} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"9OGeoPHgd71Fu2Q","output_index":1,"sequence_number":67} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"s6cu3jSjqRKaIAe","output_index":1,"sequence_number":68} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"This","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"GJPbyyZAMLBW","output_index":1,"sequence_number":69} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" vaccine","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"C7xXY4ex","output_index":1,"sequence_number":70} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"6pSvdPh3GGMs0Tx","output_index":1,"sequence_number":71} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" tailored","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"GMeIxhd","output_index":1,"sequence_number":72} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"2QDVOF7zyFtZg","output_index":1,"sequence_number":73} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" individual","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"pfCju","output_index":1,"sequence_number":74} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" patients","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Qd121DK","output_index":1,"sequence_number":75} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" based","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ZarQ1fbyr6","output_index":1,"sequence_number":76} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" on","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"kY87a5vR3AdvB","output_index":1,"sequence_number":77} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" specific","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"3ZNILhp","output_index":1,"sequence_number":78} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" tumor","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"V8SsAvWObI","output_index":1,"sequence_number":79} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" mutations","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"rN64Po","output_index":1,"sequence_number":80} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"F9aswXPEgR9J9MV","output_index":1,"sequence_number":81} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" was","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"acvAZp0jwfo0","output_index":1,"sequence_number":82} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" tested","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"82ZHpbyCO","output_index":1,"sequence_number":83} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" alongside","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"7ECqKM","output_index":1,"sequence_number":84} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Mer","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"m7535TMaC2ue","output_index":1,"sequence_number":85} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"ck","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"zzjOtJaZsVw61N","output_index":1,"sequence_number":86} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"'s","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"EeMXuOmDUNvYoB","output_index":1,"sequence_number":87} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" immun","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"UzimI0CtWC","output_index":1,"sequence_number":88} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"otherapy","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"UAr1JP3v","output_index":1,"sequence_number":89} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" drug","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"PdAh2vbVkgu","output_index":1,"sequence_number":90} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"iQSoeOW1T2RIQ0o","output_index":1,"sequence_number":91} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Key","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"sRbgCh6Ks377","output_index":1,"sequence_number":92} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"tr","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"tyanbizDfdmfIq","output_index":1,"sequence_number":93} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"uda","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"wJJr1fk7vfNm3","output_index":1,"sequence_number":94} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"fpt5SH26R4pgo0T","output_index":1,"sequence_number":95} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"RnZODU7iq1nWM","output_index":1,"sequence_number":96} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" over","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"SQLMnWciWCT","output_index":1,"sequence_number":97} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"nmdaca9CHxKzYPo","output_index":1,"sequence_number":98} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"1","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"eHhAWE8vxZye4Lu","output_index":1,"sequence_number":99} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"bdjSYKZ6ouKi7L9","output_index":1,"sequence_number":100} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"100","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"s2oge40kgvDfk","output_index":1,"sequence_number":101} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" high","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"S7UPsaeU8d5","output_index":1,"sequence_number":102} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-risk","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"xamea6fVJ3J","output_index":1,"sequence_number":103} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" melanoma","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"IYZAAZf","output_index":1,"sequence_number":104} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" patients","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"eLGf1Rh","output_index":1,"sequence_number":105} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"msrMjXxjmgB1kqO","output_index":1,"sequence_number":106} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"pWikIAhc9plfsN7","output_index":1,"sequence_number":107} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"BEQ6767YG4Fyo","output_index":1,"sequence_number":108} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" combination","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"QhB8","output_index":1,"sequence_number":109} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" therapy","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Sexd3jru","output_index":1,"sequence_number":110} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" demonstrated","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"on7","output_index":1,"sequence_number":111} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"FuTgJ7Qt9o9MH0","output_index":1,"sequence_number":112} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" statistically","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Z1","output_index":1,"sequence_number":113} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" significant","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"mAuU","output_index":1,"sequence_number":114} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" reduction","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"azE9Km","output_index":1,"sequence_number":115} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"6oVtHy4TcRPQH","output_index":1,"sequence_number":116} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" cancer","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"OKLW5k8ja","output_index":1,"sequence_number":117} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" recurrence","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"XTwKq","output_index":1,"sequence_number":118} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"BsLZ51xKj42h","output_index":1,"sequence_number":119} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" metast","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"LfZuPeelq","output_index":1,"sequence_number":120} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"asis","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"7wCHvdQ5Nean","output_index":1,"sequence_number":121} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" compared","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"0ctiADT","output_index":1,"sequence_number":122} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"YrmSA6Kfs6tPV","output_index":1,"sequence_number":123} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Key","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"LSov9CSgH7Ec","output_index":1,"sequence_number":124} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"tr","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"cYQDRLNP3n2WRJ","output_index":1,"sequence_number":125} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"uda","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"9PthSVU229N6x","output_index":1,"sequence_number":126} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" alone","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"0Bpp9zIes6","output_index":1,"sequence_number":127} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"PEyN1GdWHV93T87","output_index":1,"sequence_number":128} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"8KFYOuzuq1PDrlN","output_index":1,"sequence_number":129} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"These","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"uO4ykwvVUxK","output_index":1,"sequence_number":130} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" findings","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"l71eGJS","output_index":1,"sequence_number":131} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" mark","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"pOuLlA6pUve","output_index":1,"sequence_number":132} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"oJ01zZfIwzd56Q","output_index":1,"sequence_number":133} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" significant","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Atk7","output_index":1,"sequence_number":134} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" advancement","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"JLVk","output_index":1,"sequence_number":135} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Nnse6NY3rLmnN","output_index":1,"sequence_number":136} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" personalized","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Rpp","output_index":1,"sequence_number":137} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" cancer","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"OzOapAevZ","output_index":1,"sequence_number":138} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" therapy","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"iPuEuW2q","output_index":1,"sequence_number":139} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ajP4W8xtK3R4rPI","output_index":1,"sequence_number":140} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"oth4LLmXHdV2QgR","output_index":1,"sequence_number":141} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"([apnews.com](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai))","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"HNc","output_index":1,"sequence_number":142} + +event: response.output_text.annotation.added +data: {"type":"response.output_text.annotation.added","annotation":{"type":"url_citation","end_index":808,"start_index":715,"title":"Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test","url":"https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai"},"annotation_index":0,"content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","output_index":1,"sequence_number":143} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"VVtJMrukJEyEKwR","output_index":1,"sequence_number":144} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n**","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"7ZyETMy5pnz7T","output_index":1,"sequence_number":145} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"FDA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"mYtXoYLr20r2P","output_index":1,"sequence_number":146} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Approval","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"xWR1d2Q","output_index":1,"sequence_number":147} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Lpkof6txumNwg","output_index":1,"sequence_number":148} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" m","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"cgNqREO7iVYHrp","output_index":1,"sequence_number":149} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"RNA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"t8RYVroHQzRAv","output_index":1,"sequence_number":150} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Flu","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"9WmBY3iVJ6Od","output_index":1,"sequence_number":151} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Vaccine","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"cWVhi07s","output_index":1,"sequence_number":152} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"**\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"HScv8QilU6Y4I","output_index":1,"sequence_number":153} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"5KY9zDUsD2x9Osw","output_index":1,"sequence_number":154} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"LbHNZ0vIQOdKk","output_index":1,"sequence_number":155} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" U","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"d05b2ZKJ62vTiP","output_index":1,"sequence_number":156} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".S","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"21zerp3Uur6G4P","output_index":1,"sequence_number":157} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"fZtNSrgc935ZeWh","output_index":1,"sequence_number":158} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Food","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"WcyJLVuM7PN","output_index":1,"sequence_number":159} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ofeV7g9oeGWO","output_index":1,"sequence_number":160} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Drug","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"lcAbS7w4bm4","output_index":1,"sequence_number":161} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Administration","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"8","output_index":1,"sequence_number":162} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" (","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ocfH1zkOCVOoEL","output_index":1,"sequence_number":163} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"FDA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"IYLmcNslKQEcy","output_index":1,"sequence_number":164} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":")","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ss483HqGpCvpFAT","output_index":1,"sequence_number":165} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" approved","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"kK4ehZ3","output_index":1,"sequence_number":166} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Moderna","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"FksUpX3u","output_index":1,"sequence_number":167} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"'s","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"NpixtyWD4k0xfI","output_index":1,"sequence_number":168} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" m","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"TWuCrFrjmbnPLt","output_index":1,"sequence_number":169} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"RNA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"rjULAfhTX23f0","output_index":1,"sequence_number":170} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-based","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"tm9UJE7rVp","output_index":1,"sequence_number":171} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" seasonal","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"kLuZuli","output_index":1,"sequence_number":172} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" flu","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"8ENhef7wmYc2","output_index":1,"sequence_number":173} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" vaccine","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"kjoEkqej","output_index":1,"sequence_number":174} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"xdT80UiCFiaI7CT","output_index":1,"sequence_number":175} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" m","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"vaX1qRIILOE8yV","output_index":1,"sequence_number":176} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"Fl","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"TdhMfHcCIJro9u","output_index":1,"sequence_number":177} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"us","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"GVTNt1SErYrt7F","output_index":1,"sequence_number":178} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"iva","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"klJYYDjl8AeYy","output_index":1,"sequence_number":179} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"beKy5XhvOlI9L1u","output_index":1,"sequence_number":180} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" for","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"x50Iyva6mR03","output_index":1,"sequence_number":181} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" adults","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"wg4WUVd4C","output_index":1,"sequence_number":182} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" aged","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"RW8myBFr8J1","output_index":1,"sequence_number":183} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"7SKZJep9B3B5hE7","output_index":1,"sequence_number":184} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"50","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"aj0kC76PT29Srs","output_index":1,"sequence_number":185} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"eS3Ezl7psZ0Q","output_index":1,"sequence_number":186} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" older","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"vLm4KSd5Ah","output_index":1,"sequence_number":187} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"rBNSQ3kMT0RTp64","output_index":1,"sequence_number":188} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ny11aY8746QpnK0","output_index":1,"sequence_number":189} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"This","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"D74Rl9pNPPlA","output_index":1,"sequence_number":190} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" approval","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"NZS3QHv","output_index":1,"sequence_number":191} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" follows","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ESkaw8CW","output_index":1,"sequence_number":192} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ICgTm8FEU7MRdZ","output_index":1,"sequence_number":193} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" previous","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"567oibs","output_index":1,"sequence_number":194} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" refusal","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"teU9iWAR","output_index":1,"sequence_number":195} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" by","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"CnxmZ2zITPKb4","output_index":1,"sequence_number":196} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" the","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ZPd3z6OP5Xe8","output_index":1,"sequence_number":197} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" FDA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Y4FWTz68G8Ts","output_index":1,"sequence_number":198} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" due","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"UcMCcrvRzfUT","output_index":1,"sequence_number":199} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"zeWA3LGlXGCW6","output_index":1,"sequence_number":200} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" concerns","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Xk4Sh5Q","output_index":1,"sequence_number":201} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" over","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"E0DOIfTtE3P","output_index":1,"sequence_number":202} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" clinical","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"LMrbdrP","output_index":1,"sequence_number":203} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" trial","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Su9E6gAeNY","output_index":1,"sequence_number":204} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" methodologies","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"eC","output_index":1,"sequence_number":205} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"aIotS9UaPcgYN3c","output_index":1,"sequence_number":206} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"GgLsLB2OzgQmwOm","output_index":1,"sequence_number":207} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"p3j4hHgeklcfL","output_index":1,"sequence_number":208} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" vaccine","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"VeKjCFV5","output_index":1,"sequence_number":209} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" demonstrated","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"6oL","output_index":1,"sequence_number":210} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"bg3kzKnR23drBG","output_index":1,"sequence_number":211} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"4d1vwaOVQMDyQ6J","output_index":1,"sequence_number":212} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"27","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"OTBwVX2bDm7bOn","output_index":1,"sequence_number":213} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"%","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"zb5goCjmPET8gGx","output_index":1,"sequence_number":214} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" reduction","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"VfWYUy","output_index":1,"sequence_number":215} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ST3Tmjear5IFr","output_index":1,"sequence_number":216} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" flu","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"jLSVmwxu7xnv","output_index":1,"sequence_number":217} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" cases","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"bfY3kBKAw3","output_index":1,"sequence_number":218} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" compared","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"AWP3tQ8","output_index":1,"sequence_number":219} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"BmVR5qGwznASj","output_index":1,"sequence_number":220} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" standard","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"CopsqgU","output_index":1,"sequence_number":221} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" vaccines","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"r3YBEGk","output_index":1,"sequence_number":222} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"dIMvtK243RyME","output_index":1,"sequence_number":223} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"yijvlLlOIh8pEZ","output_index":1,"sequence_number":224} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" study","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"pqI8aWUq3T","output_index":1,"sequence_number":225} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" involving","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"gmTB9W","output_index":1,"sequence_number":226} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"IQ3wE3DggFUWykG","output_index":1,"sequence_number":227} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"40","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"zYSv8TrudgBDc8","output_index":1,"sequence_number":228} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"YuqPT6chfirwj0L","output_index":1,"sequence_number":229} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"000","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"UfOkESdfcTRr8","output_index":1,"sequence_number":230} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" participants","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"lsr","output_index":1,"sequence_number":231} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"5PWgMeR3PjCqc2Z","output_index":1,"sequence_number":232} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"A3FeoJ00LzdR5Zy","output_index":1,"sequence_number":233} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"This","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"8EFFNwSh3Icf","output_index":1,"sequence_number":234} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" approval","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"QAYNxBd","output_index":1,"sequence_number":235} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" signifies","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"B0iSPj","output_index":1,"sequence_number":236} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"97k3rxFER52FyR","output_index":1,"sequence_number":237} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" notable","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Tv1sbfGm","output_index":1,"sequence_number":238} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" development","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"9tz2","output_index":1,"sequence_number":239} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"y5r59O5S4k45A","output_index":1,"sequence_number":240} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" influenza","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"zbVOtt","output_index":1,"sequence_number":241} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" prevention","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"6bVxd","output_index":1,"sequence_number":242} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"mlWDaqgqrxYk","output_index":1,"sequence_number":243} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" the","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"D8Uo6o1VrwXR","output_index":1,"sequence_number":244} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" broader","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Trz0Endz","output_index":1,"sequence_number":245} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" application","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"oI2o","output_index":1,"sequence_number":246} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"AWYr6FQRZcSvb","output_index":1,"sequence_number":247} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" m","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"7xReKwPgPdEjv4","output_index":1,"sequence_number":248} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"RNA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"sxll75JN0cXlJ","output_index":1,"sequence_number":249} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" vaccine","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"JOduKZub","output_index":1,"sequence_number":250} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" technology","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"YeNHU","output_index":1,"sequence_number":251} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"W2jkyyDEWYpHZlB","output_index":1,"sequence_number":252} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"RisZnhzmVTYTR1n","output_index":1,"sequence_number":253} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"([apnews.com](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai))","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"aDJ","output_index":1,"sequence_number":254} + +event: response.output_text.annotation.added +data: {"type":"response.output_text.annotation.added","annotation":{"type":"url_citation","end_index":1432,"start_index":1339,"title":"New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine","url":"https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai"},"annotation_index":1,"content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","output_index":1,"sequence_number":255} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"jj3yOw1WdvXxvXu","output_index":1,"sequence_number":256} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n**","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"5fXYNgY2sVc3V","output_index":1,"sequence_number":257} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"Financial","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"2XkDa23","output_index":1,"sequence_number":258} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Performance","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"b9R9","output_index":1,"sequence_number":259} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"**\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"0noM3hwfJ0ba8","output_index":1,"sequence_number":260} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"d6zVsXtSxj8SzuK","output_index":1,"sequence_number":261} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"In","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"NlM0bcEYssKrth","output_index":1,"sequence_number":262} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" the","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"UR5V6Iha5jxh","output_index":1,"sequence_number":263} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" second","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"4o7B7X6Ws","output_index":1,"sequence_number":264} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" quarter","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"AW37SgQV","output_index":1,"sequence_number":265} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"AyagtqBJxYKOO","output_index":1,"sequence_number":266} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"8EUj2bRZ6NkMP4Y","output_index":1,"sequence_number":267} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"202","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"sND1nfGV1JmrL","output_index":1,"sequence_number":268} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"6","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"PoyyqBAwfrrlYGc","output_index":1,"sequence_number":269} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"iebLtjHxZw6lCI3","output_index":1,"sequence_number":270} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Moderna","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"LhCJ973B","output_index":1,"sequence_number":271} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" reported","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"6JKhT9x","output_index":1,"sequence_number":272} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" total","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"DScddC91NB","output_index":1,"sequence_number":273} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" revenue","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"K58wWqJA","output_index":1,"sequence_number":274} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"VaD3moacLBfgE","output_index":1,"sequence_number":275} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" $","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"BC8Hy1SaqeoBCS","output_index":1,"sequence_number":276} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"145","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"z4pzNBjW6R6fu","output_index":1,"sequence_number":277} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" million","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"l1SFpzlq","output_index":1,"sequence_number":278} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"xt7q7pvEJFa7","output_index":1,"sequence_number":279} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"C9AW9YfWQtA7Mb","output_index":1,"sequence_number":280} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" GA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"XPoQV9MkMMGpH","output_index":1,"sequence_number":281} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"AP","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Zw7R3OPG8LV21l","output_index":1,"sequence_number":282} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" net","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"L0NMDOOyKQgr","output_index":1,"sequence_number":283} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" loss","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"htUK7p399IP","output_index":1,"sequence_number":284} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"dxRxFm34hpP2Z","output_index":1,"sequence_number":285} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" $","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ORMO70A85HGehn","output_index":1,"sequence_number":286} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"782","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"EGnPc8JA0diLA","output_index":1,"sequence_number":287} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" million","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"yB1C4TLM","output_index":1,"sequence_number":288} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"1mJOAa8eso2PgCq","output_index":1,"sequence_number":289} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"2FOKmwraHYcEqa7","output_index":1,"sequence_number":290} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"Despite","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"sUSKR6XI1","output_index":1,"sequence_number":291} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" these","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"jBjCgpwYLn","output_index":1,"sequence_number":292} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" figures","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"HRSVozOV","output_index":1,"sequence_number":293} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"L9Wl5BI8L8xoel2","output_index":1,"sequence_number":294} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" the","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"QmnyoV1ITGnh","output_index":1,"sequence_number":295} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" company","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"vGDgLxhy","output_index":1,"sequence_number":296} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" reiterated","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"x4N8m","output_index":1,"sequence_number":297} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" its","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Z36CdE1AnrHU","output_index":1,"sequence_number":298} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" plan","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"5L91sJp2P4o","output_index":1,"sequence_number":299} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"jn32Hj3CzeqoU","output_index":1,"sequence_number":300} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" achieve","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"kLdkzp1R","output_index":1,"sequence_number":301} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" up","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"gU7A7gOdiGhCc","output_index":1,"sequence_number":302} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"KN8ha6FVTCgyr","output_index":1,"sequence_number":303} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"NXbeXo5KZ1XZEUN","output_index":1,"sequence_number":304} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"10","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"be40WzbyGiGzr1","output_index":1,"sequence_number":305} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"%","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"XZLV6RIijwPwuIH","output_index":1,"sequence_number":306} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" revenue","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"DL35RI1I","output_index":1,"sequence_number":307} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" growth","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"9XaPJCYgX","output_index":1,"sequence_number":308} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" for","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"donHZ7Z2vpRD","output_index":1,"sequence_number":309} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" the","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"5VYTfGK61Wiw","output_index":1,"sequence_number":310} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" year","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"6sc9Ngubwix","output_index":1,"sequence_number":311} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"eigkQqQW1bHq4Y5","output_index":1,"sequence_number":312} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" highlighting","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"GoR","output_index":1,"sequence_number":313} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" upcoming","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"9YztB5l","output_index":1,"sequence_number":314} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" regulatory","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"YtIMM","output_index":1,"sequence_number":315} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" milestones","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Wu5hR","output_index":1,"sequence_number":316} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"0OShA4XW5DHj","output_index":1,"sequence_number":317} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ongoing","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"vMs61JIA","output_index":1,"sequence_number":318} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" late","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"RAKNPf0fMvE","output_index":1,"sequence_number":319} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-stage","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"tVJfJGdQOW","output_index":1,"sequence_number":320} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" program","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"9jJbUG1M","output_index":1,"sequence_number":321} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" read","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"jdhDUdyMgHC","output_index":1,"sequence_number":322} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"outs","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"hp8wbKyc5HOu","output_index":1,"sequence_number":323} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"G7vswEOTal0xsan","output_index":1,"sequence_number":324} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"v2ou2pcw4ps405W","output_index":1,"sequence_number":325} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"([tradingview.com](https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai))","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"nn2f","output_index":1,"sequence_number":326} + +event: response.output_text.annotation.added +data: {"type":"response.output_text.annotation.added","annotation":{"type":"url_citation","end_index":1970,"start_index":1766,"title":"Moderna reports Q2 2026 revenue $145M, GAAP net loss $0.8B and reiterates up to 10% 2026 revenue growth — TradingView News","url":"https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai"},"annotation_index":2,"content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","output_index":1,"sequence_number":327} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ou2yFlABAJLE7hB","output_index":1,"sequence_number":328} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"WsfsYMr8vhmJt47","output_index":1,"sequence_number":329} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"These","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"72fbkSBxAQX","output_index":1,"sequence_number":330} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" developments","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"BQt","output_index":1,"sequence_number":331} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" underscore","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"O37jh","output_index":1,"sequence_number":332} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Moderna","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"dQOqxCPz","output_index":1,"sequence_number":333} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"'s","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"yDtVAnfjHQwwmS","output_index":1,"sequence_number":334} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ongoing","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Z5oXwBYU","output_index":1,"sequence_number":335} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" efforts","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Z3vvzLgx","output_index":1,"sequence_number":336} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"VBm2UblBDsfLL","output_index":1,"sequence_number":337} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" expand","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"qmr7D9th6","output_index":1,"sequence_number":338} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" the","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"rD15g4qmPZrA","output_index":1,"sequence_number":339} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" applications","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"xwB","output_index":1,"sequence_number":340} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"vcsYtEyHTJzE0","output_index":1,"sequence_number":341} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" its","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"nrpt8GjSjtUa","output_index":1,"sequence_number":342} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" m","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"Ft8EcuUOJAy4HI","output_index":1,"sequence_number":343} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"RNA","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"IPV529XWpPEYT","output_index":1,"sequence_number":344} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" technology","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"J4XXj","output_index":1,"sequence_number":345} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" beyond","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"sLzdeTRQE","output_index":1,"sequence_number":346} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" COVID","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"05XyAdgHoz","output_index":1,"sequence_number":347} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"VFd0deV4v3RhGW4","output_index":1,"sequence_number":348} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"19","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"1NrCtUg84NFStE","output_index":1,"sequence_number":349} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" vaccines","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"GLh8WwM","output_index":1,"sequence_number":350} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"8jFmklDcp0wPXWJ","output_index":1,"sequence_number":351} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" focusing","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"ciOW4ni","output_index":1,"sequence_number":352} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" on","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"h2YKiDsEx5uT3","output_index":1,"sequence_number":353} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" personalized","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"SqR","output_index":1,"sequence_number":354} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" cancer","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"uPdH9Wtk6","output_index":1,"sequence_number":355} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" treatments","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"TjuZa","output_index":1,"sequence_number":356} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"W5urPNpmCQtO","output_index":1,"sequence_number":357} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" seasonal","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"G1MsQTM","output_index":1,"sequence_number":358} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" flu","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"wWC0ss0oO48k","output_index":1,"sequence_number":359} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" prevention","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"mikIh","output_index":1,"sequence_number":360} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"OA1F96kUj69wMNu","output_index":1,"sequence_number":361} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"o9PzckeLWNuWd4P","output_index":1,"sequence_number":362} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"N6Eox2PGzt5YR2M","output_index":1,"sequence_number":363} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n## Highlights:\n","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"","output_index":1,"sequence_number":364} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"- [Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai), Published on Wednesday, August 19","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"sL3W9ZSCAJEWlap","output_index":1,"sequence_number":365} + +event: response.output_text.annotation.added +data: {"type":"response.output_text.annotation.added","annotation":{"type":"url_citation","end_index":2367,"start_index":2195,"title":"Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test","url":"https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai"},"annotation_index":3,"content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","output_index":1,"sequence_number":366} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n- [FDA approves first mRNA vaccine for seasonal flu](https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai), Published on Thursday, August 06","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"bRJ","output_index":1,"sequence_number":367} + +event: response.output_text.annotation.added +data: {"type":"response.output_text.annotation.added","annotation":{"type":"url_citation","end_index":2573,"start_index":2405,"title":"FDA approves first mRNA vaccine for seasonal flu","url":"https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai"},"annotation_index":4,"content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","output_index":1,"sequence_number":368} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"\n- [New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai), Published on Thursday, August 06","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"yGM0zKJ","output_index":1,"sequence_number":369} + +event: response.output_text.annotation.added +data: {"type":"response.output_text.annotation.added","annotation":{"type":"url_citation","end_index":2774,"start_index":2610,"title":"New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine","url":"https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai"},"annotation_index":5,"content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","output_index":1,"sequence_number":370} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"obfuscation":"8nw6wLvSqVLK3Sq","output_index":1,"sequence_number":371} + +event: response.output_text.done +data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","logprobs":[],"output_index":1,"sequence_number":372,"text":"Moderna has recently achieved significant milestones in both oncology and infectious disease prevention:\n\n**Breakthrough in mRNA-Based Cancer Treatment**\n\nModerna, in collaboration with Merck, announced promising results from a late-stage clinical trial of their personalized mRNA-based cancer vaccine, intismeran. This vaccine, tailored to individual patients based on specific tumor mutations, was tested alongside Merck's immunotherapy drug, Keytruda, in over 1,100 high-risk melanoma patients. The combination therapy demonstrated a statistically significant reduction in cancer recurrence and metastasis compared to Keytruda alone. These findings mark a significant advancement in personalized cancer therapy. ([apnews.com](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai))\n\n**FDA Approval of mRNA Flu Vaccine**\n\nThe U.S. Food and Drug Administration (FDA) approved Moderna's mRNA-based seasonal flu vaccine, mFlusiva, for adults aged 50 and older. This approval follows a previous refusal by the FDA due to concerns over clinical trial methodologies. The vaccine demonstrated a 27% reduction in flu cases compared to standard vaccines in a study involving 40,000 participants. This approval signifies a notable development in influenza prevention and the broader application of mRNA vaccine technology. ([apnews.com](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai))\n\n**Financial Performance**\n\nIn the second quarter of 2026, Moderna reported total revenue of $145 million and a GAAP net loss of $782 million. Despite these figures, the company reiterated its plan to achieve up to 10% revenue growth for the year, highlighting upcoming regulatory milestones and ongoing late-stage program readouts. ([tradingview.com](https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai))\n\nThese developments underscore Moderna's ongoing efforts to expand the applications of its mRNA technology beyond COVID-19 vaccines, focusing on personalized cancer treatments and seasonal flu prevention.\n\n\n## Highlights:\n- [Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai), Published on Wednesday, August 19\n- [FDA approves first mRNA vaccine for seasonal flu](https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai), Published on Thursday, August 06\n- [New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai), Published on Thursday, August 06 "} + +event: response.content_part.done +data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","output_index":1,"part":{"type":"output_text","annotations":[{"type":"url_citation","end_index":808,"start_index":715,"title":"Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test","url":"https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai"},{"type":"url_citation","end_index":1432,"start_index":1339,"title":"New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine","url":"https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai"},{"type":"url_citation","end_index":1970,"start_index":1766,"title":"Moderna reports Q2 2026 revenue $145M, GAAP net loss $0.8B and reiterates up to 10% 2026 revenue growth — TradingView News","url":"https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai"},{"type":"url_citation","end_index":2367,"start_index":2195,"title":"Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test","url":"https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai"},{"type":"url_citation","end_index":2573,"start_index":2405,"title":"FDA approves first mRNA vaccine for seasonal flu","url":"https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai"},{"type":"url_citation","end_index":2774,"start_index":2610,"title":"New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine","url":"https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai"}],"logprobs":[],"text":"Moderna has recently achieved significant milestones in both oncology and infectious disease prevention:\n\n**Breakthrough in mRNA-Based Cancer Treatment**\n\nModerna, in collaboration with Merck, announced promising results from a late-stage clinical trial of their personalized mRNA-based cancer vaccine, intismeran. This vaccine, tailored to individual patients based on specific tumor mutations, was tested alongside Merck's immunotherapy drug, Keytruda, in over 1,100 high-risk melanoma patients. The combination therapy demonstrated a statistically significant reduction in cancer recurrence and metastasis compared to Keytruda alone. These findings mark a significant advancement in personalized cancer therapy. ([apnews.com](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai))\n\n**FDA Approval of mRNA Flu Vaccine**\n\nThe U.S. Food and Drug Administration (FDA) approved Moderna's mRNA-based seasonal flu vaccine, mFlusiva, for adults aged 50 and older. This approval follows a previous refusal by the FDA due to concerns over clinical trial methodologies. The vaccine demonstrated a 27% reduction in flu cases compared to standard vaccines in a study involving 40,000 participants. This approval signifies a notable development in influenza prevention and the broader application of mRNA vaccine technology. ([apnews.com](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai))\n\n**Financial Performance**\n\nIn the second quarter of 2026, Moderna reported total revenue of $145 million and a GAAP net loss of $782 million. Despite these figures, the company reiterated its plan to achieve up to 10% revenue growth for the year, highlighting upcoming regulatory milestones and ongoing late-stage program readouts. ([tradingview.com](https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai))\n\nThese developments underscore Moderna's ongoing efforts to expand the applications of its mRNA technology beyond COVID-19 vaccines, focusing on personalized cancer treatments and seasonal flu prevention.\n\n\n## Highlights:\n- [Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai), Published on Wednesday, August 19\n- [FDA approves first mRNA vaccine for seasonal flu](https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai), Published on Thursday, August 06\n- [New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai), Published on Thursday, August 06 "},"sequence_number":373} + +event: response.output_item.done +data: {"type":"response.output_item.done","item":{"id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","type":"message","status":"completed","content":[{"type":"output_text","annotations":[{"type":"url_citation","end_index":808,"start_index":715,"title":"Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test","url":"https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai"},{"type":"url_citation","end_index":1432,"start_index":1339,"title":"New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine","url":"https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai"},{"type":"url_citation","end_index":1970,"start_index":1766,"title":"Moderna reports Q2 2026 revenue $145M, GAAP net loss $0.8B and reiterates up to 10% 2026 revenue growth — TradingView News","url":"https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai"},{"type":"url_citation","end_index":2367,"start_index":2195,"title":"Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test","url":"https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai"},{"type":"url_citation","end_index":2573,"start_index":2405,"title":"FDA approves first mRNA vaccine for seasonal flu","url":"https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai"},{"type":"url_citation","end_index":2774,"start_index":2610,"title":"New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine","url":"https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai"}],"logprobs":[],"text":"Moderna has recently achieved significant milestones in both oncology and infectious disease prevention:\n\n**Breakthrough in mRNA-Based Cancer Treatment**\n\nModerna, in collaboration with Merck, announced promising results from a late-stage clinical trial of their personalized mRNA-based cancer vaccine, intismeran. This vaccine, tailored to individual patients based on specific tumor mutations, was tested alongside Merck's immunotherapy drug, Keytruda, in over 1,100 high-risk melanoma patients. The combination therapy demonstrated a statistically significant reduction in cancer recurrence and metastasis compared to Keytruda alone. These findings mark a significant advancement in personalized cancer therapy. ([apnews.com](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai))\n\n**FDA Approval of mRNA Flu Vaccine**\n\nThe U.S. Food and Drug Administration (FDA) approved Moderna's mRNA-based seasonal flu vaccine, mFlusiva, for adults aged 50 and older. This approval follows a previous refusal by the FDA due to concerns over clinical trial methodologies. The vaccine demonstrated a 27% reduction in flu cases compared to standard vaccines in a study involving 40,000 participants. This approval signifies a notable development in influenza prevention and the broader application of mRNA vaccine technology. ([apnews.com](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai))\n\n**Financial Performance**\n\nIn the second quarter of 2026, Moderna reported total revenue of $145 million and a GAAP net loss of $782 million. Despite these figures, the company reiterated its plan to achieve up to 10% revenue growth for the year, highlighting upcoming regulatory milestones and ongoing late-stage program readouts. ([tradingview.com](https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai))\n\nThese developments underscore Moderna's ongoing efforts to expand the applications of its mRNA technology beyond COVID-19 vaccines, focusing on personalized cancer treatments and seasonal flu prevention.\n\n\n## Highlights:\n- [Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai), Published on Wednesday, August 19\n- [FDA approves first mRNA vaccine for seasonal flu](https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai), Published on Thursday, August 06\n- [New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai), Published on Thursday, August 06 "}],"role":"assistant"},"output_index":1,"sequence_number":374} + +event: response.completed +data: {"type":"response.completed","response":{"id":"resp_04fa46b17a39180a016a888fa20f2c87d0a86633063307bf6a","object":"response","created_at":1787334562,"status":"completed","background":false,"completed_at":1787334566,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[{"id":"ws_04fa46b17a39180a016a888fa2a81087d0a961c6a9b9866bb8","type":"web_search_call","status":"completed","action":{"type":"search","queries":["Moderna news October 2023"],"query":"Moderna news October 2023"}},{"id":"msg_04fa46b17a39180a016a888fa40b6887d0bd53ceea9c4cde95","type":"message","status":"completed","content":[{"type":"output_text","annotations":[{"type":"url_citation","end_index":808,"start_index":715,"title":"Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test","url":"https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai"},{"type":"url_citation","end_index":1432,"start_index":1339,"title":"New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine","url":"https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai"},{"type":"url_citation","end_index":1970,"start_index":1766,"title":"Moderna reports Q2 2026 revenue $145M, GAAP net loss $0.8B and reiterates up to 10% 2026 revenue growth — TradingView News","url":"https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai"},{"type":"url_citation","end_index":2367,"start_index":2195,"title":"Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test","url":"https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai"},{"type":"url_citation","end_index":2573,"start_index":2405,"title":"FDA approves first mRNA vaccine for seasonal flu","url":"https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai"},{"type":"url_citation","end_index":2774,"start_index":2610,"title":"New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine","url":"https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai"}],"logprobs":[],"text":"Moderna has recently achieved significant milestones in both oncology and infectious disease prevention:\n\n**Breakthrough in mRNA-Based Cancer Treatment**\n\nModerna, in collaboration with Merck, announced promising results from a late-stage clinical trial of their personalized mRNA-based cancer vaccine, intismeran. This vaccine, tailored to individual patients based on specific tumor mutations, was tested alongside Merck's immunotherapy drug, Keytruda, in over 1,100 high-risk melanoma patients. The combination therapy demonstrated a statistically significant reduction in cancer recurrence and metastasis compared to Keytruda alone. These findings mark a significant advancement in personalized cancer therapy. ([apnews.com](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai))\n\n**FDA Approval of mRNA Flu Vaccine**\n\nThe U.S. Food and Drug Administration (FDA) approved Moderna's mRNA-based seasonal flu vaccine, mFlusiva, for adults aged 50 and older. This approval follows a previous refusal by the FDA due to concerns over clinical trial methodologies. The vaccine demonstrated a 27% reduction in flu cases compared to standard vaccines in a study involving 40,000 participants. This approval signifies a notable development in influenza prevention and the broader application of mRNA vaccine technology. ([apnews.com](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai))\n\n**Financial Performance**\n\nIn the second quarter of 2026, Moderna reported total revenue of $145 million and a GAAP net loss of $782 million. Despite these figures, the company reiterated its plan to achieve up to 10% revenue growth for the year, highlighting upcoming regulatory milestones and ongoing late-stage program readouts. ([tradingview.com](https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai))\n\nThese developments underscore Moderna's ongoing efforts to expand the applications of its mRNA technology beyond COVID-19 vaccines, focusing on personalized cancer treatments and seasonal flu prevention.\n\n\n## Highlights:\n- [Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai), Published on Wednesday, August 19\n- [FDA approves first mRNA vaccine for seasonal flu](https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai), Published on Thursday, August 06\n- [New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai), Published on Thursday, August 06 "}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":0.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":1}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":317,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":676,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":993},"user":null,"metadata":{}},"sequence_number":375} + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-1f06cb88e828.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-1f06cb88e828.json new file mode 100644 index 00000000..6c2f39c8 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-1f06cb88e828.json @@ -0,0 +1,135 @@ +{ + "id": "resp_0c1f1c94cc5718ce016a8886057a2887d0a77ac05635b112c4", + "object": "response", + "created_at": 1787332101, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787332102, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "gpt-4o-mini-2024-07-18", + "moderation": null, + "output": [ + { + "id": "msg_0c1f1c94cc5718ce016a8886060f5887d08c16276ccb7f7724", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [], + "logprobs": [], + "text": "Currently, both Paris and New York have the same temperature of 72\u00b0F and are experiencing sunny weather." + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "store": false, + "temperature": 0.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "type": "function", + "description": "Get current weather for a location", + "name": "getWeather", + "output_schema": null, + "parameters": { + "type": "object", + "properties": { + "arg0": { + "type": "string" + } + }, + "required": [ + "arg0" + ], + "additionalProperties": false + }, + "strict": true + }, + { + "type": "function", + "description": "Get weather forecast for next N days", + "name": "getForecast", + "output_schema": null, + "parameters": { + "type": "object", + "properties": { + "arg0": { + "type": "string" + }, + "arg1": { + "type": "integer" + } + }, + "required": [ + "arg0", + "arg1" + ], + "additionalProperties": false + }, + "strict": true + } + ], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 146, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 23, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 169 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-2348ab6c9b2a.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-2348ab6c9b2a.json new file mode 100644 index 00000000..cb0164df --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-2348ab6c9b2a.json @@ -0,0 +1,204 @@ +{ + "id": "resp_0c3ea78a41204e80016a888606758487d08aefcf49c7f9c6b3", + "object": "response", + "created_at": 1787332102, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787332108, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "gpt-4o-2024-08-06", + "moderation": null, + "output": [ + { + "id": "ws_0c3ea78a41204e80016a888607544c87d09a067faaef412ce5", + "type": "web_search_call", + "status": "completed", + "action": { + "type": "search", + "queries": [ + "Moderna latest news October 2023" + ], + "query": "Moderna latest news October 2023" + } + }, + { + "id": "msg_0c3ea78a41204e80016a88860859e487d0b30f080a7beea399", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [ + { + "type": "url_citation", + "end_index": 643, + "start_index": 550, + "title": "Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test", + "url": "https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai" + }, + { + "type": "url_citation", + "end_index": 1135, + "start_index": 1042, + "title": "New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine", + "url": "https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai" + }, + { + "type": "url_citation", + "end_index": 1650, + "start_index": 1446, + "title": "Moderna reports Q2 2026 revenue $145M, GAAP net loss $0.8B and reiterates up to 10% 2026 revenue growth \u2014 TradingView News", + "url": "https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai" + }, + { + "type": "url_citation", + "end_index": 2047, + "start_index": 1875, + "title": "Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test", + "url": "https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai" + }, + { + "type": "url_citation", + "end_index": 2253, + "start_index": 2085, + "title": "FDA approves first mRNA vaccine for seasonal flu", + "url": "https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai" + }, + { + "type": "url_citation", + "end_index": 2454, + "start_index": 2290, + "title": "New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine", + "url": "https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai" + } + ], + "logprobs": [], + "text": "Moderna has recently achieved significant milestones in its mRNA-based therapies:\n\n**1. mRNA-Based Cancer Vaccine Success**\n\nModerna, in collaboration with Merck, announced promising results from a late-stage clinical trial of their personalized mRNA cancer vaccine, intismeran. Tested alongside Merck's immunotherapy drug Keytruda in over 1,100 melanoma patients, the combination therapy significantly reduced the recurrence and spread of high-risk skin cancer post-surgery. This breakthrough led to a substantial increase in Moderna's stock value. ([apnews.com](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai))\n\n**2. FDA Approval of mRNA Flu Vaccine**\n\nThe U.S. Food and Drug Administration (FDA) approved Moderna's mRNA-based seasonal flu vaccine, mFlusiva, for adults aged 50 and older. This marks the first mRNA flu vaccine to receive FDA approval, offering a more adaptable response to flu virus mutations. The approval followed a unanimous advisory committee vote, despite initial regulatory challenges. ([apnews.com](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai))\n\n**3. Financial Performance**\n\nIn the second quarter of 2026, Moderna reported total revenue of $145 million and a GAAP net loss of $782 million. The company reiterated plans to achieve up to 10% revenue growth for the year, highlighting upcoming regulatory milestones and ongoing late-stage program readouts. ([tradingview.com](https://www.tradingview.com/news/tradingview%3A51f64edf05d88%3A0-moderna-reports-q2-2026-revenue-145m-gaap-net-loss-0-8b-and-reiterates-up-to-10-2026-revenue-growth/?utm_source=openai))\n\nThese developments underscore Moderna's ongoing efforts to expand the applications of its mRNA technology beyond COVID-19 vaccines, focusing on personalized cancer treatments and seasonal flu prevention.\n\n\n## Highlights:\n- [Moderna shares surge after it says its experimental mRNA cancer treatment passed a key test](https://apnews.com/article/2330dce708b0af215b68570b19d025df?utm_source=openai), Published on Wednesday, August 19\n- [FDA approves first mRNA vaccine for seasonal flu](https://www.livescience.com/health/medicine-drugs/fda-approves-first-mrna-vaccine-for-seasonal-flu?utm_source=openai), Published on Thursday, August 06\n- [New kind of flu shot is on the way as the FDA approves Moderna's mRNA-based vaccine](https://apnews.com/article/59d991a6bf70c26e2f0c210cc8ca87f1?utm_source=openai), Published on Thursday, August 06 " + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "store": false, + "temperature": 0.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 1 + } + }, + "tools": [ + { + "type": "function", + "description": "Get current weather for a location", + "name": "getWeather", + "output_schema": null, + "parameters": { + "type": "object", + "properties": { + "arg0": { + "type": "string" + } + }, + "required": [ + "arg0" + ], + "additionalProperties": false + }, + "strict": true + }, + { + "type": "function", + "description": "Get weather forecast for next N days", + "name": "getForecast", + "output_schema": null, + "parameters": { + "type": "object", + "properties": { + "arg0": { + "type": "string" + }, + "arg1": { + "type": "integer" + } + }, + "required": [ + "arg0", + "arg1" + ], + "additionalProperties": false + }, + "strict": true + }, + { + "type": "web_search_preview", + "search_content_types": [ + "text" + ], + "search_context_size": "medium", + "user_location": { + "type": "approximate", + "city": null, + "country": "US", + "region": null, + "timezone": null + } + } + ], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 378, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 630, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 1008 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-43e296c0b939.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-43e296c0b939.json new file mode 100644 index 00000000..9f2cc8ec --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-43e296c0b939.json @@ -0,0 +1,109 @@ +{ + "id": "resp_014bae2a3cae84fb006a8885902b5487d0a47aa785f35d1631", + "object": "response", + "created_at": 1787331984, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787331992, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "o4-mini-2025-04-16", + "moderation": null, + "output": [ + { + "id": "rs_014bae2a3cae84fb006a888590aa9087d09855e515d9c567e2", + "type": "reasoning", + "content": [], + "encrypted_content": "gAAAAABqiIWYwscWJF6kalsrY4RN3ugbI9CIH75-Y-Rz0XPs3TaRZX5IfZU0Vv3x_JnICUM1ZraHVJVIk1ou5uNP-EKRKf8SNftML3g__kcQcUSIBFDFtxtVfcg8ZUMZOlNbvCj40Wiw8IdL1gfRh4bwIkNi6BtEeU5MoOnaEUm7ZjiuqZlzguD-GHeZK6FmCwh10b61iZvymq56yLuTeQkZKgCjoqjWgKo9alEqb-u4_kzwLGynkePzaduAIMDp1JBVFFutxiG5KDTY2PTH0AObRAdTSDqZLxeYhbbvmk-WR-3txd6gZSKhBdXRQ_xWcPYWVioiJGZctiaK0B-zPZclOv83gtZ_xV9znOU27p5yd8yrsBkVZhFwEsUivJbdCGP1BaWQXVXYtzht5hHcYp6gjAhCnX7g4Pi-rSyzR7bed1CvovpjszfJXmKQxVT0Hn7KUWU8B9DEMZTZWsNt2fWSahTibxgcGq30HRuCjrR4bKSSim3lkqkfW4Yi0qdasuToGnuuFo5fLe9VaMvfpDky--Z8hYbukQlIFkF7Y_bLM6l1SGwbq7SO_GW_5IhmaxLg4qzYaCCwdgnsaDDqRDd2ikRQF4wO_96Zw_xmVHmRUVvfl5O_BCW-tYF-n-lUtPwMWtsy6SX1c2E8IqQwt7rJwxZjFV1hmrUFAwIjZ0ZdEVa3r0XXB8SHB5OkshuHFa-Cx3cQMWlgq1817S_aBOeyqnxDq8VrOFnGTV_ASq75Xy4O6nzYsOQg0nLMcdi4Jfw92aDvrOZCo0yb3goJNQXcvVM6DYmRzqS5IRK2fU4tMET4crXKF7Mc4KXI7dPCWLhkeMClP3qPBm-VTxYJEyn4Nb92tdl1-L_ftxEDqY9uAbchGStyIZf1OYtkJHQsnvRJi9v5-KcgQmXg8f-jT1Q7R6PBaOlL6ETFSXaydBVbMTOhdmPSv1fd9CFkbJ3ntgSwpVvXgszvfH_D3HQr2csx7DIL3gNv9jnkB3HN3caFsozbeY58r2TF03kY7JZp4Q9XNdXZiuBwSunLx4tr_nc2xysKqkMNmH2aYnyvKgJkmLKk8537Ywz6z-L7W7KD80t06egnYktbFX8PZ-iAFkXcb_bDadnpk51gTL0skYUpAeqS4Wo_Thb3TilGGrqi5wGeVDTClaguMJUo3t9DcfA54D_CvnPkcgMvTtActwX9G6jEAZHGYj_VlWVwefKDc3AD6OkPLv67xTmLImkmW-h6fc-0YY6qbsgt8P3GoTo6JoUY4ALwh6ICDMb23rIl-MnU1b-BBDVS5WqpJ1zbyLtBJnAYP6Hf6yqHVJvohsGUXxYI20KW4YMKldfoWQJC18VAlfKo6MyjstT2m37OPHkwJeZ8nnlBARuR-osXhVi86VYweWU0L_WyQG7E3pQNPifZq3dcear0ZHi57zP0BL_-NrNy1rQ0ckGnNWqAROH0XDxya_J1PM9cO32HK7deIOh_2XGfnrzY9TUL17SOcZS_4CWZ5F_lXAziDyNQgyIFef6ZiamueDi32Mg-xEXXBv2kAzRmhToBWEYmEMj2Gq7NUUPtT6LlWJMD0Uexd9WPtv7GF1lqkyDUXpkBK6K745fdwMtLCXVdVfBR-4aqRKmIwe1_30wOCMtMkrDiAz9gqoniNMvCZVxWFgj-Eyzbzqy73WrovWmuE-LwMONAt9J8B9BiUUgBgVHePuBgnRbgl6E5lJzaHDXfNNRC_K64dOkMIMlCkOlW1cj54xGF5soyVbUUBLMfQjkG-4IdGupGsY_KUGtO9EeGw-5z-kthuJfrdKlcvN_ghOmn4OO1iTxLN8FHXCcd8IzGC5c7PIZhXBHyq3WQKn0HMNplRf8QwseWfrd99Zx1_u8IwAfCFDLEVfzxR-9Nr3GwTLOt2HdEO3_vruBOjVn4iblqv2gzSU5OTmLWt9aNwlBSonDU1aOeR4wH55NuExLPiOq9o8FboiTS66YswtTN_g5AYCKWlCCaErZX08SVzzRGFkETvigPDVQzel9YidRWyQW4lMH9LNmDSx6tkyrWaWk0dVp0qpabGQJnhanD_X9VWxp5MnGP_vC3eed_E3QWyzq2v3U3WU4A_eB5yughnrGNIvvZ2W7WNst6fYHtZFFKj91pcHwDJRQWTGb2JLjhiVGShqJfmUG8PGOwYDY6NBNZsoTH9avAqf15Fs1zvOZD6ofYsZOwz0havhbKbXqV6lQnHoGGvMPcEWIcBHQCehVR1r9TyWnz-cusSr8ESlrXRo9gg-wx5NLZ3AP8f34gPGeD_y3i9ppQvSlM7z1Be8pJYXi0FLv0gA2obhXfn31gBpxWHdYvMZJtTH7nHZ4wzcVUu0AlnW4U-8llR_H0Q43NVX3gtZ8BVG2lIjwK8FfsN5OT8W1YgaCpo4Hs5R_n_wYO8I0pgJ1UpPf_bWWSNMY3vM52fazVIPBl1sbeFzvZyEApqYjkDwcscXcjLuZbuE_-L4aiKv-Wg6fDomluUDjNqQLcH466Yed8xNz0XkG21MA891R66vc0wokRfkCQN8AFJ2qrWhLwVWKb__s1Kk3bz1AvQ7r6prpRJyzq90RB-G4ap7cgw6DScBvt3UNLvLUlowFQqLD2Iu4wc-7FBP8ZUin3qApq_uqVSMgHpXCYCEEwiaTTkieMsB0SvXhZu2BWOqHZ60gbu4xK7-tD9iJPxZP8HCV6ph2U77th9l8BPQGaqHwdITeIPZn-RTqBidOw2LaulOBrkLhc6NEjwTyL6uXsRdyjzD0yluUaFvFlaLg9av6rljXAnZZwWrQFVA83JOvIYnBn4rn_K4615Hz4_-Wp7N10M8dSsTa8", + "summary": [ + { + "type": "summary_text", + "text": "**Summarizing series calculations**\n\nI'm looking at the sequence a_n = n(n+1). For example, a_10 equals 110. The sum of the first ten terms, S_10, can be calculated as the sum of squares and the sum of n values. Using formulae, I find S_10 equals 440. I can derive this from the formula for triangular numbers, noting that a_n relates to these numbers multiplied by 2. So, indeed, the final sum is 440, confirming my calculations." + }, + { + "type": "summary_text", + "text": "**Calculating terms and sums**\n\nThe user wants to know the 10th term and the sum of the first 10 terms. \n\nThe 10th term can be calculated as 10 * 11, which equals 110. The sum of the first 10 terms is calculated to be 440. I should clarify the formula for this sum, which is n(n+1)(n+2)/3. Plugging in n=10 confirms the sum as 440.\n\nTo summarize, the answers are: 10th term: 110, and the sum of the first 10 terms: 440." + } + ] + }, + { + "id": "msg_014bae2a3cae84fb006a8885976a7487d08e618a7e2867b6bf", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [], + "logprobs": [], + "text": "The 10th term is \n a\u2081\u2080 = 10\u00b7(10 + 1) = 10\u00b711 = 110. \n\nThe sum of the first 10 terms is \n S\u2081\u2080 = \u2211_{k=1}^{10} k(k+1) = \u2211k\u00b2 + \u2211k \n = [10\u00b711\u00b721/6] + [10\u00b711/2] \n = 385 + 55 = 440. \n\n(You can also use the closed\u2010form S\u2099 = n(n+1)(n+2)/3, which for n=10 gives 10\u00b711\u00b712/3 = 440.)" + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": "current_turn", + "effort": "high", + "mode": "standard", + "summary": "detailed" + }, + "safety_identifier": null, + "service_tier": "default", + "store": true, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 162, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 627, + "output_tokens_details": { + "reasoning_tokens": 448 + }, + "total_tokens": 789 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-52d34b326f90.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-52d34b326f90.json new file mode 100644 index 00000000..915b840d --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-52d34b326f90.json @@ -0,0 +1,109 @@ +{ + "id": "resp_012090f5253a42f1016a88858f4b3487d0b1ac22e032241668", + "object": "response", + "created_at": 1787331983, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787331989, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "o4-mini-2025-04-16", + "moderation": null, + "output": [ + { + "id": "rs_012090f5253a42f1016a88858fd7b887d0b2b394916984b6d5", + "type": "reasoning", + "content": [], + "encrypted_content": "gAAAAABqiIWVOtMCQGZdKNgrRFErw4F7RiI8cMdtM4eaRkYw4sbFQqrOl7-mhDmJnLgLJ57oCPUOrS8JTtGLVCgeY0XS4nZ5cqVgOttnhXos_p4tdDCvRDQLG_4j9Pg3TN8joQQmi_hTLm9hd0qm2tO_sZoFaOQMEKlN775gxbugkty7pWITVOjHSaym7eXkSp6j8wNLxzoqKZ6OR1oJkiF9UJezEqRMc2HVijKGJK3Ff73svJFrWK2S1BmbRuOuHwBOYCzltKwAhAFvx1ZqkuMV0DGAy-LL9TWTzBh2Tv6799T3Q4Jx6D_lIKqKGnWfcD9OHYjqPi8ChBzraLSfBO3iu1hzSbm5VIr1T1D8DfwaalcaahiApVVBg5PKij085yZvP8pMKARplFfOENWSHgfrArBVXmmODEA7D0l5LFlCIFzzNuWa3ULmfsUantv8Z0AxccTN0VFGn2AaEHvWtI-IcYGJgUWMLg-rIkubMYMuOec_ejaUsYVpRUeak8fCBialPKs8Gbwjjr009SDwNXNxR7x1WoXpX7hRzcvQonaoK3B6sggJTsC72RLpfYeIe5r9Nvs19DorQnKJ7jQ13RD2EXuwfz1j_ZYYqLT4KEraOH7_tpXd-SynkW2riOX4t9NZGk1ZqTjBd2mo_6DcaxfVyiiVCtqPxnqS1X7WENahEJ7Z3CvbjYIAgtQOeuHChvHm53f3QB9TQXlOTxAGcVBcq3sf0K7-bHzDc2bkJgmhveV4CIK3jjdS2iHrcVj8xHEu0uluYSMWcn40BordcWbkWoS4amTyXGedHVMv_7Vc_o4uJ4ysPXGw9k4Mvl5Lm6c03TcPZwvVS1UPpveCUW411aYyrc-DaCPD5az4OkO5hPpR2cBdb-6qPKWP1qFN0N3JP2nAV0xCQARy13CFjZEdY_GnzJGqrs6Tgwmow8DcHcK8uqIk0q-9-lraYHJfbQmzzXSeMwimipX2GpCy5YB5OBBxx-HwYHEwwa7fHnlhqOxG8dTmfc03V8Bgc5JrPr859RTGcPBMyuXNtN5vLQJX2ewdETGtxCVcDQs4-acMLEcHFjlV3KZmhyTaCWprS7AZByEwtrpXKRhpNTqpMVJCoIn-Jgc5CLsdEa_IzMcb8VeHy5JLO-yhdvmxNPgejRvjCggnJlPck5SKJExM8JfBTrQPRRkU6LJurg5FoeY6EkVMn_v7ioilTo2eJMcDIO0zQaJu363WI4VEJ1tFis0vLrNQBbN51YWJuOf59lLNqSUWfTZNrsSWYDS40R9VO_RdG4UQMrMXpf3FhUe5n88cPKVAmulzV4M5B6MG6IOD9zmzj8pzEVPpbft8S4Q92iZlJNVGqT8VFWME-zMeEGRpuqwzM_GmU-oGlrC6nS_qxlKp8VZu_UgvHHWCZeuhBnympdkPb5sPgJa-FCVmW8voZFFlYiZojb7gtFW_NN25CGiM8BLdAVOk_oSTsLLJUeMCfk7u1fEDgntjE3VGzI4ujjMNH5-A8VVu84vd13f3mqM0AZlj9q5K_5b_idGxA5PPj2kxeURWY4hhdv993IloGyWiq_j3S2tifoWbwWqferIS9G63jDLdoHTnMUU7VOnn0cOo8E_mQrg-opjEaNDpc4h_JbgUQat3GSNx4KKABPts-MEsa2QpFoL5p9iqVMuX54XHqbfcTKiihFFWdqiNJx2_rCNah-8lcTrdZHOlmBOV81Oar6e_jlFTO5dLC13vn-Bq1GEQjQLnOkKt_MbcWbzpz8f3EBFxlYC6pybqJ7B6zsR2MddONQwA3mhTOGM6Bb9IdluDm9MywPwnCY3kJcJD4ElwXR2hZWF-YoCOHWb91kwg9t-ArL7QFMO2dLzg6I3EvjeShm3DJuaxqi27s1RrbtbbI9aoulYlWg9nAkNwtn_PbATtSJrvz9COF3ksJqN58ZzC8xLWsMIy3FSHe3GTrGEzdF8roSzuQ5DawEZxMToGGkR_9GGguwJNSrT3y9Taiv2GyZIqAJIbreLYdGFWdl5YjxLbcq96Rr5FFAoZZLId2-zXYgr-XsoFlThZAgo82f_F6RPIPMJV_5JVejUyjjUEuJbpcABTgh9tIRJCMWG3ZDX9iXoAtL5L2phuElXY_v-2afNqYW_qbaItzIhvXANGqrl5shm_TLH5c_o7OMsHd9lREPOx2tEdDmrpFYtATQe7bdFbG6XsWCZZHPCWT4rSSjkUUOy5HAwcpsHDJiTAhiT--AZkM81CsFfq3YyUK5H8YFPd5kJhX_zjL-58OLXq5rGbAbdJBCRJjQfewHX7giT2PbiJth-xhjxn_776xwHrIo_VptlbsBPDy2KhfwMW9qExrGXy0urOwS43UqIgH_HJ9O05XDSxx3L9m-wxVnYgnUUTFzS9rR_RsFp8QXAExJxKnYoOdfqyei1Oin7eD_Y4NKQTFmdlFwvFFqTPISLFnlY7dDkFgPNGdj3uAhEHITfM3LEvaIo7KwglLkOj9ciLNp7J6bYMeFV-8hJtWeJVCnhIsjUV9IzA3I9-5zqwOo7gU-zXEoOSf3btL0OngK4WXGVq1YOUuk3EACqr6Df2", + "summary": [ + { + "type": "summary_text", + "text": "**Calculating pronic numbers**\n\nThe user wants the 10th pronic number, which I calculate as \\(a_{10} = 10 \\times 11 = 110\\). They also asked for the sum of the first 10 terms, which I break down step by step. Using the sum formulas, I find that the total \\(S\\) is 440. So, the 10th term is 110, and the sum of the first 10 terms is 440. I think it\u2019s also helpful to mention the formula for the sum." + }, + { + "type": "summary_text", + "text": "**Summing the first 10 terms**\n\nI want to calculate the sum of the first 10 pronic numbers. The formula involves summing \\(n(n+1)\\) from 1 to 10, giving me a final result of 440. I think it\u2019s nice to show derivation too, like breaking it down into \\( \\text{sum } n^2 + \\text{sum } n\\) for clarity. So, I\u2019ll produce the final answer without heavy formatting, but using a simple bullet list should work well!" + } + ] + }, + { + "id": "msg_012090f5253a42f1016a888595423c87d0bf9fb17dde106072", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [], + "logprobs": [], + "text": "The 10th term is \n a\u2081\u2080 = 10\u00b7(10 + 1) = 10\u00b711 = 110. \n\nThe sum of the first 10 terms is \n S\u2081\u2080 = \u2211\u2099\u208c\u2081\u00b9\u2070 n(n + 1) \n = \u2211\u2099\u208c\u2081\u00b9\u2070 n\u00b2 + \u2211\u2099\u208c\u2081\u00b9\u2070 n \n = [10\u00b711\u00b721/6] + [10\u00b711/2] \n = 385 + 55 \n = 440. \n\nYou can also use the closed\u2010form \n S\u2099 = n(n + 1)(n + 2)/3, \nso S\u2081\u2080 = 10\u00b711\u00b712/3 = 440." + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": "current_turn", + "effort": "high", + "mode": "standard", + "summary": "detailed" + }, + "safety_identifier": null, + "service_tier": "default", + "store": false, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 186, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 580, + "output_tokens_details": { + "reasoning_tokens": 384 + }, + "total_tokens": 766 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-7fc8497bbe6a.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-7fc8497bbe6a.json new file mode 100644 index 00000000..f4834f46 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-7fc8497bbe6a.json @@ -0,0 +1,127 @@ +{ + "id": "resp_01d05b55cbe8c4a5016a8885f1433087d0b6ca64613756b6f1", + "object": "response", + "created_at": 1787332081, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787332083, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "gpt-4o-2024-08-06", + "moderation": null, + "output": [ + { + "id": "ws_01d05b55cbe8c4a5016a8885f1ca6487d0b1785ad0fc537caa", + "type": "web_search_call", + "status": "completed", + "action": { + "type": "search", + "queries": [ + "recent AI news" + ], + "query": "recent AI news" + } + }, + { + "id": "msg_01d05b55cbe8c4a5016a8885f2a49887d0a403dbd3456a0c73", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [ + { + "type": "url_citation", + "end_index": 333, + "start_index": 278, + "title": "Blab AI | Artificial Intelligence News, Tools & Research", + "url": "https://ai.blab.com/?utm_source=openai" + } + ], + "logprobs": [], + "text": "On August 12, 2026, Health Secretary Robert F. Kennedy Jr. and CMS Administrator Dr. Mehmet Oz informed a Senate panel that artificial intelligence is being utilized to address healthcare disparities in rural areas, though evidence supporting its effectiveness remains limited. ([ai.blab.com](https://ai.blab.com/?utm_source=openai)) " + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "store": false, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 1 + } + }, + "tools": [ + { + "type": "web_search_preview", + "search_content_types": [ + "text" + ], + "search_context_size": "low", + "user_location": { + "type": "approximate", + "city": null, + "country": "US", + "region": null, + "timezone": null + } + } + ], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 316, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 84, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 400 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-863a18378a48.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-863a18378a48.json new file mode 100644 index 00000000..8f7dedfb --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-863a18378a48.json @@ -0,0 +1,113 @@ +{ + "id": "resp_0fd34b7865f2b4fb016a888580c77487d093ba11e867c0fe71", + "object": "response", + "created_at": 1787331968, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787331982, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "o4-mini-2025-04-16", + "moderation": null, + "output": [ + { + "id": "rs_0fd34b7865f2b4fb016a888581425487d0acd28225666210fa", + "type": "reasoning", + "content": [], + "encrypted_content": "gAAAAABqiIWOHz5M8h5bslZ6IoOp-cVpniJSp-o-Yy0QqwPVslWuroyJpr023KlTiUtvClh2hx4jjhIN-BonTL3w1DkConhfVQEWG1opx8cvsjcDaYYV_B6dsBNpqblR9DMMLgBHNIR18gbafISJWtlJGjECkbVq8p5Q0gWIRMfvZoDIGxDVxxqAB9wtRq-2knAcW7Q0tpoYVA485y83Msfm44WccYB2Y-Oo9LTtj3rn16wKwd6sTfhna5qVsyFNIYisr_VTEl6qsgad5c0HH9LOaxkAyVkRba4YpPi2N0UApv9dNY2t6lQ_q9p6_xeqjOhL8s3Mp11rGU27OWj3Nz6lDqml0nwaNejGYM18PI9bvuFELu90PRV0i5tZA4rcrPygx1n-osa1JWNCcqhVL-laF7qSBsY88JZqfkhqUApx4yvvekyXB-NXzCdR7JnEANPS7GW6z3PUX1rkiiRZ9GL47y7sdD-_AneBy-gZl5ARkrv4coJMpxUodYczfeCuvUbVJVmhWJK2FF_b9NsF3EZzU6CGcYx5O4O6NaWSv_wyycl9LDImHeUThlNbmAYi71awm8pCcjXvI8-BU8ueX0K3FAertEUEEJHzS06xECKgjGNgLros3HxNgPVZJzsHzCnct5TY6rjTMH59lE61siIoGZYSooP8wetZ5l3lijv6UJL5rgOkdCIIz8bqulOoPeWZgrXsNO_JIoV5Il5-caHTmyLj5pQ8bqnKiyq7IHnAWxlWAcfTd9T3z3FzbizFHdDT1XRXCyKOolfZCJgcpPa7fmS7HrDDTPqY2DLsywCXIVLzJRP-SN4bxLHUNdk2fjPcJN_t1kCKNH83HKOYQPbshPhiCEkuAdgPUWTVt3DocNujmOkKcIAmbLROwFSSg-j6oY28e6jMIWy-6KZgFf28l2iyupAhucKuW7VWTvmeWR9vZ1Nr8tCqG6En9VPaqDOCzSOeUPV1gVTg_HCA-TPLM0COTjjyFZi354u-CSAyaeSYyWQ21SMX8WHhhTGEMMAKUH5YPgaXXbhCQMULe5e00Dp9hVjszdmyMWwCGhYlCBUGDX311ygKcDMJA4Fxc89VceukP9Q4O6OFTBcc3mCSpZUBXdJ12XntKJwlgpfJ75_HiX7JyJi0QI799e9n8vxNcE5xr09ulNkb_Yv_nLrPOrJ3_D7dpCGjMSEjpxRpdXCZ5a78NMzzhx9Dwf4ZCyrYVaKb_SXjfndVoCoraRYKvrGNsidXYLrVGrWCA339upM-z34B5-7KitNgEPoKCnRHpjoyElze0N6IXgcI6jHK330NuK1jdfWeZboh0BDt3sP5yuQ8NIO29QrnQQT32fISeNBGNnyvcosADvRlTDEAymRshbx8myu0CgMcIXeLluEvH2jR6g7qLfQ-mctT8t0J6QV0WKCw6NBssAdGiMexvck-C-nipOdyz2coXPDoEcSKK6rf5GD9HS3XIv9I4FOdCZ2D0Yf3xe8Nx1qSdq3Mok3_X-mz3W8SDns3yKtZt9jiMG6qGJTojhhxZQzGR71gVej1V7DJZfDsS4FaPE3V6wANtT6nCMMQaZ91dey3G0drroffM7-4h58m3Pd-eYC_duqz0i1bc3DOBKAmJSWfMoSC0V4cKhVAFVOwmBwABu-QF-f7T5UXAVSbBSzpFXiZdq_7ZjPwW4o3raWxwQp2tJqLkZMDxv0m9epLByCGil2pIxydutNMjciWnQQAul6UlmPrQevo24_WjR55yLF2UTcvxcq2koF64syKGUVvm6daxg6Tyu_jwWTPC3bluKL7_iAFiYRN7bBXudRBo05vfg5LHLhgBNH-xJrnXX6h0QQyozNj-axMljlnVGz8s5FO2x1aFf9tVk_xDSa2_iEGo2pcoj_c_w_n3QZzhMU2vHSGfweLfBXbwoweXbdN6x5zAoatCoV6enaMsauD-4JsHdEORdF4bVx3cijrpyFNxuwhq8rundXgdCjuku2HMUS5d8dprPtkR_5qEwLnZfLd1a6taMOpQSUQeJAO9hycEP5IXsVaY1yrCsRmmeC1s2Uu5pTZANsYWvk-AsBAXBiRrnZbx1909pg0LYMihLphPsunmENm2KSpcVu1P35EW-_eV_sO3nUIiyrdbirk3Boc6Zeqbz5EmjRnZa7rcZJhnrRsCvEzRHiCgYUmQ_YFwXavpccpfZvBNPERa_yIRPAohnsxLWrN_bcAfde3nnWWnYf4g6r3bovU7VHit6PGOybQQaaQw71Rlzsdc_w4Ly5WtIzVx8GUqgXRxCgovt5o-ofncb2xo-na37NmO0uKJ0WrbBavpDTNsMrnoxGAUm4HDV49MVy6QR6Xy3jbuoOb29AE16v1V949O6veUDQODXVqmv3uA4H3VnlgBVeolkmM6zYFeyXbnx7HMwAUi0jnOELgdluE9dEU8ETk6o9T127A2XUVqTH20RDgLmO4cXAsS2dI2z8v-t6luzjdwMDra1KP6J3G6qaUK-c_OspKGn79N0_vpBmdU5OospataO_sxOqxCpwJdRaKFJ5y4op_PeFhPsXlOG3LIq1Lpm_B5Rkd2glEkCe2wJbAQPq9RgkUyddd5b1oLg5wFwnxcxr8hCplsbkkm2kDjDmscoo8i-1Im6EXxXnkq0dIQ_5UdGerHVfxYCoZcbAFQT1maw3-bVaMyi9HuKD1sMOXOxMclgm_1vEQMt_xp0iJapp44MTZOI8hd1KZVwhEx---qJP48BqmFe_mGN1uEv7uzVsOYdiyFGCmudEGDHQ2xBcsEuhLZn3w2yh6keCGVBzl9F0T7329oIZtxPvziEILeSXjt-S4Pld5DDYMjCnOue6wK9IA4eQNxKdXqqnwh608aJUDnFRIefV-mN29Uth9AwOVmEQ5QC59_qZF1Q7w7t9qgYH-9gwt41j2kijxWRVBvDxFaqU6rijChAcwJOIZT6N8BOE0qNQp9qzilVUlU9Eh7QV0tdNB4If6NKKRprGs87b_DQlOSlusTsgDIdPMueyApg_Cr9mlcBfEhI0wWlRZYTrnG44bHZb4I1Tsw4g-lw8vRpAfEWjqzR6Gb3Ikdj03tD5VQfFwemdRzQ81XTOnP2vlSsTY-yiIlDF6eWBw7-mOZNr_eh9LQNEJKCgOiessNqqrlWJ3snXqk3vN1BBDp_rmWt63Qb271JPI9xTwq2GrfA4xf0cN09qhKCvK7zBNUlVYP7nGDSJDg22gdyspzMnTMXGA_1X_UneGnQGgfjM86YfvMqePAevRoSdEqG9MKgSJIEXNGRF8KxCLct0KauQ1XN0Tf4oqpFXIhCWELkKZIB6jPDAX7uGRbbLkvL5t2v42mgpgpU0DeUs32MNynsTB_XeOb1fr_KULKCiJ4ArezWsVB756ITArMbBCn-7oOMqAdKl-SfCTqCLKaWzxuOyGmTmrfRmMmOsDaVgpViz5ZAnvY1BzK-iEnCCrS8gVTzyWT9OrwDGHH_Dqwf2rGuNct1rDBJTQlTcYV6e5vUNBtYLcuPEUX1dK_iG8i7coFfxlgi1wVEdFczmThdof1KKnFeF0OZz5tjlmbukLHAmwpd_mLXlHAm4m0QXdUmToT7KrYAJ8ty3fVsr1oyd20Eeh7NWaGUNQt9-yI9l3A7MVwZ9w5L3a10qRqAdnsjOg-P0OGOOwm96Si4KDa0T3B8jWha99m6rU1hBVMD5ivi9HEYLujAdZpFsf0BqStzf2A2u0TdQVP4sNQSaZF7XVMqe1omzAxLYm2LVHbrM96PzD5i4T4P-t8i7OtSKwzH7vJ0xCG9ubtdcn_UrzYG0gE5GOl918f-ZDwbx2tL0pfkFCRQDzXN94zh1PgoIBsNBxLaFINuHL62UokLmyOW9v5yNbN-CeTC3S2MCgboEiLcOgG-gTwjJu9YFY_Ri_vPiJhEo6Nr3HWtD_PCW2UrRyN58Nh0IQZQPYRk_FFpoMzU7V0jPSUKwlb1B3LPjMHbMEYNQDq0wfRC4DCYM7ymyXD4wNkgfebqt_XmPMnfUIFCvWpFcHJDcxogAqHHSRnDOOhsZ85rDkgoY5KS1y-tYPjokjxZ2cj-BE-f7M-1BSDTpPejCUf1P2U7VPDHyX6o7w-fAyz0hf-LW9O1bnE-FmooB7Uvjwvobin3XQHNG5u9bMpv7lFuPp76KeABJACv3BCBNzxX3EjNiFCI9H3kb0oWrJ0stiPai1tey4qm3iOIE6SqZmgzXV-qrvhUdyxjvopZr2M0N4agZExH7eQU5J2KYIDHqel8dn9Gcd3pDzkCNf--hR7To2jgAd5eJg-WPGj1G13noH1SjMEnsSUnY-ij5IkKXEE4Z0UECiCk524twhc4GUAbOTO0-w2MwRPQxY0sKdLvOonwIolHqOUZech1idkEWSW-11RuG80WaZ6iM92V0e9GyoblW9BjToPeK412o3UdK4ZzuzNMYAlnPnTFiRdhgnZXlLD0XzdNA5RA3NThOU-w3BCCQOzgDzzC-95_KqA7oGajbK9P7jpmeYNvJ73eG8GH0zcE2B9YUdXnb7HG61uMPV2wO0YcTVpGG1Uze8S7L1PPR_kTqgnyC1f2odlyi_xcByxB98eZEHHA0-fJMZ0DaFNt58wpfdoJEVYb3biOV1t1VD9_n7cWQM8kIXIWFEkFmCsEglJX9vpKbkdlYJ7KUv2EIs4cUCaEUUF-UC1YEyacc7goiDtcSKXCnsR97vswEROuACLmG6g_pG-7CEYyfo6T0oeTxuWa9qwwKECYppXJAagHgViYe_ONVhjCsfSgkMbu6Lv2dQgSWIXFwRZds1yc16r60NBC866HsIiEOtUk5l4z6FDbBWaEsFaFN7R7f3sw3OniMDFcldE85iYg7ZMAhnyRrxSF9yohy4H1yEug3wy5qqesLAnxaHCah8WA2yUY6CUQSOam4K7ohXsrhqgus=", + "summary": [ + { + "type": "summary_text", + "text": "**Identifying a pattern**\n\nThe user wants to find the pattern in the sequence: 2, 6, 12, 20, 30. I notice that the numbers can be expressed as products: 2 = 1*2, 6 = 2*3, 12 = 3*4, and so on, leading to the formula for the nth term being n(n+1). This yields terms like 42 for n=6. These numbers are known as pronic, oblong, or rectangular numbers, and can also be derived as a_n = n^2 + n, depending on how the indexing starts." + }, + { + "type": "summary_text", + "text": "**Clarifying the formula for pronic numbers**\n\nIf we start indexing at 0, the formula a_n = n(n+1) gives a_1 = 2. I initially thought of triangular numbers, but these aren't quite right. They're actually double triangular numbers because a_n = 2*T_n. Ultimately, the simplest expression is a_n = n(n+1) or n^2 + n. The sequence shows an increasing difference of 2, confirming it's a second-order polynomial. The main takeaway is that these are pronic numbers, with the formula being a_n = n(n+1) when starting at 1." + }, + { + "type": "summary_text", + "text": "**Explaining indexing for pronic numbers**\n\nIf we start from n=0, the formula a_n = n(n+1) gives a_0 = 0, but the sequence begins at 2, which suggests starting from n=1. So, the pattern is that of pronic (or oblong) numbers, with the nth term expressed as a_n = n(n+1) for n starting at 1. If I were to index from 0, the formula would be a_{n}= (n+1)(n+2). Since the user specifically asked for the pattern and formula, this covers the complete answer." + } + ] + }, + { + "id": "msg_0fd34b7865f2b4fb016a88858e7ed887d0b9a40d5e6371e072", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [], + "logprobs": [], + "text": "The terms are the \u201cpronic\u201d (or oblong) numbers\u2014each one is the product of two consecutive integers:\n\n1\u00b72 = 2 \n2\u00b73 = 6 \n3\u00b74 = 12 \n4\u00b75 = 20 \n5\u00b76 = 30 \n\nIf you call the first term a\u2081, the second a\u2082, etc., then\n\n\u2003a\u2099 = n\u00b7(n + 1) \n\nEquivalently,\n\n\u2003a\u2099 = n\u00b2 + n." + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": "current_turn", + "effort": "high", + "mode": "standard", + "summary": "detailed" + }, + "safety_identifier": null, + "service_tier": "default", + "store": false, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 41, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 1029, + "output_tokens_details": { + "reasoning_tokens": 896 + }, + "total_tokens": 1070 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-892784bdb435.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-892784bdb435.json new file mode 100644 index 00000000..9ba31519 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-892784bdb435.json @@ -0,0 +1,92 @@ +{ + "id": "resp_08dde0b6b0d5562a016a88860246cc87d0a0c1b9a9e5dd6ed7", + "object": "response", + "created_at": 1787332098, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787332098, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "gpt-4o-mini-2024-07-18", + "moderation": null, + "output": [ + { + "id": "msg_08dde0b6b0d5562a016a888602c22887d0af6540caaa8728de", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [], + "logprobs": [], + "text": "The capital of France is Paris." + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "store": false, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 14, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 8, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 22 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-9ec1c9b92ff9.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-9ec1c9b92ff9.json new file mode 100644 index 00000000..949fa432 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-9ec1c9b92ff9.json @@ -0,0 +1,137 @@ +{ + "id": "resp_09c031fc6e4a4e14016a8886039cdc87d0bbc2d0ca8c59d1eb", + "object": "response", + "created_at": 1787332099, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787332101, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "gpt-4o-mini-2024-07-18", + "moderation": null, + "output": [ + { + "id": "fc_09c031fc6e4a4e14016a88860516cc87d0950dfd08dbbc162e", + "type": "function_call", + "status": "completed", + "arguments": "{\"arg0\":\"Paris\"}", + "call_id": "call_bSgzx73lJpOic45Tz17TF5Zo", + "name": "getWeather" + }, + { + "id": "fc_09c031fc6e4a4e14016a88860516e487d09299471312720fc5", + "type": "function_call", + "status": "completed", + "arguments": "{\"arg0\":\"New York\"}", + "call_id": "call_IESRKUeQQSqSxxh2op9jKdrL", + "name": "getWeather" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "store": false, + "temperature": 0.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [ + { + "type": "function", + "description": "Get current weather for a location", + "name": "getWeather", + "output_schema": null, + "parameters": { + "type": "object", + "properties": { + "arg0": { + "type": "string" + } + }, + "required": [ + "arg0" + ], + "additionalProperties": false + }, + "strict": true + }, + { + "type": "function", + "description": "Get weather forecast for next N days", + "name": "getForecast", + "output_schema": null, + "parameters": { + "type": "object", + "properties": { + "arg0": { + "type": "string" + }, + "arg1": { + "type": "integer" + } + }, + "required": [ + "arg0", + "arg1" + ], + "additionalProperties": false + }, + "strict": true + } + ], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 78, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 48, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 126 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-bdae47d54959.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-bdae47d54959.json new file mode 100644 index 00000000..abe2251e --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-bdae47d54959.json @@ -0,0 +1,109 @@ +{ + "id": "resp_014bae2a3cae84fb006a888587054887d09cb90e54e098e2bb", + "object": "response", + "created_at": 1787331975, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1787331983, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "o4-mini-2025-04-16", + "moderation": null, + "output": [ + { + "id": "rs_014bae2a3cae84fb006a888587770487d089dae96990e2685f", + "type": "reasoning", + "content": [], + "encrypted_content": "gAAAAABqiIWP_je4YqhxM-Co1J2sq_Cb9Zhpv6INr_Q1SPsOwKP9H7s2q72Yt1u23mCI3F7GOpAh2PT79JFzhfEFIav1dKNWs3-dJoNmZBbHL6JQYKLIl9dPkwKWwod0eOk6cLEHxDIT-lCgTj_ILIzJNj8vHJiR7v-vwuTYifn75OPB1AuMMvNSBGxfhY88_XDaPBo_H0t8z3gew3WHJGvOqPxgew9zIks6yeomlJ6Gb73Fvo_-uxTItdaXW18lTWVqSlzwZ9MZEbaHGvXHdU78LSkwX77MJgjeoGOPQynhiLSyDff0dPfMv4p7DRCfUfu3fbm8FfqgBz5RiSwPGVb5kx1thmNt6NG-9s-DCpDMySHtJ0sZVKcK1PgDQf8Pp-4_ap_xxVY5dYN4IKGqZguMox-D2jEQvZhMP8fcUXJ7Kl_y2Eq0s73cVLEyOHM1m6fiMPVkHs5gNScMkBbYGPIXKeAP9SrqiszzVQfat6zG5S2Qs-en0kL11xW1DIiEt_FL_7a9YE0IpawfSNjj8pRgc3DKelx4EwPeOkZakACY8WeAZmTxfGgPzs1ni8Jaczc5vyyFff06n3vLPjF6APdEZsaxcw-1APn2e-SkHJE4R2B186Mh-nWCyBQ5_81wpdWqZGi_UVddKuyTrPpIs0RJC0eiEKbRzOZsTvdhQ4Co3zJuIqpcZA9ZKbSIPtnw55qQCJn2SUdh19blJwP2QAlmAXFhDQRF8h0vJvqdJws0BSenkRxVPZXJC_xYWD95Lo9qvwZb5vs7TaXMDrvgaVpw1jqD0VA0TakTzYWG8gy8bbIR_5HxkSqsRL7jByDeNPSa1gN2PqUYYCmlP_jQzbbs_307JsBO1KQjs4USw6lK1yzws-aUcJ72aTJQ8PJZcpv41U1yKHMMdFSLiYK_EeaLVAxTGIy2gxBj5ax-S0I3pCJZ6KSqhmKsAaoThHpw1YOYbnH056dLwp41ktLS1B2KVBhteJJAaEsvwLD7edJSXeSoHM8Wc-GIN_sCrKj3_rDcsHMhc30-8d6UAY0JFr_RwsQ_KUx2_YPqFLPQBR6p5zkpdncJJYbgKUhd5kaVL-FdFN49ycntgXAzt3iUil50r2iRxCJzbl9jBU1cRH8hN2X855ZxcEC2d5B-fLnQmjrEK0yXJ0dce7yTZsw2dxKJ3JFonZX1rDrGW6iGA6tRhZy2RnW6qlFIqKuXdLItpgRp06Nxarg7Cw99QEvlQojxz-fOXtt5BsUnyXzenhfjh0G7i-56cm31BhpQGvseekgdn6BkteDbGG1zZ2mqtfnze0Bx1c1BaDDkcLNlbuNXtjHNSU9wQX0B6OWZOgqjpCCxHTr_pLw-ZJmEJZOkhyibhdjexDr4ytlo8JIPTNbRl2Hu1ICrkE7oiPyUajDACP6jn3UyE75y1NeDqh1IT04CLNxiMtNmwQhqaAkK9d8SdeCGYX6hYSp0KQm1i_7mifXwwA0zVF98UTLE7FGMHDw_x5c_IiVjMQ0Ocw4QDIkViw44d2m6iqQqMFqNSb5vET-wMSsYwJ2OPEzxhQn2ZtxQkniW7Vb85ZMoeSHICApitrswUSjP9RRb7X6UR9PMSHTkH0zpdOJqq48BLNe0Hujh_jtXMCm1QhVuhTYESs1b13ktJAnFmdoPArXwLYJ8znq36CMdY0fzWAUHiOEarZ_6ZPFT6vlwGaYBM-xEM1uWiGYOtSkFv91FOcr2U6AxC_B5QcCfZCL2DUcprsUyBVb6ucm1QghaYgAfhsUJx6yBodOxVnBd9ghJdAxgDEFA0QQbHx415wuUSVChG15IP-OTxzk5Se2M5F6AR-ccwnD1SlprHsJqsl0cZuQfM9y4xrpFdMsICI9xJzJolluqm6ULdba8MXugLZheh2GxE9nqT3JutRWghupz1R1VqIaHqI9B04rZ-OstZwNmMXSDkgfutUgp-7im6PV_wVHAhEzfom7J6okzEDWgo0HMeEDGfncr5JB_4swAEd7FX-Dl77DYToh3ter3PVLsA2aFW48KdJ1vNOdqSb9h0X6NEOgyi-aNOytMMb1Yra58YqMSsbTgbAblgD6ELvNMTn1MKVhnlpwx7llI8bIyk7ZXxbeh7rjQilzoJ1SfSKwhLZZiGNK3XZyB2qbH6NuOop23Cc7d81PgwLkXCYlP0gPGzKwurtPPgqSkJYV-4C38nkCFiiYr09WgfmCAu8mCfuqTSZ5L9ziTBOY7BsojPX_lvGhHOQOIXYmqlvA9hfJzzSLZP2HwM1ZTSsWbkl44ixlMhvDsNrzY_KD85fttdeEK_CbXdpo2Dl36GCoQU6Kp-IDVp12mbYqO0OSNklv0Ae01cUvkG4ckRZwEuiGUypvc2jaqE_HBcC5QpfTbLvvqIKlkvfYRCVqLpMb8NPoTj9i-zIBiUuBk833XV0bIkZ6REryBwL3-NWX-v3tXoUOKpOzXaZStfKJVwv_SIXf4XBQJb8kreRR38uQbCZamMdtWWbTOJwQhHhKsqZmknxbsrO8WwO6KTdFZbOtBh3iM9KCK8bg72fH3zpUdGE3hJ5pUVzxfgD3CkGKlRBNmYNpH9gN2ppkd_yl1ZbWj4iIIQpt2XtGhR0P1GSOJTVu7Aul91UFqoPGTuf_uRKkgsyK7vrvwQUZZahKjzHZ6ojOw--h1SEeGAefQBUhsND_olTeWrjdKl7tfPH-uE9WG2ZvFoKNXYfUoNL_pI4S0gXBH_liPCUNML2LV9hMeHZnkMO6XR4SN7bXIcvv8vcHjSYU1IK21dwfLmhjI3Ac8Y-n6kA6KtWm4G27hJM4LvqYHZVBB7Thf0f8OCKt_lT3GwB38mmZpAOMxegcFSJcHNHQme4XI6z_eH0wpPNyE-KILuVDIvBiIDJZ9ZRs2Afy9aveF7FsKuifcsztMGWBvtaeklPSc6j7VueuQXVDML8V3ZkS6zvHqvqsgR5T_K_LeNsySeH1z7SnyrvvqhQ4ejEvNW-awsbjYwE5yLSBhXmnryKTUNduIY2VctrZUI1E6mnlUVhuJf9LeebwI2_aYVRmWKq4BZKNuu5y7PWMUElJr_Vl2jRQJ1pB3fqG1C-uR_JNE-QvAYCFF22zkwN3cjGdYQ-II8S_v2jSLlkCoex7wU50fV_0glidNxASWurjrw2HK4_sdhDpIoI1b0I9C8Su3NY-cqlHGoApeKWOPq39rASi5T2Wp982tlVu9BT7aBz8hLC3gghVliPaUZI3fXQnpui_faCuHCb4oUMVCsFWLyM6-Tls9AumwBIEvIRWXeBTCgyn6zTAbjnUDs6MiKX4I9WSTak5u8Cc4p8kT8hGw0mH1LOfGmHflRecbopIi28hUzr0mhYLX2GzSUSS_JrvsiBWT_rv-ApFbTEQFIwaD-jpPuL7SQGscxU9GrZejSHU8x41UXsLrdyXg_STevIydG-RFdlzC_cY7Ga1G3WtjIgb3T3hvWkyE9iQE1Zgc1J-NKeaCkwCro0b2baiGQFJ1sVmys8ZGcNYAualOZEjUPSvi-qwXfUjGhixm4BRDZdL98t6DRlREpPp-LbsCvcQqb9mCnMEA4xjDFbPoDbFa1utnTbTV9EZlWZqFPJdmcJTE4_DuwpfK_DC6OgJWbEmTV-vmKdlSJ50Ml93B86BW_HiXvwG8DuQfDOFIAiU8VDJ70re-BJPMvJMlCQLiK9O3_Mk9iNNDOGSQ0NvgkxfBmwW42S82uRQIRae9sCX9KFuSG8y5MonUhtXTCQZ2onxbZlzAtAXKfzu6L_YOgR97lVW6EebU0RVP1pff892nZOpbgJ3wWvWdLBUND3mnis7vFVIdilR14CazdpZssGQVWW3CxrGwP8R4k3rWLgAXvPHHHYd3sSNPP2wV-4tmaOxn9EQ7HIpLStoCJjkbMlQHSjN1maDN1NwPJU-ZN4pfJUqYvPaahQd1tOv32N9Xb6EJS4QzxEGtuYrvG43ZtArLgQmytvecT4R05-i5_a3BXDQh6v65DQb9kCnCBsPLKmUa8Xs9bmAXsXv5ck7BwDnYCSpAeJsxUlOyK1nhgZdJZJiqSsvSnjgqbWXwUKpiFhUm41ejqMI0XvBRexFyywwP5YHaGtRzgkiMi5zD-ikTDb0siXEW9kjU86-JLcw1E3yx-msFBoMcYHJZI6CEE2CB8LNaaZRkx7potQ5Pm-4iPtNSljxBhjJFmbPKq59fCNs1_FlA94Y1d_73gL9FFN-DD-yAUqiRyiQLnbw_KaIgV0dtDAWMiqOSkJVgbIbb2T9ZvOn7s-7o8QA_YzLWAH0gm__IZXLlBEcx2hYsAPgArUmb-7Q_Hwl7cG9Srz5aAdsGVOimH04BA_1nRa8wmBC_vxK2OH1hHmiMNdwq9PJwrvBPO8gNL7R5LuO69kWVW80o9e56hDmei1pF6m1TncxjDlAa88wXbN78PknYY1Dxui9HRaKuBA-UTkgyVXd71B3jHy1Md6DHHYZh7mn8V1XNJ09AvjRPyct9YS28G_67L8jiFJF-ArKA4bNHhM1jWTfr5ouuS8oSVHa1Da0rSnI1NQNbS7heH8c9J2FvmwIaJpcV8ew8R5A7KSeIfM1QTRy2avko9abhTc2Usst2jkaFeDWu6JUl1Y3CvJsk1XkcnhL5Xjsi6rXOp0weJt0udtKEJ9CQOn0uRHgzrjthcSFmNixhfuyn_tErb6QDX9gYnt5I0rXvuHTiIRuLPLlFQfrRi_aVOfFAojWQ-o9fVKTDRckylSbuXdca9UGePq0j", + "summary": [ + { + "type": "summary_text", + "text": "**Identifying sequence patterns**\n\nI\u2019m looking at the sequence: 2, 6, 12, 20, 30, and trying to identify a pattern. The differences between terms are increasing: 4, 6, 8, 10, which suggests that the n-th term can be expressed as n(n+1). Testing this works perfectly for n=1 to n=5. So, the pattern consists of pronic numbers (or oblong numbers), and I could also express it as a_n = n^2 + n. This reflects a triangular number pattern multiplied by 2." + }, + { + "type": "summary_text", + "text": "**Explaining the sequence pattern**\n\nI\u2019m reconsidering the sequence and thinking about indexing from 1 instead. If I do that, the formula would be a_n = n(n+1), revealing that this sequence represents pronic numbers, which are products of consecutive integers. The differences between terms increase by 2, leading to each term being 2 plus the sum of successive even numbers. \n\nThe final answer here is that the pattern illustrates pronic numbers, represented by the formula a_n = n(n+1)." + } + ] + }, + { + "id": "msg_014bae2a3cae84fb006a88858f5fec87d0b297014b2cba52ff", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [], + "logprobs": [], + "text": "Each term is the product of two consecutive integers:\n\n2 = 1\u00b72 \n6 = 2\u00b73 \n12 = 3\u00b74 \n20 = 4\u00b75 \n30 = 5\u00b76 \n\nSo the nth term (with n starting at 1) is\n\n\u2003a\u2099 = n(n + 1)\n\nEquivalently, a\u2099 = n\u00b2 + n." + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": "current_turn", + "effort": "high", + "mode": "standard", + "summary": "detailed" + }, + "safety_identifier": null, + "service_tier": "default", + "store": true, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 0 + } + }, + "tools": [], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 41, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 880, + "output_tokens_details": { + "reasoning_tokens": 768 + }, + "total_tokens": 921 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-c1b40cd66fd7.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-c1b40cd66fd7.txt new file mode 100644 index 00000000..eb74c53b --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-c1b40cd66fd7.txt @@ -0,0 +1,45 @@ +event: response.created +data: {"type":"response.created","response":{"id":"resp_0b4c0738f971600e016a888fa11b8487d08cbf34ac2c1aa968","object":"response","created_at":1787334561,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":0.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + +event: response.in_progress +data: {"type":"response.in_progress","response":{"id":"resp_0b4c0738f971600e016a888fa11b8487d08cbf34ac2c1aa968","object":"response","created_at":1787334561,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":0.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + +event: response.output_item.added +data: {"type":"response.output_item.added","item":{"id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + +event: response.content_part.added +data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","logprobs":[],"obfuscation":"QTPeqSnGSxMmF","output_index":0,"sequence_number":4} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" capital","item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","logprobs":[],"obfuscation":"ji3naZnM","output_index":0,"sequence_number":5} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","logprobs":[],"obfuscation":"mFObcEp2AbtpM","output_index":0,"sequence_number":6} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" France","item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","logprobs":[],"obfuscation":"hzXyWIy01","output_index":0,"sequence_number":7} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" is","item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","logprobs":[],"obfuscation":"IyHYQdKn5LKk8","output_index":0,"sequence_number":8} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Paris","item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","logprobs":[],"obfuscation":"gsfydoNfwN","output_index":0,"sequence_number":9} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","logprobs":[],"obfuscation":"Z2s4f9eccKR3Swv","output_index":0,"sequence_number":10} + +event: response.output_text.done +data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","logprobs":[],"output_index":0,"sequence_number":11,"text":"The capital of France is Paris."} + +event: response.content_part.done +data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"The capital of France is Paris."},"sequence_number":12} + +event: response.output_item.done +data: {"type":"response.output_item.done","item":{"id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The capital of France is Paris."}],"role":"assistant"},"output_index":0,"sequence_number":13} + +event: response.completed +data: {"type":"response.completed","response":{"id":"resp_0b4c0738f971600e016a888fa11b8487d08cbf34ac2c1aa968","object":"response","created_at":1787334561,"status":"completed","background":false,"completed_at":1787334561,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","moderation":null,"output":[{"id":"msg_0b4c0738f971600e016a888fa18e2c87d0be82c2425add4412","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The capital of France is Paris."}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":0.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":14,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":8,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":22},"user":null,"metadata":{}},"sequence_number":14} + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-114a3fe592b5.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-114a3fe592b5.json new file mode 100644 index 00000000..b5abf7a5 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-114a3fe592b5.json @@ -0,0 +1,49 @@ +{ + "id" : "50c9943b-632e-3dff-8453-f59bd1273f29", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"messages\":[{\"content\":\"you are a concise assistant\",\"role\":\"system\"},{\"content\":\"Say hello in one word.\",\"role\":\"user\"}],\"model\":\"gpt-4o-mini\",\"max_tokens\":100,\"n\":2,\"stream\":true,\"stream_options\":{\"include_usage\":true},\"temperature\":0.0}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-114a3fe592b5.txt", + "headers" : { + "x-request-id" : "req_7cf68331fedd433ab9b6010d37ca06fd", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3c5b0d31a33a-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999982", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:44 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=rxe5UIgls0f5Y3f5kWAxcZP7xXWDpHQJn7Feekg2ToI-1787332064.4858804-1.0.1.1-A8.eiqur4vBmUSbdyfPh9pUtcQRLJGphzQKSMQJrKGZvXB3JNCAX1sbjP4PlLziGk4dn_4_jggQJmPdeymR1hsmkq2vJEkyAmsespHSAUE97GlPxvciI8XsIyzjBcWkm; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:44 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "247", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "50c9943b-632e-3dff-8453-f59bd1273f29", + "persistent" : true, + "insertionIndex" : 56 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-1c381de89b6c.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-1c381de89b6c.json new file mode 100644 index 00000000..86f51890 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-1c381de89b6c.json @@ -0,0 +1,52 @@ +{ + "id" : "7dee2bb4-9cd4-3f7a-8b3c-7a1a7d94fcd0", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"messages\" : [ {\n \"role\" : \"system\",\n \"content\" : \"you are a concise assistant\"\n }, {\n \"role\" : \"user\",\n \"content\" : \"Say hello in one word.\"\n } ],\n \"temperature\" : 0.0,\n \"n\" : 2,\n \"stream\" : true,\n \"stream_options\" : {\n \"include_usage\" : true\n },\n \"max_tokens\" : 100\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-1c381de89b6c.txt", + "headers" : { + "x-request-id" : "req_8da8dccdb6db44a19b849774c1eb7f7d", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3c588ca35396-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999985", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:44 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=0vBp7ZZlkSDqlfN969JDXilBXtwgWCAqAwWamOcqHsM-1787332064.086166-1.0.1.1-KcPEp3KTdLaq1OvPQGSR.sL35KUXpl0ubqKk1Q22BW3vQo35Q3ERA1M8n5ROb0DyIvBgMg0zIxG47UZNj_UE29nxpkZnFXp.sM7l_h4vMORS.iAzJYrZBwM1NbzaxKA3; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:44 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "250", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "7dee2bb4-9cd4-3f7a-8b3c-7a1a7d94fcd0", + "persistent" : true, + "scenarioName" : "scenario-1-chat-completions", + "requiredScenarioState" : "Started", + "newScenarioState" : "scenario-1-chat-completions-2", + "insertionIndex" : 58 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-57dcb1d10cc1.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-57dcb1d10cc1.json new file mode 100644 index 00000000..292d8bc8 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-57dcb1d10cc1.json @@ -0,0 +1,51 @@ +{ + "id" : "5619722d-38c2-3d5a-a578-e072160b8f6b", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"messages\" : [ {\n \"role\" : \"system\",\n \"content\" : \"you are a concise assistant\"\n }, {\n \"role\" : \"user\",\n \"content\" : \"Say hello in one word.\"\n } ],\n \"temperature\" : 0.0,\n \"n\" : 2,\n \"stream\" : true,\n \"stream_options\" : {\n \"include_usage\" : true\n },\n \"max_tokens\" : 100\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-57dcb1d10cc1.txt", + "headers" : { + "x-request-id" : "req_8c358c014ea8430b9e2298e2c2e69403", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3bdba89675b4-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999982", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:24 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=WlLYy4qEpQpMqYUkJ5dagpoIFv2rhQVuV6r8JGJB4Go-1787332044.1072881-1.0.1.1-L1hS9LeH6Mx_C28jqwbfrQ7hTF1A_RlH2690cpn9oXLuYNm.XiXpmk8LOylj14nz3HKcEvn.ggKREa.56YYW7ASzNg_..Uxc7It4eANYQOJuuQM3EkWScnY68V2yZrUx; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:24 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "291", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "5619722d-38c2-3d5a-a578-e072160b8f6b", + "persistent" : true, + "scenarioName" : "scenario-2-chat-completions", + "requiredScenarioState" : "scenario-2-chat-completions-2", + "insertionIndex" : 53 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-60642de92108.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-60642de92108.json new file mode 100644 index 00000000..19289226 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-60642de92108.json @@ -0,0 +1,51 @@ +{ + "id" : "8e8e5ad2-d01b-32f7-b793-9f8cb6ba9c83", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"messages\" : [ {\n \"role\" : \"system\",\n \"content\" : \"you are a thoughtful assistant\"\n }, {\n \"role\" : \"user\",\n \"content\" : \"Count from 1 to 10 slowly.\"\n } ],\n \"temperature\" : 0.0,\n \"stream\" : true,\n \"stream_options\" : {\n \"include_usage\" : true\n },\n \"max_tokens\" : 800\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-60642de92108.txt", + "headers" : { + "x-request-id" : "req_70adfec673004ee2b9023a412ebe591e", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3bdd78c977cd-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999982", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:25 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=lko1.Aj33_j28oaXcMa.tFysWbQq44odX3CSih6XcNI-1787332044.401712-1.0.1.1-rIbhCDnqsWDmqSuM_6GTy.y2G1pllXUU0c.fI4NF.ktPsMWwgsMeObojFpjKxU0EwmfPCaYM9lbR9XKR_MNuOLzCum82OR58VxSB.uxNrjTVXfPTmkFzc89WLVzMGHMw; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:25 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "305", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "8e8e5ad2-d01b-32f7-b793-9f8cb6ba9c83", + "persistent" : true, + "scenarioName" : "scenario-1-chat-completions", + "requiredScenarioState" : "scenario-1-chat-completions-2", + "insertionIndex" : 50 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-640d051c9592.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-640d051c9592.json new file mode 100644 index 00000000..10d3bb29 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-640d051c9592.json @@ -0,0 +1,52 @@ +{ + "id" : "d39744ed-aec2-3381-9cf0-7ec78ed20c0d", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"messages\" : [ {\n \"role\" : \"system\",\n \"content\" : \"you are a concise assistant\"\n }, {\n \"role\" : \"user\",\n \"content\" : \"Say hello in one word.\"\n } ],\n \"temperature\" : 0.0,\n \"n\" : 2,\n \"stream\" : true,\n \"stream_options\" : {\n \"include_usage\" : true\n },\n \"max_tokens\" : 100\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-640d051c9592.txt", + "headers" : { + "x-request-id" : "req_20f183f3be8c4f87a182d3114c5647db", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3bd4ffb47530-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999982", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:23 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=yGcKFKmEWR8dDb7QKJ0ObqHoeAjH8gawYbEa4WN2VDM-1787332043.0396404-1.0.1.1-N67t4QdJbi87hbIdbi_pS51ny9.kwNqBmks4ZdB_O4HeCmiJnUO_2V70xainjxaUXfUWQXuzHxquYY6SMmygWsTcDG9biRoMT74IMHa_LHM5KbRmSkDQR4wTwrLBGRjD; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:23 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "250", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "d39744ed-aec2-3381-9cf0-7ec78ed20c0d", + "persistent" : true, + "scenarioName" : "scenario-2-chat-completions", + "requiredScenarioState" : "Started", + "newScenarioState" : "scenario-2-chat-completions-2", + "insertionIndex" : 57 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7aec0dd2e530.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7aec0dd2e530.json new file mode 100644 index 00000000..2f15080e --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7aec0dd2e530.json @@ -0,0 +1,51 @@ +{ + "id" : "309e7d9a-af7f-308a-bdc7-612e51aa96f1", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"o4-mini\",\n \"messages\" : [ {\n \"role\" : \"user\",\n \"content\" : \"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\"\n } ],\n \"stream\" : false,\n \"reasoning_effort\" : \"medium\"\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-7aec0dd2e530.json", + "headers" : { + "x-request-id" : "req_01755dd2f1aa455fbfdf40557e0fe642", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3b3a1f3d6834-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999970", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:08 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=MCuTTZlILsNmy6AABDDM4ieJ0x11CM7_haXdDHuNI9s-1787332018.2557828-1.0.1.1-Dk1S8QHftQgUx586QAmO.4TpvjWg1U26nv085R316OjKKx7Ss3R68HVC2Y5AtsMxah2FYVhC748OyRCr2JhboP0SWQ8LuVhsqhHVLqgVSAnpYsYlMq8s4W4uRgXU1PNh; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:08 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "9918", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "309e7d9a-af7f-308a-bdc7-612e51aa96f1", + "persistent" : true, + "scenarioName" : "scenario-1-chat-completions", + "requiredScenarioState" : "scenario-1-chat-completions-2", + "insertionIndex" : 48 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7eca37e75602.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7eca37e75602.json new file mode 100644 index 00000000..10d6b9fd --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7eca37e75602.json @@ -0,0 +1,52 @@ +{ + "id" : "a87ed34a-23ed-318c-bb38-228e0f0a781c", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"messages\" : [ {\n \"role\" : \"system\",\n \"content\" : \"you are a thoughtful assistant\"\n }, {\n \"role\" : \"user\",\n \"content\" : \"Count from 1 to 10 slowly.\"\n } ],\n \"temperature\" : 0.0,\n \"stream\" : true,\n \"stream_options\" : {\n \"include_usage\" : true\n },\n \"max_tokens\" : 800\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-7eca37e75602.txt", + "headers" : { + "x-request-id" : "req_f2d229b6e6c240c293557a6129b1b576", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3bdfaf1ae9ec-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999985", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:25 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=hhRHBP_yThcejuqdma2Kwxn6ULWAgGIHLoclFf_O9nY-1787332044.7441347-1.0.1.1-DyomIcloY7paym9pVJt_S2ldMV2giC0mx_bAbN3FM6ceutxad6MWjWfkZn3FMS4Zgw5vW3Gxm5Om0_pAGq0JGqmEYy7.TuVk1wpDiLEIb0YS8IEZ_C0DZ5DbPRPZfhKe; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:25 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "429", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "a87ed34a-23ed-318c-bb38-228e0f0a781c", + "persistent" : true, + "scenarioName" : "scenario-1-chat-completions", + "requiredScenarioState" : "Started", + "newScenarioState" : "scenario-1-chat-completions-2", + "insertionIndex" : 51 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-84ab36b6457e.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-84ab36b6457e.json new file mode 100644 index 00000000..23799de1 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-84ab36b6457e.json @@ -0,0 +1,49 @@ +{ + "id" : "c1cfdd78-d78d-3a85-8d69-c3d20a303758", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"messages\":[{\"content\":\"you are a concise assistant\",\"role\":\"system\"},{\"content\":\"Say hello in one word.\",\"role\":\"user\"}],\"model\":\"gpt-4o-mini\",\"stream_options\":{\"include_obfuscation\":false,\"include_usage\":true},\"max_tokens\":100,\"temperature\":0.0,\"n\":2,\"stream\":true}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-84ab36b6457e.txt", + "headers" : { + "x-request-id" : "req_8a48872220ee4f57817ceb67949eb341", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3c5a9d5e346b-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999982", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:44 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=hVZUsygh9ciHf8iPyie6YB9IFh5zdaJyoSQbpAhie_k-1787332064.4178524-1.0.1.1-u9GMdaU2KiopAQHVvnlnL6T2K3xHHw85.LWacDGzB4mtoph0bN32DsDD.xZRGB9wpB595QKKkM0jVkkY6fAL0RqGb241N_BM7zLG1CCJZTvRhv89.xakXAJ1jxXjohqk; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:44 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "287", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "c1cfdd78-d78d-3a85-8d69-c3d20a303758", + "persistent" : true, + "insertionIndex" : 57 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-94957db25a8e.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-94957db25a8e.json new file mode 100644 index 00000000..3ed21475 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-94957db25a8e.json @@ -0,0 +1,49 @@ +{ + "id" : "b4437717-d973-3833-bb0d-87425a0426e4", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"messages\":[{\"content\":\"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\",\"role\":\"user\"}],\"model\":\"o4-mini\",\"reasoning_effort\":\"medium\"}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-94957db25a8e.json", + "headers" : { + "Server" : "cloudflare", + "x-ratelimit-reset-tokens" : "0s", + "set-cookie" : "__cf_bm=C2AezTtbZZ3sWpk4sNdqTA2BeDam0VZ0RP4.pU5lkKI-1787332013.0744734-1.0.1.1-WOg7uUbJdxHzUbreW0PuEFQk88q3qp2ES0adBVkUZqvcPpRI44L9TbYrgtItc3a1uygyNkAqhUbpFPyRsk7Mun4GSxBRNVL.vWonfjlWIoHiJFuYP5Z.ZfdmezRa9UNX; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:36:57 GMT", + "Access-Control-Expose-Headers" : [ "CF-Ray", "X-Request-ID", "CF-Ray" ], + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json", + "x-request-id" : "req_a87ca4ec010b4f6995341cb2c004a45a", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "CF-Ray" : "a2eb3b19bd87b9bc-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999970", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:06:57 GMT", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "4817", + "alt-svc" : "h3=\":443\"; ma=86400" + } + }, + "uuid" : "b4437717-d973-3833-bb0d-87425a0426e4", + "persistent" : true, + "insertionIndex" : 52 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-b9ecbd8616dd.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-b9ecbd8616dd.json new file mode 100644 index 00000000..76415b6b --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-b9ecbd8616dd.json @@ -0,0 +1,52 @@ +{ + "id" : "e0236e27-8f65-3b57-b18d-abec0a2efd39", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"o4-mini\",\n \"messages\" : [ {\n \"role\" : \"user\",\n \"content\" : \"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\"\n } ],\n \"stream\" : false,\n \"reasoning_effort\" : \"medium\"\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-b9ecbd8616dd.json", + "headers" : { + "x-request-id" : "req_cffaffad39ff46faad7700f24c0066a9", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3b17db4fba4e-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999970", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:06:59 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=BYWvegRI04Ge35faJVSZdaSlkxgykn266MCPOMQbhOc-1787332012.7728784-1.0.1.1-DguGRjvGYKpQHpNtzV_ndU9ajqoHn3g.qF3luZ4cPXN0N24Ip24w4k95zI4FsrX2Qb7L2nbjP9Ws962DDcMEABBNk27rtJlylYXtUrQk_sg4byQbarRT.pU6R7z7Sbxd; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:36:59 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "6375", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "e0236e27-8f65-3b57-b18d-abec0a2efd39", + "persistent" : true, + "scenarioName" : "scenario-1-chat-completions", + "requiredScenarioState" : "Started", + "newScenarioState" : "scenario-1-chat-completions-2", + "insertionIndex" : 50 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-dafccec799b8.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-dafccec799b8.json new file mode 100644 index 00000000..12c72b80 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-dafccec799b8.json @@ -0,0 +1,51 @@ +{ + "id" : "af5058cb-a056-36a5-b8fb-01adaf085e3f", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"messages\" : [ {\n \"role\" : \"system\",\n \"content\" : \"you are a concise assistant\"\n }, {\n \"role\" : \"user\",\n \"content\" : \"Say hello in one word.\"\n } ],\n \"temperature\" : 0.0,\n \"n\" : 2,\n \"stream\" : true,\n \"stream_options\" : {\n \"include_usage\" : true\n },\n \"max_tokens\" : 100\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-dafccec799b8.txt", + "headers" : { + "x-request-id" : "req_4a1c3ed528394fd2b217a0338326105c", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3c5efff0bd66-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999982", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:07:45 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=9bCwlNG0cnwXo.3jy618ztjjSu5q8q8Nc00.XEhJL9U-1787332065.1225748-1.0.1.1-IkhA9HESVMJkx6bFha_hGD.LDJka.eT1iKaa.xZNPqv3zHsSJVKdsQduX7zVc55355ewDtV2QJsdzU3eTKFf0aDeFr6GvTK6GME6I6rsQnHpxpCEn1pilEg5jLcl03jQ; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:37:45 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "282", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "af5058cb-a056-36a5-b8fb-01adaf085e3f", + "persistent" : true, + "scenarioName" : "scenario-1-chat-completions", + "requiredScenarioState" : "scenario-1-chat-completions-2", + "insertionIndex" : 54 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-1967c8c76482.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-1967c8c76482.json new file mode 100644 index 00000000..743855f2 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-1967c8c76482.json @@ -0,0 +1,42 @@ +{ + "id" : "1976c326-bdcd-31e2-950f-6eb1f62a39e8", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"Do a web search for news about Moderna. What are they up to lately?\"\n } ]\n } ],\n \"stream\" : true,\n \"store\" : false,\n \"temperature\" : 0.0,\n \"tools\" : [ {\n \"type\" : \"web_search_preview\"\n } ]\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-1967c8c76482.txt", + "headers" : { + "x-request-id" : "req_d184a4289ecd4b57b7046cbb3b18d606", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb7954095a1e0b-SEA", + "X-Content-Type-Options" : "nosniff", + "Date" : "Fri, 21 Aug 2026 17:49:22 GMT", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=CvIt0i5vFgNjaxNzeUOwBOyP1JlK0EpMl2zGBOpSJI4-1787334561.9305537-1.0.1.1-RHAypL0fW_Kn7t3IyddwQNW8gJovnNW2K4O4Z0B4sy_Ys1I58dp5R4gPk.LBUFO5Fqg00nuInuUwGNGugK03mpTa1M_YeTkbG144XWgNPOaFn3RG..KNkWTO.UZOPq6x; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 18:19:22 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "130", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "1976c326-bdcd-31e2-950f-6eb1f62a39e8", + "persistent" : true, + "insertionIndex" : 61 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-1f06cb88e828.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-1f06cb88e828.json new file mode 100644 index 00000000..4998ea74 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-1f06cb88e828.json @@ -0,0 +1,48 @@ +{ + "id" : "b41a194d-ab51-3fb4-8fae-39c2fefab25a", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"is it hotter in Paris or New York right now?\"\n } ]\n }, {\n \"type\" : \"function_call\",\n \"call_id\" : \"call_bSgzx73lJpOic45Tz17TF5Zo\",\n \"name\" : \"getWeather\",\n \"arguments\" : \"{\\\"arg0\\\":\\\"Paris\\\"}\"\n }, {\n \"type\" : \"function_call\",\n \"call_id\" : \"call_IESRKUeQQSqSxxh2op9jKdrL\",\n \"name\" : \"getWeather\",\n \"arguments\" : \"{\\\"arg0\\\":\\\"New York\\\"}\"\n }, {\n \"type\" : \"function_call_output\",\n \"call_id\" : \"call_bSgzx73lJpOic45Tz17TF5Zo\",\n \"output\" : \"The weather in Paris is sunny with 72°F temperature.\"\n }, {\n \"type\" : \"function_call_output\",\n \"call_id\" : \"call_IESRKUeQQSqSxxh2op9jKdrL\",\n \"output\" : \"The weather in New York is sunny with 72°F temperature.\"\n } ],\n \"stream\" : false,\n \"store\" : false,\n \"temperature\" : 0.0,\n \"tools\" : [ {\n \"type\" : \"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 \"type\" : \"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}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-1f06cb88e828.json", + "headers" : { + "x-request-id" : "req_235e4983164f4e2184091d6479700842", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3d418f107cc8-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999637", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:08:22 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=oUnial5es5EvtUZruWZqtaX.Wgtyb53nTasrScYTQ.k-1787332101.3694963-1.0.1.1-0RlQFZxKCnMrcTW0tkr8HsYB1So7eqLfzTXkBkaFOHXb.QNeJ10xOat3exdqbuDchPWugwZgddWix_O_nHlcyKrhFaIvcQmeJt7FWxl5wlTuKh.eKszi7HbHVis8cTaC; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:38:22 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "788", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "b41a194d-ab51-3fb4-8fae-39c2fefab25a", + "persistent" : true, + "insertionIndex" : 58 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-2348ab6c9b2a.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-2348ab6c9b2a.json new file mode 100644 index 00000000..f5aa6461 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-2348ab6c9b2a.json @@ -0,0 +1,48 @@ +{ + "id" : "91f1b96e-0ad5-3ea0-b353-0594e11384ca", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"Do a web search for news about Moderna. What are they up to lately?\"\n } ]\n } ],\n \"stream\" : false,\n \"store\" : false,\n \"temperature\" : 0.0,\n \"tools\" : [ {\n \"type\" : \"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 \"type\" : \"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 \"type\" : \"web_search_preview\"\n } ]\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-2348ab6c9b2a.json", + "headers" : { + "x-request-id" : "req_1620703f102c4aa38b3d98365a09355f", + "x-ratelimit-limit-tokens" : "30000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3d47ec8c57bc-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "6ms", + "x-ratelimit-remaining-tokens" : "29998938", + "x-ratelimit-remaining-requests" : "9999", + "Date" : "Fri, 21 Aug 2026 17:08:28 GMT", + "x-ratelimit-reset-tokens" : "2ms", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=2vmsJeWAJSr8AD7KaFCpNgG4_peHcGM8nqHZGTQb79U-1787332102.3905027-1.0.1.1-5H4umw73.SZszIcaxWBYMGHZ0zIdDMS_ToL8wl.REA8hlTg4czEEVliuAqLgBEm_SCzNTprC_zDpg6yC0DUSOhCORlMBgYw.SsRqWDUGlfeMmZeKZVcOVfbg1.appijX; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:38:28 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "10000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "6093", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "91f1b96e-0ad5-3ea0-b353-0594e11384ca", + "persistent" : true, + "insertionIndex" : 57 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-43e296c0b939.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-43e296c0b939.json new file mode 100644 index 00000000..a8bedc7f --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-43e296c0b939.json @@ -0,0 +1,48 @@ +{ + "id" : "d7702574-3efa-3b62-a743-1faf32c6f340", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"input\":[{\"role\":\"user\",\"content\":\"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\"},{\"id\":\"rs_014bae2a3cae84fb006a888587770487d089dae96990e2685f\",\"summary\":[{\"text\":\"**Identifying sequence patterns**\\n\\nI’m looking at the sequence: 2, 6, 12, 20, 30, and trying to identify a pattern. The differences between terms are increasing: 4, 6, 8, 10, which suggests that the n-th term can be expressed as n(n+1). Testing this works perfectly for n=1 to n=5. So, the pattern consists of pronic numbers (or oblong numbers), and I could also express it as a_n = n^2 + n. This reflects a triangular number pattern multiplied by 2.\",\"type\":\"summary_text\"},{\"text\":\"**Explaining the sequence pattern**\\n\\nI’m reconsidering the sequence and thinking about indexing from 1 instead. If I do that, the formula would be a_n = n(n+1), revealing that this sequence represents pronic numbers, which are products of consecutive integers. The differences between terms increase by 2, leading to each term being 2 plus the sum of successive even numbers. \\n\\nThe final answer here is that the pattern illustrates pronic numbers, represented by the formula a_n = n(n+1).\",\"type\":\"summary_text\"}],\"type\":\"reasoning\",\"encrypted_content\":\"gAAAAABqiIWP_je4YqhxM-Co1J2sq_Cb9Zhpv6INr_Q1SPsOwKP9H7s2q72Yt1u23mCI3F7GOpAh2PT79JFzhfEFIav1dKNWs3-dJoNmZBbHL6JQYKLIl9dPkwKWwod0eOk6cLEHxDIT-lCgTj_ILIzJNj8vHJiR7v-vwuTYifn75OPB1AuMMvNSBGxfhY88_XDaPBo_H0t8z3gew3WHJGvOqPxgew9zIks6yeomlJ6Gb73Fvo_-uxTItdaXW18lTWVqSlzwZ9MZEbaHGvXHdU78LSkwX77MJgjeoGOPQynhiLSyDff0dPfMv4p7DRCfUfu3fbm8FfqgBz5RiSwPGVb5kx1thmNt6NG-9s-DCpDMySHtJ0sZVKcK1PgDQf8Pp-4_ap_xxVY5dYN4IKGqZguMox-D2jEQvZhMP8fcUXJ7Kl_y2Eq0s73cVLEyOHM1m6fiMPVkHs5gNScMkBbYGPIXKeAP9SrqiszzVQfat6zG5S2Qs-en0kL11xW1DIiEt_FL_7a9YE0IpawfSNjj8pRgc3DKelx4EwPeOkZakACY8WeAZmTxfGgPzs1ni8Jaczc5vyyFff06n3vLPjF6APdEZsaxcw-1APn2e-SkHJE4R2B186Mh-nWCyBQ5_81wpdWqZGi_UVddKuyTrPpIs0RJC0eiEKbRzOZsTvdhQ4Co3zJuIqpcZA9ZKbSIPtnw55qQCJn2SUdh19blJwP2QAlmAXFhDQRF8h0vJvqdJws0BSenkRxVPZXJC_xYWD95Lo9qvwZb5vs7TaXMDrvgaVpw1jqD0VA0TakTzYWG8gy8bbIR_5HxkSqsRL7jByDeNPSa1gN2PqUYYCmlP_jQzbbs_307JsBO1KQjs4USw6lK1yzws-aUcJ72aTJQ8PJZcpv41U1yKHMMdFSLiYK_EeaLVAxTGIy2gxBj5ax-S0I3pCJZ6KSqhmKsAaoThHpw1YOYbnH056dLwp41ktLS1B2KVBhteJJAaEsvwLD7edJSXeSoHM8Wc-GIN_sCrKj3_rDcsHMhc30-8d6UAY0JFr_RwsQ_KUx2_YPqFLPQBR6p5zkpdncJJYbgKUhd5kaVL-FdFN49ycntgXAzt3iUil50r2iRxCJzbl9jBU1cRH8hN2X855ZxcEC2d5B-fLnQmjrEK0yXJ0dce7yTZsw2dxKJ3JFonZX1rDrGW6iGA6tRhZy2RnW6qlFIqKuXdLItpgRp06Nxarg7Cw99QEvlQojxz-fOXtt5BsUnyXzenhfjh0G7i-56cm31BhpQGvseekgdn6BkteDbGG1zZ2mqtfnze0Bx1c1BaDDkcLNlbuNXtjHNSU9wQX0B6OWZOgqjpCCxHTr_pLw-ZJmEJZOkhyibhdjexDr4ytlo8JIPTNbRl2Hu1ICrkE7oiPyUajDACP6jn3UyE75y1NeDqh1IT04CLNxiMtNmwQhqaAkK9d8SdeCGYX6hYSp0KQm1i_7mifXwwA0zVF98UTLE7FGMHDw_x5c_IiVjMQ0Ocw4QDIkViw44d2m6iqQqMFqNSb5vET-wMSsYwJ2OPEzxhQn2ZtxQkniW7Vb85ZMoeSHICApitrswUSjP9RRb7X6UR9PMSHTkH0zpdOJqq48BLNe0Hujh_jtXMCm1QhVuhTYESs1b13ktJAnFmdoPArXwLYJ8znq36CMdY0fzWAUHiOEarZ_6ZPFT6vlwGaYBM-xEM1uWiGYOtSkFv91FOcr2U6AxC_B5QcCfZCL2DUcprsUyBVb6ucm1QghaYgAfhsUJx6yBodOxVnBd9ghJdAxgDEFA0QQbHx415wuUSVChG15IP-OTxzk5Se2M5F6AR-ccwnD1SlprHsJqsl0cZuQfM9y4xrpFdMsICI9xJzJolluqm6ULdba8MXugLZheh2GxE9nqT3JutRWghupz1R1VqIaHqI9B04rZ-OstZwNmMXSDkgfutUgp-7im6PV_wVHAhEzfom7J6okzEDWgo0HMeEDGfncr5JB_4swAEd7FX-Dl77DYToh3ter3PVLsA2aFW48KdJ1vNOdqSb9h0X6NEOgyi-aNOytMMb1Yra58YqMSsbTgbAblgD6ELvNMTn1MKVhnlpwx7llI8bIyk7ZXxbeh7rjQilzoJ1SfSKwhLZZiGNK3XZyB2qbH6NuOop23Cc7d81PgwLkXCYlP0gPGzKwurtPPgqSkJYV-4C38nkCFiiYr09WgfmCAu8mCfuqTSZ5L9ziTBOY7BsojPX_lvGhHOQOIXYmqlvA9hfJzzSLZP2HwM1ZTSsWbkl44ixlMhvDsNrzY_KD85fttdeEK_CbXdpo2Dl36GCoQU6Kp-IDVp12mbYqO0OSNklv0Ae01cUvkG4ckRZwEuiGUypvc2jaqE_HBcC5QpfTbLvvqIKlkvfYRCVqLpMb8NPoTj9i-zIBiUuBk833XV0bIkZ6REryBwL3-NWX-v3tXoUOKpOzXaZStfKJVwv_SIXf4XBQJb8kreRR38uQbCZamMdtWWbTOJwQhHhKsqZmknxbsrO8WwO6KTdFZbOtBh3iM9KCK8bg72fH3zpUdGE3hJ5pUVzxfgD3CkGKlRBNmYNpH9gN2ppkd_yl1ZbWj4iIIQpt2XtGhR0P1GSOJTVu7Aul91UFqoPGTuf_uRKkgsyK7vrvwQUZZahKjzHZ6ojOw--h1SEeGAefQBUhsND_olTeWrjdKl7tfPH-uE9WG2ZvFoKNXYfUoNL_pI4S0gXBH_liPCUNML2LV9hMeHZnkMO6XR4SN7bXIcvv8vcHjSYU1IK21dwfLmhjI3Ac8Y-n6kA6KtWm4G27hJM4LvqYHZVBB7Thf0f8OCKt_lT3GwB38mmZpAOMxegcFSJcHNHQme4XI6z_eH0wpPNyE-KILuVDIvBiIDJZ9ZRs2Afy9aveF7FsKuifcsztMGWBvtaeklPSc6j7VueuQXVDML8V3ZkS6zvHqvqsgR5T_K_LeNsySeH1z7SnyrvvqhQ4ejEvNW-awsbjYwE5yLSBhXmnryKTUNduIY2VctrZUI1E6mnlUVhuJf9LeebwI2_aYVRmWKq4BZKNuu5y7PWMUElJr_Vl2jRQJ1pB3fqG1C-uR_JNE-QvAYCFF22zkwN3cjGdYQ-II8S_v2jSLlkCoex7wU50fV_0glidNxASWurjrw2HK4_sdhDpIoI1b0I9C8Su3NY-cqlHGoApeKWOPq39rASi5T2Wp982tlVu9BT7aBz8hLC3gghVliPaUZI3fXQnpui_faCuHCb4oUMVCsFWLyM6-Tls9AumwBIEvIRWXeBTCgyn6zTAbjnUDs6MiKX4I9WSTak5u8Cc4p8kT8hGw0mH1LOfGmHflRecbopIi28hUzr0mhYLX2GzSUSS_JrvsiBWT_rv-ApFbTEQFIwaD-jpPuL7SQGscxU9GrZejSHU8x41UXsLrdyXg_STevIydG-RFdlzC_cY7Ga1G3WtjIgb3T3hvWkyE9iQE1Zgc1J-NKeaCkwCro0b2baiGQFJ1sVmys8ZGcNYAualOZEjUPSvi-qwXfUjGhixm4BRDZdL98t6DRlREpPp-LbsCvcQqb9mCnMEA4xjDFbPoDbFa1utnTbTV9EZlWZqFPJdmcJTE4_DuwpfK_DC6OgJWbEmTV-vmKdlSJ50Ml93B86BW_HiXvwG8DuQfDOFIAiU8VDJ70re-BJPMvJMlCQLiK9O3_Mk9iNNDOGSQ0NvgkxfBmwW42S82uRQIRae9sCX9KFuSG8y5MonUhtXTCQZ2onxbZlzAtAXKfzu6L_YOgR97lVW6EebU0RVP1pff892nZOpbgJ3wWvWdLBUND3mnis7vFVIdilR14CazdpZssGQVWW3CxrGwP8R4k3rWLgAXvPHHHYd3sSNPP2wV-4tmaOxn9EQ7HIpLStoCJjkbMlQHSjN1maDN1NwPJU-ZN4pfJUqYvPaahQd1tOv32N9Xb6EJS4QzxEGtuYrvG43ZtArLgQmytvecT4R05-i5_a3BXDQh6v65DQb9kCnCBsPLKmUa8Xs9bmAXsXv5ck7BwDnYCSpAeJsxUlOyK1nhgZdJZJiqSsvSnjgqbWXwUKpiFhUm41ejqMI0XvBRexFyywwP5YHaGtRzgkiMi5zD-ikTDb0siXEW9kjU86-JLcw1E3yx-msFBoMcYHJZI6CEE2CB8LNaaZRkx7potQ5Pm-4iPtNSljxBhjJFmbPKq59fCNs1_FlA94Y1d_73gL9FFN-DD-yAUqiRyiQLnbw_KaIgV0dtDAWMiqOSkJVgbIbb2T9ZvOn7s-7o8QA_YzLWAH0gm__IZXLlBEcx2hYsAPgArUmb-7Q_Hwl7cG9Srz5aAdsGVOimH04BA_1nRa8wmBC_vxK2OH1hHmiMNdwq9PJwrvBPO8gNL7R5LuO69kWVW80o9e56hDmei1pF6m1TncxjDlAa88wXbN78PknYY1Dxui9HRaKuBA-UTkgyVXd71B3jHy1Md6DHHYZh7mn8V1XNJ09AvjRPyct9YS28G_67L8jiFJF-ArKA4bNHhM1jWTfr5ouuS8oSVHa1Da0rSnI1NQNbS7heH8c9J2FvmwIaJpcV8ew8R5A7KSeIfM1QTRy2avko9abhTc2Usst2jkaFeDWu6JUl1Y3CvJsk1XkcnhL5Xjsi6rXOp0weJt0udtKEJ9CQOn0uRHgzrjthcSFmNixhfuyn_tErb6QDX9gYnt5I0rXvuHTiIRuLPLlFQfrRi_aVOfFAojWQ-o9fVKTDRckylSbuXdca9UGePq0j\",\"content\":[]},{\"id\":\"msg_014bae2a3cae84fb006a88858f5fec87d0b297014b2cba52ff\",\"content\":[{\"annotations\":[],\"text\":\"Each term is the product of two consecutive integers:\\n\\n2 = 1·2 \\n6 = 2·3 \\n12 = 3·4 \\n20 = 4·5 \\n30 = 5·6 \\n\\nSo the nth term (with n starting at 1) is\\n\\n aₙ = n(n + 1)\\n\\nEquivalently, aₙ = n² + n.\",\"type\":\"output_text\",\"logprobs\":[]}],\"role\":\"assistant\",\"status\":\"completed\",\"type\":\"message\"},{\"role\":\"user\",\"content\":\"Using the pattern you discovered, what would be the 10th term? And can you find the sum of the first 10 terms?\"}],\"model\":\"o4-mini\",\"reasoning\":{\"effort\":\"high\",\"summary\":\"detailed\"}}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-43e296c0b939.json", + "headers" : { + "x-request-id" : "req_b6547cd685774c6ead2649dfd5bca99b", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3a64697231b0-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999630", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:06:32 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=.jbOB5k1A6LrastfbWipm4Cxf3qit84l0xxnDU.smjs-1787331984.0691485-1.0.1.1-u5IIp4G5S_BJxOpEmhOiXLurSTGaRwqSjYvkQXmI86p.6nM7DdpQ2PUIxSKWqHE7s9Zwq8Hf63hEP2O3oYQSeMJDrEMFezP3FhIn.md7Fz1FGTpupLWhkPjIUQ5eOxvY; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:36:32 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "8233", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "d7702574-3efa-3b62-a743-1faf32c6f340", + "persistent" : true, + "insertionIndex" : 41 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-52d34b326f90.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-52d34b326f90.json new file mode 100644 index 00000000..96e8e103 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-52d34b326f90.json @@ -0,0 +1,48 @@ +{ + "id" : "f11d491d-e30e-3b9c-99a0-7689398730fc", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"o4-mini\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\"\n } ]\n }, {\n \"type\" : \"reasoning\",\n \"encrypted_content\" : \"gAAAAABqiIWOHz5M8h5bslZ6IoOp-cVpniJSp-o-Yy0QqwPVslWuroyJpr023KlTiUtvClh2hx4jjhIN-BonTL3w1DkConhfVQEWG1opx8cvsjcDaYYV_B6dsBNpqblR9DMMLgBHNIR18gbafISJWtlJGjECkbVq8p5Q0gWIRMfvZoDIGxDVxxqAB9wtRq-2knAcW7Q0tpoYVA485y83Msfm44WccYB2Y-Oo9LTtj3rn16wKwd6sTfhna5qVsyFNIYisr_VTEl6qsgad5c0HH9LOaxkAyVkRba4YpPi2N0UApv9dNY2t6lQ_q9p6_xeqjOhL8s3Mp11rGU27OWj3Nz6lDqml0nwaNejGYM18PI9bvuFELu90PRV0i5tZA4rcrPygx1n-osa1JWNCcqhVL-laF7qSBsY88JZqfkhqUApx4yvvekyXB-NXzCdR7JnEANPS7GW6z3PUX1rkiiRZ9GL47y7sdD-_AneBy-gZl5ARkrv4coJMpxUodYczfeCuvUbVJVmhWJK2FF_b9NsF3EZzU6CGcYx5O4O6NaWSv_wyycl9LDImHeUThlNbmAYi71awm8pCcjXvI8-BU8ueX0K3FAertEUEEJHzS06xECKgjGNgLros3HxNgPVZJzsHzCnct5TY6rjTMH59lE61siIoGZYSooP8wetZ5l3lijv6UJL5rgOkdCIIz8bqulOoPeWZgrXsNO_JIoV5Il5-caHTmyLj5pQ8bqnKiyq7IHnAWxlWAcfTd9T3z3FzbizFHdDT1XRXCyKOolfZCJgcpPa7fmS7HrDDTPqY2DLsywCXIVLzJRP-SN4bxLHUNdk2fjPcJN_t1kCKNH83HKOYQPbshPhiCEkuAdgPUWTVt3DocNujmOkKcIAmbLROwFSSg-j6oY28e6jMIWy-6KZgFf28l2iyupAhucKuW7VWTvmeWR9vZ1Nr8tCqG6En9VPaqDOCzSOeUPV1gVTg_HCA-TPLM0COTjjyFZi354u-CSAyaeSYyWQ21SMX8WHhhTGEMMAKUH5YPgaXXbhCQMULe5e00Dp9hVjszdmyMWwCGhYlCBUGDX311ygKcDMJA4Fxc89VceukP9Q4O6OFTBcc3mCSpZUBXdJ12XntKJwlgpfJ75_HiX7JyJi0QI799e9n8vxNcE5xr09ulNkb_Yv_nLrPOrJ3_D7dpCGjMSEjpxRpdXCZ5a78NMzzhx9Dwf4ZCyrYVaKb_SXjfndVoCoraRYKvrGNsidXYLrVGrWCA339upM-z34B5-7KitNgEPoKCnRHpjoyElze0N6IXgcI6jHK330NuK1jdfWeZboh0BDt3sP5yuQ8NIO29QrnQQT32fISeNBGNnyvcosADvRlTDEAymRshbx8myu0CgMcIXeLluEvH2jR6g7qLfQ-mctT8t0J6QV0WKCw6NBssAdGiMexvck-C-nipOdyz2coXPDoEcSKK6rf5GD9HS3XIv9I4FOdCZ2D0Yf3xe8Nx1qSdq3Mok3_X-mz3W8SDns3yKtZt9jiMG6qGJTojhhxZQzGR71gVej1V7DJZfDsS4FaPE3V6wANtT6nCMMQaZ91dey3G0drroffM7-4h58m3Pd-eYC_duqz0i1bc3DOBKAmJSWfMoSC0V4cKhVAFVOwmBwABu-QF-f7T5UXAVSbBSzpFXiZdq_7ZjPwW4o3raWxwQp2tJqLkZMDxv0m9epLByCGil2pIxydutNMjciWnQQAul6UlmPrQevo24_WjR55yLF2UTcvxcq2koF64syKGUVvm6daxg6Tyu_jwWTPC3bluKL7_iAFiYRN7bBXudRBo05vfg5LHLhgBNH-xJrnXX6h0QQyozNj-axMljlnVGz8s5FO2x1aFf9tVk_xDSa2_iEGo2pcoj_c_w_n3QZzhMU2vHSGfweLfBXbwoweXbdN6x5zAoatCoV6enaMsauD-4JsHdEORdF4bVx3cijrpyFNxuwhq8rundXgdCjuku2HMUS5d8dprPtkR_5qEwLnZfLd1a6taMOpQSUQeJAO9hycEP5IXsVaY1yrCsRmmeC1s2Uu5pTZANsYWvk-AsBAXBiRrnZbx1909pg0LYMihLphPsunmENm2KSpcVu1P35EW-_eV_sO3nUIiyrdbirk3Boc6Zeqbz5EmjRnZa7rcZJhnrRsCvEzRHiCgYUmQ_YFwXavpccpfZvBNPERa_yIRPAohnsxLWrN_bcAfde3nnWWnYf4g6r3bovU7VHit6PGOybQQaaQw71Rlzsdc_w4Ly5WtIzVx8GUqgXRxCgovt5o-ofncb2xo-na37NmO0uKJ0WrbBavpDTNsMrnoxGAUm4HDV49MVy6QR6Xy3jbuoOb29AE16v1V949O6veUDQODXVqmv3uA4H3VnlgBVeolkmM6zYFeyXbnx7HMwAUi0jnOELgdluE9dEU8ETk6o9T127A2XUVqTH20RDgLmO4cXAsS2dI2z8v-t6luzjdwMDra1KP6J3G6qaUK-c_OspKGn79N0_vpBmdU5OospataO_sxOqxCpwJdRaKFJ5y4op_PeFhPsXlOG3LIq1Lpm_B5Rkd2glEkCe2wJbAQPq9RgkUyddd5b1oLg5wFwnxcxr8hCplsbkkm2kDjDmscoo8i-1Im6EXxXnkq0dIQ_5UdGerHVfxYCoZcbAFQT1maw3-bVaMyi9HuKD1sMOXOxMclgm_1vEQMt_xp0iJapp44MTZOI8hd1KZVwhEx---qJP48BqmFe_mGN1uEv7uzVsOYdiyFGCmudEGDHQ2xBcsEuhLZn3w2yh6keCGVBzl9F0T7329oIZtxPvziEILeSXjt-S4Pld5DDYMjCnOue6wK9IA4eQNxKdXqqnwh608aJUDnFRIefV-mN29Uth9AwOVmEQ5QC59_qZF1Q7w7t9qgYH-9gwt41j2kijxWRVBvDxFaqU6rijChAcwJOIZT6N8BOE0qNQp9qzilVUlU9Eh7QV0tdNB4If6NKKRprGs87b_DQlOSlusTsgDIdPMueyApg_Cr9mlcBfEhI0wWlRZYTrnG44bHZb4I1Tsw4g-lw8vRpAfEWjqzR6Gb3Ikdj03tD5VQfFwemdRzQ81XTOnP2vlSsTY-yiIlDF6eWBw7-mOZNr_eh9LQNEJKCgOiessNqqrlWJ3snXqk3vN1BBDp_rmWt63Qb271JPI9xTwq2GrfA4xf0cN09qhKCvK7zBNUlVYP7nGDSJDg22gdyspzMnTMXGA_1X_UneGnQGgfjM86YfvMqePAevRoSdEqG9MKgSJIEXNGRF8KxCLct0KauQ1XN0Tf4oqpFXIhCWELkKZIB6jPDAX7uGRbbLkvL5t2v42mgpgpU0DeUs32MNynsTB_XeOb1fr_KULKCiJ4ArezWsVB756ITArMbBCn-7oOMqAdKl-SfCTqCLKaWzxuOyGmTmrfRmMmOsDaVgpViz5ZAnvY1BzK-iEnCCrS8gVTzyWT9OrwDGHH_Dqwf2rGuNct1rDBJTQlTcYV6e5vUNBtYLcuPEUX1dK_iG8i7coFfxlgi1wVEdFczmThdof1KKnFeF0OZz5tjlmbukLHAmwpd_mLXlHAm4m0QXdUmToT7KrYAJ8ty3fVsr1oyd20Eeh7NWaGUNQt9-yI9l3A7MVwZ9w5L3a10qRqAdnsjOg-P0OGOOwm96Si4KDa0T3B8jWha99m6rU1hBVMD5ivi9HEYLujAdZpFsf0BqStzf2A2u0TdQVP4sNQSaZF7XVMqe1omzAxLYm2LVHbrM96PzD5i4T4P-t8i7OtSKwzH7vJ0xCG9ubtdcn_UrzYG0gE5GOl918f-ZDwbx2tL0pfkFCRQDzXN94zh1PgoIBsNBxLaFINuHL62UokLmyOW9v5yNbN-CeTC3S2MCgboEiLcOgG-gTwjJu9YFY_Ri_vPiJhEo6Nr3HWtD_PCW2UrRyN58Nh0IQZQPYRk_FFpoMzU7V0jPSUKwlb1B3LPjMHbMEYNQDq0wfRC4DCYM7ymyXD4wNkgfebqt_XmPMnfUIFCvWpFcHJDcxogAqHHSRnDOOhsZ85rDkgoY5KS1y-tYPjokjxZ2cj-BE-f7M-1BSDTpPejCUf1P2U7VPDHyX6o7w-fAyz0hf-LW9O1bnE-FmooB7Uvjwvobin3XQHNG5u9bMpv7lFuPp76KeABJACv3BCBNzxX3EjNiFCI9H3kb0oWrJ0stiPai1tey4qm3iOIE6SqZmgzXV-qrvhUdyxjvopZr2M0N4agZExH7eQU5J2KYIDHqel8dn9Gcd3pDzkCNf--hR7To2jgAd5eJg-WPGj1G13noH1SjMEnsSUnY-ij5IkKXEE4Z0UECiCk524twhc4GUAbOTO0-w2MwRPQxY0sKdLvOonwIolHqOUZech1idkEWSW-11RuG80WaZ6iM92V0e9GyoblW9BjToPeK412o3UdK4ZzuzNMYAlnPnTFiRdhgnZXlLD0XzdNA5RA3NThOU-w3BCCQOzgDzzC-95_KqA7oGajbK9P7jpmeYNvJ73eG8GH0zcE2B9YUdXnb7HG61uMPV2wO0YcTVpGG1Uze8S7L1PPR_kTqgnyC1f2odlyi_xcByxB98eZEHHA0-fJMZ0DaFNt58wpfdoJEVYb3biOV1t1VD9_n7cWQM8kIXIWFEkFmCsEglJX9vpKbkdlYJ7KUv2EIs4cUCaEUUF-UC1YEyacc7goiDtcSKXCnsR97vswEROuACLmG6g_pG-7CEYyfo6T0oeTxuWa9qwwKECYppXJAagHgViYe_ONVhjCsfSgkMbu6Lv2dQgSWIXFwRZds1yc16r60NBC866HsIiEOtUk5l4z6FDbBWaEsFaFN7R7f3sw3OniMDFcldE85iYg7ZMAhnyRrxSF9yohy4H1yEug3wy5qqesLAnxaHCah8WA2yUY6CUQSOam4K7ohXsrhqgus=\",\n \"summary\" : [ {\n \"type\" : \"summary_text\",\n \"text\" : \"**Identifying a pattern**\\n\\nThe user wants to find the pattern in the sequence: 2, 6, 12, 20, 30. I notice that the numbers can be expressed as products: 2 = 1*2, 6 = 2*3, 12 = 3*4, and so on, leading to the formula for the nth term being n(n+1). This yields terms like 42 for n=6. These numbers are known as pronic, oblong, or rectangular numbers, and can also be derived as a_n = n^2 + n, depending on how the indexing starts.**Clarifying the formula for pronic numbers**\\n\\nIf we start indexing at 0, the formula a_n = n(n+1) gives a_1 = 2. I initially thought of triangular numbers, but these aren't quite right. They're actually double triangular numbers because a_n = 2*T_n. Ultimately, the simplest expression is a_n = n(n+1) or n^2 + n. The sequence shows an increasing difference of 2, confirming it's a second-order polynomial. The main takeaway is that these are pronic numbers, with the formula being a_n = n(n+1) when starting at 1.**Explaining indexing for pronic numbers**\\n\\nIf we start from n=0, the formula a_n = n(n+1) gives a_0 = 0, but the sequence begins at 2, which suggests starting from n=1. So, the pattern is that of pronic (or oblong) numbers, with the nth term expressed as a_n = n(n+1) for n starting at 1. If I were to index from 0, the formula would be a_{n}= (n+1)(n+2). Since the user specifically asked for the pattern and formula, this covers the complete answer.\"\n } ]\n }, {\n \"type\" : \"message\",\n \"role\" : \"assistant\",\n \"content\" : [ {\n \"type\" : \"output_text\",\n \"text\" : \"The terms are the “pronic” (or oblong) numbers—each one is the product of two consecutive integers:\\n\\n1·2 = 2 \\n2·3 = 6 \\n3·4 = 12 \\n4·5 = 20 \\n5·6 = 30 \\n\\nIf you call the first term a₁, the second a₂, etc., then\\n\\n aₙ = n·(n + 1) \\n\\nEquivalently,\\n\\n aₙ = n² + n.\"\n } ]\n }, {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"Using the pattern you discovered, what would be the 10th term? And can you find the sum of the first 10 terms?\"\n } ]\n } ],\n \"stream\" : false,\n \"store\" : false,\n \"include\" : [ \"reasoning.encrypted_content\" ],\n \"reasoning\" : {\n \"effort\" : \"high\",\n \"summary\" : \"detailed\"\n }\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-52d34b326f90.json", + "headers" : { + "x-request-id" : "req_22d03547aca54af09ea9f1e73156bcdf", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3a5f0afc761e-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999607", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:06:29 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=gs065sHKQqIS2tc33H.vPP9k3bcX6M2Xre0_HHooHEo-1787331983.2069604-1.0.1.1-nlgybKK6JYBLdVsFGckmVZkRhQ9IpYtm7tVBpwjY1hauMkc3FxUHggyum1LNVYwYMOATvNQyemEXcclyNzf8WjaP.JAKHI5PEbp_1DOib.t.CdKYj8uNt_bN8I_CnGtL; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:36:29 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "6381", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "f11d491d-e30e-3b9c-99a0-7689398730fc", + "persistent" : true, + "insertionIndex" : 42 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-7fc8497bbe6a.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-7fc8497bbe6a.json new file mode 100644 index 00000000..aad377a9 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-7fc8497bbe6a.json @@ -0,0 +1,48 @@ +{ + "id" : "a609787f-ace8-3110-a131-eae3f56c8c70", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"Search the web for a recent AI news headline and answer in one sentence.\"\n } ]\n } ],\n \"stream\" : false,\n \"store\" : false,\n \"tools\" : [ {\n \"type\" : \"web_search_preview\",\n \"search_context_size\" : \"low\"\n } ]\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-7fc8497bbe6a.json", + "headers" : { + "x-request-id" : "req_9595963a9fb74e60990e288f703b2fd6", + "x-ratelimit-limit-tokens" : "30000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3cc25c13a3bf-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "6ms", + "x-ratelimit-remaining-tokens" : "29999665", + "x-ratelimit-remaining-requests" : "9999", + "Date" : "Fri, 21 Aug 2026 17:08:03 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=Q__zsiQeDjcC0IjDoxmI2_VngyH1hVU1GYz4MHfMOnc-1787332081.0170724-1.0.1.1-.e7MlhExq7OQrSLHg5W.exlft4DcjNfqDyBP1VIhfaiqCdNNPde9bJ7QvyzxuhEehRHCfbSJxziWqx12qH4P9Uj9T9tOMCt4OxGAHHk3fySpzAzIU74fI3y.tbFFLDko; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:38:03 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "10000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "2057", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "a609787f-ace8-3110-a131-eae3f56c8c70", + "persistent" : true, + "insertionIndex" : 57 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-863a18378a48.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-863a18378a48.json new file mode 100644 index 00000000..014804ad --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-863a18378a48.json @@ -0,0 +1,48 @@ +{ + "id" : "a5fb65e6-7334-3418-9d94-15a9941968b7", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"o4-mini\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\"\n } ]\n } ],\n \"stream\" : false,\n \"store\" : false,\n \"include\" : [ \"reasoning.encrypted_content\" ],\n \"reasoning\" : {\n \"effort\" : \"high\",\n \"summary\" : \"detailed\"\n }\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-863a18378a48.json", + "headers" : { + "x-request-id" : "req_e62cffde097243c584bdeedcf64f6b1a", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3a037c98ba34-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999752", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:06:23 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=K6F1Hg4nly2WUJMH2A0RJKj9Rl1jKUJKGfh0tfTy6YE-1787331968.5613189-1.0.1.1-XtC3jfyYFQG9Its2Z9LIPk3RqdOTNJK70fGc8TVLfWtYKSbF9dnabCOnmGFaH0apZerr2ShZ6ltcLtnkBKPQwx6ipFQ9VWkN9BduK3V5.u6IGOsbq0ClwJF9p5gnjsBT; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:36:23 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "14223", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "a5fb65e6-7334-3418-9d94-15a9941968b7", + "persistent" : true, + "insertionIndex" : 45 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-892784bdb435.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-892784bdb435.json new file mode 100644 index 00000000..e9e22401 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-892784bdb435.json @@ -0,0 +1,48 @@ +{ + "id" : "bbd3d5ab-69e7-3a37-9c5b-ff00f3acd4f5", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"What is the capital of France?\"\n } ]\n } ],\n \"stream\" : false,\n \"store\" : false\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-892784bdb435.json", + "headers" : { + "x-request-id" : "req_a01a14159d574d1d8b5dd863978f5f03", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3d2d9f97b9dc-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999967", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:08:18 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=3nKsGMZTYQq1qrUgzqEgvgCJ0oHk2VuADm8vrDUXqj4-1787332098.1738515-1.0.1.1-fXFxh9DJ1l9MCXqXkuMGQp5BB82kFB7vKcitdJT_ycigcplzSRhZHv.DbJIaIX0fe7PQZbxGsZcXa8e06cC1jGLGNOx6ODBruwsTUYdjLvRX3CQbrrH3IWwID5PKFR9X; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:38:18 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "599", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "bbd3d5ab-69e7-3a37-9c5b-ff00f3acd4f5", + "persistent" : true, + "insertionIndex" : 60 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-9ec1c9b92ff9.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-9ec1c9b92ff9.json new file mode 100644 index 00000000..ec02e8ac --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-9ec1c9b92ff9.json @@ -0,0 +1,48 @@ +{ + "id" : "39f0d87b-178c-33a6-831e-c551fb849139", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"is it hotter in Paris or New York right now?\"\n } ]\n } ],\n \"stream\" : false,\n \"store\" : false,\n \"temperature\" : 0.0,\n \"tools\" : [ {\n \"type\" : \"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 \"type\" : \"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}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-9ec1c9b92ff9.json", + "headers" : { + "x-request-id" : "req_3d0356d233a746fe809040b0d865a54e", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3d360d1f75ca-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999705", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:08:21 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=pz_2ZHtKln5kmQYXQrXjRFjxpHkrFNur.hXk0X6OSh0-1787332099.528624-1.0.1.1-hyDAGM9r6UVwm2CQW2jAr76dpw1CQrsRS_Hw9tEIzpqKkKF9QNjru3fMmwJ8aK1MR8NQwzGVKL.eDWc9QAvt96TWH2bVQqaA0A0dGU9AKx7uuNxkaya7sI5OjNzevQiv; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:38:21 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "1522", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "39f0d87b-178c-33a6-831e-c551fb849139", + "persistent" : true, + "insertionIndex" : 59 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-bdae47d54959.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-bdae47d54959.json new file mode 100644 index 00000000..f692571b --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-bdae47d54959.json @@ -0,0 +1,48 @@ +{ + "id" : "3ec1669f-6378-3bd4-b5cd-dc7acc2f8acd", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"input\":[{\"role\":\"user\",\"content\":\"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\"}],\"model\":\"o4-mini\",\"reasoning\":{\"effort\":\"high\",\"summary\":\"detailed\"}}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-bdae47d54959.json", + "headers" : { + "x-request-id" : "req_726fd390ed4b4fc586b365582ce73c3e", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb3a2b6c3eba00-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999752", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 21 Aug 2026 17:06:23 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=LXuB65HamlnEpDTA.J31LBHa8vIB_fo1ycAciwuyO2E-1787331974.94298-1.0.1.1-udeZVWacugoznQj4Ipmgr4EJNwBbNK3KlqCgHJRqMNhpauxh.qYundOH1AupBhH8XVM4ukexhWWFfvXU6y6mRxfHdl_DG8RHpyk8.go7C0k3uZxlUGHhMoUyriBD7l_d; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 17:36:23 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "8820", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "3ec1669f-6378-3bd4-b5cd-dc7acc2f8acd", + "persistent" : true, + "insertionIndex" : 43 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-c1b40cd66fd7.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-c1b40cd66fd7.json new file mode 100644 index 00000000..22dd86e7 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-c1b40cd66fd7.json @@ -0,0 +1,42 @@ +{ + "id" : "c2474bf5-50bc-3bbb-8eb0-7ce0526dc342", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"gpt-4o-mini\",\n \"input\" : [ {\n \"type\" : \"message\",\n \"role\" : \"user\",\n \"content\" : [ {\n \"type\" : \"input_text\",\n \"text\" : \"What is the capital of France?\"\n } ]\n } ],\n \"stream\" : true,\n \"store\" : false,\n \"temperature\" : 0.0\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-c1b40cd66fd7.txt", + "headers" : { + "x-request-id" : "req_e2731e417197412cad96fb0d8bda980f", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2eb794dc812a3c2-SEA", + "X-Content-Type-Options" : "nosniff", + "Date" : "Fri, 21 Aug 2026 17:49:21 GMT", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=i11f1Ogoz2tN0wZuIdzr6h9_ArHFIcwAZbjWQXXE0pw-1787334560.9299479-1.0.1.1-9JtmG_DDSejlvn37UEnOB.aVJXsRdL.9BmhDnCQ8O_w5ILYGg50VKUj48xqG3fRWw8.keh1Pw9XkEKnxiPCtdaGrElLG53XESpJefLa5kS44PHAf9_Q_Lhx.OJUNVnVy; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 21 Aug 2026 18:19:21 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "119", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "c2474bf5-50bc-3bbb-8eb0-7ce0526dc342", + "persistent" : true, + "insertionIndex" : 62 +} \ No newline at end of file