AI-powered fact verification for PDF documents.
Upload a PDF. Extract factual claims. Verify each one against live web evidence. Export a transparent, source-cited report.
Reports, whitepapers, marketing documents, and AI-generated content all contain factual claims — market sizes, funding rounds, benchmark scores, regulatory actions, historical events. Most go unchecked.
2see automates the verification pipeline: extract → search → verify → report. Every verdict is grounded in retrieved evidence with source credibility scoring, so you can see exactly why a claim was marked Verified, Inaccurate, False, or Unverifiable.
flowchart TD
A["📄 PDF Upload"] --> B["Text Extraction"]
B --> C["Claim Extraction"]
C --> D["Materiality Scoring"]
D --> E["Evidence Retrieval"]
E --> F["Verdict Synthesis"]
F --> G["📊 Report Export"]
classDef default fill:#2d3436,stroke:#636e72,stroke-width:1.5px,color:#dfe6e9,rx:8
linkStyle default stroke:#636e72,stroke-width:1.5px
- PDF → Text — Server-side extraction via
pdfjs-distwith layout-aware line grouping and section heading detection. - Text → Claims — Llama models extract verifiable factual assertions per page. Claims are normalized, deduplicated (Jaccard + Dice trigram similarity), split into independent assertions, and merged when complementary.
- Claims → Scored — Each claim is scored across 25+ materiality signals: named entities, metrics, temporal markers, attribution, financial terms, regulatory language, claim completeness, and table-relationship detection. Low-materiality fragments and derivative metrics are suppressed.
- Scored → Evidence — A multi-step retrieval cascade runs per claim:
- Deterministic queries (literal, metric-focused, entity, attribution, source-domain, official)
- LLM-generated queries (when deterministic evidence is weak)
- Wikipedia fallback
- Source text fetching for top-credibility results
- Evidence → Verdict — Retrieved sources are scored against a curated authority database (250+ domains across government, academic, news, and tech). A guardrail comparator pre-checks evidence before LLM synthesis. If the LLM call fails (rate limit, timeout), verdicts are derived from evidence guardrails alone.
- Verdict → Report — Results stream to the client via SSE. Export as JSON (machine-readable) or PDF (formatted report with decision metadata).
| Feature | Detail |
|---|---|
| Claim Extraction | Per-page extraction with multi-strategy JSON parsing, truncation repair, and Zod validation |
| Materiality Scoring | 25+ feature signals — metrics, entities, temporal markers, attribution, section importance, claim completeness |
| Evidence Retrieval | Cascading search: Tavily → Serper → Mojeek (scrape) → Wikipedia API, with result caching and source text fetching |
| Source Authority | 250+ curated domains across 6 tiers (government → academic → global news → official platforms → tech media → low-authority) |
| Verdict System | Verified · Inaccurate · False · Unverifiable — with confidence scores, corrected facts, and decision path transparency |
| Decision Metadata | Per-claim: decision_path, evidence_status, retrieval_status, reason_codes, comparator_verdict, search_query_count, duration_ms |
| Knowledge Fallback | When search engines return zero results, parametric model knowledge is used with a confidence cap of 65% |
| Export | JSON report (full metadata) or PDF report (formatted with @react-pdf/renderer) |
| Model Selection | Llama 4 Scout 17B (fast) or Llama 3.3 70B (thorough) via Groq's OpenAI-compatible API |
| Streaming | Server-Sent Events for real-time claim-by-claim progress with batch tracking |
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router, React 19) |
| Language | TypeScript 5 |
| LLM Provider | Groq (OpenAI-compatible API) |
| Models | meta-llama/llama-4-scout-17b-16e-instruct, llama-3.3-70b-versatile |
| PDF Parsing | pdfjs-dist (legacy build, server-side) |
| PDF Export | @react-pdf/renderer |
| Schema Validation | Zod 4 |
| Search APIs | Tavily, Serper, Mojeek (scrape), Wikipedia |
| Styling | Tailwind CSS 4, Radix UI primitives |
| Animations | Framer Motion |
| Testing | Vitest, Playwright |
| CI | GitHub Actions (lint → typecheck → test → build) |
| Deployment | Vercel (Node.js runtime, 60s function timeout) |
git clone https://github.com/de2pressed/2see.git
cd 2see
npm ciCreate a .env file from the example:
cp .env.example .env| Variable | Required | Description |
|---|---|---|
GROQ_API_KEY |
Yes | Groq API key for Llama model access. Get one free → |
OPENAI_API_KEY |
No | Optional fallback if GROQ_API_KEY is not set |
TAVILY_API_KEY |
Yes | Tavily search API for semantic evidence retrieval. Get one → |
SERPER_API_KEY |
No | Serper API for Google search results. Get one → |
Minimum viable setup: Only
GROQ_API_KEYis required. Without Tavily or Serper, evidence retrieval falls back to Mojeek web scraping and the Wikipedia API.
npm run devOpen http://localhost:3000. Select a model, upload a PDF, and start analysis.
| Endpoint | Method | Description |
|---|---|---|
/api/check-key |
GET |
Validates the configured Groq API key |
/api/extract-claims |
POST |
Accepts a PDF (multipart/form-data), returns extracted + scored claims |
/api/verify-claims |
POST |
Accepts claims JSON, returns SSE stream of verification results |
/api/export-report |
POST |
Accepts a full report JSON, returns a formatted PDF |
Compare a verification report against a golden-truth file:
node scripts/evaluate-report.mjs golden.json exported-report.jsonOutputs verdict match rates, missing claims, forbidden verdict hits, source authority distribution, and average per-claim duration.
Each verified claim includes:
2see/
├── app/
│ ├── api/
│ │ ├── check-key/ # API key validation
│ │ ├── extract-claims/ # PDF upload → claim extraction
│ │ ├── verify-claims/ # Claim verification (SSE stream)
│ │ └── export-report/ # Report → PDF generation
│ ├── globals.css # Design tokens (oklch palette, shimmer animations)
│ ├── layout.tsx # Root layout, fonts, metadata
│ └── page.tsx # Entry point → VerificationApp
├── components/
│ ├── verification-app.tsx # Main UI (1400+ lines — upload, progress, results, filters, export)
│ ├── model-selector.tsx # Llama model picker
│ └── ui/ # Badge, Button, Progress (Radix-based)
├── services/
│ ├── openai.ts # LLM calls, search cascade, evidence processing, verdict synthesis (~2800 lines)
│ ├── pdf.ts # PDF text extraction with layout-aware line grouping
│ └── verification.ts # Batched verification orchestrator with retries
├── utils/
│ ├── claims.ts # Materiality scoring, deduplication, claim merging (~800 lines)
│ ├── sources.ts # Domain authority database, credibility scoring, evidence/hallucination risk
│ ├── ai.ts # JSON extraction, sanitization, truncation repair, Zod parsing
│ ├── chunk-text.ts # Text chunking for long documents
│ ├── async.ts # Concurrency-limited map, delay helper
│ └── files.ts # PDF validation, filename sanitization
├── lib/
│ ├── schemas/index.ts # Zod schemas for all data types
│ ├── models.ts # Model definitions and selection helpers
│ ├── llm.ts # Shared completion parameters
│ ├── claim-cache.ts # SHA-256 content-addressed claim cache
│ ├── search-fixtures.ts # Test fixture loader for search results
│ └── utils.ts # cn() (clsx + tailwind-merge)
├── types/
│ └── report.ts # Re-exported schema types
├── hooks/
│ └── use-model-selection.ts # LocalStorage-backed model preference
├── scripts/
│ └── evaluate-report.mjs # Golden-truth report evaluation harness
├── tests/ # Vitest unit tests + fixtures
├── .github/workflows/ci.yml # CI: lint → typecheck → test → build
└── .env.example
- Free-tier rate limits. Groq's free tier has token-per-minute and request-per-minute caps. Batch sizes and delays are tuned for this, but large documents may hit throttling.
- Image-based PDFs. Text extraction requires selectable text. Scanned/image-only PDFs will fail with a clear error.
- Batch PDF processing
- Persistent report storage
- Additional LLM provider support
- Configurable claim extraction rules
- Source page screenshot capture
- Claim diff across document versions
- API-first mode (headless verification)
npm run dev # Start dev server
npm run lint # ESLint
npm run typecheck # TypeScript strict check
npm test # Run Vitest suite
npm run build # Production buildContributions welcome. Open an issue first for non-trivial changes.
- Fork the repository
- Create a feature branch (
git checkout -b feat/my-change) - Commit with clear messages
- Open a pull request against
main
All PRs must pass the CI pipeline (lint, typecheck, test, build).
MIT
{ "claim_id": "claim-1", "claim": "Global AI market reached $196.6 billion in 2023.", "type": "financial", "verdict": "Verified", // Verified | Inaccurate | False | Unverifiable "confidence": 88, // 0–100 "explanation": "Multiple high-authority sources confirm...", "corrected_fact": "", // populated when verdict is Inaccurate or False "sources": [ { "title": "AI Market Size Report", "url": "https://...", "domain": "statista.com", "credibility": "High", // High | Medium | Low "snippet": "...", "retrieved_at": "2025-01-15T..." } ], "decision_path": "llm", // guardrail | llm | fallback | knowledge "evidence_status": "direct", // direct | related | weak | absent | conflicting | technical_failure "retrieval_status": "searched", // not_needed | searched | fallback_searched | exhausted | quota_limited | technical_failure "reason_codes": [], "duration_ms": 4200 }