fix: Use Uint8Array instead of Buffer for file type messages (works w… (#1921)

This commit is contained in:
Marcus Schiesser
2025-05-08 13:19:59 +07:00
committed by GitHub
parent b29521bf6c
commit 9b2e25a184
17 changed files with 74 additions and 18 deletions
+10
View File
@@ -0,0 +1,10 @@
---
"@llamaindex/anthropic": patch
"@llamaindex/google": patch
"@llamaindex/openai": patch
"@llamaindex/core": patch
"@llamaindex/env": patch
"@llamaindex/examples": patch
---
Use Uint8Array instead of Buffer for file type messages (works with non-NodeJS)
+1 -1
View File
@@ -25,7 +25,7 @@ async function main() {
},
{
type: "file",
data: fs.readFileSync("./data/manga.pdf"),
data: Uint8Array.from(fs.readFileSync("./data/manga.pdf")),
mimeType: "application/pdf",
},
],
+1 -1
View File
@@ -32,7 +32,7 @@ import fs from "fs";
},
{
type: "file",
data: fs.readFileSync("./data/manga.pdf"),
data: Uint8Array.from(fs.readFileSync("./data/manga.pdf")),
mimeType: "application/pdf",
},
],
+1 -1
View File
@@ -26,7 +26,7 @@ import fs from "fs";
},
{
type: "file",
data: fs.readFileSync("./data/manga.pdf"),
data: new Uint8Array(fs.readFileSync("./data/manga.pdf")),
mimeType: "application/pdf",
},
],
+1 -1
View File
@@ -21,7 +21,7 @@ async function main() {
},
{
type: "file",
data: fs.readFileSync("./data/manga.pdf"),
data: Uint8Array.from(fs.readFileSync("./data/manga.pdf")),
mimeType: "application/pdf",
},
],
+1 -1
View File
@@ -165,7 +165,7 @@ export type MessageContentImageDetail = {
export type MessageContentFileDetail = {
type: "file";
data: Buffer;
data: Uint8Array;
mimeType: string;
};
+2 -1
View File
@@ -6,7 +6,8 @@
import "./global-check.js";
export * from "./als/index.web.js";
export { consoleLogger, emptyLogger, type Logger } from "./logger/index.js";
export * from "./logger/index.js";
export * from "./utils/base64.js";
export { NotSupportCurrentRuntimeClass } from "./utils/shared.js";
export * from "./web-polyfill.js";
if (typeof window === "undefined") {
+2 -1
View File
@@ -5,6 +5,7 @@
*/
export * from "./als/index.non-node.js";
export { consoleLogger, emptyLogger, type Logger } from "./logger/index.js";
export * from "./logger/index.js";
export * from "./node-polyfill.js";
export * from "./utils/base64.js";
export { NotSupportCurrentRuntimeClass } from "./utils/shared.js";
+2 -1
View File
@@ -37,7 +37,8 @@ export function createSHA256(): SHA256 {
export const process = globalThis.process;
export * from "./als/index.node.js";
export { consoleLogger, emptyLogger, type Logger } from "./logger/index.js";
export * from "./logger/index.js";
export * from "./utils/base64.js";
export { CustomEvent, getEnv, setEnvs } from "./utils/index.js";
export { NotSupportCurrentRuntimeClass } from "./utils/shared.js";
export {
+2 -1
View File
@@ -11,9 +11,10 @@ export * from "./als/index.workerd.js";
export { NotSupportCurrentRuntimeClass } from "./utils/shared.js";
export * from "./node-polyfill.js";
export * from "./utils/base64.js";
export function getEnv(name: string): string | undefined {
return INTERNAL_ENV[name];
}
export { consoleLogger, emptyLogger, type Logger } from "./logger/index.js";
export * from "./logger/index.js";
+38
View File
@@ -0,0 +1,38 @@
/**
* Converts a Uint8Array to a base64 string.
* For large arrays, it processes the data in chunks to avoid memory issues.
* Falls back to Buffer if available for better performance.
*
* @param bytes - The Uint8Array to convert
* @returns The base64 string representation
*/
export function uint8ArrayToBase64(bytes: Uint8Array): string {
// Use Buffer if available (Node.js environment)
if (typeof Buffer !== "undefined") {
return Buffer.from(bytes).toString("base64");
}
// For browsers and other environments without Buffer
// Process in chunks for large arrays to avoid memory issues
const CHUNK_SIZE = 32768; // 32KB chunks
let result = "";
// For small arrays, use the built-in btoa function directly
if (bytes.length < CHUNK_SIZE) {
const binary = Array.from(bytes)
.map((byte) => String.fromCharCode(byte))
.join("");
return globalThis.btoa(binary);
}
// For large arrays, process in chunks
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
const chunk = bytes.subarray(i, i + CHUNK_SIZE);
const binary = Array.from(chunk)
.map((byte) => String.fromCharCode(byte))
.join("");
result += globalThis.btoa(binary);
}
return result;
}
+2 -2
View File
@@ -28,7 +28,7 @@ import type {
} from "@llamaindex/core/llms";
import { ToolCallLLM } from "@llamaindex/core/llms";
import { extractText } from "@llamaindex/core/utils";
import { getEnv } from "@llamaindex/env";
import { getEnv, uint8ArrayToBase64 } from "@llamaindex/env";
import { isDeepEqual } from "remeda";
export class AnthropicSession {
@@ -332,7 +332,7 @@ export class Anthropic extends ToolCallLLM<
source: {
type: "base64" as const,
media_type: content.mimeType,
data: content.data.toString("base64"),
data: uint8ArrayToBase64(content.data),
},
};
}
+1 -1
View File
@@ -289,7 +289,7 @@ export class GeminiHelper {
if (fileContents.length > 0) {
for (const file of fileContents) {
const uploadResponse = await GeminiHelper.uploadFile(
file.data,
Buffer.from(file.data),
file.mimeType,
);
parts.push({
+3 -2
View File
@@ -13,7 +13,7 @@ import {
type ToolCallLLMMessageOptions,
} from "@llamaindex/core/llms";
import { extractText } from "@llamaindex/core/utils";
import { getEnv } from "@llamaindex/env";
import { getEnv, uint8ArrayToBase64 } from "@llamaindex/env";
import { Tokenizers } from "@llamaindex/env/tokenizers";
import type {
AzureClientOptions,
@@ -217,10 +217,11 @@ export class OpenAI extends ToolCallLLM<OpenAIAdditionalChatOptions> {
if (item.mimeType !== "application/pdf") {
throw new Error("Only PDF files are supported");
}
const base64Data = uint8ArrayToBase64(item.data);
return {
type: "file",
file: {
file_data: `data:${item.mimeType};base64,${item.data.toString("base64")}`,
file_data: `data:${item.mimeType};base64,${base64Data}`,
filename: `part-${index}.pdf`,
},
} satisfies ChatCompletionContentPart.File;
+3 -2
View File
@@ -16,7 +16,7 @@ import {
} from "@llamaindex/core/llms";
import type { StoredValue } from "@llamaindex/core/schema";
import { extractText } from "@llamaindex/core/utils";
import { getEnv } from "@llamaindex/env";
import { getEnv, uint8ArrayToBase64 } from "@llamaindex/env";
import {
OpenAI as OpenAILLM,
type AzureClientOptions,
@@ -703,10 +703,11 @@ export class OpenAIResponses extends ToolCallLLM<OpenAIResponsesChatOptions> {
);
}
const base64Data = uint8ArrayToBase64(item.data);
return {
type: "input_file",
filename: `part-${index}.pdf`,
file_data: `data:${item.mimeType};base64,${item.data.toString("base64")}`,
file_data: `data:${item.mimeType};base64,${base64Data}`,
};
}
throw new Error("Unsupported content type");
@@ -205,7 +205,7 @@ describe("OpenAIResponses Unit Tests", () => {
{
type: "file",
mimeType: "image/jpeg",
data: Buffer.from("test image content"),
data: Uint8Array.from(Buffer.from("test image content")),
},
];
// @ts-expect-error accessing private method
+3 -1
View File
@@ -2,7 +2,9 @@ import { describe, expect, test } from "vitest";
import { duckduckgo, type DuckDuckGoToolOutput } from "../src/tools/duckduckgo";
describe("DuckDuckGo Tool", () => {
test("performs search and returns results", async () => {
// Needs to be skipped: duck-duck-scrape@2.2.7 throws an error:
// DDG detected an anomaly in the request, you are likely making requests too quickly.
test.skip("performs search and returns results", async () => {
const searchTool = duckduckgo();
const results = (await searchTool.call({
query: "OpenAI ChatGPT",