curated_feed.claude

The Anthropic-backed scorer: the one place that talks to a model.

Everything the API needs is decided here — the cache split, the token ceiling, what counts as a failed response — so that score.py stays a protocol, a retry loop and a failure policy, and the rest of the pipeline never learns that a provider exists.

Three choices are worth knowing about.

The policy documents are the system prompt, the candidates are the user turn. That is the boundary policy.py already draws: the documents are byte-identical every run and are most of the tokens, so they carry the cache breakpoint and are read back at a tenth of the price. Interpolating anything volatile above them would silently cost full price on every run.

Responses stream. max_tokens is deliberately generous — truncation is a hard failure rather than a short day — and the SDK refuses a non-streaming request it estimates will outrun the HTTP timeout.

No `thinking` parameter is sent. Models differ in how thinking is configured and which values they accept; the default is the portable choice, and effort (when the configured model supports it) is the knob that matters.

Attributes

Classes

TokenUsage

What a day of scoring cost, in tokens.

AnthropicScorer

Scores a day of candidates with Claude.

Functions

build_client(→ anthropic.Anthropic)

A client with credentials resolved by the SDK.

Module Contents

curated_feed.claude.CACHE_CONTROL
curated_feed.claude.TRUNCATED = 'max_tokens'
curated_feed.claude.REFUSED = 'refusal'
curated_feed.claude.RETRY_INSTRUCTION = Multiline-String
Show Value
"""Your previous response did not validate: {feedback}
Return the whole response again as valid JSON, corrected."""
class curated_feed.claude.TokenUsage[source]

What a day of scoring cost, in tokens.

Accumulated across attempts, so a day that needed a retry reports what the retry actually cost rather than what the successful call did.

calls: int = 0
input_tokens: int = 0
output_tokens: int = 0
cache_read_tokens: int = 0
cache_write_tokens: int = 0
add(usage: object) → None[source]

Fold one response’s usage into the running total.

format() → str[source]

One line for the run report.

>>> TokenUsage(calls=1, input_tokens=2, output_tokens=3).format()
'1 call, 2 in (0 cached), 3 out'
class curated_feed.claude.AnthropicScorer(*, model: str, max_tokens: int, effort: str = '', timeout_seconds: float | None = None, client: anthropic.Anthropic | None = None)[source]

Scores a day of candidates with Claude.

name = 'anthropic'
model
max_tokens
effort = ''
usage
generate(candidates: collections.abc.Sequence[curated_feed.models.Candidate], prompt: curated_feed.policy.Prompt, *, feedback: str | None = None) → str[source]

One call: the day’s prompt in, the raw response text out.

curated_feed.claude.build_client(timeout_seconds: float | None) → anthropic.Anthropic[source]

A client with credentials resolved by the SDK.

No key is read here on purpose: the SDK also accepts an ant auth login profile and workload identity, so an unset ANTHROPIC_API_KEY does not mean there are no credentials. An actual failure surfaces on the first call.

Public because it is the seam. A scorer built by hand takes its client directly, but the one the pipeline builds cannot: score.make_scorer reads configuration, and a client is not something configuration should hold. This factory is therefore what a test substitutes to run the whole chain against a fake, and a default nobody can substitute is the thing deploy.py already learned to avoid.