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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/light-plants-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@braintrust/deepseek-harness": major
"braintrust": major
---

feat!: Add `BRAINTRUST_CAPTURE_ATTACHMENTS` env var option and `captureAttachments` logger option for all integrations
67 changes: 67 additions & 0 deletions e2e/helpers/attachment-capture-assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { expect, test } from "vitest";
import { withScenarioHarness } from "./scenario-harness";
import { findLatestSpan } from "./trace-selectors";
import { captureCases } from "./attachment-capture-scenario.mjs";

export function defineAttachmentCaptureTests(options: {
scenarioDir: string;
originalScenarioDir: string;
provider: "openai" | "anthropic";
packageName: string;
variantKey: string;
}) {
for (const mode of ["wrapped", "auto"]) {
test(`attachment capture policies (${options.variantKey}, ${mode})`, async () => {
await withScenarioHarness(async (harness) => {
await harness.runNodeScenarioDir({
scenarioDir: options.scenarioDir,
entry: "scenario.capture.mjs",
nodeArgs: mode === "auto" ? ["--import", "braintrust/hook.mjs"] : [],
env: {
CAPTURE_PACKAGE_NAME: options.packageName,
CAPTURE_WRAPPED: String(mode === "wrapped"),
BRAINTRUST_CAPTURE_ATTACHMENTS: "",
},
runContext: {
variantKey: options.variantKey,
originalScenarioDir: options.originalScenarioDir,
// The same Chat Completions request works on all supported SDK majors.
// Its real-provider recording lives in the existing v6 media cassette.
...(options.provider === "openai"
? { cassette: { variantKey: "openai-v6" } }
: {}),
},
timeoutMs: 180_000,
});
const events = harness.events();
for (const testCase of captureCases) {
const root = findLatestSpan(
events,
`attachment-capture-${testCase.name}`,
);
expect(root).toBeDefined();
const children = events.filter(
(event) =>
event.row.root_span_id === root?.row.root_span_id &&
event.span.id !== root?.span.id,
);
expect(children.length).toBeGreaterThan(0);
const payload = JSON.stringify(children);
if (testCase.enabled)
expect(payload).toContain("braintrust_attachment");
else {
expect(payload).not.toContain("braintrust_attachment");
expect(payload).not.toContain('"file_data":');
expect(payload).not.toContain('"b64_json":');
expect(payload).not.toContain('"data":');
expect(payload).not.toContain("data:image/png;base64,");
}
expect(payload).toContain("completion_tokens");
expect(children.some((event) => event.row.output !== undefined)).toBe(
true,
);
}
});
}, 240_000);
}
}
125 changes: 125 additions & 0 deletions e2e/helpers/attachment-capture-scenario.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { initLogger, wrapOpenAI, wrapAnthropic } from "braintrust";
import { MINIMAL_PNG_BASE64 } from "./media-fixtures.mjs";
import { scopedName } from "./provider-runtime.mjs";

export const captureCases = [
{ name: "default", env: "", enabled: false },
{ name: "local-default", env: "", global: true, local: true, enabled: false },
{
name: "local-enabled",
env: "false",
global: false,
local: true,
option: true,
enabled: true,
},
{
name: "local-disabled",
env: "true",
global: true,
local: true,
option: false,
enabled: false,
},
{
name: "local-environment",
env: "true",
global: false,
local: true,
enabled: true,
},
{ name: "global-enabled", env: "false", option: true, enabled: true },
{ name: "global-disabled", env: "true", option: false, enabled: false },
];

export async function runAttachmentCaptureScenario(provider, scenarioUrl, sdk) {
const wrapped = process.env.CAPTURE_WRAPPED === "true";
let request;
if (provider === "anthropic") {
const raw = new sdk.default({
apiKey: process.env.ANTHROPIC_API_KEY,
baseURL: process.env.ANTHROPIC_BASE_URL,
});
const client = wrapped ? wrapAnthropic(raw) : raw;
const data = (
await readFile(new URL("./test-image.png", scenarioUrl))
).toString("base64");
request = async () => {
const result = await client.messages.create({
model: "claude-haiku-4-5",
max_tokens: 32,
temperature: 0,
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Describe the attached image in one short sentence.",
},
{
type: "image",
source: { type: "base64", media_type: "image/png", data },
},
],
},
],
});
assert.ok(result.content.length > 0);
};
} else if (provider === "openai") {
const raw = new sdk.default({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL,
});
const client = wrapped ? wrapOpenAI(raw) : raw;
request = async () => {
const result = await client.chat.completions.create({
model: "gpt-4o-mini-2024-07-18",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Describe this image in three words or fewer.",
},
{
type: "image_url",
image_url: {
url: `data:image/png;base64,${MINIMAL_PNG_BASE64}`,
},
},
],
},
],
max_tokens: 24,
temperature: 0,
});
assert.ok(result.choices[0].message.content);
};
}
const projectName = scopedName("tmp-luca-attachment-capture");
for (const testCase of captureCases) {
process.env.BRAINTRUST_CAPTURE_ATTACHMENTS = testCase.env;
if (testCase.local)
initLogger({ projectName, captureAttachments: testCase.global });
const logger = initLogger({
projectName,
setCurrent: !testCase.local,
captureAttachments: testCase.option,
});
await logger.traced(request, {
name: `attachment-capture-${testCase.name}`,
event: {
metadata: {
scenario: `${provider}-instrumentation`,
testRunId: process.env.BRAINTRUST_E2E_RUN_ID,
},
},
});
await logger.flush();
}
}
2 changes: 2 additions & 0 deletions e2e/scenarios/ai-sdk-instrumentation/scenario.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describeVariants("variants", () => {
await runScenarioDir({
entry: scenario.wrapperEntry,
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
AI_SDK_PACKAGE_NAME: scenario.packageName,
AI_SDK_OPENAI_PACKAGE_NAME: scenario.openaiModuleName,
AI_SDK_SUPPORTS_EVALUATE_STRING_MODEL: String(
Expand Down Expand Up @@ -116,6 +117,7 @@ describeVariants("variants", () => {
await runNodeScenarioDir({
entry: scenario.autoEntry,
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
AI_SDK_PACKAGE_NAME: scenario.packageName,
AI_SDK_OPENAI_PACKAGE_NAME: scenario.openaiModuleName,
AI_SDK_SUPPORTS_EVALUATE_STRING_MODEL: String(
Expand Down
10 changes: 10 additions & 0 deletions e2e/scenarios/anthropic-instrumentation/scenario.capture.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { runMain } from "../../helpers/provider-runtime.mjs";
import { runAttachmentCaptureScenario } from "../../helpers/attachment-capture-scenario.mjs";

runMain(async () =>
runAttachmentCaptureScenario(
"anthropic",
import.meta.url,
await import(process.env.CAPTURE_PACKAGE_NAME),
),
);
21 changes: 19 additions & 2 deletions e2e/scenarios/anthropic-instrumentation/scenario.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { defineAttachmentCaptureTests } from "../../helpers/attachment-capture-assertions";
import { describe } from "vitest";
import {
prepareScenarioDir,
Expand Down Expand Up @@ -52,7 +53,10 @@ describe.concurrent("variants", () => {
runScenario: async ({ runScenarioDir }) => {
await runScenarioDir({
entry: scenario.wrapperEntry,
env: { ANTHROPIC_PACKAGE_NAME: scenario.dependencyName },
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
ANTHROPIC_PACKAGE_NAME: scenario.dependencyName,
},
runContext: {
variantKey: scenario.snapshotName,
originalScenarioDir,
Expand All @@ -76,7 +80,10 @@ describe.concurrent("variants", () => {
runScenario: async ({ runNodeScenarioDir }) => {
await runNodeScenarioDir({
entry: scenario.autoEntry,
env: { ANTHROPIC_PACKAGE_NAME: scenario.dependencyName },
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
ANTHROPIC_PACKAGE_NAME: scenario.dependencyName,
},
nodeArgs: ["--import", "braintrust/hook.mjs"],
runContext: {
variantKey: scenario.snapshotName,
Expand All @@ -98,3 +105,13 @@ describe.concurrent("variants", () => {
});
}
});

for (const scenario of anthropicScenarios) {
defineAttachmentCaptureTests({
scenarioDir,
originalScenarioDir,
provider: "anthropic",
packageName: scenario.dependencyName,
variantKey: scenario.snapshotName,
});
}
1 change: 1 addition & 0 deletions e2e/scenarios/elevenlabs-instrumentation/scenario.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ for (const variant of ["elevenlabs-v2", "elevenlabs-v2-latest"]) {
? ["--import", "braintrust/hook.mjs"]
: [],
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
ELEVENLABS_PACKAGE_NAME: variant.replace(
"elevenlabs-",
"elevenlabs-sdk-",
Expand Down
10 changes: 8 additions & 2 deletions e2e/scenarios/google-genai-instrumentation/scenario.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ describe.concurrent("variants", () => {
runScenario: async ({ runScenarioDir }) => {
await runScenarioDir({
entry: scenario.wrapperEntry,
env: { GOOGLE_GENAI_PACKAGE_NAME: scenario.dependencyName },
env: {
GOOGLE_GENAI_PACKAGE_NAME: scenario.dependencyName,
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
},
runContext: {
variantKey: scenario.snapshotName,
originalScenarioDir,
Expand All @@ -75,7 +78,10 @@ describe.concurrent("variants", () => {
runScenario: async ({ runNodeScenarioDir }) => {
await runNodeScenarioDir({
entry: scenario.autoEntry,
env: { GOOGLE_GENAI_PACKAGE_NAME: scenario.dependencyName },
env: {
GOOGLE_GENAI_PACKAGE_NAME: scenario.dependencyName,
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
},
nodeArgs: ["--import", "braintrust/hook.mjs"],
runContext: {
variantKey: scenario.snapshotName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ describe.concurrent("variants", () => {
runScenario: async ({ runScenarioDir }) => {
await runScenarioDir({
entry: "scenario.ts",
env: { GOOGLE_GENERATIVE_AI_PACKAGE_NAME: scenario.dependencyName },
env: {
GOOGLE_GENERATIVE_AI_PACKAGE_NAME: scenario.dependencyName,
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
},
runContext: {
originalScenarioDir,
variantKey: scenario.snapshotName,
Expand All @@ -50,7 +53,10 @@ describe.concurrent("variants", () => {
await runNodeScenarioDir({
entry: "scenario.mjs",
nodeArgs: ["--import", "braintrust/hook.mjs"],
env: { GOOGLE_GENERATIVE_AI_PACKAGE_NAME: scenario.dependencyName },
env: {
GOOGLE_GENERATIVE_AI_PACKAGE_NAME: scenario.dependencyName,
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
},
runContext: {
originalScenarioDir,
variantKey: scenario.snapshotName,
Expand Down
2 changes: 2 additions & 0 deletions e2e/scenarios/groq-instrumentation/scenario.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe.concurrent("variants", () => {
await runScenarioDir({
entry: "scenario.ts",
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
GROQ_AUDIO_FILE: audioFile,
GROQ_PACKAGE_NAME: scenario.dependencyName,
},
Expand All @@ -66,6 +67,7 @@ describe.concurrent("variants", () => {
await runNodeScenarioDir({
entry: "scenario.mjs",
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
GROQ_AUDIO_FILE: audioFile,
GROQ_PACKAGE_NAME: scenario.dependencyName,
},
Expand Down
10 changes: 10 additions & 0 deletions e2e/scenarios/openai-instrumentation/scenario.capture.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { runMain } from "../../helpers/provider-runtime.mjs";
import { runAttachmentCaptureScenario } from "../../helpers/attachment-capture-scenario.mjs";

runMain(async () =>
runAttachmentCaptureScenario(
"openai",
import.meta.url,
await import(process.env.CAPTURE_PACKAGE_NAME),
),
);
11 changes: 11 additions & 0 deletions e2e/scenarios/openai-instrumentation/scenario.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { defineAttachmentCaptureTests } from "../../helpers/attachment-capture-assertions";
import { describe, it } from "vitest";
import {
prepareScenarioDir,
Expand Down Expand Up @@ -136,3 +137,13 @@ describe.concurrent("variants", () => {
});
}
});

for (const scenario of openaiScenarios) {
defineAttachmentCaptureTests({
scenarioDir,
originalScenarioDir,
provider: "openai",
packageName: scenario.dependencyName,
variantKey: scenario.snapshotName,
});
}
10 changes: 8 additions & 2 deletions e2e/scenarios/strands-agent-sdk-instrumentation/scenario.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ describe.concurrent("variants", () => {
runScenario: async ({ runScenarioDir }) => {
await runScenarioDir({
entry: scenario.wrapperEntry,
env: { STRANDS_AGENT_SDK_PACKAGE_NAME: scenario.dependencyName },
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
STRANDS_AGENT_SDK_PACKAGE_NAME: scenario.dependencyName,
},
runContext: {
variantKey: scenario.variantKey,
originalScenarioDir,
Expand All @@ -68,7 +71,10 @@ describe.concurrent("variants", () => {
runScenario: async ({ runNodeScenarioDir }) => {
await runNodeScenarioDir({
entry: scenario.autoEntry,
env: { STRANDS_AGENT_SDK_PACKAGE_NAME: scenario.dependencyName },
env: {
BRAINTRUST_CAPTURE_ATTACHMENTS: "true",
STRANDS_AGENT_SDK_PACKAGE_NAME: scenario.dependencyName,
},
nodeArgs: ["--import", "braintrust/hook.mjs"],
runContext: {
variantKey: scenario.variantKey,
Expand Down
Loading
Loading