justjavac/wechat-miniapp-radar · error · Error
AI provider returned an empty response for ${model}.
Error message
AI provider returned an empty response for ${model}. What it means
Thrown by requestChatCompletion() in lib/ai-client.ts after a 2xx response when payload.choices[0].message.content is absent, not a string, or whitespace-only. It guards the contract that the provider returned usable text before parseJsonObject() runs. Typical of content-filter refusals, reasoning models that populate a reasoning field but leave content empty, or responses truncated by the small max_tokens (DEFAULT_AI_MAX_TOKENS = 1400). createAiJsonCompletion() catches it and retries the fallback model.
Source
Thrown at lib/ai-client.ts:161
messages,
temperature: 0.2,
max_tokens: DEFAULT_AI_MAX_TOKENS,
stream: false,
...responseFormatForModel(config, model)
})
},
timeoutMs
);
const payload = parseCompletionPayload(text);
if (!response.ok) {
const providerError = stringifyProviderError(payload?.error);
throw new Error(providerError ? `AI provider rejected ${model}: ${providerError}` : `AI provider rejected ${model} with HTTP ${response.status}.`);
}
const content = payload?.choices?.[0]?.message?.content;
if (typeof content !== "string" || content.trim().length === 0) {
throw new Error(`AI provider returned an empty response for ${model}.`);
}
return parseJsonObject<T>(content);
}
export async function createAiJsonCompletion<T>({
messages,
timeoutMs = DEFAULT_AI_TIMEOUT_MS,
totalTimeoutMs = DEFAULT_AI_TOTAL_TIMEOUT_MS
}: {
messages: AiPromptMessage[];
timeoutMs?: number;
totalTimeoutMs?: number;
}): Promise<AiJsonCompletionResult<T>> {
const config = getAiConfig();
if (!config.configured) {
return {
ok: false,View on GitHub (pinned to 02a010ecea)
Solutions
- Inspect result.error/result.model from createAiJsonCompletion; if the primary model is the culprit, rely on the automatic fallback and ensure OPENAI_FALLBACK_MODEL is set and available.
- If refusals are likely, soften or rewrite the prompt and re-send.
- If truncation is likely, raise the token budget for the call.
- When all models return empty, use the rule-based fallback so the user-facing flow still answers.
Example fix
// before
const { value } = await requestChatCompletion<T>({ config, model, messages, timeoutMs });
// after (use the public API and handle empty-response failure)
const result = await createAiJsonCompletion<T>({ messages });
if (!result.ok || !result.value) {
return ruleBasedFallback();
} Defensive patterns
Strategy: fallback
Try / catch
const result = await createAiJsonCompletion<T>({ messages });
if (!result.ok || !result.value) {
// 'AI provider returned an empty response for <model>.' is in result.error
return ruleBasedFallback();
}
return result.value; Prevention
- Always consume createAiJsonCompletion and branch on ok/value rather than calling requestChatCompletion directly.
- Keep a distinct, available OPENAI_FALLBACK_MODEL.
- Avoid prompts likely to trigger content-filter refusals and keep requested JSON within the 1400-token budget.
When it happens
Trigger: Provider returns 200 with choices[0].message.content equal to null or '' (content filter, safety refusal, or empty completion); a reasoning model places output in a non-content field; the response shape lacks choices/message; output larger than max_tokens and returned empty; OpenRouter routing glitch returning a 200 with no choices.
Common situations: Free models with aggressive safety filters; prompts that trip refusals; switching to a 'reasoning' variant that does not populate content; requested JSON exceeding the 1400-token cap; provider returning an unrelated 200 JSON without choices.
Related errors
- AI provider rejected ${model}: ${providerError}
- AI response JSON must be an object.
- OPENAI_API_KEY is not configured.
AI-assisted analysis of justjavac/wechat-miniapp-radar@02a010ecea (2026-08-12).
Data as JSON: /api/errors/8eeed5df3d87deee.
Report an issue: GitHub.