From 29887ddfefe52a4da8e306dc9cc145a758b78b57 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Sun, 26 Apr 2026 13:17:34 +0800 Subject: [PATCH] fix(core): match DeepSeek provider by model name for sglang/vllm (#3613) (#3620) Some OpenAI-compatible servers (notably sglang's deepseek-v4 jinja template) crash on the array form of message content even when it carries a single text block, with `TypeError: sequence item 0: expected str instance, list found` at `encoding_dsv4.py:336`. The DeepSeekOpenAICompatibleProvider already flattens content arrays into joined strings in buildRequest, but isDeepSeekProvider only matched on the official api.deepseek.com baseUrl. DeepSeek models served behind sglang / vllm / ollama / etc. bypass the workaround and hit the bug. Extend the matcher to also detect by model name (case-insensitive substring 'deepseek'), so any OpenAI-compatible endpoint serving a DeepSeek model picks up the same content-format flattening. Fixes #3613 Co-authored-by: wenshao --- .../provider/deepseek.test.ts | 27 ++++++++++++++++++- .../provider/deepseek.ts | 11 +++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/core/src/core/openaiContentGenerator/provider/deepseek.test.ts b/packages/core/src/core/openaiContentGenerator/provider/deepseek.test.ts index f4ced4c45..6570e4aec 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/deepseek.test.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/deepseek.test.ts @@ -49,16 +49,41 @@ describe('DeepSeekOpenAICompatibleProvider', () => { expect(result).toBe(true); }); - it('returns false for non deepseek baseUrl', () => { + it('returns false when neither baseUrl nor model match deepseek', () => { const config = { ...mockContentGeneratorConfig, baseUrl: 'https://api.example.com/v1', + model: 'gpt-4o', } as ContentGeneratorConfig; const result = DeepSeekOpenAICompatibleProvider.isDeepSeekProvider(config); expect(result).toBe(false); }); + + it('returns true for deepseek model on a non-deepseek baseUrl (e.g. sglang) — issue #3613', () => { + const config = { + ...mockContentGeneratorConfig, + baseUrl: 'https://my-sglang.example.com:8000/v1', + model: 'deepseek-v4-pro', + } as ContentGeneratorConfig; + + const result = + DeepSeekOpenAICompatibleProvider.isDeepSeekProvider(config); + expect(result).toBe(true); + }); + + it('matches model name case-insensitively', () => { + const config = { + ...mockContentGeneratorConfig, + baseUrl: 'https://my-vllm.example.com/v1', + model: 'DeepSeek-R1', + } as ContentGeneratorConfig; + + const result = + DeepSeekOpenAICompatibleProvider.isDeepSeekProvider(config); + expect(result).toBe(true); + }); }); describe('buildRequest', () => { diff --git a/packages/core/src/core/openaiContentGenerator/provider/deepseek.ts b/packages/core/src/core/openaiContentGenerator/provider/deepseek.ts index e34dc724d..5c8fc1212 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/deepseek.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/deepseek.ts @@ -22,8 +22,17 @@ export class DeepSeekOpenAICompatibleProvider extends DefaultOpenAICompatiblePro contentGeneratorConfig: ContentGeneratorConfig, ): boolean { const baseUrl = contentGeneratorConfig.baseUrl ?? ''; + if (baseUrl.toLowerCase().includes('api.deepseek.com')) { + return true; + } - return baseUrl.toLowerCase().includes('api.deepseek.com'); + // DeepSeek models served behind any OpenAI-compatible endpoint (sglang, + // vllm, ollama, etc.) share the same content-format constraint that the + // official api.deepseek.com endpoint has. Detect them by model name so + // the buildRequest flattening below kicks in. + // See https://github.com/QwenLM/qwen-code/issues/3613 + const model = contentGeneratorConfig.model ?? ''; + return model.toLowerCase().includes('deepseek'); } /**