fix(ai): declare DashScope's documented 10-item embedding batch cap (#2643 concept, refs #2103 #2405)

DashScope's OpenAI-compatible /embeddings endpoint rejects requests with
more than 10 input items (documented Model Studio cap). The generic
per-recipe max_batch_items field + gateway capBatchItems pre-split
already exist (#1281); the in-tree dashscope recipe just never declared
the cap, so large embed backfills would send oversized batches and get
rejected server-side. Declare max_batch_items: 10 on the dashscope
embedding touchpoint; max_batch_tokens stays as the aggregate
token-size guard.

Test: pins dashscope's max_batch_items === 10 (+ max_batch_tokens
unchanged) and that 25 items pre-split into groups of at most 10 via
capBatchItems, alongside the existing llama-server cap pin.

No new models or recipes; no error-sniffing/halving recovery — the
pre-split makes the failure unreachable. Item-cap concept credited to
declined community PRs #2643 and #2405.

Also verified (no code change needed): #2103's litellm three-way dead
end is already fixed on master by a25209bb (#2271) via trust_custom_dims.

Co-authored-by: Yicong <charlieyiconghuang@gmail.com>
Co-authored-by: Cheng Zijun <robotics.chengzijun@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-07-27 16:17:50 -07:00
co-authored by Yicong Cheng Zijun Claude Fable 5
parent 032af6e5f7
commit 609cde6e83
2 changed files with 19 additions and 1 deletions
+5
View File
@@ -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.
@@ -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();