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) {