From f3176b4b236ef4516d13669d58f16eacf17f4508 Mon Sep 17 00:00:00 2001 From: mustafabozkaya Date: Tue, 26 May 2026 11:22:31 +0700 Subject: [PATCH] fix: implement json_schema fallback for OpenAI-compatible providers (#1343) When json_schema response_format is sent to models that do not natively support Structured Output (e.g. DeepSeek/Ollama/local LLMs via openai-compatible-* providers), they often return empty or malformed content. Detect response_format.type === "json_schema", inject the schema into the system prompt, and downgrade response_format to json_object so Structured Output works transparently across openai-compatible providers. Gated to provider.startsWith("openai-compatible-") so providers with native Structured Output support are not downgraded. Co-authored-by: Cursor --- open-sse/executors/default.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/open-sse/executors/default.js b/open-sse/executors/default.js index bca92216..634b82e8 100644 --- a/open-sse/executors/default.js +++ b/open-sse/executors/default.js @@ -12,7 +12,28 @@ export class DefaultExecutor extends BaseExecutor { } transformRequest(model, body) { - return injectReasoningContent({ provider: this.provider, model, body }); + const transformed = this.applyJsonSchemaFallback(body); + return injectReasoningContent({ provider: this.provider, model, body: transformed }); + } + + // Fallback json_schema → json_object for openai-compatible providers without native Structured Output. + applyJsonSchemaFallback(body) { + if (!this.provider?.startsWith?.("openai-compatible-")) return body; + const rf = body?.response_format; + if (rf?.type !== "json_schema" || !rf.json_schema?.schema) return body; + + const schemaJson = JSON.stringify(rf.json_schema.schema, null, 2); + const prompt = `You must respond with valid JSON that strictly follows this JSON schema:\n\`\`\`json\n${schemaJson}\n\`\`\`\nRespond ONLY with the JSON object, no other text.`; + + const messages = Array.isArray(body.messages) ? body.messages.map(m => ({ ...m })) : []; + const sys = messages.find(m => m.role === "system"); + if (sys) { + if (typeof sys.content === "string") sys.content = `${sys.content}\n\n${prompt}`; + else if (Array.isArray(sys.content)) sys.content.push({ type: "text", text: `\n\n${prompt}` }); + } else { + messages.unshift({ role: "system", content: prompt }); + } + return { ...body, messages, response_format: { type: "json_object" } }; } buildUrl(model, stream, urlIndex = 0, credentials = null) {