rohitg00/agentmemory · critical · Error

Embedding dimension mismatch in ${provider.name}.${where}: e

Error message

Embedding dimension mismatch in ${provider.name}.${where}: expected ${expected}, got ${v.length}

What it means

agentmemory wraps every embedding provider with withDimensionGuard, which compares each returned vector's length against the provider's declared `dimensions`. If a provider returns a vector of a different length (model change, wrong dimensions param, silent upstream downgrade), the bad vector would be stored and never match anything, leaving memories silently invisible. The guard throws at the boundary instead.

Source

Thrown at src/providers/embedding/index.ts:60

      return withDimensionGuard(new CohereEmbeddingProvider(getEnvVar("COHERE_API_KEY")!));
    case "openrouter":
      return withDimensionGuard(new OpenRouterEmbeddingProvider(getEnvVar("OPENROUTER_API_KEY")!));
    case "local":
      return withDimensionGuard(new LocalEmbeddingProvider());
    default:
      return null;
  }
}

// Wrong-dimension vectors corrupt the index silently: vector-index.ts
// returns 0 from cosineSimilarity on length mismatch instead of throwing,
// so a bad vector is stored, never matches anything, and the memory
// becomes invisible without an error. Catch it at the boundary.
export function withDimensionGuard(provider: EmbeddingProvider): EmbeddingProvider {
  const expected = provider.dimensions;
  const check = (v: Float32Array, where: string): Float32Array => {
    if (v.length !== expected) {
      throw new Error(
        `Embedding dimension mismatch in ${provider.name}.${where}: expected ${expected}, got ${v.length}`,
      );
    }
    return v;
  };
  // Preserve the provider's prototype chain so `instanceof` checks
  // against concrete classes (e.g. GeminiEmbeddingProvider) keep working.
  const wrapped = Object.create(provider) as EmbeddingProvider;
  wrapped.embed = async (t) => check(await provider.embed(t), "embed");
  wrapped.embedBatch = async (ts) => {
    const out = await provider.embedBatch(ts);
    out.forEach((v, i) => check(v, `embedBatch[${i}]`));
    return out;
  };
  if (provider.embedImage) {
    wrapped.embedImage = async (s: string) =>
      check(await provider.embedImage!(s), "embedImage");
  }

View on GitHub (pinned to e04ba88819)

Solutions

  1. Make provider.dimensions match the actual model output: set the *_EMBEDDING_DIMENSIONS env var or pass explicit dimensions to the model call
  2. Re-embed your stored memories after any model change — old vectors of a different size are incompatible
  3. Verify which model the endpoint actually serves (curl the API and inspect the returned vector length)
  4. If intentionally truncating (e.g. Matryoshka dims), ensure the provider requests the reduced dimensions from the API, not just declares them

Example fix

// before: model changed but dimensions not updated
const provider = new OpenAIEmbeddingProvider(); // text-embedding-3-large returns 3072, declared 1536
// after: request and declare matching dimensions
const provider = new OpenAIEmbeddingProvider(undefined, undefined, 'text-embedding-3-large');
// ensure class sets readonly dimensions = 3072 for that model
Defensive patterns

Strategy: validation

Validate before calling

function assertDimensions(provider: { name: string; dimensions: number }, vectors: Float32Array[]) {
  for (const v of vectors) {
    if (v.length !== provider.dimensions) {
      throw new Error(`${provider.name}: expected ${provider.dimensions} dims, got ${v.length} — re-embed stored memories after model changes`);
    }
  }
}
// call before persisting: assertDimensions(provider, await provider.embedBatch(texts));

Type guard

const hasExpectedDimensions = (v: Float32Array, expected: number): v is Float32Array & { length: number } => v.length === expected;

Try / catch

try {
  const v = await provider.embed(text);
  await store(id, v);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Embedding dimension mismatch')) {
    logger.error({ provider: provider.name }, 'embedding model changed — schedule re-embedding');
    await reEmbedAll();
  } else throw err;
}

Prevention

When it happens

Trigger: Calling provider.embed()/embedBatch() (via embedWithProvider or memory store/recall paths wrapped by withDimensionGuard) when the returned Float32Array length differs from provider.dimensions — e.g. the configured embedding model changed its output size, OPENROUTER_EMBEDDING_DIMENSIONS was resolved incorrectly, or a proxy/base-URL override routes to a different model than declared.

Common situations: Switching OPENAI_EMBEDDING_MODEL from text-embedding-3-small (1536) to text-embedding-3-large (3072) after vectors were stored; setting OPENROUTER_EMBEDDING_DIMENSIONS to a value the model ignores; a self-hosted/proxy endpoint silently serving a different model; upgrading a local transformers model to one with a different hidden size.

Related errors


AI-assisted analysis of rohitg00/agentmemory@e04ba88819 (2026-08-30). Data as JSON: /api/errors/b44ec486591625de. Report an issue: GitHub.