Unexpected string response for embedding field using text-embedding-3-small API in Node.js SDK

Hi everyone,

I’m encountering an unexpected issue when calling the text-embedding-3-small model using the Node.js OpenAI SDK. Instead of receiving an array of numbers for the embedding field, I’m getting what looks like a long encoded string.

Here’s a simplified version of my code:

import OpenAI from "openai";

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const embedding = await openai.embeddings.create({
  model: "text-embedding-3-small",
  input: "Your text string goes here",
});

console.log(embedding);

And here’s the actual response I’m getting:

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": "lTKoO/5AjTyLLZm8BwiYvEGJQb2MHPi806viPBvgbDtiyjc8U9nSO5sN3bolj4E8SW2ouq/...."
    }
  ],
  "model": "text-embedding-3-small",
  "usage": {
    "prompt_tokens": 5,
    "total_tokens": 5
  }
}

The embedding field should be an array of floats, but instead it’s a string. I also tested the same input and model using Python, and it worked fine—the embedding came back as an array of floats, as expected.

Has anyone else experienced this? Am I missing a config setting or decoding step in the Node.js SDK?

Any help would be greatly appreciated!

It seems that you are getting “base64” results, instead of “float”, despite that the default is supposed to be float. Perhaps the SDK is doing this client-side so that the data is immediately useful for javascript arrays? Or instead, you can specify the “embedding_format” explicitly.

Receiving base64 over the wire is more efficient, taking less data than floats as strings, and also doesn’t require string conversion into decimal numbers then for conversion into the underlying number format.

Below is a plain-ESM Node.js script that mirrors how a Python script uses its request for base64 data - and with multiple strings as a list as input. It also prioritizes you setting the dimensions of embeddings of the embedding-3 models (1536 or up to 3072 for 3-large):

/*
 * Demonstration of requesting multiple embeddings with
 *   encoding_format: "base64"
 * and converting each record to Float32Array for fast math.
 *
 * Requires:
 *   npm install openai          (SDK v4 or newer)
 *   Node.js 18+ (for global fetch)
 */

import OpenAI from "openai";
const openai = new OpenAI();

const texts = [
  "The food was delicious and the waiter...",
  "Restaurants are too expensive now."
  // ... up to 2048 items
];

const dims = 256;         // must match the `dimensions` parameter
                          // (text-embedding-3-small allows up to 1536)

/* ------------------------------------------------------------------ */
/* helper: quick dot product of two same-length Float32Arrays         */
function dot(a, b) {
  let s = 0.0;
  for (let i = 0; i < a.length; ++i) s += a[i] * b[i];
  return s;
}
/* ------------------------------------------------------------------ */

async function main() {
  /* --- call the API ------------------------------------------------ */
  const resp = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: texts,
    encoding_format: "base64",
    dimensions: dims
  });

  const n = resp.data.length;            // number of returned vectors

  /* --- decode efficiently ------------------------------------------ */
  // make one big contiguous block: (n × dims) float32 values
  const all = new Float32Array(n * dims);

  // sort by .index so output order matches input order
  for (const rec of resp.data.sort((a, b) => a.index - b.index)) {
    const offset = rec.index * dims;
    const buf = Buffer.from(rec.embedding, "base64");  // raw bytes
    // view those bytes as float32 without extra copies
    const view = new Float32Array(
      buf.buffer,
      buf.byteOffset,
      Math.min(dims, buf.byteLength >> 2) // safety cap
    );
    all.set(view, offset);               // copy into final array
  }

  /* --- use: slice views for dot product etc ------------------------ */
  const v0 = all.subarray(0, dims);
  const v1 = all.subarray(dims, 2 * dims);

  console.log(`shape: (${n}, ${dims})`);
  console.log("dot(v0, v1) =", dot(v0, v1));
}

main().catch(err => {
  console.error(err);
  process.exit(1);
});

What this does

  • Calls the text-embedding-3-small model with encoding_format: "base64" and any dimension dims you pick (≤ 1536).
  • Receives each embedding as a Base-64 string of 4-byte IEEE-754 little-endian floats.
  • Decodes each string once with Buffer.from(b64, "base64") and views the bytes as a Float32Array (zero-copy).
  • Copies that view into a single contiguous Float32Array named all, making downstream math cache-friendly.
  • Shows how to grab two rows (subarray) and take their dot product.

You now have the embeddings in a compact numeric format ready for cosine similarity, clustering, or any other vector operation. A little in-script demo of comparison of two strings is offered.

Python script version on which this is based that does similar, yielding “embeddings” ready for use as numpy multidimensional 2D array:

import base64
import numpy as np
from openai import OpenAI
client = OpenAI()

texts = [
    "The food was delicious and the waiter...",
    "Restaurants are too expensive now.",
    # ... up to 2048 strings
]

dims = 256  # same value you pass to the endpoint, 1536 max for "small"

# --- call the API ------------------------------------------------------------
resp = client.embeddings.create(
    model="text-embedding-3-small",
    input=texts,
    encoding_format="base64",
    dimensions=dims,
)

n = len(resp.data)  # received item count
embeddings = np.empty((n, dims), dtype=np.float32)  # reserve full block

# the embedding is base64-encoded 4-byte raw float32
# Decoded, is like b'S)Y\xbe=}\xb7;\x9d\xc8\x13\xbf\x9f\xfb\xed\xbe\xb4\xb6\x7f>!...'
for row, rec in enumerate(sorted(resp.data, key=lambda r: r.index)):
    embeddings[row] = np.frombuffer(
        base64.b64decode(rec.embedding, validate=True),
        dtype=np.float32,
        count=dims,     # safety: stops if the byte-string is untruncated
    )

# `embeddings` is now an (n × dims) NumPy array in the same order as `texts`
print(embeddings.shape)   # (2, 256) - count, dimensions

print(np.dot(embeddings[0], embeddings[1]))  # make a comparison with dot product

# example b64 to np float array output (1d):
# array([-0.2120717 ,  0.00559965, -0.57727987, -0.46481034,  0.2497204 ,
#       -0.24463703, -0.07847448,  0.2900696 , -0.24241306, -0.36663783, ...],
#      dtype=float32)