justjavac/wechat-miniapp-radar · error · Error

AI provider rejected ${model}: ${providerError}

Error message

AI provider rejected ${model}: ${providerError}

What it means

Thrown by requestChatCompletion() in lib/ai-client.ts when the provider returns a non-2xx HTTP status. It first parses the error body via parseCompletionPayload() and stringifyProviderError() (which reads message/code and metadata.reason/raw/provider_name) and formats `AI provider rejected <model>: <detail>`; if no detail is parseable it falls back to `... with HTTP <status>`. The model name interpolated is the one actually requested (primary or fallback). createAiJsonCompletion() catches this, records the message, and tries the next model before returning ok:false.

Source

Thrown at lib/ai-client.ts:156

        authorization: `Bearer ${apiKey}`,
        ...openRouterHeaders(config)
      },
      body: JSON.stringify({
        model,
        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;

View on GitHub (pinned to 02a010ecea)

Solutions

  1. Decode the interpolated detail first: 401/403 fix OPENAI_API_KEY; 402 add credits or switch model; 404 correct OPENAI_MODEL; 429 back off or raise quota; 400 response_format remove the model or add it to OPENROUTER_JSON_RESPONSE_FORMAT_MODELS.
  2. Let createAiJsonCompletion() try config.fallbackModel automatically; ensure OPENAI_FALLBACK_MODEL is a different, currently available model.
  3. For 429/5xx, retry with bounded exponential backoff at the caller.
  4. If all models fail, fall back to the app's rule-based advisor/scoring.

Example fix

// before
const result = await createAiJsonCompletion<T>({ messages });
// result.error may be 'AI provider rejected openai/gpt-oss-20b:free: ...'

// after
if (!result.ok) {
  console.warn(result.error);
  return ruleBasedFallback();
}
Defensive patterns

Strategy: fallback

Try / catch

const result = await createAiJsonCompletion<T>({ messages });
if (!result.ok || !result.value) {
  // result.error lists per-model rejection reasons joined by ' | '
  return ruleBasedFallback();
}
return result.value;

Prevention

When it happens

Trigger: 401/403 invalid or revoked OPENAI_API_KEY; 402 insufficient credits (common with OpenRouter free quota); 404 unknown or deprecated model id in OPENAI_MODEL/OPENAI_FALLBACK_MODEL; 429 rate limit or daily free quota exhausted; 400 unsupported response_format for a model not in OPENROUTER_JSON_RESPONSE_FORMAT_MODELS; provider 5xx.

Common situations: OpenRouter free-tier daily limit hit; model renamed or retired upstream (e.g. a :free variant removed); wrong OPENAI_API_URL (custom endpoint returning HTML error pages); key without permission for the chosen model; burst traffic triggering 429.

Related errors


AI-assisted analysis of justjavac/wechat-miniapp-radar@02a010ecea (2026-08-12). Data as JSON: /api/errors/684c33a47d9bbdaa. Report an issue: GitHub.