Skip to content

solvi.specialist

The specialist contract (preview): a model proposes a typed spec, code checks it against the source, code renders it, and a hash-chained trace replays to identical bytes.

Verifiable specialists: a model proposes, code checks against the source, code renders, everything is in the trace.

One contract for every specialist (charts first; slides, tables, speech later):

  1. propose — a model (small and fast, an LLM, or a rule-based extractor) writes a typed intermediate spec (a pydantic model), never the result itself;
  2. check — deterministic code verifies the spec against the source and the specialist's rules; what does not verify is dropped or changed, each with a reason (an Issue) — it is marked, never invented or repaired by a guess;
  3. render — deterministic code builds the result from the verified spec only (same verified spec → same bytes);
  4. trace — every step goes into a hash chain: the source's hash, the proposal, the check, the output's hash. Specialist.replay re-checks the recorded proposal and re-renders it: identical issues and identical bytes, or it says what differs.

    from solvi.charts import ChartSpecialist, RuleProposer run = ChartSpecialist(RuleProposer()).run(text, "revenue by region") run.output # the SVG, or None when nothing verified print(run.report()) # what was kept, dropped, changed and why ChartSpecialist().replay(run.to_dict(), text).ok # True: same checks, same bytes

A subclass defines spec_type (the pydantic model of the proposal), check(spec, source) -> Checked and render(checked) -> str | None; the proposer is any callable (source, question) -> spec | dict (an id attribute, when it has one, names it in the trace). A proposer is not replayed — its output is recorded; the check and the render are, from that record.

Issue dataclass

Issue(severity: str, code: str, message: str, path: str = '')

One finding of the check: where in the spec (path, e.g. "series[0].points[2]"), what (code, stable, machine-readable), how bad (severity) and why (message, for people).

Checked dataclass

Checked(verified: Any, issues: list = list(), kept: list = list(), meta: dict = dict())

The check's result: the verified spec (only what passed; None when nothing did) and every issue.

Trace

Trace(entries=None)

A hash chain of steps: entry i stores its step, its data, the previous entry's hash and its own hash = sha256(prev + canonical(step, data)). Editing any entry breaks every hash after it.

verify

verify()

→ a list of problems ([] when the chain is intact).

Run dataclass

Run(specialist: str, version: str, source: str, question: str | None, proposal: Any, checked: Checked, output: str | None, trace: Trace)

One run of a specialist: the proposal, the check, the output (None when nothing verified) and the trace.

ok property

ok

True when there is an output (with or without dropped parts: see issues).

report

report()

What was kept, dropped, changed and why — plain text, one line per item.

to_dict

to_dict(with_source=False)

The record to store: everything replay needs except the source (pass with_source=True to include it).

Replay dataclass

Replay(ok: bool, problems: list)

The result of replaying a specialist's recorded run: ok (true in a boolean context) and the problems found.

Specialist

Specialist(proposer=None)

The contract (see the module docs). Subclasses set name, version, spec_type and implement check and render; proposer is the default proposer for run.

parse

parse(proposal)

A proposer's output (a spec_type, a dict or a JSON string) → spec_type; ValidationError / ValueError.

run

run(source, question=None, *, proposer=None, proposal=None)

source (a text) → Run. proposer overrides the default; proposal= skips proposing (a recorded or hand-written spec).

replay

replay(record, source=None)

Re-check the recorded proposal against the source and re-render it: the trace chain intact, the same source, the same issues — in the trace and in the record's own issues — identical output bytes. A run that was blocked before any spec (a failed proposer, an invalid proposal) has nothing to re-check: its chain and its record's copies are verified. record: a Run or its to_dict(); source: the text (or in the record).

canonical

canonical(obj)

Canonical JSON bytes (sorted keys, no spaces, UTF-8): the input of every hash in the trace.