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

  1. 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.
  2. If refusals are likely, soften or rewrite the prompt and re-send.
  3. If truncation is likely, raise the token budget for the call.
  4. 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

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-assisted analysis of justjavac/wechat-miniapp-radar@02a010ecea (2026-08-12). Data as JSON: /api/errors/8eeed5df3d87deee. Report an issue: GitHub.