embedding: support embedding.dimensions for openai-compatible providers (#405)

Merged
YI
11h ago
+650-14

Approved issue: #405

Harness coverage (required)

  • OpenCode 1: covered — schema, identity, provider body, width check and routing fallback live in packages/plugin (shared core); doctor-opencode probe sends dimensions.
  • OpenCode 2: covered — same shared core (packages/plugin) and the same doctor-opencode probe.
  • Pi: covered — pi-plugin parses config with the core MagicContextConfigSchema and boots embeddings through the core resolveEmbeddingRouting / project registry; doctor-pi probe sends dimensions.
  • OMP: covered — runs through pi-plugin, same path as Pi.
  • Rust module: not applicable — surface does not exist there (the Rust crates store embedding rows but never call an embeddings endpoint). The dashboard's Rust Test Connection probe (packages/dashboard/src-tauri) is covered.

What this does

Adds embedding.dimensions for openai-compatible providers (Matryoshka output width, e.g. Qwen3-Embedding, text-embedding-3-*).

AreaChange
Schemadimensions (positive int) on the openai-compatible block; carried through the transform, including the synapse block so an openai-compatible fallback keeps the width (the same P1 raised in #406). Local provider: not applicable.
IdentityIncluded only when set, in both identity computations (getEmbeddingProviderIdentity and the provider's own constructor). Unset configs hash byte-for-byte as before — the pinned legacy-hash test in project-embedding-registry.test.ts still passes, so no existing user is re-embedded. Chunk ids inherit it via the provider identity.
Config rebuildsresolveEmbeddingConfig (registry), embedding.ts, embedding-routing.ts fallbackConfig, and EMBEDDING_AFFECTING_KEYS all pass it through.
RuntimeRequest body carries dimensions. A batch whose returned width ≠ dimensions is rejected as a non-retryable failure instead of storing mixed-width vectors (which cosineSimilarity would silently score as 0).
Probedoctor (OpenCode/Pi) and dashboard Test Connection (Rust embedding_probe.rs + ConfigEditor field) send dimensions and report a width mismatch rather than success.
SynapseDeliberately not given a static dimensions: synapse pins its width from the first response (validateResponse).
Generatedassets/magic-context.schema.json, configuration.md, issue-log-templates.generated.ts regenerated.

Verification (macOS arm64, bun 1.4.2, rustc 1.99.1)

  • Affected TS suites: openai / probe / schema / routing / bootstrap-helpers / embedding / registry — all pass; new tests cover body passthrough, unset ⇒ unchanged identity, width rejection, fallback routing, probe mismatch.
  • Full suites: pi-plugin 4153 pass / 0 fail; retina 34 / 0; dashboard TS 194 / 0; dashboard Rust embedding_probe 44 / 0 (4 new tests for classify_returned_width); cargo clippy --workspace --all-targets -D warnings clean; typecheck, Biome, cargo fmt --check clean.
  • Remaining local failures reproduce identically on a clean master checkout and are environmental: 4 plugin and 6 cli tests (identical failing-test set on master), local-pooling integration (needs HF model cache), content-language golden (Intl data of the local bun), gate.test (macOS bash 3.2 lacks mapfile).

Known follow-ups (not in this PR)

  1. A local provider with dimensions set silently ignores it — could warn on load like other dropped keys.
  2. A width mismatch surfaces to users as the generic MC-E04; the actionable reason ("remove dimensions or use a model that supports it") is only in logs and doctor.
  3. Setting dimensions for the first time — even to the model's native width — changes identity and re-embeds once. Intentional; documented in the field description only as "changing the width re-embeds".

Summary by cubic

Adds embedding.dimensions for openai-compatible providers so a configured Matryoshka output width (e.g., Qwen3-Embedding-8B 768/1024/2048/4096, text-embedding-3-*) is sent on every embedding request and folded into the provider identity.

  • Optional positive integer (max 8192); sent only when set, so unset configs keep the same identity and are not re-embedded.
  • When set, a batch whose returned width differs is rejected as a non-retryable error instead of storing mixed-width vectors.
  • The synapse fallback config carries the width; the local provider does not support it.
  • dimensions is now a user-tier field, stripped from project config so a repository cannot override the width or its re-embed behavior.
  • doctor (OpenCode/Pi) and the dashboard Test Connection send dimensions within the runtime's 1–8192 bounds and flag a width mismatch rather than pass.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge, with the existing non-blocking dashboard probe issue still outstanding.

Findings

  1. P2 Invalid width looks connected ▶

Summary

Adds optional embedding.dimensions for OpenAI-compatible providers.

  • Embedding requests can use a chosen output width.
  • Embedding connection checks verify the requested vector width.

yiyi-wei lists three known follow-ups: local providers ignore the field, runtime errors use generic MC-E04 messaging, and first setting a width changes identity even when it matches the model’s default. These were not reported again.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  C[User embedding config] --> R{Selected provider}
  R -->|OpenAI-compatible| O[Keep configured dimensions]
  R -->|Synapse unavailable| F[Build fallback config]
  F -->|OpenAI-compatible| O
  O --> I[Include width in provider identity]
  O --> Q[Send width with embedding request]
  Q --> W{Returned width matches?}
  W -->|Yes| V[Return vectors]
  W -->|No| E[Reject batch]

Reviews (11) · Last reviewed commit: "Merge master (a050e1bf) and regenerate i..." · Reviewed by Greptile