Fouttypen in de Gemini API
Gemini-fouten vallen in twee categorieën. HTTP-fouten betekenen dat de request de API bereikte maar werd geweigerd of mislukte. Content-fouten betekenen dat de request werd verwerkt, maar dat het resultaat niet bruikbaar is, bijvoorbeeld omdat een safety-filter het antwoord blokkeerde.
Sinds 2025 is de aanbevolen Python-bibliotheek de google-genai SDK (import from google import genai). De oudere google.generativeai SDK is uitgefaseerd en krijgt geen nieuwe modellen of features meer. De voorbeelden hieronder gebruiken daarom de actuele SDK.
Houd je SDK actueel
Update regelmatig met pip install -U google-genai. Oudere SDK-versies sturen request-formaten die de API niet meer accepteert, wat onnodige 400-fouten oplevert.
HTTP-foutcodes
| Code | Naam | Oorzaak | Oplossing | Retry |
|---|---|---|---|---|
| 400 | INVALID_ARGUMENT | Ongeldig request-formaat | Controleer de request-structuur | Nee |
| 403 | PERMISSION_DENIED | Ongeldige sleutel, ontbrekende permissies of regio zonder billing | Controleer je GEMINI_API_KEY, IAM-permissies of facturatie |
Nee |
| 404 | NOT_FOUND | Onbekend model of resource | Controleer de modelnaam en API-versie | Nee |
| 429 | RESOURCE_EXHAUSTED | Rate limit of quota bereikt | Exponentiële backoff toepassen | Ja |
| 500 | INTERNAL | Google-zijdige fout, soms te lange context | Context verkleinen, wachten en opnieuw proberen | Ja |
| 503 | UNAVAILABLE | Model tijdelijk overbelast of offline | Wachten en opnieuw proberen | Ja |
| 504 | DEADLINE_EXCEEDED | Prompt of context te groot voor de tijdslimiet | Verhoog je client-timeout of verklein de context | Ja |
Let op: authenticatieproblemen geven bij de Gemini API een 403 PERMISSION_DENIED, niet 401. Een 401 is geen standaard Gemini-respons.
Foutafhandeling met de google-genai SDK
De SDK gooit errors.APIError voor fouten van de modelservice. Daaronder vallen errors.ClientError (4xx) en errors.ServerError (5xx). Het exacte HTTP-statusnummer staat in het code-attribuut, zodat je per code een strategie kunt kiezen.
from google import genai
from google.genai import errors
import time
import random
import logging
from enum import Enum
logger = logging.getLogger(__name__)
client = genai.Client(api_key="GEMINI_API_KEY")
class RetryStrategy(Enum):
NO_RETRY = "no_retry"
EXPONENTIAL = "exponential"
RETRYABLE_CODES = {429, 500, 503, 504}
def strategy_for_code(code: int) -> RetryStrategy:
if code in RETRYABLE_CODES:
return RetryStrategy.EXPONENTIAL
return RetryStrategy.NO_RETRY
def generate_with_full_error_handling(
model: str,
prompt: str,
max_retries: int = 5,
) -> tuple:
for attempt in range(max_retries + 1):
try:
response = client.models.generate_content(
model=model,
contents=prompt,
)
feedback = response.prompt_feedback
if feedback and feedback.block_reason:
return None, f"Prompt geblokkeerd: {feedback.block_reason.name}"
candidates = response.candidates
if not candidates:
return None, "Geen kandidaatantwoorden gegenereerd"
candidate = candidates[0]
if candidate.finish_reason and candidate.finish_reason.name == "SAFETY":
categorien = [
r.category.name for r in (candidate.safety_ratings or [])
if r.probability and r.probability.name in ("HIGH", "MEDIUM")
]
return None, f"Antwoord geblokkeerd door safety: {categorien}"
if candidate.finish_reason and candidate.finish_reason.name == "MAX_TOKENS":
logger.warning("Antwoord afgekort door MAX_TOKENS")
return response.text, None
except errors.APIError as e:
strategy = strategy_for_code(e.code)
if strategy == RetryStrategy.NO_RETRY or attempt == max_retries:
logger.error(f"Fatale fout {e.code}: {e.message}")
return None, e.message
wait_time = (2 ** attempt) + random.uniform(0, 1)
logger.warning(
f"Fout {e.code}, poging {attempt + 1}/{max_retries}. Wacht {wait_time:.1f}s"
)
time.sleep(wait_time)
return None, "Maximaal aantal pogingen bereikt"
De random.uniform(0, 1) voegt jitter toe. Zonder die ruis proberen meerdere clients precies tegelijk opnieuw, wat de overbelasting in stand houdt.
Safety-fouten specifiek afhandelen
Een blokkade kan op twee plekken ontstaan: in de prompt (prompt_feedback.block_reason) of in het gegenereerde antwoord (finish_reason == SAFETY). Behandel ze apart, want de boodschap aan de gebruiker verschilt.
def handle_safety_block(response) -> dict:
result = {
"blocked": False,
"reason": None,
"categories": [],
"text": None,
}
feedback = response.prompt_feedback
if feedback and feedback.block_reason:
result["blocked"] = True
result["reason"] = "prompt_blocked"
result["categories"] = [feedback.block_reason.name]
return result
if response.candidates:
candidate = response.candidates[0]
if candidate.finish_reason and candidate.finish_reason.name == "SAFETY":
result["blocked"] = True
result["reason"] = "output_blocked"
result["categories"] = [
r.category.name
for r in (candidate.safety_ratings or [])
if r.probability and r.probability.name in ("HIGH", "MEDIUM")
]
return result
if candidate.content and candidate.content.parts:
result["text"] = candidate.content.parts[0].text
return result
Safety is geen crash
Behandel safety-blokkades nooit als onherstelbare fouten in je UX. Geef gebruikers een begrijpelijke melding en, waar relevant, een suggestie om hun vraag te herformuleren.
Context-limiet fouten
Bij erg lange invoer loop je tegen het context-venster aan of krijg je een 500 of 504 omdat de verwerking te zwaar wordt. Splits de tekst dan in stukken en verwerk die afzonderlijk.
def split_and_process(model: str, long_text: str, chunk_size: int = 50000) -> list:
words = long_text.split()
chunks = []
current = []
for word in words:
current.append(word)
if len(current) >= chunk_size:
chunks.append(" ".join(current))
current = []
if current:
chunks.append(" ".join(current))
results = []
for i, chunk in enumerate(chunks):
prompt = f"Verwerk deel {i + 1}/{len(chunks)}:
{chunk}"
text, error = generate_with_full_error_handling(model, prompt)
if error:
logger.error(f"Deel {i + 1} mislukt: {error}")
results.append({"deel": i + 1, "tekst": text, "fout": error})
return results
Foutlogging voor productie
Log fouten gestructureerd, zodat je ze later kunt filteren op error_code en patronen kunt herkennen (bijvoorbeeld een piek aan 429's die op te lage quota wijst).
import json
import traceback
from datetime import datetime, timezone
def log_api_error(error: Exception, context: dict) -> None:
log_entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"error_type": type(error).__name__,
"error_code": getattr(error, "code", None),
"error_message": str(error),
"context": context,
"stacktrace": traceback.format_exc(),
}
logger.error("gemini_api_error", extra={"data": json.dumps(log_entry)})
Rate limits in de praktijk
De free tier is in december 2025 verlaagd: het aantal verzoeken per minuut ligt nu lager dan voorheen, waardoor apps die eerder werkten ineens 429-fouten konden krijgen. Reken niet op de free-tier limieten voor productie. Limieten gelden over drie assen: verzoeken per minuut (RPM), tokens per minuut (TPM) en verzoeken per dag (RPD), die rond middernacht Pacific Time reset. Schakel facturatie in of vraag een quotaverhoging aan voordat je live gaat, en respecteer altijd de exponentiële backoff uit het voorbeeld hierboven.
Hoe onderscheid ik een tijdelijke fout van een permanente?
Kijk naar de code op de APIError. De codes 429, 500, 503 en 504 zijn tijdelijk en lenen zich voor retry. De overige 4xx-fouten (400, 403, 404) zijn permanent: je request klopt niet en opnieuw proberen helpt niet. Codeer dit expliciet, zoals in het voorbeeld met RETRYABLE_CODES.
Welke SDK moet ik gebruiken, google.generativeai of google-genai?
Gebruik google-genai (import from google import genai). Dat is de actuele, ondersteunde bibliotheek voor zowel de Gemini Developer API als Vertex AI. De oudere google.generativeai is uitgefaseerd en ondersteunt nieuwe modellen niet meer.
Wat doe ik als mijn API-sleutel niet werkt?
Een ongeldige of niet-geautoriseerde sleutel geeft een 403 PERMISSION_DENIED, niet 401. Controleer of de sleutel volledig en zonder spaties is overgenomen en of hij rechten heeft op het gekozen model. Bij Vertex AI controleer je de service-account credentials en hun vervaldatum.
Hoe vang ik specifiek een rate limit (429) op?
Vang errors.APIError (of errors.ClientError) en lees e.code. Is die 429, pas dan exponentiële backoff met jitter toe en probeer opnieuw. Houd ook rekening met de RPM-, TPM- en RPD-limieten van je tier, want een 429 kan op elk van die drie slaan.
Hoe test ik foutafhandeling zonder echte fouten te triggeren?
Gebruik een mock van client.models.generate_content die een errors.APIError met een gekozen code gooit, of een respons met een block_reason. Test elk pad apart: 429-retry, 403-stop, prompt-blokkade en output-blokkade. Tools voor chaos testing kunnen willekeurig fouten injecteren om de robuustheid onder load te toetsen.
Moet ik alle API-fouten aan de gebruiker tonen?
Nee. Log alles intern, maar toon gebruikers alleen een bruikbare melding. Gebruik "Probeer het opnieuw" voor tijdelijke fouten, "Je vraag is geblokkeerd" voor safety en een neutrale "Er ging iets mis" voor interne fouten.