Skip to Content
ExamplesAnthropic

Anthropic

The native anthropic client from donkey.anthropic.client(), on the governed transport. It sends /v1/messages, so it needs a proxy provisioned Format=Anthropic (for example ddk-anthropic-inbound). The default DDK proxies are Format=OpenAI and return 404 on /v1/messages. Because the client shares the governed transport, last_call and simulate() work just as they do for OpenAI.

#ScriptShowsNeeds
01native-messages.pyA native Messages call and last_callProxy credentials, Format=Anthropic
02typed-refusals-simulated.pyFour simulated refusals, typedNothing — any placeholder values

Install

Follow the examples setup first, then:

python -m pip install -e "../donkey-development-kit/python[llm,anthropic]" set -a; source .env.local; set +a export DONKEY_LLM_PROXY_URL=https://<host>/ddk-anthropic-inbound/ # 01 only; overrides the file

anthropic 0.x and 1.x both work. The anthropic 1.x releases (September 2026) moved to httpx2 and reject an httpx client, so the SDK hands them an httpx2 client that sends through the governed transport (Anthropic SDK). Published donkey-kit builds up to 0.1.1.dev1 predate this and fail on 1.x with TypeError: Invalid http_client argument; with one of those, add "anthropic<1" to the install line. These examples were run against the proxy with anthropic 0.125.0.

The client id and secret are the same pair as the other proxies. The model is claude-haiku-4-5-20251001 (the MODEL constant).

01 — Native Messages

python "demos/human-made/anthropic/01 - native-messages.py"
async with Donkey.from_env() as donkey: client = donkey.anthropic.client() # native AsyncAnthropic, POST /v1/messages raw = await client.messages.with_raw_response.create( model=MODEL, max_tokens=32, messages=[{"role": "user", "content": "Say hello in exactly three words."}], ) print(raw.parse().content[0].text) last = donkey.last_call print("served_provider", last.served_provider) print("served_model ", last.served_model) print("input_tokens ", last.input_tokens) print("output_tokens ", last.output_tokens) print("request-id ", raw.headers.get("request-id")) print("request_id ", last.request_id)

You should see: the reply; served_provider, served_model, and input and output tokens from last_call; then Anthropic’s request-id header next to last_call.request_id. request-id is not one of the headers the SDK reads for request_id yet, so the two can differ, or the field can be None.

02 — Typed refusals, simulated

python "demos/human-made/anthropic/02 - typed-refusals-simulated.py"

donkey.simulate(...) works for the Anthropic client too, because it shares the governed transport. Each refusal surfaces as the matching Anthropic error class, and classify() maps its response to the SDK type.

client = donkey.anthropic.client() for refusal in REFUSALS: async with donkey.run(id=f"anthropic-simulated-{refusal.__name__}"): with donkey.simulate(refusal): try: await client.messages.create(model="claude-haiku-4-5-20251001", max_tokens=32, messages=[{"role": "user", "content": "hello"}]) except anthropic.APIStatusError as err: error = classify(err.response) print(type(err).__name__, "->", type(error).__name__, error.policy, error.correlation_id)
Output
PermissionDeniedError -> PIIDetected pii-detection anthropic-simulated-PIIDetected BadRequestError -> PromptInjectionBlocked prompt-injection-protection anthropic-simulated-PromptInjectionBlocked PermissionDeniedError -> ContentSafetyBlocked content-safety anthropic-simulated-ContentSafetyBlocked RateLimitError -> TokenBudgetExceeded token-rate-limit anthropic-simulated-TokenBudgetExceeded

The left column is what Anthropic’s client alone would tell you — a 403 is a PermissionDeniedError whether it was PII or content safety. The right column is what the gateway actually decided.

Learn more: Anthropic SDK

Source: demos/human-made/anthropic/

Last updated on