From b6febba2bf365d9a1d4dc6f3dc4dd2dbf87e025f Mon Sep 17 00:00:00 2001 From: "Siren.W" Date: Tue, 19 May 2026 21:45:59 +0800 Subject: [PATCH] Fix standalone DeepSeek L1 extraction --- src/adapters/standalone/llm-runner.ts | 36 +++++++++++++++++++-------- src/gateway/config.ts | 12 +++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/adapters/standalone/llm-runner.ts b/src/adapters/standalone/llm-runner.ts index 2f7c9e1..8724c30 100644 --- a/src/adapters/standalone/llm-runner.ts +++ b/src/adapters/standalone/llm-runner.ts @@ -49,6 +49,8 @@ export interface StandaloneLLMConfig { maxTokens?: number; /** Request timeout in milliseconds (default: 120_000). */ timeoutMs?: number; + /** Disable thinking/reasoning mode (DeepSeek: sets thinking.type=disabled in request body). */ + disableThinking?: boolean; } // ============================ @@ -149,12 +151,6 @@ function createSandboxedTools(workspaceDir: string, logger?: Logger) { }; } -/** Read-only tool subset — used when enableTools=false to avoid empty tools rejection. */ -function createReadOnlyTools(workspaceDir: string, logger?: Logger) { - const all = createSandboxedTools(workspaceDir, logger); - return { read_file: all.read_file }; -} - // ============================ // StandaloneLLMRunner // ============================ @@ -185,29 +181,49 @@ export class StandaloneLLMRunner implements LLMRunner { this.logger?.debug?.( `${TAG} run() start: taskId=${params.taskId}, model=${this.model}, ` + - `tools=${this.enableTools}, timeout=${timeoutMs}ms`, + `tools=${this.enableTools}, disableThinking=${this.config.disableThinking === true}, timeout=${timeoutMs}ms`, ); // Create OpenAI-compatible provider via AI SDK // Use "compatible" mode to call /chat/completions (not Responses API), // which works with all OpenAI-compatible backends (DeepSeek, Qwen, etc.) + const disableThinking = this.config.disableThinking === true; const provider = createOpenAI({ baseURL: this.config.baseUrl, apiKey: this.config.apiKey, compatibility: "compatible", + ...(disableThinking + ? { + fetch: async (url, init) => { + if (typeof init?.body === "string") { + try { + const body = JSON.parse(init.body); + body.thinking = { type: "disabled" }; + init = { ...init, body: JSON.stringify(body) }; + } catch (error) { + const errMsg = error instanceof Error ? error.message : String(error); + this.logger?.warn?.(`${TAG} failed to inject thinking=disabled: ${errMsg}`); + } + } + return fetch(url, init); + }, + } + : {}), }); - // Select tools based on mode + // For pure text tasks like L1 extraction, avoid exposing any tools. + // DeepSeek may opportunistically issue tool calls even when the prompt + // asks for plain JSON, which can lead to empty/non-JSON output. const tools = this.enableTools ? createSandboxedTools(workspaceDir, this.logger) - : createReadOnlyTools(workspaceDir, this.logger); + : undefined; try { const result = await generateText({ model: provider.chat(this.model), system: params.systemPrompt, prompt: params.prompt, - tools, + ...(tools ? { tools } : {}), stopWhen: stepCountIs(this.enableTools ? MAX_TOOL_ITERATIONS : 1), maxOutputTokens: maxTokens, abortSignal: AbortSignal.timeout(timeoutMs), diff --git a/src/gateway/config.ts b/src/gateway/config.ts index 81c55ad..1a91321 100644 --- a/src/gateway/config.ts +++ b/src/gateway/config.ts @@ -92,6 +92,7 @@ export function loadGatewayConfig(overrides?: Partial): GatewayCo model: env("TDAI_LLM_MODEL") ?? str(llmConfig, "model") ?? "gpt-4o", maxTokens: envInt("TDAI_LLM_MAX_TOKENS") ?? num(llmConfig, "maxTokens") ?? 4096, timeoutMs: envInt("TDAI_LLM_TIMEOUT_MS") ?? num(llmConfig, "timeoutMs") ?? 120_000, + disableThinking: envBool("TDAI_LLM_DISABLE_THINKING") ?? bool(llmConfig, "disableThinking") ?? false, }; // Memory config (reuse the plugin's parseConfig for full compatibility) @@ -184,6 +185,17 @@ function envInt(key: string): number | undefined { return Number.isFinite(n) ? n : undefined; } +function envBool(key: string): boolean | undefined { + const v = env(key); + if (!v) return undefined; + return v === "1" || v.toLowerCase() === "true"; +} + +function bool(src: Record, key: string): boolean | undefined { + const v = src[key]; + return typeof v === "boolean" ? v : undefined; +} + function obj(c: Record, key: string): Record { const v = c[key]; return v && typeof v === "object" && !Array.isArray(v) ? v as Record : {};