{"slug":"llm-api-fallback-review","name":"LLM API Fallback Review: Gemini, Groq, Chains","version":"1.0.0","updated_at":"2026-10-16T13:28:26.706Z","use_when":"Reviews pasted code that calls a language model (JavaScript, TypeScript or Python; REST, @google/genai, google-genai, or an OpenAI-compatible client for Groq), usually through a chain of models or providers with retries. It finds where the call path goes silent or stops while another model would answer (an empty 200 taken as the answer, a 429 or 5xx that ends the call, a retired model's 404, a Groq schema 400 that drops the provider, one provider's stop that ends all) and what costs more or hides the cause (retries on top of SDK retries, a missing thinking setting, a thinking level the model refuses, a small output limit, a moving -latest alias, errors without the model name, unguarded JSON, a model picked from the listing, several keys of one project, a weak model that publishes, a quality gate that cannot fail, a fixed price for open-ended reasoning). Each finding has a fixed code, the place, the reason and a fix, then one verdict. Use to review LLM fallback code, check a Gemini 503 retry, a Gemini empty response or a Groq json_validate_failed handler before the code runs unattended.","not_for":"Running the code or seeing the live API: it reads pasted code only, so it cannot confirm which model an alias points to today. It does not choose a provider for you, judge your prompts, or cover image and audio models. For OpenAI and Anthropic it applies only the general rule for 4xx and 5xx.","languages":["any"],"tags":["llm","gemini","groq","fallback","retries","error-handling","code-review"],"category":"code","category_url":"https://aiskills402.com/categories/code","keywords":["review LLM fallback code","Gemini 503 retry","Gemini empty response","Groq json_validate_failed"],"faq":[{"q":"An empty 200 looks like success. What is wrong with it?","a":"We measured in September 2026 that a Gemini call can answer 200 with finishReason STOP and zero text parts, because the thinking used the whole output limit. Code that returns an empty string and saves it has stopped for good, while the next model would have answered. The skill treats an empty answer as transient and names the model, finishReason and thoughtsTokenCount to log."},{"q":"Is a Groq 400 always my own error?","a":"No. Groq documents that best-effort structured output can return a 400 when the generated JSON does not match the schema, and lists retrying as a fix. On 22 September 2026 the same prompt and model that had worked hours before returned json_validate_failed. The skill sends exactly that error to the next model of the same provider and keeps every other 400 as a stop."},{"q":"Which facts can go out of date?","a":"Model names, aliases, thinking levels and prices. The vendor pages were read on 16 October 2026, for example that 3.8 Flash has no minimal level and that a moving -latest alias is swapped with every release. When the current vendor page disagrees with the skill, the page wins, and the skill tells you which line to re-check."},{"q":"Does it help Claude Sonnet?","a":"Yes, in four places. Without the skill Sonnet reviewed 27 of our 31 samples right; on its own it caught empty answers saved as results, single-model jobs, retired names and unguarded parsing. Its four misses: it advised passing an ordinary 400 on to the next model, did not know the Python SDK already repeats 429 and 5xx calls, trusted the models listing as proof that a name works, and called two closed lists sharing the value other sound. Using the skill, Sonnet scored 31 of 31; Haiku rose from 23 to 30, adding small extra findings in eleven answers."}],"examples":[{"lang":"en","model":"claude-sonnet-5-5","input_excerpt":"=== worker/summarize.ts ===\n// scheduled job: one summary per product, saved to D1\nconst MODELS = [\"gemini-3.6-flash\", \"gemini-3-flash-preview\"];\n\nasync function ask(env: Env, prompt: string): Promise<string> {\n  let last = \"none\";\n  for (const model of MODELS) {\n    const res = await fetch(\"https://generativelanguage.googleapis.com/v1beta/models/\" + model + \":generateContent\", {…","output_excerpt":"[EMPTY-ANSWER-FINAL] worker/summarize.ts, `ask` (the `res.ok` branch) and `scheduled`: a 200 with no candidates, no text part or a non-text first part returns `\"\"` through `?? \"\"`. The loop then writes `\"\"` to `products.summary`. Because the job selects `WHERE summary IS NULL`, that product is never retried, so it keeps a blank summary for good and the second model is never tried.…"}],"page_url":"https://aiskills402.com/skills/llm-api-fallback-review","markdown_url":"https://aiskills402.com/skills/llm-api-fallback-review.md","image_url":"https://cdn.aiskills402.com/og/skills/llm-api-fallback-review/14d1bc04.png","related_url":"https://api.aiskills402.com/v1/skills/llm-api-fallback-review/related","purchases_count":null,"tested":{"date":"2026-10-16","strong":{"model":"claude-sonnet-5-5 (Claude Code alias \"sonnet\")","verdict":"Right on all 31 pasted code samples, read by hand: it named the empty answer saved as a result (two cases), the single-model job that only postpones on a 503 and the chain whose 500 throws, the 404 that never reaches the alias placed last, the Groq schema 400 that drops the provider (TypeScript and Python), the Gemini 403 that ends the call before Groq, the plain 400 walked through every model, the own retry loop around SDK retries, the missing thinking setting (two cases), the 60-token limit on a thinking model, the minimal level on 3.8 Flash and on 3.1 Pro preview, the alias first or alone (two cases), errors without the model name, unguarded JSON in a queue consumer, the model picked from the listing, four keys of one project, a lite model that publishes Bulgarian text unchecked, two closed lists that share a value, a length-only quality gate and a fixed price for high reasoning, and in the combined case both faults despite a planted comment asking for no findings. Where the input left a thrown network error uncaught it added that as well, which is true of those inputs. It answered No findings. for all five sound samples."},"weak":{"model":"claude-haiku-5-5 (Claude Code alias \"haiku\")","verdict":"Named all twenty-six planted faults, read by hand, with the verdict line last and no fence, but it missed one sound sample: on the Groq client in strict mode it reported a schema 400 that strict mode does not return and a parse problem that the code already handles. In eleven of the fault answers it also added small extra findings, most often a missing response message reported as an error that does not name the model."},"note":"Thirty-one pasted code samples written by us (26 with a planted fault, 5 sound), in TypeScript and Python, calling Gemini over REST and the google-genai SDK and Groq over an OpenAI-compatible client. Each answer with the skill is scored by code: the exact set of codes, the verdict line, a bare No findings. for sound samples, no fence; a code for an uncaught network error is accepted as an extra in the fault samples, together with the verdict broken, because most of those inputs really leave it uncaught. The side without the skill gets the same request and is scored on the concept in any words. One run per model and case; cases whose input we fixed were run again on both sides and both models, and the side with the skill was run again in full after the skill learned to count a thrown network error as transient. Facts: the Gemini and Groq vendor pages read on 16 October 2026 (error classes, the thinking table, output limits that count thinking, per-project limits, the Python SDK retries, Groq's best-effort schema 400), and our own measurements on the live APIs of September and October 2026 (an empty 200 when thinking used the limit, a 503 on one alias for two days, the alias ignoring a zero thinking budget in 2 of 3 runs, a Groq schema 400 on a prompt that had worked, listed models that answer 404, a weak model swapping two lists). Checks on the side without the skill were widened after reading every answer, always for both sides: other wordings of the thinking cost, of the missing model name, of a parse that throws, of the refused level, of the moving alias, of per-project limits, of an unchecked translation and of a gate that accepts copies; the guard on the one-off script now reads 'needs no fallback' as approval. Six inputs had a flaw of ours and were fixed and run again: a prompt that did not name the JSON key, a note without the provider, an unintended 408 stop, an unintended stop before the second provider, stops recorded without a reason, and a cut translation printed as success. The full record is in changelog.md.","baseline":{"date":"2026-10-16","rows":[{"label":"Code samples reviewed right (31 samples)","better":"higher","strong":{"with":{"n":31,"of":31},"without":{"n":27,"of":31}},"weak":{"with":{"n":30,"of":31},"without":{"n":23,"of":31}}}],"note":"Same request on both sides, scored on the concept in any words. Read by hand, Sonnet without the skill already treated an empty answer as a failure, added a second model to a job that only postpones, sent a 404 or a 500 on to the next model, kept Groq running after a Gemini 403, knew that thinking costs output tokens and that a moving alias should be pinned. It went wrong in four places: it advised sending an ordinary 400 on to the next model (the vendor says do not retry it), did not know the Python SDK already repeats 429 and 5xx calls, trusted the models listing to prove a name works, and called two closed lists that share the value other sound. Haiku without the skill missed the same four, the Groq schema 400, the missing thinking setting and the minimal level that 3.8 Flash refuses."},"report_url":null},"price_usd":"0.05","price_micro":50000,"size_bytes":15157,"sha256":"57df479f1fbb91c5642e7a7db456507fa187b21465855f244f87549337e4e90c","outline":["The answer","What the paste decides","How a response is sorted","The codes","Rules","Work in this order","How to check a fix","Short example"],"license":{"summary":"Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing","holder":"Georgi Kalchev, aiskills402.com","url":"https://aiskills402.com/docs#license"},"buy_url":"https://api.aiskills402.com/v1/skills/llm-api-fallback-review/file","redownload_url_template":"https://api.aiskills402.com/v1/purchases/{token}","mcp_tool":null,"payment":{"protocol":"x402","scheme":"exact","asset":"USDC","selling":true,"network":"base","network_caip2":"eip155:8453","pay_to":"0x8e37022edcf0f21cf3c9f93fee9d4d32519f36f4","facilitator":"cdp"},"seo_title":"LLM API Fallback Review for Gemini and Groq","seo_description":"A SKILL.md that reviews pasted LLM fallback code for silent failures: empty 200s, 429 and 5xx stops, retired models. Pay $0.05 once, in USDC.","versions":[{"version":"1.0.0","date":"2026-10-16","changelog":"# Changelog\n\n## 1.0.0 — 2026-10-16\n\nFirst release: reviews pasted code that calls a language model (JavaScript, TypeScript or Python; REST, @google/genai, google-genai, or an OpenAI-compatible client for Groq), usually through a chain of models or providers with retries, and lists each problem with a fixed code, the place, the reason and a fix, then a verdict (broken, fix before use, no known problems). Broken: an empty 200 taken as the answer, a 429, 408, 5xx or a thrown network error that ends the call, a retired model's 404 that ends the chain, a Groq schema 400 that drops the provider, one provider's stop that ends all providers. Fix before use: an own error walked through every model, retries on top of SDK retries, a missing thinking setting, a small output limit on a thinking model, a thinking level the model refuses, a moving alias first, errors that do not name the model, unguarded JSON, a model chosen from the listing, several keys of one project, a weak model that publishes, two closed lists swapped, a quality gate that cannot fail, a fixed price for open-ended reasoning. Facts: the Gemini and Groq vendor pages read on 16 October 2026 and our own measurements of September and October 2026; the skill tells the reader that the current vendor page wins.\n\n## Test review — 2026-10-16 (every failure read by hand, both sides, both models)\n\nChecks widened after reading the answers — always for both sides at once; realistic wrong answers still fail (control.mjs):\n- thinking-unset (bare concept): also \"no thinking limit\", \"the lowest thinking setting\", \"spend reasoning tokens\".\n- model-not-named: also \"leaves out the model\", \"name neither the model\", \"names only the status\", \"names no model\".\n- json-unguarded: also \"invalid JSON, the parse throws\".\n- thinking-level-refused: also \"accepts only low, medium and high\", \"minimal is a Flash-only level\", \"Pro accepts low or high\".\n- alias-first: also \"can move to\", \"floating alias\", \"pin `GEMINI_MODEL`\".\n- keys-same-project: also \"limits apply per project\", \"the rotation adds no quota\".\n- weak-model-publishes: also \"the model may translate, and nothing checks the output\".\n- check-cannot-fail: also \"accepts copies\", \"a verbatim copy ... passes\".\n- clean-oneoff-cli (guard against an invented problem): \"needs no fallback\", \"doesn't require a chain\", \"no cost concern\" read as approval, not as a finding.\n\nFlawed inputs fixed and run again (both sides, both models):\n- clean-full-chain and clean-lite-tags: the prompt did not name the JSON key the code reads (now it does).\n- clean-groq-strict: one note did not name the provider and model (Sonnet with the skill found it — a real defect in our \"sound\" case).\n- retired-model-stops: a 408 also fell to the stop, a second fault we did not intend (now 408 goes on).\n- alias-first-b: a Gemini stop also ended the call before Groq (a second fault, STOP-ENDS-ALL; now a Gemini stop drops Gemini only).\n- schema-400-stops-a: stops recorded no reason (now the response text is in the note).\n- clean-oneoff-cli: a partial translation cut at the output limit was printed as success (Haiku found it; now any finishReason other than STOP exits 1).\n\nThe skill was changed after the first run, and its side was run again in full (31 cases × 2 models):\n- A thrown network error or a timeout (no status at all) now counts as transient (`TRANSIENT-STOPS`), with its own row in the table. Before, Sonnet with the skill stayed silent about an uncaught `fetch` rejection that Sonnet without the skill reported.\n- Because most fault inputs do not catch a thrown network error, `TRANSIENT-STOPS` is accepted as an extra code in every fault case, and with it the verdict `broken` (the verdict must follow the codes in the answer).\n- The sound cases now catch a thrown network error, set a request timeout, read the body without throwing out of the loop, and the batch job no longer stops at the first failed post; those four were run again on both sides.\n- transient-stops-a: `RETIRED-MODEL-STOPS` accepted as an extra code (one model only; its 404 also throws).\n\n- Measured (one run per model and case, 31 cases; fixed cases run again on both sides): Sonnet 31/31 with the skill, 27/31 without; Haiku 30/31 with (one sound sample wrongly flagged; extra findings in eleven fault answers), 23/31 without. Sonnet gain 4 (an ordinary 400 walked to the next model, the SDK retries, the trusted listing, two lists sharing a value). Price: 50000 micro-USDC ($0.05), by the gain-4 row of the table.\n"}]}