mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-03 07:23:33 +00:00
* feat(rust): one typed decline from core up to each host Rust core says it cannot serve a request with one Decline that travels unchanged through llms and inference up to the host. The Python bridge raises RustBridgeDeclined for it in route_failure before the public exception mapping, so dispatch falls back to legacy Python instead of failing the call, and the gateway answers 501. Every route collapses errors after the send with Error::after_send, which keeps a provider answer and a response fault as they are and nests anything else as the source of an invalid response, so a billed call can never read as a decline. Response-side cases that used to decline are now ErrorDetail::Unhandled response faults. The rust-error skill documents declined() as the first question both boundaries ask, and the Python suites pin that a native decline reaches the dispatcher before any provider IO. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(rust): keep post-provider hook failures terminal --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
8.2 KiB
8.2 KiB
Layering
gateway-inference(serving) ->inference-<fmt>(one API's call) ->inference(shared base)- This crate is the shared base every format crate builds on
Error(src/error.rs),CallOptions(src/lib.rs),CallContext(src/context.rs)- diagnostic spans (
src/diagnostic.rs), outbound send and signing (src/outbound.rs), provider resolution (src/provider.rs),CoreResources(src/resources.rs) - response caching (
src/caching.rs):Cachable,StreamCachable,CacheRequest,CallCache,execute_unary,execute_streaming, stream capture - Shared test helpers belong in litellm-inference-testing, used only as a dev dependency
- Nothing here names an API format. Format-specific code, constants, tests and test builders live in their
inference-<fmt>crate - Never depend on an
inference-*crate from here
Format crates
inference-chat,inference-messages,inference-responses,inference-ocr,inference-transcription, one per API- Each owns its entrypoint, route request types (
*Request<'a>), credential fallback, provider dispatch, and the handler glue that runs a provider config - Each owns its
Protocol, itsCachable/StreamCachableimpl, its constants, and its tests inference-*is orchestration only- shared API data contracts belong in
litellm-llms-types - adapter contracts and shared transformation machinery in
llms/src/base_llm/<format>/ - provider policy in
llms/src/<provider>/<format>/
- shared API data contracts belong in
- Select concrete adapters, then invoke their contracts instead of applying one provider's policy to every call
- Route types describe call envelopes and execution state, not duplicate public payload schemas
- Format-specific rules go in that crate's
AGENTS.md
Routes
- Hosts assemble route objects from shared
CoreResources, HTTP settings, and secret sources- each route owns its provider client and authentication dependencies
- gateway routes live for the gateway lifetime. Python assembles routes per call from its settings snapshot
- Calls pass
Interceptorsand an optionalObservationSenderseparately- use
&()for no hooks andNonefor no observer - handlers accept
Interceptors, never a concreteChannelInterceptors
- use
- Construction does no work. Preparation and lifecycle observation begin when the future is polled
- Native observers receive start and terminal events through the shared call runner. A stream retains its lifecycle until exhaustion, error, or drop. Hosted routes leave terminal observation to their driver
- A hosted format's
route.rsdeclares the concreteProtocoland a route method that takes a typed request and builds alitellm_host::call::HostedMachinewithhosted_call. Audio transcription has no hosted machine and returns its response directly- the shared call plumbing owns stream opening, delivery, backpressure, and detachment
- request decoding belongs to the boundary before the machine starts
- route closures only supply execution dependencies and route-specific host capabilities
- Python uses its own shared driver and preserves caller-task callback execution
- Not here: serving HTTP (axum routes, extractors), config file reading, rollout state, databases, or callback execution of any kind. Routes run as machines that yield host operations and call events. Which integrations consume those events is the host's business
Crate boundaries
- Dependencies only point down. Python package names identify counterparts, not ownership
litellm-llms-typesowns shared inference API contracts, grouped by format: pure serde data and shape validation, no I/Olitellm-core-utilsmirrorslitellm/litellm_core_utils/: pure helpers (provider resolution, prompt factory, call arguments, settings lookup and layer merge), no network I/Olitellm-httpis Rust-only and route-neutral: settings resolution, pooledreqwestclients, TLS, proxies, the SSRF-safe media fetcher, request and header helpers, and transport errorslitellm-llmsmirrorslitellm/llms/:base_llm/<api>/transformation.rs,<provider>/<api>/transformation.rs, andbase_llm/ocr/handler.rslitellm-inference-<fmt>mirrors the route packages (litellm/ocr/,litellm/messages/, ...)- Provider code never imports from
inferenceorinference-* - Handlers belong in
inference-*orllms, never in a host crate - Import every item from its canonical path. Never re-export another crate's items or give an item a second public path. The only allowed re-exports are a private submodule surfacing its item at its module root (
mod error; pub use error::Error;) and each format crate'spub use litellm_inference::Error;, which keeps the pre-split<fmt>::Errorname
Error placement
- The rust-error skill shapes each crate's error. This section decides which crate and module a failure belongs to
- A failure is declared once, by the lowest crate that raises it
- every crate above nests it in one variant and delegates its predicates, as
Error::Llm,Error::AuthandError::Httpdo, or translates it once in the sharedFromwhen its meaning changes, asFrom<litellm_llms::Error>does forUnsupportedandFrom<litellm_http::Error>does for a header the signer computes Errorcollects route failures for every format crate and never re-declares a variant a lower crate raises. Routes build transformation failures withError::invalid_requestandError::invalid_responseand collapse anything raised after the send withError::after_send, and callers askdeclined(),is_request(),is_response(),is_missing_credential(),dispatched(),upstream_response()andrejected_request()instead of matching nested variants
- every crate above nests it in one variant and delegates its predicates, as
- Scope follows the concept, not the first caller
- an error type under
litellm-llms's<provider>/is private to that provider: no other provider and nothing inbase_llmmay import it - a failure two providers or two routes can hit (wire framing, stream event decoding, a malformed provider response) belongs to the crate that owns the concept:
litellm-framerfor framing,litellm_llms::Errorfor the transformation layer
- an error type under
litellm_llms::Error(crates/llms/src/error.rs) is the one transformation error for every provider and API.base_llm/ocr/error.rsis the recorded exception until OCR folds into it
Response caching and accounting
- Attach a
litellm_cache_response::ScopedCachewithroute.with_cache(cache). Cached and uncached routes use the sameexecuteandmachinemethods CallOptionscarries a scope-freeCachePolicyand observation. Per-call policy never replaces the attached scope or service- Provider transport does not own cache orchestration. Stream capture stays in
src/caching.rs - This crate and the format crates own request identity, typed response reconstruction, and stream capture and replay
cache-responseowns cache policy, namespacing, scope encoding, versioned envelopes, and freshness- The SDK explicitly chooses shared scope. The gateway derives isolated scope from authenticated identity before attaching its service
ExecutionFactsare delivered through the awaitedResultReadyhost operation for both provider and cached results, before public response processing or stream opening- facts carry resolved model/provider and result source, including the hit key
- usage remains in the typed response or delivered stream, where completion and cancellation determine what was actually reported
- passive observation is not an accounting delivery mechanism
- Inference does not calculate prices, charge budgets, or update rate-limit counters
- the legacy Python callback adapter translates execution facts into the existing Python logging contract. Python remains the accounting owner on that path
- native gateway accounting belongs to gateway dependencies, independently of
host-python - response-cache services expose no coordination counters or reservation APIs. A shared Redis deployment does not make response storage and accounting coordination the same dependency
- Cache lookup follows provider preparation, credential resolution, and the request interceptor
- keys describe the effective provider URL, authenticated headers, and rewritten body
- signed requests bypass caching until the signing identity has a stable cache representation