Skip to content

Commit 704597f

Browse files
authored
fix: Record tools in metadata for AI SDK and Google gen ai (#1570)
Record tools for AI SDK in metadata for ai sdk and google gen ai Ref: https://braintrustdata.slack.com/archives/C083GCUTVDZ/p1773612247369699
1 parent 42e5ff7 commit 704597f

File tree

11 files changed

+564
-109
lines changed

11 files changed

+564
-109
lines changed

e2e/scenarios/wrap-ai-sdk-generation-traces/__snapshots__/scenario.test.ts.snap

Lines changed: 320 additions & 4 deletions
Large diffs are not rendered by default.

e2e/scenarios/wrap-ai-sdk-generation-traces/scenario.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,13 @@ test.each(wrapAISDKScenarios)(
323323

324324
expect(toolParent?.input).toBeDefined();
325325
expect(toolParent?.output).toBeDefined();
326+
expect(
327+
(toolParent?.row.metadata as { tools?: unknown } | undefined)?.tools,
328+
).toMatchObject({
329+
get_weather: {
330+
description: "Get the weather for a location",
331+
},
332+
});
326333
if (supportsToolExecution) {
327334
expect(toolModelSpans.length).toBeGreaterThanOrEqual(2);
328335
expect(toolSpans.length).toBeGreaterThanOrEqual(1);
@@ -416,6 +423,7 @@ test.each(wrapAISDKScenarios)(
416423
"provider",
417424
"model",
418425
"operation",
426+
"tools",
419427
"braintrust",
420428
"scenario",
421429
]),

e2e/scenarios/wrap-google-genai-content-traces/__snapshots__/scenario.test.ts.snap

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -808,28 +808,6 @@ exports[`wrap-google-genai-content-traces captures generate, attachment, stream,
808808
"mode": "ANY",
809809
},
810810
},
811-
"tools": [
812-
{
813-
"functionDeclarations": [
814-
{
815-
"description": "Get the current weather in a given location",
816-
"name": "get_weather",
817-
"parametersJsonSchema": {
818-
"properties": {
819-
"location": {
820-
"description": "The city and state or city and country",
821-
"type": "string",
822-
},
823-
},
824-
"required": [
825-
"location",
826-
],
827-
"type": "object",
828-
},
829-
},
830-
],
831-
},
832-
],
833811
},
834812
"contents": {
835813
"text": "Use the get_weather function for Paris, France. Do not answer from memory.",
@@ -849,6 +827,28 @@ exports[`wrap-google-genai-content-traces captures generate, attachment, stream,
849827
"mode": "ANY",
850828
},
851829
},
830+
"tools": [
831+
{
832+
"functionDeclarations": [
833+
{
834+
"description": "Get the current weather in a given location",
835+
"name": "get_weather",
836+
"parametersJsonSchema": {
837+
"properties": {
838+
"location": {
839+
"description": "The city and state or city and country",
840+
"type": "string",
841+
},
842+
},
843+
"required": [
844+
"location",
845+
],
846+
"type": "object",
847+
},
848+
},
849+
],
850+
},
851+
],
852852
},
853853
"metrics": {
854854
"start": 0,

e2e/scenarios/wrap-google-genai-content-traces/scenario.test.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,23 +147,30 @@ test("wrap-google-genai-content-traces captures generate, attachment, stream, ea
147147

148148
expect(JSON.stringify(attachmentSpan?.input)).toContain("file.png");
149149

150-
const toolInput = toolSpan?.input as
150+
const toolMetadata = toolSpan?.row.metadata as
151151
| {
152-
config?: {
153-
tools?: Array<{
154-
functionDeclarations?: Array<{ name?: string }>;
155-
}>;
156-
};
152+
tools?: Array<{
153+
functionDeclarations?: Array<{ name?: string }>;
154+
}>;
157155
}
158156
| undefined;
159157
expect(
160-
toolInput?.config?.tools?.some((tool) =>
158+
toolMetadata?.tools?.some((tool) =>
161159
tool.functionDeclarations?.some(
162160
(declaration) => declaration.name === "get_weather",
163161
),
164162
),
165163
).toBe(true);
166164

165+
const toolInput = toolSpan?.input as
166+
| {
167+
config?: {
168+
tools?: Array<unknown>;
169+
};
170+
}
171+
| undefined;
172+
expect(toolInput?.config?.tools).toBeUndefined();
173+
167174
const toolOutput = toolSpan?.output as
168175
| {
169176
candidates?: Array<{

js/src/instrumentation/plugins/ai-sdk-plugin.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ vi.mock("../../isomorph", () => ({
1010
import { AISDKPlugin } from "./ai-sdk-plugin";
1111
import { Attachment } from "../../logger";
1212
import iso from "../../isomorph";
13+
import { serializeAISDKToolsForLogging } from "../../wrappers/ai-sdk/tool-serialization";
1314

1415
const mockNewTracingChannel = iso.newTracingChannel as ReturnType<typeof vi.fn>;
1516

@@ -840,6 +841,36 @@ describe("AI SDK utility functions", () => {
840841
expect(metadata.model).toBeUndefined();
841842
expect(metadata.braintrust.integration_name).toBe("ai-sdk");
842843
});
844+
845+
it("should put tools in metadata", () => {
846+
const params = {
847+
model: "gpt-4",
848+
tools: {
849+
echo: {
850+
description: "Echo the message",
851+
execute: async () => "ok",
852+
parameters: {
853+
type: "object",
854+
properties: {
855+
message: {
856+
type: "string",
857+
},
858+
},
859+
},
860+
},
861+
},
862+
};
863+
const metadata = extractMetadataFromParams(params);
864+
expect(metadata.tools).toMatchObject({
865+
echo: {
866+
description: "Echo the message",
867+
parameters: {
868+
type: "object",
869+
},
870+
},
871+
});
872+
expect(metadata.tools).not.toHaveProperty("echo.execute");
873+
});
843874
});
844875

845876
describe("processAISDKOutput", () => {
@@ -1189,6 +1220,10 @@ function extractMetadataFromParams(params: any): Record<string, any> {
11891220
if (provider) {
11901221
metadata.provider = provider;
11911222
}
1223+
const tools = serializeAISDKToolsForLogging(params.tools);
1224+
if (tools) {
1225+
metadata.tools = tools;
1226+
}
11921227

11931228
return metadata;
11941229
}

js/src/instrumentation/plugins/ai-sdk-plugin.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { traceStreamingChannel, unsubscribeAll } from "../core/channel-tracing";
33
import { SpanTypeAttribute } from "../../../util/index";
44
import { getCurrentUnixTimestamp } from "../../util";
55
import { processInputAttachments } from "../../wrappers/attachment-utils";
6+
import { serializeAISDKToolsForLogging } from "../../wrappers/ai-sdk/tool-serialization";
67
import { aiSDKChannels } from "./ai-sdk-channels";
78
import type {
89
AISDKCallParams,
@@ -234,8 +235,15 @@ export class AISDKPlugin extends BasePlugin {
234235
function processAISDKInput(params: AISDKCallParams): unknown {
235236
if (!params) return params;
236237

237-
// Use the attachment processing from the manual wrapper
238-
return processInputAttachments(params);
238+
// Use the attachment processing from the manual wrapper, but keep tool
239+
// definitions in metadata to match the OpenAI wrapper convention.
240+
const input = processInputAttachments(params);
241+
if (!input || typeof input !== "object" || Array.isArray(input)) {
242+
return input;
243+
}
244+
245+
const { tools: _tools, ...rest } = input as Record<string, unknown>;
246+
return rest;
239247
}
240248

241249
/**
@@ -260,6 +268,10 @@ function extractMetadataFromParams(
260268
if (provider) {
261269
metadata.provider = provider;
262270
}
271+
const tools = serializeAISDKToolsForLogging(params.tools);
272+
if (tools) {
273+
metadata.tools = tools;
274+
}
263275

264276
return metadata;
265277
}

js/src/wrappers/ai-sdk/ai-sdk.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,30 @@ describe("ai sdk client unit tests", TEST_SUITE_OPTIONS, () => {
13961396
// Should have at least 2 spans: the main generateText span and the tool execution span
13971397
expect(spans.length).toBeGreaterThanOrEqual(2);
13981398

1399+
const generateTextSpan = spans.find(
1400+
(span: any) => span.span_attributes?.name === "generateText",
1401+
);
1402+
expect(generateTextSpan).toBeDefined();
1403+
expect((generateTextSpan as any).input.tools).toMatchObject({
1404+
calculate: {
1405+
description: "Perform a mathematical calculation",
1406+
inputSchema: {
1407+
type: "object",
1408+
},
1409+
},
1410+
});
1411+
expect((generateTextSpan as any).metadata.tools).toMatchObject({
1412+
calculate: {
1413+
description: "Perform a mathematical calculation",
1414+
},
1415+
});
1416+
expect((generateTextSpan as any).input.tools).not.toHaveProperty(
1417+
"calculate.execute",
1418+
);
1419+
expect((generateTextSpan as any).metadata.tools).not.toHaveProperty(
1420+
"calculate.execute",
1421+
);
1422+
13991423
// Find the tool execution span
14001424
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
14011425
const toolSpan = spans.find(

0 commit comments

Comments
 (0)