Skip to content

Commit 155040c

Browse files
authored
fix(js): Trace AI SDK OTEL exported images as base64 (#1712)
1 parent 2189419 commit 155040c

File tree

2 files changed

+85
-4
lines changed

2 files changed

+85
-4
lines changed

js/src/tests/vercel_node_otel.int.test.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { generateText, tool } from "ai";
55
import { openai } from "@ai-sdk/openai";
66
import { z } from "zod";
77

8+
import * as fs from "fs";
9+
import { fileURLToPath } from "url";
10+
import path from "path";
11+
812
import { AISDKExporter } from "../vercel.js";
913
import { waitUntilRunFound } from "./utils.js";
1014
import { Client } from "../index.js";
@@ -25,7 +29,7 @@ test("nested generateText", async () => {
2529
const wrapper = traceable(
2630
async () => {
2731
return generateText({
28-
model: openai("gpt-4.1-mini"),
32+
model: openai("gpt-4.1-nano"),
2933
messages: [
3034
{
3135
role: "user",
@@ -43,15 +47,52 @@ test("nested generateText", async () => {
4347
description: "view tracking information for a specific order",
4448
parameters: z.object({ orderId: z.string() }),
4549
execute: async () => {
50+
const pathname = path.join(
51+
path.dirname(fileURLToPath(import.meta.url)),
52+
"test_data",
53+
"parrot-icon.png"
54+
);
55+
const buffer = fs.readFileSync(pathname);
4656
await generateText({
47-
model: openai("gpt-4.1-mini"),
57+
model: openai("gpt-4.1-nano"),
4858
experimental_telemetry: AISDKExporter.getSettings({
4959
runName: "How are you 1",
5060
}),
5161
messages: [
5262
{
5363
role: "user",
54-
content: `How are you feeling?`,
64+
content: [
65+
{
66+
type: "text",
67+
text: "What is this?",
68+
},
69+
// Node Buffer
70+
{
71+
type: "image",
72+
image: Buffer.from(buffer),
73+
},
74+
// ArrayBuffer
75+
{
76+
type: "image",
77+
image: buffer.buffer.slice(
78+
buffer.byteOffset,
79+
buffer.byteOffset + buffer.byteLength
80+
),
81+
},
82+
{
83+
type: "image",
84+
image: new Uint8Array(buffer),
85+
},
86+
{
87+
type: "image",
88+
image: buffer.toString("base64"),
89+
},
90+
{
91+
type: "image",
92+
image:
93+
"https://png.pngtree.com/png-vector/20221025/ourmid/pngtree-navigation-bar-3d-search-url-png-image_6360655.png",
94+
},
95+
],
5596
},
5697
],
5798
});
@@ -122,4 +163,12 @@ test("nested generateText", async () => {
122163
expect(
123164
storedRun.child_runs?.[0]?.child_runs?.[3]?.child_runs?.[1].name
124165
).toEqual("How are you 2");
166+
expect(
167+
storedRun.child_runs?.[0]?.child_runs?.[3]?.child_runs?.[0].child_runs
168+
?.length
169+
).toEqual(1);
170+
expect(
171+
storedRun.child_runs?.[0]?.child_runs?.[3]?.child_runs?.[1].child_runs
172+
?.length
173+
).toEqual(1);
125174
});

js/src/vercel.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,41 @@ function convertCoreToSmith(
124124
}
125125

126126
if (part.type === "image") {
127+
let imageUrl = part.image;
128+
if (typeof imageUrl !== "string") {
129+
let uint8Array;
130+
if (
131+
imageUrl != null &&
132+
typeof imageUrl === "object" &&
133+
"type" in imageUrl &&
134+
"data" in imageUrl
135+
) {
136+
// Typing is wrong here if a buffer is passed in
137+
uint8Array = new Uint8Array(imageUrl.data as Uint8Array);
138+
} else if (
139+
imageUrl != null &&
140+
typeof imageUrl === "object" &&
141+
Object.keys(imageUrl).every((key) => !isNaN(Number(key)))
142+
) {
143+
// ArrayBuffers get turned into objects with numeric keys for some reason
144+
uint8Array = new Uint8Array(
145+
Array.from({
146+
...imageUrl,
147+
length: Object.keys(imageUrl).length,
148+
})
149+
);
150+
}
151+
if (uint8Array) {
152+
let binary = "";
153+
for (let i = 0; i < uint8Array.length; i++) {
154+
binary += String.fromCharCode(uint8Array[i]);
155+
}
156+
imageUrl = btoa(binary);
157+
}
158+
}
127159
return {
128160
type: "image_url",
129-
image_url: part.image,
161+
image_url: imageUrl,
130162
...part.experimental_providerMetadata,
131163
};
132164
}

0 commit comments

Comments
 (0)