Skip to main content

dr-providers

CI PyPI

Repo Definitions (terms, contracts) dr-serialize dr-wire

dr-providers makes LLM provider calls through explicit, typed contracts. It supports OpenRouter, OpenAI, Gemini, and Anthropic while keeping call identity, provider translation, transport policy, and outcomes separate.

Package map

Package Responsibility
dr_providers.modeling Identity-bearing definitions, configs, requests, routes, controls, and transcripts
dr_providers.translation Pure provider request-body construction and parsed-response translation
dr_providers.transport Credentials, endpoints, timeout policy, and one-invocation HTTP execution over the dr-wire bounded client
dr_providers.outcomes Typed responses, expected failures, invocation evidence, and conformance warnings
dr_providers.lifecycle Invocation classification, serializable retry state, deterministic transitions, and terminal call results
dr_providers.core Shared provider protocol and failure vocabulary
dr_providers.surfaces.testing Deterministic ScriptedProvider for network-free tests
dr_providers.surfaces.cli Optional dr-providers one-shot CLI

The top-level dr_providers exports are the stable general import surface. Functional-area module paths primarily make ownership discoverable; they are not a second compatibility surface.

.defs/terms.toml and its rendered defs site are the authoritative reference for that public surface: every export is mapped to a term there and checked by scripts/check_defs.py. This README illustrates common paths rather than enumerating them, so an export it does not mention is supported, not unsupported.

Potential future features records directions this package deliberately does not build today.

Install

dr-providers requires Python 3.12 or newer.

uv add dr-providers

Unless an API key is injected directly, real HTTP calls read the credential selected by their transport policy:

Provider Environment variable
OpenRouter OPENROUTER_API_KEY
OpenAI OPENAI_API_KEY
Gemini GEMINI_API_KEY
Anthropic ANTHROPIC_API_KEY

Python quickstart

This OpenAI example uses the stable package import surface:

from threading import Event

from dr_providers import (
    AcceptAllSemanticResponseClassifier,
    GenerationControls,
    HttpProvider,
    MessageRole,
    PromptMessage,
    ProviderCallOutcomeKind,
    ProviderCallRequest,
    ProviderCallState,
    ProviderKind,
    StandardProviderCallRetryPolicy,
    Transcript,
    openai_responses_config,
    policy_for,
    run_local_provider_call,
)

config = openai_responses_config(
    model="gpt-5-mini",
    controls=GenerationControls(token_limit=256),
)
request = ProviderCallRequest(
    config=config,
    transcript=Transcript(
        messages=(
            PromptMessage(
                role=MessageRole.USER,
                content="Say hello in one word.",
            ),
        )
    ),
)

classifier = AcceptAllSemanticResponseClassifier()
state = ProviderCallState.initial(
    request=request,
    retry_policy=StandardProviderCallRetryPolicy(),
    classifier_identifier=classifier.identifier,
)
with HttpProvider(
    policy=policy_for(
        ProviderKind.OPENAI,
        timeout_seconds=120.0,
        connect_timeout_seconds=30.0,
        idle_timeout_seconds=90.0,
        max_connections=1,
        max_keepalive_connections=1,
        max_request_bytes=1024 * 1024,
        max_response_bytes=8 * 1024 * 1024,
    )
) as provider:
    result = run_local_provider_call(
        provider=provider,
        state=state,
        classifier=classifier,
        cancellation=Event(),
    )

evidence = result.completed_invocations[-1].observation.evidence
if result.outcome.kind is ProviderCallOutcomeKind.ACCEPTED:
    assert evidence.response is not None
    print(evidence.response.text)
else:
    print(result.outcome)

Expected transport failures are retained in invocation evidence and classified into the terminal ProviderCallResult. Unexpected programming or infrastructure errors can still raise.

CLI

Install and run the one-shot CLI:

uv add 'dr-providers[cli]'
uv run dr-providers --provider openai-responses \
  --model gpt-5-mini \
  --token-limit 256 \
  -m 'Say hello in one word.'

# Anthropic requires --token-limit:
uv run dr-providers --provider anthropic \
  --model claude-sonnet-4-6 \
  --token-limit 256 \
  -m 'Say hello in one word.'

Outcome and evidence boundaries

HttpProvider.invoke() makes at most one provider wire request and returns versioned serializable ProviderInvocationEvidence. The evidence binds the request identity hash and transport-policy identity to structured HTTP request metadata and exactly one response or expected failure. The HTTP request evidence is the sole owner of the constructed request-body mapping.

run_local_provider_call() classifies each invocation, applies the selected serializable retry policy through the deterministic lifecycle transition, and returns the complete ordered ProviderCallResult. The standard policy permits exactly one invocation with no auto-retry. Transient network/provider failures and contained transport timeouts are terminal unless the caller selects an explicit custom retry policy. The standard HTTP provider uses direct synchronous native phase timeouts, so it observes a timeout only after the local HTTP operation has ended. It owns and reuses one bounded client; a clean close stops offload admission, drains offloaded work, stops invocation admission, drains active invocations, and closes that client once. Invocation admission stays open to every caller, on any thread, until the offload drain finishes. An exception escaping a drain wait, such as a keyboard interrupt, aborts the close: the provider still becomes terminal and releases the executor and client without joining workers, so no later caller blocks, but the drain does not complete. Connect, write, and pool phase timeouts and the response-read idle timeout are each declared explicitly on transport policy and do not bound the total wall-clock duration of a slow response that keeps producing bytes.

Every ProviderTransportPolicy and policy_for() call must declare native connect, write/pool, and response-read idle timeouts, connection-pool limits, and request/response byte caps. There are no library-wide implicit sizing defaults. One-shot examples in this repository configure one open and one keep-alive connection because each run admits a single invocation. A caller that shares one HttpProvider across concurrent work must size both connection limits to its own maximum concurrent invoke() calls.

run_local_provider_call_async() is the asynchronous entry point. It submits the same synchronous driver to the provider's own executor through HttpProvider.offload() and awaits the result, so the transport stays one bounded synchronous client. That executor is created on first offload and sized from max_connections, which also bounds the client connection pool, so thread count and pool size cannot disagree. Cancelling the awaiting asyncio task does not interrupt the offloaded call: the offloaded future is shielded, so cancellation flows through the cancellation event, and a clean close() drains admitted offloaded work. Offloaded work must not call close() or offload() on the provider running it: closing from inside offloaded work waits on that same work, and offloaded work blocking on a nested offload starves once every worker is held that way.

ProviderCallState, ProviderRetryInstruction, and ProviderCallResult are JSON-serializable handoff values. A durable consumer can persist the declared next state and schedule the instruction's delay before invoking again; restoring at that boundary produces the same terminal result as the uninterrupted local driver. The local driver follows transition outputs and performs only its declared cancellation-aware wait; the deterministic transition owns retry and terminal decisions.

Cancellation is draining: it starts no successor and retains an active invocation observation if that invocation completes. It does not promise remote provider cancellation or prompt release of provider capacity. Lifecycle values are neutral to storage and workflow runtimes. This package does not provide durable persistence, workflow scheduling, global admission, or exactly-once provider effects.

The exact encoded request body and decompressed response body are bounded by identity-bearing transport policy limits. Complete in-limit response bodies are retained as JSON when possible or as text otherwise; over-limit responses retain no partial body. Failure summary messages are unbudgeted. Wire-path failures retain the underlying exception traceback in invocation evidence; other transport failures leave traceback unset. Original HTTP wire bytes are not retained. The standard HTTP path redacts known credential header names. Direct ProviderHttpRequestEvidence construction and deserialization remain trusted-data paths.

Repository validation

The default suite is offline: pytest excludes tests marked live.

uv sync --locked --all-extras
uv run pre-commit install
scripts/pre-check.sh
uv build

Run the complete live matrix without changing the committed wire corpus:

uv run python scripts/run_live_matrix.py

Capturing and promoting replacement corpus data is a separate, deliberate operation. It stages outside the repository, validates and redacts the complete five-case capture, then updates data/wire-corpus/:

uv run python scripts/capture_live_corpus.py capture --promote

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dr_providers-0.3.1.tar.gz (42.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dr_providers-0.3.1-py3-none-any.whl (63.2 kB view details)

Uploaded Python 3

File details

Details for the file dr_providers-0.3.1.tar.gz.

File metadata

  • Download URL: dr_providers-0.3.1.tar.gz
  • Upload date:
  • Size: 42.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dr_providers-0.3.1.tar.gz
Algorithm Hash digest
SHA256 ae37f3c45ded1c1f4317107e5b07417c0842a882869d5468f3058e70ed28d62b
MD5 aee502cd0bda1cc5b9a426c39a86d9ef
BLAKE2b-256 092179bee03226f0484f3ca7b3f34a5404c4b04dc1a7d513dbf668ec2df989a1

See more details on using hashes here.

Provenance

The following attestation bundles were made for dr_providers-0.3.1.tar.gz:

Publisher: release.yaml on danielle-rothermel/dr-providers

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dr_providers-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: dr_providers-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 63.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dr_providers-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 45c1d691a837625f258e8eb846c9f396b6cafc61fe0f5b1b6ae67f8b0f10aa1c
MD5 844c6f4cc3ae20c0eb3e64ecbfa7387a
BLAKE2b-256 e76855a20dcf0df00a71e884159a5ea14945ac4c3261c58ffd890604320ffe9d

See more details on using hashes here.

Provenance

The following attestation bundles were made for dr_providers-0.3.1-py3-none-any.whl:

Publisher: release.yaml on danielle-rothermel/dr-providers

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page