Repository navigation
feat(skeleton): surface gateway routing/fallback, never double-retry it (#309) - #378
Merged
Merged
Conversation
…ry it (#309) Consume the five live-verified x-llm-proxy routing headers (already captured in responses.success.headers.txt) off the shared transport's _on_response hook, so the raw client benefits with no framework required (BG §1.1): - core/lastcall.py: parse routing-type/-fallback/-llm-provider/-llm-model onto LastCall (served_provider/served_model/routing_type/fallback, plus a substituted property); tri-state fallback (None when absent, never a default False, §0.3) and a definitive-True-only is_fallback() for the retry gate. - core/transport.py: thread the requested model onto the record; emit gen_ai.response.model / donkey.routing.type / donkey.routing.fallback span attrs; and DO NOT retry a 502/503/504 the gateway already marked a fallback (must not fight the retry policy in #183). - core/telemetry.py: pinned gen_ai.response.model + stable donkey.routing.* keys, allowlisted; build_genai_attributes emits fallback=False, drops None. - core/config.py + donkey.py: on_model_substitution ("off"|"raise"), resolved kwarg → env → toml → default (off); Donkey.from_env(on_model_substitution=…); ModelSubstituted typed error (public export) raised on served≠requested. Docs (cross-surface lockstep): verified-apis.md records the fallback + routing headers and the #309 consumption citing the fixture; telemetry.mdx gains a Routing & resilience section (last_call fields, span attrs, no-double-retry, on_model_substitution) and flags TypeScript parity as Phase 5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…outing-fallback # Conflicts: # docs/verified-apis.md # python/src/donkey_kit/core/lastcall.py # python/src/donkey_kit/core/telemetry.py # python/src/donkey_kit/core/transport.py # python/src/donkey_kit/donkey.py # python/tests/unit/test_lastcall.py # python/tests/unit/test_telemetry.py # website/pages/telemetry.mdx # website/public/llms-full.txt # website/public/telemetry.md
This was referenced Sep 15, 2026
tommasobolis
added a commit
that referenced
this pull request
Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #309.
What
Surfaces what the Omni Gateway actually did with each request — served provider/model, routing strategy, and whether it was a fallback — and stops the SDK from stacking a retry on top of a failover the gateway already performed. The five
x-llm-proxy-*routing headers are already live-verified (responses.success.headers.txt); this is a consumption task, not a verification one.Per the #362 precedent, routing fields hang off the existing
donkey.last_callrecord (not a newdonkey.last_routing), beside the identity/usage fields.Changes
core/lastcall.py— parserouting-type/routing-fallback/llm-provider/llm-modelontoLastCall(served_provider,served_model,routing_type,fallback, plus asubstitutedproperty).fallbackis tri-state (Nonewhen the header is absent — never a defaultFalse, §0.3);is_fallback()is definitive-True-only, for the retry gate.core/transport.py— thread the requested model onto the record; emitgen_ai.response.model/donkey.routing.type/donkey.routing.fallbackspan attrs; and do not retry a502/503/504the gateway already marked a fallback (must not fight the retry policy, errors: prove policy refusals are terminal and openai's built-in 429 retry never fires #183).core/telemetry.py— pinnedgen_ai.response.model+ stabledonkey.routing.*keys, allowlisted;build_genai_attributesemitsfallback=Falsebut dropsNone.core/config.py+donkey.py—on_model_substitution("off"|"raise"), resolved kwarg → env → toml → default (off);Donkey.from_env(on_model_substitution=…);ModelSubstitutedtyped error (public export) raised on served≠requested.verified-apis.mdrecords the fallback + routing headers and the skeleton: surface gateway-side routing and fallback, and never double-retry it #309 consumption citing the fixture;telemetry.mdxgains a Routing & resilience section and flags TypeScript parity as Phase 5.Acceptance (all met)
None— a non-proxy / simulated response does not crash.on_model_substitutiondefaults off;"raise"raises a typed error.Gate
pytest -q tests/unit→ 384 passed, 8 skipped ·mypy --strictclean ·ruff check .clean ·lint-imports4/4 kept.🤖 Generated with Claude Code