Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions assets/magic-context.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,12 @@
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"dimensions": {
"description": "Optional embedding dimensions for Matryoshka models like Qwen3-Embedding (e.g. 768, 1024, 4096). When set, sent as dimensions in the embedding request body. Omitted keeps provider default.",
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 8192
},
"local_runtime": {
"default": "auto",
"description": "Local provider only: ONNX runtime selection. 'auto' uses native under Node and uses WASM under Bun versions before 1.4.0, where Bun's NAPI teardown race can panic on quit; native is restored automatically on Bun 1.4.0+. Set 'native' only to prefer speed while accepting that pre-1.4.0 Bun crash risk, or 'wasm' to avoid loading the native addon.",
Expand Down
11 changes: 11 additions & 0 deletions packages/plugin/src/config/schema/magic-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,15 @@ const BaseEmbeddingConfigSchema = z
.describe(
"Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.",
),
dimensions: z
.number()
.int()
.positive()
.max(8192)
.optional()
.describe(
"Optional embedding dimensions for Matryoshka models like Qwen3-Embedding (e.g. 768, 1024, 4096). When set, sent as dimensions in the embedding request body. Omitted keeps provider default.",
),
local_runtime: z
.enum(["auto", "native", "wasm"])
.default("auto")
Expand Down Expand Up @@ -683,6 +692,7 @@ export const EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data)
...(inputType ? { input_type: inputType } : {}),
...(queryInputType ? { query_input_type: queryInputType } : {}),
...(truncate ? { truncate } : {}),
...(data.dimensions ? { dimensions: data.dimensions } : {}),
Comment thread
greptile-apps[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a synapse embedding uses its OpenAI-compatible fallback, preserve data.dimensions while constructing fallbackConfig; otherwise fallback requests use the provider-default vector width and produce vectors that do not match the configured embedding identity.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/config/schema/magic-context.ts, line 695:

<comment>When a `synapse` embedding uses its OpenAI-compatible fallback, preserve `data.dimensions` while constructing `fallbackConfig`; otherwise fallback requests use the provider-default vector width and produce vectors that do not match the configured embedding identity.</comment>

<file context>
@@ -683,6 +692,7 @@ export const EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data)
             ...(inputType ? { input_type: inputType } : {}),
             ...(queryInputType ? { query_input_type: queryInputType } : {}),
             ...(truncate ? { truncate } : {}),
+            ...(data.dimensions ? { dimensions: data.dimensions } : {}),
             ...(data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}),
         };
</file context>

...(data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}),
};
}
Expand Down Expand Up @@ -716,6 +726,7 @@ export const EmbeddingConfigSchema = BaseEmbeddingConfigSchema.transform((data)
...(inputType ? { input_type: inputType } : {}),
...(queryInputType ? { query_input_type: queryInputType } : {}),
...(truncate ? { truncate } : {}),
...(data.dimensions ? { dimensions: data.dimensions } : {}),
...(data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export function getEmbeddingProviderIdentity(config: EmbeddingConfig): string {
}

const truncate = config.provider === "openai-compatible" ? config.truncate?.trim() : undefined;
const dimensions =
config.provider === "openai-compatible"
? (config as unknown as { dimensions?: number }).dimensions
: undefined;
// local_dtype changes the produced vectors (a quantized ONNX model emits
// different embeddings than fp32), so a non-default dtype MUST fold into the
// model identity — switching dtype re-embeds rather than mixing vector
Expand Down Expand Up @@ -66,6 +70,7 @@ export function getEmbeddingProviderIdentity(config: EmbeddingConfig): string {
// lazily GCs, never a destructive wipe).
inputType: config.input_type?.trim() || "",
...(truncate ? { truncate } : {}),
...(dimensions ? { dimensions } : {}),
}
: {
provider: "local",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ interface OpenAICompatibleEmbeddingProviderOptions {
queryInputType?: string;
/** Optional `truncate` body field (e.g. NVIDIA NIM 'NONE'/'START'/'END'). */
truncate?: string;
/** Optional `dimensions` body field for Matryoshka models like Qwen3-Embedding (e.g. 768, 1024, 4096). */
dimensions?: number;
/** Maximum safe input tokens for chunk embeddings. */
maxInputTokens?: number;
}
Expand Down Expand Up @@ -142,6 +144,7 @@ export class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
private readonly inputType: string;
private readonly queryInputType: string;
private readonly truncate: string;
private readonly dimensions: number | undefined;
private initialized = false;

// Circuit breaker state (per provider instance — resets when config
Expand All @@ -166,6 +169,10 @@ export class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
this.inputType = options.inputType?.trim() ?? "";
this.queryInputType = options.queryInputType?.trim() ?? "";
this.truncate = options.truncate?.trim() ?? "";
this.dimensions =
typeof options.dimensions === "number" && Number.isFinite(options.dimensions)
? Math.max(1, Math.floor(options.dimensions))
: undefined;
this.maxInputTokens =
typeof options.maxInputTokens === "number" && Number.isFinite(options.maxInputTokens)
? Math.max(1, Math.floor(options.maxInputTokens))
Expand All @@ -182,6 +189,7 @@ export class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
// different model_id than reads/GC resolve, silently zeroing results
// and reaping valid vectors.
...(this.truncate ? { truncate: this.truncate } : {}),
...(this.dimensions ? { dimensions: this.dimensions } : {}),
});
}

Expand Down Expand Up @@ -300,6 +308,7 @@ export class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
// unaffected.
...(inputTypeForRequest ? { input_type: inputTypeForRequest } : {}),
...(this.truncate ? { truncate: this.truncate } : {}),
...(this.dimensions ? { dimensions: this.dimensions } : {}),
}),
// SSRF: refuse to FOLLOW redirects. The pre-flight SSRF check only
// validates the configured endpoint; default redirect-follow would
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function resolveEmbeddingConfig(config?: EmbeddingConfig): EmbeddingConfig {
...(inputType ? { input_type: inputType } : {}),
...(queryInputType ? { query_input_type: queryInputType } : {}),
...(truncate ? { truncate } : {}),
...(config.dimensions ? { dimensions: config.dimensions } : {}),
...(config.max_input_tokens
? {
max_input_tokens: normalizeCompartmentChunkMaxInputTokens(
Expand Down Expand Up @@ -118,6 +119,7 @@ function createProvider(config: EmbeddingConfig): EmbeddingProvider | null {
inputType: config.input_type,
queryInputType: config.query_input_type,
truncate: config.truncate,
dimensions: (config as unknown as { dimensions?: number }).dimensions,
maxInputTokens: config.max_input_tokens,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ function resolveEmbeddingConfig(config?: EmbeddingConfig): EmbeddingConfig {
...(inputType ? { input_type: inputType } : {}),
...(queryInputType ? { query_input_type: queryInputType } : {}),
...(truncate ? { truncate } : {}),
...(config.dimensions ? { dimensions: config.dimensions } : {}),
...(config.max_input_tokens
? {
max_input_tokens: normalizeCompartmentChunkMaxInputTokens(
Expand Down Expand Up @@ -502,6 +503,7 @@ function createProvider(
inputType: config.input_type,
queryInputType: config.query_input_type,
truncate: config.truncate,
dimensions: (config as unknown as { dimensions?: number }).dimensions,
maxInputTokens: config.max_input_tokens,
});
}
Expand Down Expand Up @@ -585,6 +587,7 @@ function getChunkEmbeddingModelId(config: EmbeddingConfig, providerIdentity: str
"max_input_tokens" in config ? config.max_input_tokens : undefined,
),
truncate: config.provider === "openai-compatible" ? (config.truncate ?? "") : "",
dimensions: config.provider === "openai-compatible" ? ((config as unknown as { dimensions?: number }).dimensions ?? "") : "",
};
return `${providerIdentity}:chunk:${sha256Prefix(stableStringify(chunkIdentity))}`;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/plugin/src/plugin/embedding-bootstrap-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ export const EMBEDDING_AFFECTING_KEYS = new Set([
// not let a broken value re-register mid-session.
"embedding.input_type",
"embedding.truncate",
// max_input_tokens + query_input_type fold into the chunk-embedding identity
// max_input_tokens + query_input_type + dimensions fold into the chunk-embedding identity
// (getChunkEmbeddingModelId); a failed substitution on either would otherwise
// register as trusted and could drive a bogus chunk identity / GC.
"embedding.max_input_tokens",
"embedding.dimensions",
"embedding.query_input_type",
"embedding.fallback_provider",
"subc",
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin/src/plugin/embedding-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function fallbackConfig(
const queryInputType =
typeof raw.query_input_type === "string" ? raw.query_input_type.trim() : "";
const truncate = typeof raw.truncate === "string" ? raw.truncate.trim() : "";
const dimensions = typeof raw.dimensions === "number" ? raw.dimensions : undefined;
const maxInputTokens =
typeof raw.max_input_tokens === "number" ? raw.max_input_tokens : undefined;

Expand All @@ -66,6 +67,7 @@ function fallbackConfig(
...(inputType ? { input_type: inputType } : {}),
...(queryInputType ? { query_input_type: queryInputType } : {}),
...(truncate ? { truncate } : {}),
...(dimensions !== undefined ? { dimensions } : {}),
...(maxInputTokens !== undefined ? { max_input_tokens: maxInputTokens } : {}),
};
}
Expand Down