Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

98 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Antifake

Automated fact-checker for Russian-language claims. Takes a claim, gathers evidence from open sources (DuckDuckGo, Wikipedia, Wikidata), runs it through NLI + LLM, and returns a verdict with reasoning and source list.

Verdicts (returned verbatim in Russian): ΠŸΠ ΠΠ’Π”Π (true) Β· Π›ΠžΠ–Π¬ (false) Β· НЕ УВЕРЕНА (unsure) Β· БКАМ (scam).


Accuracy

The system is evaluated on 28 probe claims, split into three sets by falsification type. All runs are deterministic, on a single adapter (fact_checker_lora_v2).

πŸ“˜ Core set

10 canonical claims

9 / 10

90 %

🌐 Out-of-distribution

8 claims not seen in training

7 / 8

87.5 %

πŸ•΅οΈ Myths & fakes

10 conspiracies and misconceptions

8 / 10

80 %

Total: 24 of 28 Β· 86 %


πŸ“˜ Core set

Canonical facts and their substitutions β€” probes the main falsification patterns:

  • Name swaps. Who created Bitcoin? Who wrote Eugene Onegin?
  • Location swaps. What's the capital of Australia? Where is the Eiffel Tower?
  • Date swaps. When did World War II end?
  • Directional facts. Does the Sun orbit the Earth?
  • Numerical facts. How much oxygen is in the atmosphere?

Main mechanism β€” Wikidata lookup. If the claim says "Bitcoin creator = Vitalik Buterin" and Wikidata says P178 = Satoshi Nakamoto, the mismatch is detected automatically without calling the LLM judge.


🌐 Out-of-distribution set

Claims not seen in training or in the core set. Measures whether the mechanisms generalise to fresh claims.

Covers:

  • Multi-word entities in quotes. Β«Π ΠΎΠΌΠ°Π½ β€žΠ’ΠΎΠΉΠ½Π° ΠΈ ΠΌΠΈΡ€β€œ написал ДостоСвский» (claims Dostoevsky wrote War and Peace)
  • Geographic swaps. Capital of Canada = Toronto
  • Scientific authorship. Newton formulated the theory of relativity
  • Event dates. Berlin Wall fell in 1979
  • Common truths. Moscow is the capital of Russia; Everest is the tallest mountain

πŸ•΅οΈ Myths & fakes

Claims with no clear structural contradiction in the knowledge graph:

  • Moon landing hoax
  • "We only use 10% of our brain"
  • Vikings wore horned helmets
  • Vaccines cause autism
  • 5G radiation causes cancer
  • Flat Earth
  • Great Wall of China visible from the Moon
  • COVID-19 engineered as a bioweapon
  • 97% of climatologists agree on anthropogenic warming

  • Napoleon was short

A separate myth probe runs here via peft.disable_adapter() against the base Mistral 7B β€” it classifies the claim as МИЀ / ЀАКВ / ΠΠ•Π˜Π—Π’Π•Π‘Π’ΠΠž (myth / fact / unknown). Accuracy is lower because the base model doesn't parametrically know every named myth, and search doesn't always surface a debunk source for a given claim.


⚠️ Known limitations

Search variability. DuckDuckGo and Wikipedia can return different source sets across runs β€” within a single run the result is deterministic, but cross-session flips on borderline claims are possible.

LLM run-to-run flakiness. The Mistral 7B parametric probe sometimes returns ΠΠ•Π˜Π—Π’Π•Π‘Π’ΠΠž (unknown) on a claim it answered confidently in another run β€” an artefact of 4-bit bitsandbytes quantisation. Sample size (28 claims) means 1 flip β‰ˆ Β±3.5 % in the metric.

Myths without structural signal. For niche myths the base Mistral doesn't know parametrically (horned Vikings, Napoleon's height), the system returns НЕ УВЕРЕНА (unsure) β€” an honest defer rather than a confidently wrong verdict.


Architecture

Claim
    β”‚
    β–Ό
[PARSE]        claim classification, extract numbers/dates, detect scam
    β”‚
    β–Ό
[DECOMPOSE]    rule-based split on conjunctions (quote-aware); Mistral for complex cases
    β”‚
    β–Ό
[SEARCH]       DDG (3 parallel frames) + Wikipedia entity lookup
    β”‚          + verification queries + counter-search (debunk framing)
    β”‚          + Wikipedia "common misconceptions" frame
    β”‚          + quoted-number query (for claims with number+unit)
    β”‚          + rate-limiter 0.35s/host + 429-aware retry
    β–Ό
[RANK]         multilingual-e5-base (bi-encoder) + mmarco cross-encoder reranker
    β”‚          + fact-checker boost + TRUSTED_SOURCES filter
    β–Ό
[EVIDENCE]     4 parallel signals:
    β”‚          β€’ Wikidata SPARQL β€” structured KG check
    β”‚                              structural entity-mismatch for
    β”‚                              single-value props (capital, country,
    β”‚                              continent, author, founder, developer)
    β”‚          β€’ NUM comparison β€” deterministic numeric check
    β”‚          β€’ NLI β€” mDeBERTa sentence-level +
    β”‚                  cross-encoder tiebreaker + doc-level CE fallback +
    β”‚                  subject-mention guard
    β”‚          β€’ LLM knowledge probe (Mistral parametric memory)
    β”‚          β€’ LLM myth probe (via disable_adapter β€” base Mistral,
    β”‚                            not SFT β€” classifies as МИЀ/ЀАКВ β€” myth/fact)
    β–Ό
[DECIDE]       priority-based tree over the signals:
    β”‚          TIER 1: WD hard-mismatch β†’ Π›ΠžΠ–Π¬ 90  (false, confidence 90)
    β”‚          TIER 2: NUM Β±1 (with LLM/myth consensus override)
    β”‚          TIER 3: debunk-aware stance gate (myth detection)
    β”‚          TIER 4: NLI gap zones (strong / moderate / ambiguous)
    β”‚                  + subject-verification gates
    β”‚                  + LLM coherence overrides
    β”‚          TIER 5: LLM parametric / myth fallback
    β–Ό
[EXPLAIN]      LLM generates the reasoning text (the verdict is already
               decided β€” the model only explains why)
    β–Ό
Verdict + credibility_score (0-100) + reasoning + sources

LLM is an explainer, not a judge β€” all verdicts are derived deterministically from signals. This keeps verdicts reproducible and debuggable.


Stack

Component Model
Base LLM unsloth/mistral-7b-instruct-v0.3-bnb-4bit
SFT adapter adapters/fact_checker_lora_v2 (custom QLoRA r=16)
Bi-encoder ranker intfloat/multilingual-e5-base
Cross-encoder reranker cross-encoder/mmarco-mMiniLMv2-L12-H384-v1
NLI (primary) MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7
NLI (cross-encoder fallback) cross-encoder/nli-deberta-v3-base
Translator (RU→EN fallback) facebook/nllb-200-distilled-600M
NER + lemmatisation Natasha + pymorphy2
Knowledge graph Wikidata SPARQL endpoint
Search DuckDuckGo (async) + MediaWiki API

GPU: 4-bit Mistral fits in ~5 GB VRAM, NLI/reranker run on CPU, embeddings on CPU. Tested on RTX 5070 12 GB (Blackwell sm_120); xformers is disabled β€” incompatible with sm_120.


Install

git clone https://github.com/sakenuGOD/antifake.git
cd antifake
python -m venv venv
source venv/bin/activate          # Linux/Mac
# or:
venv\Scripts\activate             # Windows

pip install -r requirements.txt

Environment variables

Variable Required Purpose
SERPAPI_API_KEY no Google Search fallback (without it β€” DDG only)
HF_TOKEN no higher HuggingFace rate limit when pulling weights
ANTIFAKE_DETERMINISTIC=1 no strict-reproducibility mode (cuDNN deterministic)

Stored in .env at the project root.


Usage

Streamlit UI

streamlit run app.py

The UI shows 13 pipeline stages in real time: parsing, decomposition, keywords, found sources, Wikidata facts, NUM comparisons, NLI signals, debunk count, LLM probes, decision, aggregation, and explanation.

Python API

from pipeline import FactCheckPipeline

pipeline = FactCheckPipeline(adapter_path="adapters/fact_checker_lora_v2")

result = pipeline.check("Π ΠΎΠΌΠ°Π½ Β«Π’ΠΎΠΉΠ½Π° ΠΈ ΠΌΠΈΡ€Β» написал Π€Ρ‘Π΄ΠΎΡ€ ДостоСвский")
# (claim: "the novel War and Peace was written by Fyodor Dostoevsky")

print(result["verdict"])              # "Π›ΠžΠ–Π¬"  (false)
print(result["credibility_score"])    # 10
print(result["reasoning"])            # reasoning text (in Russian)
for src in result["sources"][:3]:
    print(src["link"], src["title"])

Full result signature β€” see pipeline.py::check().

CLI

python main.py "Москва β€” столица России"
# (claim: "Moscow is the capital of Russia")

Project layout

antifake/
β”œβ”€β”€ app.py                       # Streamlit entry point
β”œβ”€β”€ main.py                      # CLI entry point
β”œβ”€β”€ pipeline.py                  # Main evidence-first pipeline
β”œβ”€β”€ search.py                    # DDG/Wiki/Wikidata + rate-limiter + frames
β”œβ”€β”€ nli_checker.py               # mDeBERTa + cross-encoder + doc-level CE
β”œβ”€β”€ wikidata.py                  # SPARQL + structural entity-mismatch
β”œβ”€β”€ counter_search.py            # Multi-frame debunk/verify queries
β”œβ”€β”€ model.py                     # Mistral + LoRA loader
β”œβ”€β”€ prompts.py                   # Knowledge + myth probe templates
β”œβ”€β”€ claim_parser.py              # Numbers/dates/locations + scam patterns
β”œβ”€β”€ embeddings.py                # Semantic ranker + CE reranker
β”œβ”€β”€ evidence_tiers.py            # T1-T3 authority weighting
β”œβ”€β”€ nlp_russian.py               # Natasha/pymorphy2 helpers
β”œβ”€β”€ source_credibility.py        # Domain trust boosting
β”œβ”€β”€ cache.py                     # Disk-based search cache (24h TTL)
β”œβ”€β”€ fact_cache.py                # Verified facts cache
β”œβ”€β”€ config.py                    # All constants and thresholds
β”œβ”€β”€ utils.py
β”‚
β”œβ”€β”€ tests/                       # Regression suites
β”‚   β”œβ”€β”€ _path.py                 # sys.path shim
β”‚   β”œβ”€β”€ test_hard10.py           # 10 canonical claims
β”‚   β”œβ”€β”€ test_manipulative.py     # 10 conspiracy/myth claims
β”‚   β”œβ”€β”€ test_ood_probe.py        # 8 out-of-distribution claims
β”‚   └── test_*.py (archive)
β”‚
β”œβ”€β”€ eval/                        # Metrics on larger datasets
β”‚   β”œβ”€β”€ evaluate.py
β”‚   β”œβ”€β”€ evaluate_universal.py
β”‚   └── quick_eval.py
β”‚
β”œβ”€β”€ scripts/                     # Training / data-gen / housekeeping
β”‚   β”œβ”€β”€ train.py                 # SFT
β”‚   β”œβ”€β”€ train_grpo.py            # Reward-based
β”‚   β”œβ”€β”€ generate_*.py            # Training data
β”‚   β”œβ”€β”€ merge_training_data.py
β”‚   β”œβ”€β”€ audit_training_data.py
β”‚   β”œβ”€β”€ download_dataset.py
β”‚   β”œβ”€β”€ sanity_check.py
β”‚   β”œβ”€β”€ summarize_night.py
β”‚   └── run_training.sh, setup_and_train.sh, night_run.{sh,ps1}
β”‚
β”œβ”€β”€ data/                        # Datasets + results + caches
β”‚   β”œβ”€β”€ train_*.jsonl            # Training sets
β”‚   β”œβ”€β”€ hard10_results.json
β”‚   β”œβ”€β”€ manipulative_results.json
β”‚   β”œβ”€β”€ ood_probe_results.json
β”‚   └── wikidata_cache.json
β”‚
β”œβ”€β”€ adapters/                    # LoRA / GRPO adapters (weights gitignored)
β”œβ”€β”€ logs/                        # Pipeline runs (gitignored)
└── requirements.txt

Imports inside subfolders

Files in tests/, eval/, scripts/ start with import _path # noqa: F401,E402. This shim adds the project root to sys.path so from pipeline import … works without installing the project as a package.


Training

SFT on your own data

python scripts/train.py \
    --dataset data/train_v2_combined.jsonl \
    --output adapters/fact_checker_lora_v3 \
    --epochs 4 \
    --learning-rate 2e-5 \
    --resume adapters/fact_checker_lora_v2   # optional

GRPO (reward-based)

python scripts/train_grpo.py \
    --load-adapter adapters/fact_checker_lora_v2 \
    --steps 200

Data generation

python scripts/generate_russian_data.py --output data/train.jsonl
python scripts/generate_failure_patterns.py --output data/failure_patterns.jsonl
python scripts/merge_training_data.py \
    data/train.jsonl data/failure_patterns.jsonl \
    --output data/train_v2_combined.jsonl

Tests

# Small regression suites (~15-25 min per suite)
python tests/test_hard10.py       --adapter adapters/fact_checker_lora_v2
python tests/test_manipulative.py --adapter adapters/fact_checker_lora_v2
python tests/test_ood_probe.py    --adapter adapters/fact_checker_lora_v2

# Debug a single claim
python tests/debug_single.py "Bitcoin создал Π’ΠΈΡ‚Π°Π»ΠΈΠΊ Π‘ΡƒΡ‚Π΅Ρ€ΠΈΠ½"
# (claim: "Bitcoin was created by Vitalik Buterin")

Results land in data/*_results.json with a detailed log in logs/.


Configuration

All thresholds and settings live in config.py:

Class What it controls
ModelConfig base model path, max_seq_length, load_in_4bit
PipelineConfig NLI device, cross-encoder model, re-ranker on/off
SearchConfig DDG timeout, num_results, SerpAPI
DecisionThresholds strong_gap, moderate_gap, num_nli_override

External APIs

Service Rate limit Fallback
DuckDuckGo no formal limit, 429 under burst retry + async parallel
Wikipedia / Wikidata ~30 req/s anonymous 0.35s/host token bucket + 429 retry
SerpAPI per-plan key DDG (primary channel)

Rate-limiter lives in search.py::_wiki_rate_limit β€” global per-host token bucket with tenacity retry (2-30s exponential backoff).


License

See LICENSE (if present).

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages