curated_feed.score¶
Turning a day of candidates into the model’s judgements.
A scorer produces the raw response text and nothing else. Persisting it, validating it and retrying live here, in one place, so that every backend gets the same treatment and a new one is a single small class.
Two backends so far, neither needing an API client or a key:
StubScorer invents a response from publication dates alone. It exists to prove the stages after it — selection, rendering, publishing — against a real feed before a token is spent.
FileScorer reads a response saved from a chat session, which is how the evaluation workflow drives the real pipeline.
The failure policy is decided here rather than at six in the morning: save the raw text before parsing, retry once with the validation error appended, and on a second failure publish nothing. The previously published feed stays valid and served, so a failed day costs one missing day.
Attributes¶
Exceptions¶
Raised when a usable response cannot be obtained. |
Classes¶
Anything that can produce a response for a day of candidates. |
|
Grades the most recently published candidates, calling nothing. |
|
Reads a response saved from a chat session. |
Functions¶
|
Obtain a validated response for one day. |
|
Validate raw response text, tolerating a Markdown code fence. |
|
Read a saved response artifact. |
|
The JSON inside a Markdown code fence, or the text unchanged. |
|
The scorer named in configuration. |
|
Human-readable run report. |
|
|
|
Module Contents¶
- curated_feed.score.CODE_FENCE¶
- curated_feed.score.NO_SUBSTANCE = 'no-substance'¶
- curated_feed.score.MAX_ATTEMPTS = 2¶
- curated_feed.score.RESPONSE_STAGE = 'response'¶
- exception curated_feed.score.ScoreError[source]¶
Bases:
ExceptionRaised when a usable response cannot be obtained.
- class curated_feed.score.Scorer[source]¶
Bases:
ProtocolAnything that can produce a response for a day of candidates.
feedback carries the validation error from a previous attempt, and is None on the first. A backend that cannot act on it should say so rather than silently repeating itself.
- generate(candidates: collections.abc.Sequence[curated_feed.models.Candidate], prompt: curated_feed.policy.Prompt, *, feedback: str | None = None) str[source]¶
- class curated_feed.score.StubScorer(*, items: int)[source]¶
Grades the most recently published candidates, calling nothing.
Judgement is not the point: publishing something Feedly can ingest is. The graded items therefore clear the floor on consequence alone, and are marked durable so the staleness adjustment cannot pull them back under it.
- name = 'stub'¶
- items¶
- generate(candidates: collections.abc.Sequence[curated_feed.models.Candidate], prompt: curated_feed.policy.Prompt, *, feedback: str | None = None) str[source]¶
- class curated_feed.score.FileScorer(path: pathlib.Path)[source]¶
Reads a response saved from a chat session.
This is what connects the manual evaluation loop to the pipeline: export a day, paste it into a chat, save the JSON, and the real selection, rendering and publishing run against it.
- name = 'file'¶
- path¶
- generate(candidates: collections.abc.Sequence[curated_feed.models.Candidate], prompt: curated_feed.policy.Prompt, *, feedback: str | None = None) str[source]¶
- curated_feed.score.score_day(scorer: Scorer, candidates: collections.abc.Sequence[curated_feed.models.Candidate], prompt: curated_feed.policy.Prompt, *, raw_path: pathlib.Path) curated_feed.models.Response[source]¶
Obtain a validated response for one day.
The raw text of each attempt is written to raw_path before it is parsed, so a response that fails validation can still be read afterwards — fence and all, since what is saved is what arrived. The last attempt is the one left on disk.
- curated_feed.score.parse_response(raw: str) curated_feed.models.Response[source]¶
Validate raw response text, tolerating a Markdown code fence.
- curated_feed.score.read_response(path: pathlib.Path) curated_feed.models.Response[source]¶
Read a saved response artifact.
The artifact is the raw text the model returned, fence and all, because it is the audit trail for a day that went wrong. Everything that reads it back therefore has to be as tolerant as the parse that first accepted it — which is why this exists rather than a bare read_artifact(path, Response).
- curated_feed.score.unfence(text: str) str[source]¶
The JSON inside a Markdown code fence, or the text unchanged.
Asking for bare JSON does not reliably get it: wrapping it in a fence is a deeply trained habit, and a response saved from a chat session almost always arrives fenced. Spending the day’s one retry on punctuation would be absurd, so the fence is simply removed here.
>>> unfence('```json\n{"graded": []}\n```') '{"graded": []}' >>> unfence('{"graded": []}') '{"graded": []}'
- curated_feed.score.make_scorer(config: curated_feed.config.Config, *, response_path: pathlib.Path | None) Scorer[source]¶
The scorer named in configuration.
The provider SDK is imported only when it is the one selected, so the offline backends — and every other command — stay free of it.
- curated_feed.score.format_summary(response: curated_feed.models.Response, *, candidates: int, path: pathlib.Path, scorer: Scorer) str[source]¶
Human-readable run report.
- curated_feed.score.build_parser() argparse.ArgumentParser[source]¶