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 <wenshao@U-K7F6PQY3-2157.local>
This commit is contained in:
Shaojin Wen 2026-04-26 13:17:34 +08:00 committed by GitHub
parent 569cfe10fa
commit 29887ddfef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 36 additions and 2 deletions

View file

@ -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', () => {

View file

@ -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');
}
/**