Skip to Content
Quickstart

Quickstart

Make your first governed model call, then see what a plain base_url can’t give you — a typed refusal, a budget and a span — in a few minutes, on your laptop. You start against DDK’s local gateway simulator, so you need no Anypoint account and no credentials. Switching to your real Omni Gateway later is an environment change, not a code change.

What you can use

CapabilityStatusLearn more
Governed model access for 8 frameworksLiveFrameworks
Typed refusalsLiveTyped refusals
Budget & pacingLiveBudget & pacing
OpenTelemetry spans & cost attributionLiveTelemetry & cost
Local simulator & simulate()LiveLocal simulator
Conformance testing with pytestLiveTesting & conformance
CLI (init, doctor, mock, test) & decoratorsLiveCLI & decorators
Governed tool access (MCP)RoadmapTool access
A2A agents (serve, expose, dev)RoadmapA2A agents
On-behalf-of identityRoadmapIdentity
Human-in-the-loopRoadmapHuman-in-the-loop
Scan & publish to the registryRoadmapScan & publish
Policy handshakeRoadmapPolicy handshake

Your first governed call

Install

pip install "donkey-kit[llm,local,otel]"

llm adds the OpenAI client, local the gateway simulator, otel the OpenTelemetry SDK. Quote the extras — zsh treats unquoted brackets as a glob.

Start the local gateway

In one terminal:

donkey mock

The simulator listens on 127.0.0.1:8080 and replays real gateway responses, including every refusal shape. It enforces no policy and ignores credentials, and every response carries x-donkey-simulator: true.

Point DDK at it

In a second terminal:

export DONKEY_LLM_PROXY_URL="http://127.0.0.1:8080" export DONKEY_LLM_PROXY_CLIENT_ID="local" # placeholder — the simulator ignores auth export DONKEY_LLM_PROXY_CLIENT_SECRET="local"

Make the call

hello.py
from donkey_kit import Donkey with Donkey.from_env() as donkey: client = donkey.llm.client(sync=True) # a real openai.OpenAI, governed reply = client.responses.create( model="gpt-5.1", input="Tell me a one-sentence bedtime story about a unicorn.", ) print(reply.output_text) print("budget remaining:", donkey.budget.remaining, "tokens")
Expected output
A sleepy unicorn named Luma painted soft silver stars across the night sky with her glowing horn, then curled up on a moonbeam so all the children below could fall asleep beneath her gentle, sparkling light. budget remaining: 99500 tokens

donkey.llm.client() returns the OpenAI SDK’s own client, routed through DDK — so credentials, correlation and attribution headers are injected, and donkey.budget is updated from the gateway’s response. The simulator replays a response captured from a real gateway, so you get this story whatever you ask; against your Omni Gateway the model answers your actual prompt.

Prefer one command? Run python -m examples.quickstart.main from the python/ directory of the SDK repository : it boots the simulator for you and runs the steps above end to end.

Catch a typed refusal Live

Ask the simulator for a specific rejection by using the model id donkey-sim/<shape>, then bridge the raw response into DDK’s taxonomy with classify():

import openai from donkey_kit import PIIDetected from donkey_kit.core.errors import classify try: client.responses.create( model="donkey-sim/pii-detected", input="Email the report to jane.doe@example.com.", ) except openai.APIStatusError as exc: governed = classify(exc.response) if isinstance(governed, PIIDetected): print("blocked, entities:", governed.entities)
Expected output
blocked, entities: ['Email']

A PII block is not an auth error, and a policy 429 must never be retried — typed refusals let the agent react correctly. The framework adapters (donkey.langgraph, …) classify for you. See Typed refusals.

Pace against the budget Live

The gateway’s token window is a first-class object. Keep a reserve in hand and wait for the window to reset instead of failing:

from donkey_kit import BudgetReserveReached async with Donkey.from_env() as donkey: while True: try: async with donkey.budget.pace(reserve=0.05): await enrich(batch) break except BudgetReserveReached: await donkey.budget.wait_for_reset()

See Budget & pacing.

See a span Live

With otel installed, every governed call emits an OpenTelemetry GenAI span. Print them to the console:

from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor provider = TracerProvider() provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter())) trace.set_tracer_provider(provider) # set before the first call

Put these lines at the top of hello.py and run it again. Alongside the story, the console prints the span:

Expected output
{ "name": "donkey.llm.chat", "context": { "trace_id": "0x604e2f805df7f12cfdc402f83a66c8f4", "span_id": "0x212735c846ec8f18", "trace_state": "[]" }, "kind": "SpanKind.INTERNAL", "parent_id": null, "status": { "status_code": "UNSET" }, "attributes": { "gen_ai.request.model": "gpt-5.1", "gen_ai.system": "openai", "gen_ai.response.model": "gpt-5.1", "donkey.routing.type": "ModelBased", "donkey.routing.fallback": false, "gen_ai.usage.input_tokens": 17, "gen_ai.usage.output_tokens": 51, "donkey.usage.cached_tokens": 0, "donkey.usage.cache_write_tokens": 0, "donkey.usage.reasoning_tokens": 0, "donkey.policy.decision": "allow", "donkey.budget.remaining": 99000, "donkey.correlation_id": "e2fec0b686694055846da9446fa24c96" }, ... }

Each donkey.llm.chat span carries gen_ai.usage.* token counts, donkey.policy.decision, donkey.budget.remaining and the correlation ID. Set OTEL_EXPORTER_OTLP_ENDPOINT and DDK exports over OTLP with no code at all. See Telemetry & cost.

Attribute cost to a run Live

Group one logical task under a single correlation ID and cost tags, so the gateway record, your logs and your spans all join up:

async with donkey.run(id=ticket.id, team="support", project="triage"): await triage_agent.ainvoke(ticket)

Or fold it into one line with a decorator:

@donkey.governed(team="support") async def handle_ticket(ticket): ...

See CLI & decorators.

Test the refusal branch Live

Inject a real gateway refusal in-process — no server, no network:

from donkey_kit import PIIDetected with donkey.simulate(PIIDetected): await agent.ainvoke(...) # the next call fails as a real PIIDetected

Then grade your own agent against every refusal shape with the pytest conformance suite. See Testing & conformance.

Connect to your Omni Gateway

The same code runs against your real gateway — stop the simulator and set three values. The proxy authenticates on a client_id / client_secret header pair (consumer auth), not a bearer token.

export DONKEY_LLM_PROXY_URL="https://<ingress-gw>/<instance>/" # note: no /v1 export DONKEY_LLM_PROXY_CLIENT_ID="<consumer client id>" export DONKEY_LLM_PROXY_CLIENT_SECRET="<consumer client secret>"

Where the values come from:

  • Create the model proxy  on Omni Gateway with Format=OpenAI. Its consumer endpoint is your DONKEY_LLM_PROXY_URL.
  • Request access  to the proxy in Exchange. That registers a client application and issues the client_id / client_secret pair.

Then check the setup:

donkey doctor
Expected output
[ok] config env (3 fields) [ok] gateway reachable, responded [ok] credentials client_id accepted [ok] model accepted by the proxy [i] budget 99,000 / 100,000 remaining, resets in 59s, observed 0s ago

donkey doctor tells a wrong URL from wrong credentials from a model that is not on the proxy’s allow-list, instead of one opaque failure.

Next steps

  • Pick your framework — get a native LangGraph, ADK, Strands, LlamaIndex, CrewAI, OpenAI Agents SDK, Anthropic SDK or Agent Framework object in three lines.
  • Examples — runnable demos for every capability on this page.
  • Scenarios — support triage, a nightly batch and an internal copilot, built end to end.
Last updated on