Skip to content

Provider Contracts

Routing depends on the provider-neutral StructuredProvider port. Provider SDKs and Instructor remain inside adapters. Capability inspection must pass before source content is sent.

Start with AI Providers for provider selection, credentials, runtime setup, retries, and production checks.

base

Provider-neutral structured generation port and safe normalized results.

StructuredProvider

Bases: Protocol

Generic async port consumed directly by application and routing code.

adapter_identity property

adapter_identity: str

Return a stable adapter implementation identity.

provider_name property

provider_name: str

Return the normalized provider name.

model property

model: str

Return the configured model or deployment identifier.

max_input_tokens property

max_input_tokens: int

Return the named model row's input token limit.

inspect_schema

inspect_schema(
    response_model: type[T],
) -> SchemaDescriptor

Transform and validate a response schema without sending request data.

estimate_tokens

estimate_tokens(messages: Sequence[ProviderMessage]) -> int

Return a deterministic request-token estimate.

generate async

generate(
    *,
    messages: Sequence[ProviderMessage],
    response_model: type[T],
    generation: GenerationConfig,
    retry: RetryConfig,
    limiter: ConcurrencyLimiter,
) -> ProviderResponse[T]

Generate one validated response through the shared limiter.

ProviderResponse dataclass

ProviderResponse(
    output: T,
    usage: NormalizedUsage | None,
    finish_reason: str | None,
    provider: str,
    model: str,
    response_id: str | None,
    transport_attempts: int,
    validation_attempts: int,
)

Bases: Generic[T]

Validated model output with normalized non-sensitive provider metadata.

output instance-attribute

output: T

usage instance-attribute

usage: NormalizedUsage | None

finish_reason instance-attribute

finish_reason: str | None

provider instance-attribute

provider: str

model instance-attribute

model: str

response_id instance-attribute

response_id: str | None

transport_attempts instance-attribute

transport_attempts: int

validation_attempts instance-attribute

validation_attempts: int

truncated property

truncated: bool

Return whether the normalized finish reason means incomplete output.

require_complete

require_complete() -> ProviderResponse[T]

Fail closed when a provider reports token or length truncation.

ProviderMessage dataclass

ProviderMessage(
    role: Literal["system", "user", "assistant"],
    content: str,
)

One provider-neutral chat message retained only for the active request.

role instance-attribute

role: Literal['system', 'user', 'assistant']

content instance-attribute

content: str

NormalizedUsage dataclass

NormalizedUsage(
    input_tokens: int, output_tokens: int, total_tokens: int
)

Token usage without raw provider response data.

input_tokens instance-attribute

input_tokens: int

output_tokens instance-attribute

output_tokens: int

total_tokens instance-attribute

total_tokens: int

ConcurrencyLimiter

ConcurrencyLimiter(limit: int)

One shared asynchronous ceiling for all outbound attempts in a run.

peak_active property

peak_active: int

Return the maximum simultaneous outbound operations observed.

slot async

slot() -> AsyncIterator[None]

Acquire one outbound-operation slot, including for a retry.

SchemaDescriptor dataclass

SchemaDescriptor(
    canonical_schema_sha256: str,
    request_schema_sha256: str,
    canonical_schema_json: str,
    request_schema_json: str,
)

Canonical and adapter-transformed schema identities for one model type.

canonical_schema_sha256 instance-attribute

canonical_schema_sha256: str

request_schema_sha256 instance-attribute

request_schema_sha256: str

canonical_schema_json instance-attribute

canonical_schema_json: str

request_schema_json instance-attribute

request_schema_json: str

canonical_schema property

canonical_schema: Mapping[str, object]

Return a detached canonical schema mapping.

request_schema property

request_schema: Mapping[str, object]

Return a detached transformed request schema mapping.

capabilities

Named provider/model capabilities and strict request-schema transformation.

ModelCapabilities dataclass

ModelCapabilities(
    provider: str,
    model: str,
    structured_output: bool,
    strict_schema: bool,
    max_input_tokens: int,
    max_output_tokens: int,
    supported_generation_settings: frozenset[str],
    evidence: CapabilityEvidence,
    tested_sdk_version: str,
    token_estimator: TokenEstimator = ConservativeTokenEstimator(),
)

One explicit tested or configuration-only provider/model capability row.

provider instance-attribute

provider: str

model instance-attribute

model: str

structured_output instance-attribute

structured_output: bool

strict_schema instance-attribute

strict_schema: bool

max_input_tokens instance-attribute

max_input_tokens: int

max_output_tokens instance-attribute

max_output_tokens: int

supported_generation_settings instance-attribute

supported_generation_settings: frozenset[str]

evidence instance-attribute

tested_sdk_version instance-attribute

tested_sdk_version: str

token_estimator class-attribute instance-attribute

token_estimator: TokenEstimator = field(
    default_factory=ConservativeTokenEstimator
)

inspect_schema

inspect_schema(
    response_model: type[BaseModel],
) -> SchemaDescriptor

Fail closed, then return canonical and transformed schema hashes.

validate_generation

validate_generation(generation: GenerationConfig) -> None

Reject active unsupported settings and output limits before transport.

estimate_messages

estimate_messages(
    messages: tuple[ProviderMessage, ...],
) -> int

Estimate complete role and content transport deterministically.

CapabilityEvidence

Bases: str, Enum

Evidence state for one named provider and model capability row.

verified class-attribute instance-attribute

verified = 'verified'

configuration_only class-attribute instance-attribute

configuration_only = 'configuration-only'

unknown class-attribute instance-attribute

unknown = 'unknown'

ConservativeTokenEstimator dataclass

ConservativeTokenEstimator()

Dependency-free conservative estimate of one token per UTF-8 byte.

estimate

estimate(text: str) -> int

Return a safe deterministic upper estimate.

TokenEstimator

Bases: Protocol

Provider-neutral deterministic token estimation contract.

estimate

estimate(text: str) -> int

Estimate tokens for one string without network access.

schema_descriptor

schema_descriptor(
    response_model: type[BaseModel],
) -> SchemaDescriptor

Build deterministic canonical and strict OpenAI-compatible request schemas.

Supported adapters and evidence

All shipped adapter paths have deterministic contract tests. No named live model/version has completed the protected verification process, so every model row created by the public facade is configuration-only. This label means the declared limits are used for validation; it is not a claim that the endpoint, model behavior, context window, or extraction quality was verified.

Provider value Extra Endpoint configuration Credential Evidence
openai openai Reviewed OpenAI preset, or OPENAI_BASE_URL OPENAI_API_KEY configuration-only
openrouter openai Reviewed default preset; explicit base_url overrides it OPENROUTER_API_KEY configuration-only
vercel openai Reviewed default preset; explicit base_url overrides it AI_GATEWAY_API_KEY configuration-only
custom openai Explicit base_url required; use HTTPS in production SURVEY_SCRIBE_API_KEY configuration-only
azure, azure_openai openai Endpoint, API version, deployment API key or token callback configuration-only
anthropic anthropic Dedicated Anthropic adapter ANTHROPIC_API_KEY configuration-only

Use survey-scribe providers to inspect this same evidence boundary. A verified row would require a protected contract run against the exact named provider, model/version, and SDK version. unknown evidence fails closed before transport.

Configuration examples

# OpenAI
SURVEY_SCRIBE_PROVIDER=openai SURVEY_SCRIBE_MODEL=model-id survey-scribe config check

# OpenRouter
SURVEY_SCRIBE_PROVIDER=openrouter SURVEY_SCRIBE_MODEL=model-id survey-scribe config check

# Vercel AI Gateway
SURVEY_SCRIBE_PROVIDER=vercel SURVEY_SCRIBE_MODEL=model-id survey-scribe config check

# Custom OpenAI-compatible HTTPS gateway
survey-scribe config check --provider custom --model model-id --base-url https://gateway.example/v1

# Azure OpenAI-compatible chat completions
survey-scribe config check --provider azure --model deployment-name \
  --base-url https://resource.example/ --api-version 2025-04-01-preview

# Anthropic
SURVEY_SCRIBE_PROVIDER=anthropic SURVEY_SCRIBE_MODEL=model-id survey-scribe config check

These commands require the matching credential environment variable. They validate adapter construction but do not make a model request.

Adapter construction

InstructorOpenAIProvider is the packaged OpenAI-compatible adapter. Applications must supply an administrator-owned ModelCapabilities row. A configuration-only row allows deterministic schema and request checks; it does not prove that a live provider or model was tested.

from survey_scribe import QuestionnaireRouter
from survey_scribe.providers import CapabilityEvidence, ModelCapabilities
from survey_scribe.providers.openai_compatible import InstructorOpenAIProvider


async def synthetic_completion(**request: object) -> object:
    response_model = request["response_model"]
    payload = build_synthetic_response_for(response_model)
    return payload


capabilities = ModelCapabilities(
    provider="test-gateway",
    model="configured-model",
    structured_output=True,
    strict_schema=True,
    max_input_tokens=32_000,
    max_output_tokens=4_096,
    supported_generation_settings=frozenset(
        {"temperature", "max_output_tokens", "seed"}
    ),
    evidence=CapabilityEvidence.configuration_only,
    tested_sdk_version="synthetic-no-sdk",
)
provider = InstructorOpenAIProvider(
    model=capabilities.model,
    capabilities=capabilities,
    completion=synthetic_completion,
)
router = QuestionnaireRouter(provider)

The injected completion keeps this construction credential-free and avoids an SDK import. Production applications normally omit it, install the openai extra, and supply credentials from protected runtime configuration. Direct OpenAI or LangChain clients are not routing inputs. Adapt them to StructuredProvider.

Named presets are available for OpenAI, OpenRouter, and Vercel AI Gateway. OpenRouter accepts only the non-secret HTTP-Referer and X-Title headers. An explicit facade base_url overrides any named preset. Review that destination as a custom egress route. Custom gateways require an explicit base URL and use the same header allowlist.

Azure OpenAI uses AzureOpenAIProvider. Configure exactly one API key or refreshable token callback. Survey Scribe retains and passes the callback to the Azure SDK without calling it during construction or persisting returned token values. InstructorAnthropicProvider is available through the optional anthropic extra. All SDK imports remain lazy.

Generic Azure-compatible gateway headers

Applications that need caller-defined gateway headers construct AzureOpenAIProvider directly and inject it through the existing StructuredProvider boundary. The facade, CLI, TOML, and environment schema do not define gateway-specific fields.

import os

from survey_scribe import SurveyScribe
from survey_scribe.providers import CapabilityEvidence, ModelCapabilities
from survey_scribe.providers.azure import AzureOpenAIProvider


def acquire_token() -> str:
    return os.environ["SYNTHETIC_AZURE_RUNTIME_TOKEN"]


def sensitive_headers() -> dict[str, str]:
    return {
        "X-Synthetic-Aux-Key": os.environ["SYNTHETIC_GATEWAY_AUX_KEY"],
    }


capabilities = ModelCapabilities(
    provider="azure_openai",
    model="deployment-name",
    structured_output=True,
    strict_schema=True,
    max_input_tokens=32_000,
    max_output_tokens=4_096,
    supported_generation_settings=frozenset(
        {"temperature", "max_output_tokens", "seed"}
    ),
    evidence=CapabilityEvidence.configuration_only,
    tested_sdk_version="application-managed",
)
provider = AzureOpenAIProvider(
    deployment=capabilities.model,
    azure_endpoint="https://gateway.example/azure",
    api_version="api-version",
    token_callback=acquire_token,
    metadata_headers={"X-Synthetic-Route": "questionnaire-extraction"},
    sensitive_headers_callback=sensitive_headers,
    required_headers={"X-Synthetic-Route", "X-Synthetic-Aux-Key"},
    capabilities=capabilities,
)
client = SurveyScribe(provider)

metadata_headers is only for non-secret, static request metadata. The adapter copies it during construction. sensitive_headers_callback is a bounded, synchronous function invoked for each package-owned request attempt. The adapter copies its returned values into an attempt-local mapping; it does not store or serialize those values. required_headers fails safely before transport when a required name is absent.

The application owns token and auxiliary-secret acquisition. Keep these callbacks non-blocking, and do not make a network request from them. A gateway route or header confirms only the configured route; it does not prove the exact backend model or service. The adapter reports configured route identity, not a dynamic backend model identity. Only Microsoft Foundry endpoints that expose the Azure OpenAI-compatible chat-completions API are supported. See Security and API Keys for credential handling rules.

Survey Scribe disables each SDK's internal retry loop and applies only the configured package retry policy. Transport and structured-validation attempts are therefore bounded and reported consistently. Anthropic capability rows must not advertise seed; explicit unsupported settings fail before transport.

No live model row is advertised as verified by this table. A row becomes verified only after a protected, named model/version contract run records that evidence. Unknown rows fail closed during schema inspection.

The placeholder build_synthetic_response_for() represents an application test fixture and is not a package function. No quality claim follows from this example.

openai_compatible

Lazy Instructor-backed adapter for OpenAI-compatible structured endpoints.

InstructorOpenAIProvider

InstructorOpenAIProvider(
    *,
    model: str,
    api_key: str | None = None,
    base_url: str | None = None,
    capabilities: ModelCapabilities,
    completion: Completion | None = None,
    default_headers: Mapping[str, str] | None = None,
)

Structured OpenAI-compatible provider with Instructor kept internal.

capabilities instance-attribute

capabilities = capabilities

adapter_identity property

adapter_identity: str

provider_name property

provider_name: str

model property

model: str

max_input_tokens property

max_input_tokens: int

base_url property

base_url: str | None

Return the configured non-secret endpoint.

from_preset classmethod

from_preset(
    preset: OpenAICompatiblePreset,
    *,
    model: str,
    capabilities: ModelCapabilities,
    api_key: str | None = None,
    base_url: str | None = None,
    default_headers: Mapping[str, str] | None = None,
    completion: Completion | None = None,
) -> InstructorOpenAIProvider

Construct a reviewed named endpoint or an explicit custom gateway.

inspect_schema

inspect_schema(
    response_model: type[T],
) -> SchemaDescriptor

estimate_tokens

estimate_tokens(messages: Sequence[ProviderMessage]) -> int

generate async

generate(
    *,
    messages: Sequence[ProviderMessage],
    response_model: type[T],
    generation: GenerationConfig,
    retry: RetryConfig,
    limiter: ConcurrencyLimiter,
) -> ProviderResponse[T]

aclose async

aclose() -> None

Close a lazily created SDK client when the adapter owns one.

OpenAICompatiblePreset

Bases: StrEnum

Reviewed endpoint presets plus an explicit custom gateway mode.

openai class-attribute instance-attribute

openai = 'openai'

openrouter class-attribute instance-attribute

openrouter = 'openrouter'

vercel class-attribute instance-attribute

vercel = 'vercel'

custom class-attribute instance-attribute

custom = 'custom'

azure

Lazy Azure OpenAI adapter with key or refreshable token credentials.

AzureOpenAIProvider

AzureOpenAIProvider(
    *,
    deployment: str,
    azure_endpoint: str,
    api_version: str,
    capabilities: ModelCapabilities,
    api_key: str | None = None,
    token_callback: Callable[[], str] | None = None,
    metadata_headers: Mapping[str, str] | None = None,
    sensitive_headers_callback: Callable[
        [], Mapping[str, str]
    ]
    | None = None,
    required_headers: Collection[str] = (),
    completion: Completion | None = None,
)

Bases: InstructorOpenAIProvider

Instructor-backed Azure adapter with per-attempt gateway headers.

metadata_headers is copied at construction and must contain only non-secret values. sensitive_headers_callback is invoked immediately before each package-owned request attempt. required_headers is copied and checked case-insensitively after the two channels are merged. Caller-defined headers cannot replace authentication or HTTP transport headers.

Callback or required-header failures become detached, non-retryable provider authentication errors. The callback must be synchronous and non-blocking.

Parameters:

Name Type Description Default
metadata_headers Mapping[str, str] | None

Non-secret static headers copied during construction.

None
sensitive_headers_callback Callable[[], Mapping[str, str]] | None

Synchronous provider of attempt-local secret headers. The returned mapping is validated and not retained.

None
required_headers Collection[str]

Header names that must exist after the two channels merge. Names are copied and compared case-insensitively.

()

Raises:

Type Description
TypeError

A callback or required-header collection has the wrong shape.

ValueError

Credentials, endpoint settings, or header rules are invalid.

adapter_identity property

adapter_identity: str

anthropic

Lazy Instructor-backed Anthropic structured-output adapter.

InstructorAnthropicProvider

InstructorAnthropicProvider(
    *,
    model: str,
    capabilities: ModelCapabilities,
    api_key: str | None = None,
    completion: Completion | None = None,
)

Bases: InstructorOpenAIProvider

Anthropic adapter with the same normalized provider response contract.

adapter_identity property

adapter_identity: str