SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
13 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
3.9 KB · 73 lines python
Raw Blame History
1#!/usr/bin/env python32"""Robust Anthropic error handling (Python SDK): typed exceptions, retry matrix, request ids, streaming errors.34STATUS: LIVE_VERIFIED 2026-09-18 (deliberate 404 + 400 on claude-haiku-4-5-20251001; nothing billed).5Run: .venv/bin/python examples/shared/errors/anthropic_error_handling.py6"""7import random8import sys9import time10from pathlib import Path1112sys.path.insert(0, str(Path(__file__).resolve().parents[3]))13from scripts import live  # noqa: E40214import anthropic  # noqa: E4021516# max_retries=0 so the demo shows the raw first failure; production: keep the default 2 (SDK retries 408/409/429/5xx + connection errors, honors retry-after).17client = anthropic.Anthropic(max_retries=0, timeout=60)18MODEL = "claude-haiku-4-5-20251001"1920RETRYABLE = (anthropic.RateLimitError, anthropic.InternalServerError, anthropic.APIConnectionError, anthropic.APITimeoutError)  # 429, >=500 (incl. 529 OverloadedError), network21FATAL = (anthropic.BadRequestError, anthropic.AuthenticationError, anthropic.PermissionDeniedError, anthropic.NotFoundError, anthropic.UnprocessableEntityError)  # 400/401/403/404/422 — fix the request222324def call_with_backoff(fn, attempts=4, base=0.5, cap=8.0):25    for i in range(attempts):26        try:27            return fn()28        except anthropic.APIStatusError as e:29            should_retry = e.response.headers.get("x-should-retry")          # live: 'false' on every 4xx30            retry_after = e.response.headers.get("retry-after")               # seconds, absent on spend-cap 42931            if isinstance(e, FATAL) or should_retry == "false" or i == attempts - 1:32                raise33            delay = float(retry_after) if retry_after else min(cap, base * 2 ** i) * (1 + random.random() * 0.25)34            print(f"  retryable {e.status_code} {type(e).__name__} (request-id {e.request_id}); sleeping {delay:.2f}s")35            time.sleep(delay)36        except RETRYABLE as e:                                                # connection/timeouts have no status37            if i == attempts - 1:38                raise39            time.sleep(min(cap, base * 2 ** i))404142def demo(label, fn):43    try:44        r = call_with_backoff(fn)45        print(f"{label}: ok -> {r}")46    except anthropic.APIStatusError as e:47        body = e.body if isinstance(e.body, dict) else {}48        err = body.get("error", {})49        print(f"{label}: HTTP {e.status_code} {type(e).__name__} type={err.get('type')} msg={err.get('message')!r} request_id={e.request_id}")50        live.log_request("anthropic", "POST", "/v1/messages", e.status_code, 0, f"error-handling demo {label}")51    except anthropic.APIConnectionError as e:                                 # includes APITimeoutError52        print(f"{label}: connection problem {type(e).__name__}: {e}")535455demo("404 unknown model", lambda: client.messages.create(model="claude-does-not-exist", max_tokens=8, messages=[{"role": "user", "content": "Reply with OK."}]))56demo("400 bad temperature", lambda: client.messages.create(model=MODEL, max_tokens=8, extra_body={"temperature": 5}, messages=[{"role": "user", "content": "Reply with OK."}]))575859# NOTE: anthropic 1.7.0 removed the deprecated `temperature`/`top_p`/`top_k` keyword arguments from messages.create();60# send them with extra_body={...} if you still target a model that accepts them.61# Streaming: an `error` event can arrive after HTTP 200; the SDK raises inside the iteration.62def stream_demo():63    try:64        with client.messages.stream(model=MODEL, max_tokens=8, messages=[{"role": "user", "content": "Reply with OK."}]) as s:65            text = "".join(s.text_stream)66        print("stream ok:", text.strip(), "| stop_reason:", s.get_final_message().stop_reason)67        live.log_request("anthropic", "POST", "/v1/messages", 200, 0.00004, "error-handling demo stream")68    except anthropic.APIError as e:  # mid-stream overloaded_error etc. → retry the whole request with backoff69        print("stream failed:", type(e).__name__, e)707172stream_demo()73