feat: server-side citation verification against source text - #25
Open
amal66 wants to merge 1 commit into
Open
Conversation
Port of the fork's document-quote verification (apps/api/src/lib/tools/ verifyCitations.ts and its wiring) to upstream layout. After the model's <CITATIONS> block is parsed, each document quote is located in the document's extracted source text (exact, then whitespace/case-tolerant, then punctuation-tolerant matching). Quotes get a per-quote verification record and each citation an aggregate verification_status: verified | repaired (exact source excerpt swapped in) | unverified. Case-law citations pass through untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hallucinated citations are the failure mode that gets a small firm sanctioned. This PR adds server-side verification of assistant citations: after the model's
<CITATIONS>block is parsed, every document quote is checked against the actual extracted text of the cited document before the citation is streamed to the client or persisted.Each quote is located in the source with progressively more tolerant matching — exact substring, then whitespace/case-normalized, then punctuation-tolerant — and gets a per-quote
verificationrecord plus char offsets into the extracted text. Each citation gets an aggregateverification_status:verified— quote is character-identical to the sourcerepaired— quote was found under tolerant matching but drifted; the exact source excerpt is swapped into the displayed quote so drifted text is never presented as the source's wordsunverified— no match; the model's text is preserved but must be treated as untrustedCross-page quotes (
[[PAGE_BREAK]]sentinel) are split and each segment verified independently. Case-law citations pass through untouched — their existence is already verified via CourtListener and is never re-marked here.Changes
backend/src/lib/chat/verifyCitations.ts(new) — quote location + verification + annotation/batch verification logicbackend/src/lib/chat/verifyCitations.test.ts(new) — 16 focused unit tests (exact/drifted/fabricated quotes, unreadable sources, cross-page quotes, case-citation passthrough, aggregation)backend/src/lib/chat/streaming.ts— verify final citations before thecitationsSSE frame; source text is fetched at most once per document per turn (memoized), viareadDocumentContent(..., { emitEvents: false })so no extra events firebackend/src/lib/chat/tools/documentOps.ts—normalizeWithMapgains an optionalstripPunctuationmode and is exported (used by tier-3 tolerant matching)backend/src/lib/chat/types.ts—CitationVerificationStatus/QuoteVerificationtypesbackend/tsconfig.json— exclude*.test.ts/__tests__from thetscbuild so tests don't require test deps at build timeNo new runtime dependencies. No frontend changes: the new fields ride along on the existing citation annotations; a missing
verification_statusis documented as "treat as untrusted", so existing clients are unaffected.Why
Citation trust is mission-core: the assistant's answer is only usable in legal work if every quote it attributes to a document is actually in that document. This closes the gap server-side, where the source of truth (document bytes in storage) lives — the client never has to be trusted to check.
No new vendors. Negligible compute cost: verification is pure string matching over document text the server already extracts, with source text memoized per document per turn — no extra LLM calls and no network egress (the tests explicitly prove verification only reads bytes handed to it).
Testing
npm install && npm run buildinbackend/— green on this branch as committednpx vitest run src/lib/chat/verifyCitations.test.ts— 16/16 passed (vitest installed locally with--no-saveand not committed; upstream has no test harness yet, so tests run atop the test-harness PR)frontend/untouchedProvenance
All changes are mechanical ports of code in amal66/mike@origin/main (commit b3166dd); exceptions: none. Sources:
apps/api/src/lib/tools/verifyCitations.ts+.test.ts(logic and tests, verbatim modulo import paths and mock/comment path rewrites),apps/api/src/lib/tools/stream.ts(streaming hook, verbatim modulocreateCitationAnnotation→ upstream'screateCitation),apps/api/src/lib/tools/docRead.ts(normalizeWithMapextension, verbatim),packages/core/src/types.ts(the two verification types, inlined verbatim),apps/api/tsconfig.json(test-file build exclusion).Credits & prior art
🤖 Generated with Claude Code
https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC