Skip to content

osrforge.providers.claude

The Claude adapter: the only module allowed to import anthropic.

It fills the tracing stage's reading role (ReadingProvider) through the Messages API: it streams every response, sends images as base64 PNG, asks for each answer as JSON that fits the kind's schema (output_config.format), runs every request with adaptive thinking at the kind's effort, and marks a cache breakpoint on the system prompt and at the end of each shared part. It reserves each request's worst-case cost on the Claude ledger before sending and settles it from the answer's usage. The body it sends is request_body. The server-side refusal fallbacks parameter stays off, because a fallback would answer on another model mid-run and change the recording key with it.

API_KEY_ENV module-attribute

API_KEY_ENV = 'ANTHROPIC_API_KEY'

LEDGER_ENV module-attribute

LEDGER_ENV = 'OSRFORGE_CLAUDE_LEDGER'

LEDGER_STOP_ENV module-attribute

LEDGER_STOP_ENV = 'OSRFORGE_CLAUDE_LEDGER_STOP'

MODEL_ENV module-attribute

MODEL_ENV = 'OSRFORGE_CLAUDE_MODEL'

PRICES module-attribute

PRICES: dict[str, ModelPrices] = {
    "claude-sonnet-5-5": ModelPrices(input=2e-06, output=1e-05, cache_write=2.5e-06, cache_read=2e-07),
    "claude-opus-5-5": ModelPrices(input=4e-06, output=2e-05, cache_write=5e-06, cache_read=2e-07),
}

The list prices of the models the provider accepts, in dollars per token.

A model missing here can't be reserved against the ledger, so the provider refuses it.

ClaudeProvider

ClaudeProvider(
    settings: ClaudeSettings,
    client: Any | None = None,
    sleep: Callable[[float], None] = sleep,
    now: Callable[[], str] | None = None,
)

Claude over the Messages API, with the ledger and the cache discipline.

The constructor performs no I/O. Transport policy: at most five attempts per request, with exponential backoff bounded at 30 seconds, on 408, 409, 429, every 5xx, and connection failures, including a transport failure while the stream is read. A Retry-After header sets the wait, within the same bound. An error event inside a stream is retried like a 5xx; the SDK raises it with the stream's own status, which is 200. Any other 4xx, including an auth failure, raises at once. The attempts share the request's one reservation, settled once at the end, also when the answer can't be read. A malformed answer isn't re-prompted here, because the tracing stage makes the one retry with its own recording. No error message contains the API key.

Bind to a model and a ledger.

Parameters:

Name Type Description Default
settings ClaudeSettings

The settings.

required
client Any | None

An injectable client with the anthropic.Anthropic interface, for tests. When omitted, a real client is built from the settings with the SDK's retries off, so the provider's own backoff is the only retry.

None
sleep Callable[[float], None]

The backoff sleeper, injectable for tests.

sleep
now Callable[[], str] | None

Gives the ledger entry's t as local time YYYY-MM-DDTHH:MM:SS; injectable for tests.

None

Raises:

Type Description
ProviderError

If the settings' model has no prices.

settings instance-attribute

settings = settings

read

read(request: ReadingRequest) -> ReadingResponse

Send one request and return its answer.

Before sending, the provider reserves worst_case_usd on a new ledger entry {"t", "script": "osrforge", "label": "<kind> <effort>", "model"} in one step (Ledger.open_reserved), so a refusal writes nothing to the ledger. It streams the response, settles the reservation at cost_usd of the answer's usage whether or not the answer is good (a request whose attempts all fail keeps its worst case), and then checks the answer: a stop_reason of max_tokens or refusal is a ReadingFailure, and the first text block's JSON is checked against the kind's schema.

Parameters:

Name Type Description Default
request ReadingRequest

The request.

required

Returns:

Type Description
ReadingResponse

The answer, with cache_read_tokens and cache_write_tokens

ReadingResponse

from the usage's cache_read_input_tokens and

ReadingResponse

cache_creation_input_tokens.

Raises:

Type Description
ReadingFailure

If the answer stopped at max_tokens or with a refusal.

SchemaValidationError

If the answer doesn't fit the kind's schema.

LedgerStopError

If the reservation would pass the stop.

ProviderError

On transport, auth, or rate-limit exhaustion.

read_all

Send several requests, each shared part written to the cache by one request before the rest read it.

Requests are sent on threads. A request is sent when every shared part it contains is warm, or when it is the one request chosen to warm the first of its parts that isn't. A part becomes warm when the first event of that request's stream arrives. So the first request of a run goes alone, and requests about different pages that share the same instructions go one after the other at first and then together.

Parameters:

Name Type Description Default
requests Sequence[ReadingRequest]

The requests.

required

Returns:

Type Description
list[ReadingResponse | Exception]

One item per request, in order: its answer, or the exception

list[ReadingResponse | Exception]

read raised for it.

ClaudeSettings

Bases: BaseModel

The provider's settings: the key, the model, and the ledger.

Examples:

from pathlib import Path

from pydantic import SecretStr

from osrforge.providers.claude import ClaudeProvider, ClaudeSettings

settings = ClaudeSettings(api_key=SecretStr("sk-..."), ledger_path=Path("claude-ledger.json"))
provider = ClaudeProvider(settings)

api_key instance-attribute

api_key: SecretStr

The API key. Never written to a log, a recording, or an error message.

ledger_path instance-attribute

ledger_path: Path

The Claude ledger file.

model class-attribute instance-attribute

model: str = DEFAULT_MODEL

The model id. One model serves every kind of request. It must be in PRICES.

ledger_stop_usd class-attribute instance-attribute

ledger_stop_usd: float | None = None

The stop, or None to use the ledger file's own.

from_env classmethod

from_env() -> ClaudeSettings

Build settings from the environment.

Returns:

Type Description
ClaudeSettings

Settings from ANTHROPIC_API_KEY and OSRFORGE_CLAUDE_LEDGER

ClaudeSettings

(both required), OSRFORGE_CLAUDE_MODEL, and

ClaudeSettings

OSRFORGE_CLAUDE_LEDGER_STOP.

Raises:

Type Description
ProviderError

If a required variable is missing or empty, or the stop isn't a number.

ModelPrices

Bases: BaseModel

One model's list prices in dollars per token.

input instance-attribute

input: float

output instance-attribute

output: float

cache_write instance-attribute

cache_write: float

The price of writing a token to the cache with the five-minute lifetime.

cache_read instance-attribute

cache_read: float

cost_usd

cost_usd(model: str, usage: ReadingUsage) -> float

Return what an answer cost at list price.

Parameters:

Name Type Description Default
model str

The model id, a key of PRICES.

required
usage ReadingUsage

The answer's usage.

required

Returns:

Type Description
float

`input_tokens * input + output_tokens * output + cache_write_tokens * cache_write + cache_read_tokens *

float

cache_read`, in dollars, rounded to whole micro-dollars as the ledger keeps it.

Raises:

Type Description
ProviderError

If the model has no prices.

estimate_input_tokens

estimate_input_tokens(request: ReadingRequest) -> int

Estimate a request's input tokens from above, for the reservation.

The estimate is one token per UTF-8 byte of every text part, the system prompt, the retry's answer and fault, and the schema's compact JSON (sorted keys, compact separators), plus ceil(width * height / 750) for each image, plus 64. An image is counted at the size the API reads it: an image longer than 2,576 pixels on its long edge, or larger than 3.75 megapixels, is scaled down to fit both limits with its proportions kept, and each side of the scaled size is rounded up. A token is never shorter than a byte, and the API charges an image about one token per 784 pixels of that size (about 4,784 tokens at the limit), so the estimate is an upper bound on what the answer's usage reports, and the over-reservation is small beside max_tokens at the output price.

Parameters:

Name Type Description Default
request ReadingRequest

The request.

required

Returns:

Type Description
int

The estimate.

worst_case_usd

worst_case_usd(model: str, request: ReadingRequest) -> float

Return a request's worst-case cost: its input at the cache-write price and max_tokens at the output price.

Parameters:

Name Type Description Default
model str

The model id, a key of PRICES.

required
request ReadingRequest

The request.

required

Returns:

Type Description
float

The worst-case cost in dollars, rounded to whole micro-dollars as

float

the ledger keeps it.

Raises:

Type Description
ProviderError

If the model has no prices.