{"record":{"id":"ead51e4d245fc73e","repo":"mastra-ai/mastra","slug":"batch-embedder-returned-embeddings-length-embed","errorCode":null,"errorMessage":"Batch embedder returned ${embeddings.length} embeddings for ${docs.length} inputs.","messagePattern":"Batch embedder returned (.+?) embeddings for (.+?) inputs\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/core/src/workspace/search/search-engine.ts","lineNumber":814,"sourceCode":"      });\n    }\n  }\n\n  /**\n   * Embed one group of documents with a single embedder call, then write the vectors using\n   * upserts no larger than {@link MAX_VECTORS_PER_UPSERT}.\n   *\n   * Vectors are paired with their documents positionally, so the embedder must return exactly\n   * one embedding per input in input order.\n   */\n  async #embedAndUpsertGroup(docs: IndexDocument[]): Promise<void> {\n    if (!this.#vectorConfig || docs.length === 0) return;\n\n    const { vectorStore, indexName } = this.#vectorConfig;\n\n    const embeddings = await this.#embedAll(docs.map(d => d.content));\n    if (embeddings.length !== docs.length) {\n      throw new Error(`Batch embedder returned ${embeddings.length} embeddings for ${docs.length} inputs.`);\n    }\n\n    if (!this.#vectorIndexReady) {\n      const dim = embeddings[0]!.length;\n      try {\n        await vectorStore.createIndex({ indexName, dimension: dim });\n      } catch {\n        // Already exists, temporarily unavailable, or not required by backend.\n      }\n    }\n\n    for (let start = 0; start < docs.length; start += MAX_VECTORS_PER_UPSERT) {\n      const slice = docs.slice(start, start + MAX_VECTORS_PER_UPSERT);\n      await vectorStore.upsert({\n        indexName,\n        vectors: embeddings.slice(start, start + MAX_VECTORS_PER_UPSERT),\n        metadata: slice.map(doc => ({\n          id: doc.id,","sourceCodeStart":796,"sourceCodeEnd":832,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/workspace/search/search-engine.ts#L796-L832","documentation":"After batch embedding, SearchEngine asserts the embedder returned exactly one embedding per input document. A count mismatch means the batch embedder implementation violated the embed-many contract (isBatchEmbedder claimed batch capability but returned a misaligned result). The engine throws rather than silently upserting documents with wrong/missing vectors.","triggerScenarios":"A custom embedder marked as batch-capable returns fewer/more embeddings than inputs (e.g. drops failed items, deduplicates inputs, chunks results incorrectly, or returns a flattened multi-chunk result); an embedder provider silently truncates oversized batches.","commonSituations":"Writing a custom `doEmbed`/batch embedder wrapper that filters out failed texts; using an embedder that batches internally and returns partial results on API errors; provider returning embeddings per chunk rather than per input.","solutions":["Fix the custom batch embedder so it returns exactly one embedding per input, preserving order; on failure, throw or pad/serialize requests instead of dropping items.","Disable batch capability (make the embedder non-batch) so the engine falls back to parallel single-text calls, isolating per-item failures.","Log inputs/outputs at the embedder boundary to find which inputs are being dropped or duplicated, and correct the mapping."],"exampleFix":"// before\nasync doEmbed({ values }) {\n  const out = [];\n  for (const v of values) {\n    try { out.push(await embedOne(v)); } catch { /* skipped -> mismatch */ }\n  }\n  return { embeddings: out };\n}\n// after\nasync doEmbed({ values }) {\n  const embeddings = await Promise.all(values.map(v => embedOne(v))); // throws on failure, 1:1 mapping\n  return { embeddings };\n}","handlingStrategy":"try-catch","validationCode":"function isValidBatchEmbedder(e) {\n  return typeof e?.doEmbed === 'function';\n}\n// smoke-test before indexing: 1:1 output contract\nconst probe = await embedder.doEmbed({ values: ['a', 'b'] });\nif (probe.embeddings.length !== 2) throw new Error('Embedder violates 1:1 batch contract');","typeGuard":null,"tryCatchPattern":"try {\n  await engine.upsert(docs);\n} catch (err) {\n  if (err instanceof Error && /Batch embedder returned \\d+ embeddings/.test(err.message)) {\n    console.error('Embedder 1:1 contract violated; rebuild embedder or switch to non-batch fallback.', err);\n    // recreate engine with a compliant or non-batch embedder and retry\n  } else throw err;\n}","preventionTips":["Unit-test custom embedders with N distinct inputs and assert embeddings.length === N and order preservation.","Never swallow per-item embed errors inside batch loops; propagate or serialize them.","Prefer the engine's parallel single-text fallback (non-batch embedder) unless batch behavior is well tested.","Watch provider rate limits that cause partial batch responses and add retries at the provider client level."],"tags":["embedding","contract-violation","batch"],"backgroundTag":"embedder-count-mismatch","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}