{"record":{"id":"b44ec486591625de","repo":"rohitg00/agentmemory","slug":"embedding-dimension-mismatch-in-provider-name","errorCode":null,"errorMessage":"Embedding dimension mismatch in ${provider.name}.${where}: expected ${expected}, got ${v.length}","messagePattern":"Embedding dimension mismatch in (.+?)\\.(.+?): expected (.+?), got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/providers/embedding/index.ts","lineNumber":60,"sourceCode":"      return withDimensionGuard(new CohereEmbeddingProvider(getEnvVar(\"COHERE_API_KEY\")!));\n    case \"openrouter\":\n      return withDimensionGuard(new OpenRouterEmbeddingProvider(getEnvVar(\"OPENROUTER_API_KEY\")!));\n    case \"local\":\n      return withDimensionGuard(new LocalEmbeddingProvider());\n    default:\n      return null;\n  }\n}\n\n// Wrong-dimension vectors corrupt the index silently: vector-index.ts\n// returns 0 from cosineSimilarity on length mismatch instead of throwing,\n// so a bad vector is stored, never matches anything, and the memory\n// becomes invisible without an error. Catch it at the boundary.\nexport function withDimensionGuard(provider: EmbeddingProvider): EmbeddingProvider {\n  const expected = provider.dimensions;\n  const check = (v: Float32Array, where: string): Float32Array => {\n    if (v.length !== expected) {\n      throw new Error(\n        `Embedding dimension mismatch in ${provider.name}.${where}: expected ${expected}, got ${v.length}`,\n      );\n    }\n    return v;\n  };\n  // Preserve the provider's prototype chain so `instanceof` checks\n  // against concrete classes (e.g. GeminiEmbeddingProvider) keep working.\n  const wrapped = Object.create(provider) as EmbeddingProvider;\n  wrapped.embed = async (t) => check(await provider.embed(t), \"embed\");\n  wrapped.embedBatch = async (ts) => {\n    const out = await provider.embedBatch(ts);\n    out.forEach((v, i) => check(v, `embedBatch[${i}]`));\n    return out;\n  };\n  if (provider.embedImage) {\n    wrapped.embedImage = async (s: string) =>\n      check(await provider.embedImage!(s), \"embedImage\");\n  }","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/rohitg00/agentmemory/blob/e04ba88819c365c9acf9d6661ea802143e728bd6/src/providers/embedding/index.ts#L42-L78","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Make provider.dimensions match the actual model output: set the *_EMBEDDING_DIMENSIONS env var or pass explicit dimensions to the model call","Re-embed your stored memories after any model change — old vectors of a different size are incompatible","Verify which model the endpoint actually serves (curl the API and inspect the returned vector length)","If intentionally truncating (e.g. Matryoshka dims), ensure the provider requests the reduced dimensions from the API, not just declares them"],"exampleFix":"// before: model changed but dimensions not updated\nconst provider = new OpenAIEmbeddingProvider(); // text-embedding-3-large returns 3072, declared 1536\n// after: request and declare matching dimensions\nconst provider = new OpenAIEmbeddingProvider(undefined, undefined, 'text-embedding-3-large');\n// ensure class sets readonly dimensions = 3072 for that model","handlingStrategy":"validation","validationCode":"function assertDimensions(provider: { name: string; dimensions: number }, vectors: Float32Array[]) {\n  for (const v of vectors) {\n    if (v.length !== provider.dimensions) {\n      throw new Error(`${provider.name}: expected ${provider.dimensions} dims, got ${v.length} — re-embed stored memories after model changes`);\n    }\n  }\n}\n// call before persisting: assertDimensions(provider, await provider.embedBatch(texts));","typeGuard":"const hasExpectedDimensions = (v: Float32Array, expected: number): v is Float32Array & { length: number } => v.length === expected;","tryCatchPattern":"try {\n  const v = await provider.embed(text);\n  await store(id, v);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Embedding dimension mismatch')) {\n    logger.error({ provider: provider.name }, 'embedding model changed — schedule re-embedding');\n    await reEmbedAll();\n  } else throw err;\n}","preventionTips":["Treat embedding model + dimensions as one atomic config unit; change them together","Store the model name/dims alongside each stored vector and detect drift at startup","Add a smoke test that embeds 'hello' and asserts vector length on boot","Never hand-edit *_EMBEDDING_DIMENSIONS without verifying actual model output"],"tags":["embedding","dimension-mismatch","configuration"],"backgroundTag":"embedding-dimension-mismatch","analyzedSha":"e04ba88819c365c9acf9d6661ea802143e728bd6","analyzedAt":"2026-08-30T01:07:40.754Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}