# Foutafhandeling in Gemini API-apps [[TOC]] ## 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. :::tip title="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. ```python 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. ```python 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 ``` :::warn title="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. ```python 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). ```python 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. :::faq ### 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. :::