Skip to content

feat(embedding): support embedding.dimensions for openai-compatible Matryoshka models (e.g. Qwen3-Embedding-8B 768/1024/4096) #405

Description

@GuJi08233

Short description

feat(embedding): support embedding.dimensions for openai-compatible Matryoshka models (e.g. Qwen3-Embedding-8B 768/1024/4096)

What problem does this solve?

Qwen3-Embedding-8B (and other Matryoshka models) supports variable output dimensions via the OpenAI-compatible dimensions request field. The model can return 768, 1024, 2048, or 4096 truncated vectors from the same weights via MRL.

Magic Context 0.41.1 has no embedding.dimensions config. The openai-compatible provider always omits dimensions, so the endpoint's default is used:

  • POST /v1/embeddings {model: Qwen3-Embedding-8B, input: "hello world"} → 768 dims
  • POST /v1/embeddings {model: Qwen3-Embedding-8B, input: "测试中文"} → 1024 dims (same endpoint, same model, different input language)
  • POST /v1/embeddings {model: Qwen3-Embedding-8B, dimensions: 4096} → 4096 dims (native)

We use Magic Context with a mixed Chinese/English codebase (Pi). local Xenova/all-MiniLM-L6-v2 (384 dims) has poor Chinese→English recall, and the default 768/1024 from the remote endpoint is also unstable across languages. For a small 17-case bilingual retrieval test (same as #259 style), 4096 preserved the best recall. There's currently no way to pin the store to the native 4096 without forking the plugin.

Proposed solution

Add an optional embedding.dimensions for the openai-compatible provider, plumbed end-to-end:

{
  "embedding": {
    "provider": "openai-compatible",
    "endpoint": "https://api.example.com/v1",
    "model": "Qwen3-Embedding-8B",
    "api_key": "sk-...",
    "dimensions": 4096 // 768 | 1024 | 2048 | 4096 | omitted = provider default
  }
}

Implementation (mirrors truncate/input_type pattern):

  1. Schema packages/plugin/src/config/schema/magic-context.ts:
    BaseEmbeddingConfigSchema.dimensions: z.number().int().positive().max(8192).optional() + include in EmbeddingConfigSchema.transform for openai-compatible (and synapse for completeness).

  2. Identity embedding-identity.ts:
    getEmbeddingProviderIdentity includes ...dimensions?{dimensions}:{}
    (changing dimensions must re-embed; omitted keeps byte-identical identity for existing configs).

  3. Runtime embedding.ts / project-embedding-registry.ts:
    resolveEmbeddingConfig preserves dimensions; getChunkEmbeddingModelId includes dimensions in chunkIdentity; EMBEDDING_AFFECTING_KEYS includes embedding.dimensions.

  4. Transport embedding-openai.ts:
    OpenAICompatibleEmbeddingProviderOptions.dimensions, this.dimensions, modelId includes dimensions, embedBatch adds ...this.dimensions?{dimensions}:{} to POST /v1/embeddings body.

Verified locally on pi-magic-context@0.41.1 (Pi 0.84.4, Qwen3-Embedding-8B via https://api.example.com/v1):

Request dims bytes model_id
no dimensions (en) 768 3072 b2b64c6a1044797a0a3f8f26672942a5
dimensions:1024 (zh) 1024 4096
dimensions:4096 (zh) 4096 16384 0f39523135a2dfd6c3a58933ede73966:chunk:2b0c...

After dimensions:4096 + /reload, memory 2 → 16384 bytes / 4096 dims correctly.

Omitted dimensions keeps current behavior (no re-embed for existing users).

Alternatives considered

  • Keep provider default (768/1024): unstable across languages, lower recall for bilingual store; same limitation that motivated Make the local embedding dtype configurable for practical multilingual recall #259 local_dtype.
  • Deploy a separate model alias that defaults to 4096: requires server-side change, not client-configurable.
  • Switch to local multilingual model with q8: solves recall but ~858 MB RSS vs remote 0 MB, and still fixed dims.

Area

Memory / historian / dreamer

Additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions