diff --git a/src/core/ai/recipes/dashscope.ts b/src/core/ai/recipes/dashscope.ts index 7aa2c273f..4c5c595ec 100644 --- a/src/core/ai/recipes/dashscope.ts +++ b/src/core/ai/recipes/dashscope.ts @@ -31,6 +31,11 @@ export const dashscope: Recipe = { // path. Conservative declaration so the gateway pre-splits before // hitting whatever undocumented server-side limit exists. max_batch_tokens: 8192, + // DashScope's OpenAI-compat /embeddings endpoint rejects requests with + // more than 10 input items (documented Model Studio cap). The gateway's + // capBatchItems pre-split enforces this; max_batch_tokens above keeps + // guarding aggregate token size. Concept from community PRs #2643/#2405. + max_batch_items: 10, // text-embedding-v3 mixes English + CJK heavily; the tokenizer is // closer to Voyage density than OpenAI tiktoken for CJK-dominant // content. Conservative chars_per_token=2 leaves headroom. diff --git a/test/ai/no-batch-cap-suppression.serial.test.ts b/test/ai/no-batch-cap-suppression.serial.test.ts index 5a5d00f60..fe52f4d52 100644 --- a/test/ai/no-batch-cap-suppression.serial.test.ts +++ b/test/ai/no-batch-cap-suppression.serial.test.ts @@ -10,7 +10,7 @@ */ import { afterAll, beforeAll, describe, expect, mock, test } from 'bun:test'; -import { configureGateway, resetGateway } from '../../src/core/ai/gateway.ts'; +import { capBatchItems, configureGateway, resetGateway } from '../../src/core/ai/gateway.ts'; import { listRecipes, getRecipe } from '../../src/core/ai/recipes/index.ts'; describe('v0.32 #779: no_batch_cap suppresses the missing-max_batch_tokens warning', () => { @@ -49,6 +49,19 @@ describe('v0.32 #779: no_batch_cap suppresses the missing-max_batch_tokens warni expect(r!.touchpoints.embedding?.no_batch_cap).toBeUndefined(); }); + test('dashscope declares the documented 10-item embedding cap (max_batch_items: 10)', () => { + // DashScope's OpenAI-compat /embeddings endpoint rejects >10-item batches + // (documented Model Studio cap; concept from community PRs #2643/#2405). + // max_batch_tokens stays as the aggregate token-size guard. + const r = getRecipe('dashscope'); + expect(r, 'dashscope not registered').toBeDefined(); + expect(r!.touchpoints.embedding?.max_batch_items).toBe(10); + expect(r!.touchpoints.embedding?.max_batch_tokens).toBe(8192); + // 25 items pre-split into DashScope-sized groups of at most 10. + const texts = Array.from({ length: 25 }, (_, i) => `t${i}`); + expect(capBatchItems(texts, 10).map(b => b.length)).toEqual([10, 10, 5]); + }); + test('configureGateway does NOT warn for ollama/litellm/llama-server', () => { warnSpy.mockClear(); resetGateway();