From 609cde6e83ede17e46d1afedc7f0e0c8793af092 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 27 Jul 2026 16:17:50 -0700 Subject: [PATCH] fix(ai): declare DashScope's documented 10-item embedding batch cap (#2643 concept, refs #2103 #2405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-authored-by: Cheng Zijun Co-Authored-By: Claude Fable 5 --- src/core/ai/recipes/dashscope.ts | 5 +++++ test/ai/no-batch-cap-suppression.serial.test.ts | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) 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();