Skip to content

Commit 7702d54

Browse files
committed
perf(embedding): always run embedding creation as base64
Requesting base64 encoded embeddings returns smaller body sizes, on average ~60% smaller than float32 encoded. In other words, the size of the response body containing embeddings in float32 is ~2.3x bigger than base64 encoded embedding. We always request embedding creating encoded as base64, and then decoded them to float32 based on the user's provided encoding_format parameter. Closes #1310
1 parent 41a7ce3 commit 7702d54

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

src/resources/embeddings.ts

+39-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,46 @@ export class Embeddings extends APIResource {
99
*/
1010
create(
1111
body: EmbeddingCreateParams,
12-
options?: Core.RequestOptions,
12+
options?: Core.RequestOptions<EmbeddingCreateParams>,
1313
): Core.APIPromise<CreateEmbeddingResponse> {
14-
return this._client.post('/embeddings', { body, ...options });
14+
const userInitialEncodingFormat = body.encoding_format;
15+
Core.debug('request', 'Sending request with arguments:', { body, ...options });
16+
17+
const base64Response = this._client.post<EmbeddingCreateParams, CreateEmbeddingResponse>('/embeddings', {
18+
body: {
19+
...body,
20+
// Force base64 encoding for vector embeddings creation
21+
// See https://github.com/openai/openai-node/issues/1310
22+
encoding_format: 'base64',
23+
},
24+
...options,
25+
});
26+
27+
if (userInitialEncodingFormat === 'base64') {
28+
// if the user requested base64 encoding_format, return the response as-is
29+
return base64Response;
30+
} else {
31+
// we decode the base64 embeddings to float32 array if:
32+
// 1- the user requested 'float' encoding_format,
33+
// 2- the user did not specify an encoding_format (which defaults to 'float') in order to keep backwards compatibility
34+
Core.debug('response', `User requested encoding_format=${userInitialEncodingFormat || 'default'}`);
35+
Core.debug('response', 'Decoding base64 embeddings to float32 array');
36+
37+
return base64Response._thenUnwrap((response) => {
38+
if (response && response.data) {
39+
response.data.forEach((embeddingBase64Obj) => {
40+
console.log(embeddingBase64Obj);
41+
const embeddingBase64Str = embeddingBase64Obj.embedding as unknown as string;
42+
embeddingBase64Obj.embedding = Array.from(
43+
new Float32Array(Buffer.from(embeddingBase64Str, 'base64').buffer),
44+
);
45+
});
46+
Core.debug('response', 'Decoded embeddings:', response.data);
47+
}
48+
49+
return response;
50+
});
51+
}
1552
}
1653
}
1754

tests/api-resources/embeddings.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,16 @@ describe('resource embeddings', () => {
3232
user: 'user-1234',
3333
});
3434
});
35+
36+
test('create: encoding_format=default should create float32 embeddings', async () => {
37+
const responsePromise = client.embeddings.create({
38+
input: 'The quick brown fox jumped over the lazy dog',
39+
model: 'text-embedding-3-small',
40+
});
41+
const response = await responsePromise;
42+
console.log(response.data?.at(0)?.embedding);
43+
44+
expect(response.data?.at(0)?.embedding).toBeInstanceOf(Array);
45+
expect(Number.isFinite(response.data?.at(0)?.embedding.at(0))).toBe(true);
46+
});
3547
});

0 commit comments

Comments
 (0)