cadrumo.adapters.outbound.llm.providers.base module

Provider adapter contract for the LLM outbound subpackage.

Defines the normalized request and response shapes that every concrete provider adapter (Anthropic, OpenAI, Gemini, and local) consumes and produces, so the higher-level LLMClient can stay provider-agnostic. Adapters live in sibling modules under llm.providers.

class ProviderRequest(**data)[source]

Bases: BaseModel

Normalized inbound payload passed to a provider adapter.

Variables:
  • request_id – Stable opaque hash identifying the public request, suitable for cross-referencing cache and usage records.

  • model – Fully resolved provider model identifier.

  • prompt – Rendered prompt text sent to the provider.

  • system – Optional system prompt prepended to the conversation.

  • max_tokens – Maximum number of output tokens to request.

  • temperature – Sampling temperature in the inclusive range [0.0, 1.0], or None meaning the parameter is OMITTED from the wire rather than sent at some default. The distinction is load-bearing: a vendor’s newer models reject an explicitly-sent temperature with a 400, so “no preference” cannot be expressed as a number.

  • timeout_s – Per-request timeout in seconds.

  • images – On-host-prepared image inputs for a multimodal read (empty for a text-only request), each carrying its base64 payload AND its declared media type – a cloud provider needs the type on the wire, so the adapter cannot receive a bare string. Transient and in-memory only; an adapter forwards them to a vision model and they are never persisted (sensitive-financial-data-secure-storage-only).

Parameters:

data (Any)

request_id: str
model: str
prompt: str
system: str | None
max_tokens: int
temperature: float | None
timeout_s: int
images: tuple[MultimodalImageInput, ...]
class ProviderCompletion(**data)[source]

Bases: BaseModel

Normalized provider response returned to the public client.

Variables:
  • text – Generated text payload.

  • model – Provider model that actually served the request (may differ from the requested model when a vendor performs upstream routing).

  • input_tokens – Provider-reported prompt token count.

  • output_tokens – Provider-reported output token count.

  • provider_request_id – Provider-native request or message id when available, otherwise None.

Parameters:

data (Any)

text: str
model: str
input_tokens: int
output_tokens: int
provider_request_id: str | None
class ProviderAdapter[source]

Bases: ABC

The interface every concrete provider adapter implements.

Public because it is a shared contract, not an implementation detail: the client type-hints against it, each vendor module subclasses it, and the outbound-adapter tests build their own conforming double. A name that says private while four packages depend on it teaches the wrong thing about where the boundary is.

Concrete subclasses bind provider to a member of LLMProvider and implement complete() to translate the normalized ProviderRequest into a vendor-specific call.

Variables:
  • provider – Identifier of the LLM vendor this adapter speaks to.

  • supports_images – Whether complete() actually forwards ProviderRequest.images to the vendor. Declared as DATA on the class rather than left to each adapter to remember, and enforced once at the client’s single dispatch point – the same reasoning post_provider_request() documents for the transport boundary. Defaults to False so a newly added adapter is text-only until it has genuinely implemented the image path; the failure of the opposite default is silent and unbounded.

provider: LLMProvider
supports_images: bool
unsupported_parameters(model)[source]

Return the request parameters this adapter must NOT put on the wire for model.

The second half of the capability axis supports_images opened. Both defects are one shape – a request field the transport assumed was universal – and they failed in opposite directions: images was silently DROPPED by adapters that could not carry it, and temperature is unconditionally SENT to models that reject it. One was dropped without telling anyone; one is sent without asking.

Declared per MODEL rather than per adapter because that is where vendors actually differ: the same Anthropic adapter serves models that accept temperature and models that refuse it, so an adapter-wide flag cannot express the constraint.

Defaults to the empty set – an adapter carries every parameter until it declares otherwise. That is the opposite default from supports_images, deliberately: silently dropping an image makes a model answer from nothing, which is unbounded harm, while omitting a sampling parameter falls back to the vendor’s own default. The restrictive default belongs where the failure is silent, not where it is merely a different temperature.

Return type:

frozenset[str]

Parameters:

model (str)

abstractmethod async complete(request)[source]

Execute a completion request against the underlying provider.

Parameters:

request (ProviderRequest) – Normalized request payload.

Return type:

ProviderCompletion

Returns:

Normalized ProviderCompletion response.

parse_retry_after(value)[source]

Parse an HTTP Retry-After header value into seconds.

Parameters:

value (str | None) – Raw header value, or None when the header is absent.

Return type:

float | None

Returns:

Finite, non-negative seconds to wait, or None when the value is missing or cannot represent a safe delay.

raise_rate_limit(*, provider_name, model, retry_after)[source]

Raise a normalized rate-limit error with parsed retry hint.

Parameters:
  • provider_name (str) – Stable provider identity observed at the transport boundary.

  • model (str) – Model identifier carried by the rejected request.

  • retry_after (str | None) – Raw Retry-After header value supplied by the provider.

Raises:

LLMRateLimitError – Always raised with the parsed retry hint.

Return type:

NoReturn

parse_provider_response(response, *, provider_name, response_model)[source]

Validate a successful provider response at the outbound boundary.

Parameters:
  • response (Response) – Successful HTTP response whose JSON body must match the provider schema.

  • provider_name (str) – Human-readable provider label for the public error.

  • response_model (type[TypeVar(ProviderResponse, bound= BaseModel)]) – Strict Pydantic model for the provider response envelope.

Return type:

TypeVar(ProviderResponse, bound= BaseModel)

Returns:

The validated provider response.

Raises:

LLMProviderError – When a 2xx response body is malformed for its provider.

require_provider_response_item(items, *, provider_name, item_name)[source]

Return the first required response item or raise a declared provider error.

Parameters:
  • items (tuple[TypeVar(ProviderResponseItem), ...]) – Provider response collection expected to contain a primary item.

  • provider_name (str) – Human-readable provider label for the public error.

  • item_name (str) – Plural provider-specific item name used in diagnostics.

Return type:

TypeVar(ProviderResponseItem)

Returns:

The first response item.

Raises:

LLMProviderError – When a successful provider response has no primary item.

async post_provider_request(client, url, *, provider_name, model, logger, headers=None, json)[source]

POST to a provider endpoint, mapping transport failure to the typed boundary.

LLMProviderError is the declared transport/provider boundary of this subpackage, and the Anthropic adapter maps its SDK’s connection and timeout failures onto it. The httpx-based adapters did not agree: Gemini caught httpx.RequestError while OpenAI and the local runtime called client.post bare, so an unreachable endpoint surfaced as a raw ConnectError / ConnectTimeout from two of the four providers. The error TYPE, and therefore every caller’s recovery path, depended on which provider happened to be configured — for one and the same failure.

Routing every httpx provider through this one call makes the boundary a property of the transport rather than of each adapter’s memory to catch, and keeps the provider context (name, model) attached to the failure.

Parameters:
  • client (AsyncClient) – The provider adapter’s own httpx.AsyncClient.

  • url (str) – Fully resolved endpoint.

  • provider_name (str) – Human-readable provider label for log and error text.

  • model (str) – Model identifier, included in the log context.

  • logger (Logger) – Adapter logger for transport diagnostics.

  • headers (Mapping[str, str] | None) – Optional request headers (auth material, API keys).

  • json (object) – JSON-serialisable request body.

Return type:

Response

Returns:

The provider’s httpx.Response, unexamined — status dispatch remains check_http_error()’s job.

Raises:

LLMTransientTransportError – When the request never produced a response (connection refused, DNS failure, timeout, protocol error). Typed as the TRANSIENT half of the boundary because that is what the failure is: nothing was decided at the model, and a local runtime that is still loading a multi-gigabyte model refuses connections for exactly as long as the load takes. httpx.TimeoutException is a RequestError subclass, so a read timeout arrives here too.

check_http_error(response, *, provider_name, model, logger)[source]

Raise a normalized error for a non-2xx LLM HTTP response.

A 429 raises a rate-limit error carrying the parsed Retry-After hint; any other non-2xx status raises a provider error. Shared by the OpenAI, Gemini, and local adapters, whose status-dispatch is otherwise identical.

The 5xx and 4xx branches raise DIFFERENT types, and the split is the whole basis of the retry decision. A 5xx is the server saying it failed to answer a request it accepted, which the same request may survive on a second attempt; a 4xx is the server saying the request itself is wrong, which the same request will never survive. Retrying the second kind spends a bounded budget on a certainty and delays the real refusal.

Parameters:
  • response (Response) – Provider HTTP response to inspect.

  • provider_name (str) – Human-readable provider label for log and error text.

  • model (str) – Model identifier, included in the log context.

  • logger (Logger) – Adapter logger for status diagnostics.

Raises:
Return type:

None