fix(deepseek-web): return 400 when client sends tools[] - chat.deepseek.com has no tool support (#2854)

Integrated into release/v3.8.6.
This commit is contained in:
Tushar Agarwal
2026-05-29 09:26:16 +05:30
committed by GitHub
parent 0c9740c771
commit f3404227d2
2 changed files with 81 additions and 0 deletions

View File

@@ -683,6 +683,30 @@ export class DeepSeekWebExecutor extends BaseExecutor {
async execute({ model, body, stream, credentials, signal, log }: ExecuteInput) {
const bodyObj = (body || {}) as Record<string, unknown>;
// chat.deepseek.com's web API only accepts {prompt, ref_file_ids,
// thinking_enabled, search_enabled} - no tools field. Silently dropping
// tools[] is misleading: models reply as if no tools were ever offered,
// causing agentic clients (OpenAI-compatible) to hallucinate "I don't
// have that tool". Fail fast with a clear error so callers route to the
// official DeepSeek API (provider: 'deepseek') or a different provider
// for tool-using requests. See #2848.
const requestedTools = bodyObj.tools;
if (Array.isArray(requestedTools) && requestedTools.length > 0) {
return {
response: errorResponse(
400,
"deepseek-web upstream (chat.deepseek.com) does not support function calling. " +
"The web interface only accepts text + thinking_enabled + search_enabled flags. " +
"Use provider 'deepseek' (official api.deepseek.com) for tool-using requests, " +
"or route through a different provider."
),
url: COMPLETION_URL,
headers: {},
transformedBody: body,
};
}
const messages = (Array.isArray(bodyObj.messages) ? bodyObj.messages : []) as Array<{
role: string;
content: string;

View File

@@ -58,6 +58,63 @@ test("execute returns 400 with empty apiKey", async () => {
assert.equal(result.response.status, 400);
});
test("execute returns 400 when client sends non-empty tools[] (chat.deepseek.com has no tool support) - issue #2848", async () => {
const executor = new DeepSeekWebExecutor();
const result = await executor.execute({
model: "default",
body: {
messages: [{ role: "user", content: "call my_tool" }],
tools: [
{
type: "function",
function: {
name: "my_tool",
description: "test",
parameters: { type: "object", properties: {} },
},
},
],
},
stream: false,
credentials: { apiKey: "any-valid-looking-token" },
signal: AbortSignal.timeout(5000),
});
assert.equal(result.response.status, 400);
const text = await result.response.text();
assert.ok(
/does not support function calling/i.test(text),
`expected explanatory error body, got: ${text}`
);
assert.ok(
/provider 'deepseek'|api\.deepseek\.com/i.test(text),
`expected guidance to use official deepseek provider, got: ${text}`
);
});
test("execute does NOT 400 on tools[]=[] (empty array, equivalent to no tools)", async () => {
const executor = new DeepSeekWebExecutor();
const result = await executor.execute({
model: "default",
body: {
messages: [{ role: "user", content: "hi" }],
tools: [],
},
stream: false,
credentials: {},
signal: AbortSignal.timeout(5000),
});
assert.equal(
result.response.status,
400,
"still returns 400 but for missing userToken, NOT for tools[]"
);
const text = await result.response.text();
assert.ok(
text.includes("userToken"),
`expected userToken error (not tools error) for empty tools[], got: ${text}`
);
});
// ─── Test connection ─────────────────────────────────────────────────────
test("testConnection returns false with empty credentials", async () => {