Faithfulness¶
Question: is every claim in the final answer backed by what the tools returned?
How it grades¶
- The final answer is split into atomic, checkable claims. Hedges, opinions and generic advice are dropped.
- Claims are checked against the run's tool outputs and artifacts in batches of 15, with one
model call per batch. At most 40 claims are checked (
max_claims=onevaluate(),--max-claimsonattest runandattest review). Each claim past the cap is reportedunverifiablewithcheckedfalse, so a long answer goes toreviewinstead of passing on its first 40 claims, andattest reviewdrops a code review output with more claims than the cap. - Each claim gets
supported,unsupportedorunverifiable, a reason, and the deciding quote. When the quote comes from exactly one tool output or artifact, the finding'smetadata["location"]says which, such assteps[2].tool_call.artifacts[0].
The score is supported / (supported + unsupported). Unverifiable claims don't count against
the score, but they appear as warnings. The check passes only when nothing is unsupported.
metadata counts the claims checked against the evidence (claims_checked) and every claim
extracted from the answer (claims_total), including any past the 40-claim limit.
Inconclusive is not a pass. When the answer has claims but none of them can be checked
(for example, the run recorded no relevant evidence), there's no score and the report's verdict
is review, not pass. Change it with
VerdictPolicy(on_inconclusive_faithfulness=...). An answer that makes no claims at all
passes.
Minimum coverage. A run where only a few claims could be checked still passes by default. To require more, set a minimum share of claims checked; below it, the run goes to review:
Coverage is kept separate from the score: 1 claim checked out of 40 can still score 1.0. The
report gives it directly as report.claim_coverage (0 to 1, or null when faithfulness didn't
run or the answer made no claims).
A failed call is not a pass either. If claim extraction or a verification call fails, or a
verifier returns no verdict for a claim, the claims it covered are unverifiable with
metadata["checked"] set to false and the error in reason. Each one is review (the same
on_inconclusive_faithfulness setting), even when other claims were supported.
metadata["claims_unchecked"] counts them.
Files as evidence¶
Artifact text counts as evidence, so a claim answered from an attached document can be checked. PDFs, images and other binary files are read only when you opt in:
grader = Attest(extract_artifacts=True) # data_base64 artifacts
grader = Attest(extract_artifacts=True, allow_paths=True) # path artifacts too
Extraction uses the same limits as the injection scan. A file that was cut short or couldn't be
read is shown to the grading model as incomplete. Without the opt-in, binary and path artifacts
are left out and listed in metadata["artifacts_unread"]; files read only in part are listed in
metadata["artifacts_incomplete"]. See Evidence.
Choosing the standard¶
Pass answer_kind when the answer isn't a plain factual report (see
Answer kinds):
grader.evaluate(run, answer_kind="plan") # a plan or recommendation
grader.evaluate(run, answer_kind="code") # an AI code reviewer's output
grader.evaluate(run, answer_kind="auto") # pick factual or code from the evidence
The CLI takes the same choice as --kind.
Bring your own extractor or verifier¶
Both steps are plain functions you can replace:
extract(answer) -> list[str]splits the answer into claims.verify(claims, evidence) -> list[Finding]checks the claims against the evidence text.
def extract(answer: str) -> list[str]:
return [s.strip() for s in answer.split(".") if s.strip()]
report = grader.evaluate(run, checks=("faithfulness",), extract=extract)
Compared with a plain LLM judge¶
attest demo grades the same run twice: once with a plain "is this answer good?" LLM judge
that reads everything, and once with attest:
Each comparison's outcome says who caught what: caught means attest found a false claim the
LLM judge passed. The other outcomes are both_caught, both_passed and judge_only.