fix(dims): handle prefixed model IDs on openai-compatible path (#2325) (#3309)

OpenRouter (and potentially other proxy providers) expose OpenAI's
text-embedding-3 models with a provider prefix in the model ID, e.g.
`openai/text-embedding-3-large` rather than bare `text-embedding-3-large`.

`dimsProviderOptions()` checks `modelId.startsWith('text-embedding-3')`
which fails for the prefixed form, so the `dimensions` parameter is never
sent. The upstream provider returns its native dimensionality (3072 for
-large) instead of the configured value (e.g. 1536), causing an immediate
"dim mismatch" error on first embed.

The default OpenRouter embedding (`text-embedding-3-small` at 1536d)
masked this because its native size happens to match the default config.
The bug surfaces when using `-large`, or `-small` with a non-1536 dim
(512, 768, 1024 — all listed in the recipe's `dims_options`).

Fix: strip the provider prefix before the `startsWith` check. The full
prefixed ID is preserved in the error message for user clarity.

Co-authored-by: Noetherly <280958447+noetherly@users.noreply.github.com>
This commit is contained in:
Time Attakc
2026-07-23 18:01:01 -07:00
committed by GitHub
co-authored by Noetherly
parent eba9680775
commit 2944d9b7ae
2 changed files with 31 additions and 3 deletions
+4 -3
View File
@@ -285,9 +285,10 @@ export function dimsProviderOptions(
// configured for a smaller width (e.g. 1536) hard-fail at first embed.
// Azure/OpenAI-compat embeddings are symmetric — inputType ignored.
// v0.36.0.0 (D13): same range validation as native-openai path.
if (modelId.startsWith('text-embedding-3')) {
if (isOpenAITextEmbedding3Model(modelId) && !isValidOpenAITextEmbedding3Dim(modelId, dims)) {
const max = maxOpenAITextEmbedding3Dim(modelId)!;
const bareModelId = modelId.includes('/') ? modelId.split('/').pop()! : modelId;
if (bareModelId.startsWith('text-embedding-3')) {
if (isOpenAITextEmbedding3Model(bareModelId) && !isValidOpenAITextEmbedding3Dim(bareModelId, dims)) {
const max = maxOpenAITextEmbedding3Dim(bareModelId)!;
throw new AIConfigError(
`OpenAI model "${modelId}" supports embedding_dimensions in 1..${max}, got ${dims}.`,
`Set \`embedding_dimensions\` to a value between 1 and ${max} ` +
+27
View File
@@ -134,3 +134,30 @@ describe('dimsProviderOptions — OpenAI on openai-compatible adapter (Azure cas
expect(JSON.stringify(opts)).not.toContain('input_type');
});
});
describe('dimsProviderOptions — prefixed model IDs (OpenRouter / proxy providers)', () => {
test('openai/text-embedding-3-large at 1536d returns dimensions=1536', () => {
const opts = dimsProviderOptions('openai-compatible', 'openai/text-embedding-3-large', 1536);
expect(opts).toEqual({ openaiCompatible: { dimensions: 1536 } });
});
test('openai/text-embedding-3-small at 768d returns dimensions=768', () => {
const opts = dimsProviderOptions('openai-compatible', 'openai/text-embedding-3-small', 768);
expect(opts).toEqual({ openaiCompatible: { dimensions: 768 } });
});
test('openai/text-embedding-3-large at 5000d throws AIConfigError', () => {
expect(() => dimsProviderOptions('openai-compatible', 'openai/text-embedding-3-large', 5000))
.toThrow(AIConfigError);
});
test('error message preserves full prefixed model ID for clarity', () => {
try {
dimsProviderOptions('openai-compatible', 'openai/text-embedding-3-large', 5000);
throw new Error('should have thrown');
} catch (err) {
expect(err).toBeInstanceOf(AIConfigError);
expect((err as Error).message).toContain('openai/text-embedding-3-large');
}
});
});