Tjek nyhedspåstande med AI: en Python-guide (2026)
De fleste faktatjek-skrivninger stopper ved "brug en LLM til at verificere påstanden." Det virker i en demo og bryder sammen, når en reel nyhedscyklus rammer — påstande er tvetydige, beviser er spredt over tusindvis af udgivelser, og modellen hallucinerer selvsikkert citater, der ikke eksisterer.
Denne faktatjek-workflow har fem trin: udtræk et udsagn, find artikler, ranger beviserne, sammenlign dem med udsagnet, og forklar dommen med kildelinks. Den returnerer Support, Afvis, Ikke nok info, eller Blandet. Separate trin giver dig mulighed for at ændre modeller, måle responstid og omkostninger og spore fejl.
Denne guide gennemgår Python-kode for hvert trin. Den bruger APITube's /v1/news/everything til at finde beviser, forklarer fem almindelige fejlmodes og sammenligner en flertrins-workflow med et enkelt modelkald.
APITube udgiver denne guide. Dens API bruges til evidenssøgning. NewsAPI.org, NewsData.io, eller din egen nyhedskilde kan tjene samme rolle.
5 Pipeline-stadier i et overblik
- Claim Extraction—konverter fritekstinput til atomare, kontrollerbare claims med struktureret skema (subjekt, prædikat, objekt, tidsanker).
- Bevishentning—forespørg et live nyhedsindeks for dokumenter, der nævner claimets entiteter og tidsvindue.
- Bevisrangering—score hentede dokumenter på relevans, kildekredibilitet og tidsmæssig nærhed; behold top-k.
- NLI Konklusion—kør en entailment-model over påstanden og rangerede beviser; output Support / Refute / NEI / Mixed med en tillidsscore.
- Begrundelse—generer en menneskelæselig forklaring med inline-citater til de anvendte beviser.
Hvert trin har målbare input, output og fejltilfælde.
Fase 1 — Ekstraktion af påstande
Mål: konverter en uklar input ("Apples omsætning faldt sidste kvartal") til et eller flere atomare claims med strukturerede slots, som downstream-stadier kan handle på.
Et atomart udsagn har en subjekt-entitet, et prædikat, et objekt/værdi og et tidsanker. "Apples omsætning faldt sidste kvartal" nedbrydes til: subjekt Apple Inc. (Wikidata Q312), prædikat revenue change, objekt decrease, tid Q1 2026. Vage forbehold og meninger filtreres fra på dette stadie; de kortslutter resten af pipelinen.
from openai import OpenAI
client = OpenAI()
EXTRACT_PROMPT = """Extract atomic factual claims from the input.
Return JSON: a list of {subject, predicate, object, time_anchor, check_worthy}.
Set check_worthy=false for opinions, predictions, vague hedges.
Input: {input}"""
def extract_claims(text: str) -> list[dict]:
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": EXTRACT_PROMPT.format(input=text)}],
response_format={"type": "json_object"},
temperature=0,
)
import json
return [c for c in json.loads(r.choices[0].message.content)["claims"] if c["check_worthy"]]
Faldgrube: ekstraktionskvalitet dominerer downstream-nøjagtighed. Hvis subjekt-entiteten er tvetydig ("Apple" frugten vs. firmaet), bruger hentningsstadiet budget på at hente irrelevante artikler. Afklar subjekter til kanoniske entiteter på dette stadie i stedet for at sende rå strenge videre — kortlæg hvert subjekt til APITube's numeriske entity.id (afklar via /v1/news/entity eller entities[]-arrayet), så afklaringsindsatsen her betaler sig i stadie 2.
Fase 2 — Hentning af beviser (Live Nyheds API)
Mål: hent en kandidat-bevispulje, der er bred nok til at indeholde tilbagevisende artikler, smal nok til at passe ind i modellens kontekstvindue efter rangering.
De fleste akademiske pipelines henter fra et statisk Wikipedia-dump (FEVER, AVeriTeC). Det er fint til benchmarking, men forkert for nyheder, hvor beviset er den live pressecyklus. Brug en nyheds-API i stedet. Nedenfor forespørges APITube's /v1/news/everything med den opløste entitet, tidsvinduet fra påstandens tidspunkt, og et troværdighedsfilter.
import os, requests
APITUBE = "https://api.apitube.io/v1/news/everything"
HEADERS = {"X-API-Key": os.environ["APITUBE_KEY"]}
def retrieve_evidence(claim: dict, k: int = 50) -> list[dict]:
params = {
"entity.id": claim["subject_entity_id"], # numeric APITube entity id (resolve via /v1/news/entity)
"published_at.start": claim["time_anchor_start"],
"published_at.end": claim["time_anchor_end"],
"language.code": "en",
"per_page": k,
"sort.by": "published_at",
"sort.order": "desc",
}
r = requests.get(APITUBE, params=params, headers=HEADERS, timeout=10)
r.raise_for_status()
articles = r.json().get("results", [])
return [a for a in articles if not a.get("is_duplicate", False)]
Filtrer gentagne kopier ved hjælp af responsens is_duplicate-felt før rangering. Brug entity.id til at søge efter en bestemt person eller organisation i stedet for en tvetydig overskriftsterm som title=apple.
Faldgrube 1: gratis niveau returnerer kun første side og begrænser til 30 anmodninger / 30 minutter. For benchmark-kørsler over 200 claims, batch efter entitet (én entitet kan tjene mange claims) eller opgrader.
Faldgrube 2: tidsmæssige claims kræver et generøst tidsvindue. "Sidste kvartal" → udvid til et 4-måneders vindue (kvartalet plus måneden før og efter), fordi indtjeningsdiskussioner strækker sig over de omkringliggende uger. Stramme vinduer dræber genkaldelse.
Fase 3 — Rangering af beviser
Mål: fra kandidatpuljen af 30-50 artikler, fremhæv de 3-7 dokumenter, der mest sandsynligt indebærer eller tilbageviser claimet, vægtet efter kildekredibilitet.
En simpel, forsvarlig rangordner er en vægtet sum af tre signaler: semantisk lighed mellem påstand og artikel (cosinus på sentence-transformer embeddings), kildekredibilitet (APITube returnerer source.rankings.opr – en objektiv page rank score), og tidsmæssig nærhed (tættere på påstandens tidsanker vinder).
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("all-MiniLM-L6-v2")
def rank_evidence(claim_text: str, articles: list[dict], k: int = 5) -> list[dict]:
claim_emb = model.encode(claim_text, convert_to_tensor=True)
scored = []
for a in articles:
body = (a.get("title", "") + ". " + a.get("description", ""))[:1000]
sim = util.cos_sim(claim_emb, model.encode(body, convert_to_tensor=True)).item()
opr = a.get("source", {}).get("rankings", {}).get("opr", 50) / 100 # 0-1
bias_penalty = 0.85 if a.get("source", {}).get("bias") in ("far_left", "far_right") else 1.0
score = 0.6 * sim + 0.3 * opr + 0.1 * 1.0 # (recency handled by query sort)
score *= bias_penalty
scored.append((score, a))
scored.sort(key=lambda x: x[0], reverse=True)
return [a for _, a in scored[:k]]
source.bias-straffen er en blød nedgradering, ikke et hårdt filter — udgivelser med perifer bias vises stadig, hvis de er de eneste, der rapporterer, men de dominerer ikke top-k. Hold vægtene justerbare, og genbesøg dem efter benchmark-fasen; den rette blanding er forskellig for politiske vs. finansielle vs. videnskabelige påstande.
Fase 4 — NLI Dom
Mål: givet claimet og rangerede beviser, output en af SUPPORT, REFUTE, NEI (Ikke nok information) eller MIXED med en kalibreret konfidens.
To produktionsklare tilgange eksisterer: et finjusteret NLI-hoved (DeBERTa-v3-large finjusteret på FEVER + AVeriTeC) eller en zero-shot prompt mod en instruktions-tunet LLM. Nedenfor er LLM-versionen, fordi den er billigere at vedligeholde og tilpasser sig nye domæner uden genoptræning.
NLI_PROMPT = """You are a fact-checker. Given a claim and {n} evidence excerpts,
output ONE of: SUPPORT, REFUTE, NEI, MIXED.
Rules:
- SUPPORT only if at least one evidence excerpt directly entails the claim.
- REFUTE only if at least one evidence excerpt directly contradicts the claim.
- MIXED if evidence both supports and refutes.
- NEI if no evidence is decisive.
- Cite which evidence indices (1-based) drove the verdict.
Claim: {claim}
Evidence:
{evidence_block}
Output JSON: {{"verdict": str, "confidence": 0-1, "citations": [int]}}"""
def nli_verdict(claim_text: str, evidence: list[dict]) -> dict:
block = "\n".join(
f"[{i+1}] {a['title']}. {a.get('description','')[:300]}"
for i, a in enumerate(evidence)
)
r = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user",
"content": NLI_PROMPT.format(n=len(evidence), claim=claim_text, evidence_block=block)}],
response_format={"type": "json_object"},
temperature=0,
)
import json
return json.loads(r.choices[0].message.content)
Brug en temperatur på 0, når du sammenligner domme. Selv ved 0 kan en model citere et manglende bevispunkt. Tjek hver citationsindeks mod [1, len(evidence)]. Hvis et indeks er ugyldigt, prøv igen eller nedgrader resultatet til NEI.
Fase 5 — Begrundelse
Mål: producer et menneskelæsbart afsnit, der forklarer dommen, med inline citater til bevis-URL'erne.
En god begrundelse besvarer tre spørgsmål: hvad dommen er, hvilke kilder der driver den, og hvilken restusikkerhed der forbliver. Strukturen betyder mere end prosaen – downstream-forbrugere (redaktører, slutbrugere, revisionslogfiler) parser citationsmønsteret.
def justify(claim_text: str, evidence: list[dict], verdict: dict) -> str:
cited = [evidence[i-1] for i in verdict["citations"] if 1 <= i <= len(evidence)]
prompt = f"""Claim: {claim_text}
Verdict: {verdict['verdict']} (confidence {verdict['confidence']:.2f})
Sources used:
{chr(10).join(f"- {a['title']} ({a['href']})" for a in cited)}
Write a 2-sentence justification with inline [1], [2] citations to the sources."""
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
return r.choices[0].message.content
For revisionsklar applikationer (nyhedsrumsintegration, compliance), fastgør de citerede URL'er på dette trin og skriv dem til din revisionslog sammen med tidsstemplet og de anvendte modelversioner. Begrundelser uden vedvarende kilde-URL'er kan ikke verificeres senere.
End-to-End Lim
def fact_check(text: str) -> list[dict]:
results = []
for claim in extract_claims(text):
evidence = retrieve_evidence(claim)
ranked = rank_evidence(claim["claim_text"], evidence, k=5)
verdict = nli_verdict(claim["claim_text"], ranked)
verdict["justification"] = justify(claim["claim_text"], ranked, verdict)
verdict["sources"] = [a["href"] for a in ranked]
results.append({"claim": claim, **verdict})
return results
Evaluer dit eget kravsæt
Test workflowet på påstande med menneskeligt tildelte domme. Mål præcision, genkaldelse, citationsnøjagtighed, svartid og omkostninger. Brug påstande fra det domæne, din applikation vil dække.
Tjek, hvilke fejl der fører til ukorrekte domme eller manglende beviser. Sammenlign modelvalg og indstillinger på det samme sæt, før du stoler på resultaterne.
Fejltilstands-taksonomi
Hver faktatjek-pipeline fejler på karakteristiske måder. At navngive tilstandene er halvdelen af løsningen.
1. Numerisk drift
Claim: "Inflationen ramte 9.1% i juni 2022."
Pipen henter artikler, der citerer 9.1%, men verificerer ikke enheden (årlig vs. månedlig), geografien (US-CPI vs. EU-HICP) eller den præcise måned. Dommen kommer tilbage SUPPORT for et lignende tal, der faktisk er anderledes. Løsning: udvid ekstraktionen til at fange enheder og geografisk omfang som påkrævede felter; afvis beviser, der ikke matcher.
2. Tidsmæssige påstande (var vs. er)
Påstand: "Tesla er den største elbilproducent målt på markedsværdi."
En artikel fra 2021 kan beskrive en anden situation end en, der er udgivet i 2026. At blande dem kan give en MIXED-dom, når brugeren har brug for det aktuelle svar. Ved ekstraktionstidspunkt, sæt time_anchor til "nu" for nutidige påstande og begræns hentning til de sidste 30 dage.
3. Attributionspåstande
Claim: "Sam Altman sagde GPT-5 vil ankomme i 2026."
Bevis er et tweet fra nogen, der citerer Altman — ikke Altman direkte. Pipeline kan ikke se det på overfladen. Løsning: NLI prompt skal kræve bevis for at tilskrive udsagnet til det navngivne emne, ikke kun nævne dem; flag indirekte tilskrivninger som NEI.
4. Mening forvekslet med fakta
Claim: "Bitcoin vil ramme $200k inden årets udgang."
Dette er en forudsigelse, ikke et faktum, der kan verificeres. check_worthy=false-filteret i ekstraktionsfasen håndterer dette — verificer, at det er aktiveret. Løsning: log droppede påstande og gennemgå falsk-negative-raten; juster prompten, hvis forudsigelser slipper igennem.
5. Vage forbehold
Claim: "Mange eksperter mener..."
Hedgen kan ikke verificeres alene. NLI returnerer NEI, men downstream-forbrugere kan fortolke NEI som "vi ved det endnu ikke" snarere end "dette kan ikke verificeres som design." Løsning: tilføj en femte domsklasse UNCHECKABLE forskellig fra NEI, eller vis et is_checkable flag.
Hvornår du ikke behøver hele pipelinen
Under ~10K påstande/dag med ikke-modstridende input, vinder et enkelt LLM-kald, der bruger værktøjer, normalt på enkelhed og er konkurrencedygtig på nøjagtighed.
TOOLS = [{
"type": "function",
"function": {
"name": "search_news",
"description": "Search live news for evidence. Args: query, start_date, end_date.",
"parameters": {"type": "object", "properties": {
"query": {"type": "string"},
"start_date": {"type": "string"},
"end_date": {"type": "string"},
}}
}
}]
def fact_check_oneshot(claim: str) -> dict:
r = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": f"Fact-check: {claim}. Use the tool to gather evidence, cite sources."}],
tools=TOOLS,
)
# ...handle tool calls, loop until final response...
Sammenlign et enkelt modelkald og en flertrins-workflow på de samme mærkede påstande. Mål nøjagtighed, responstid og omkostninger, før du vælger. Tabellen nedenfor sammenligner, hvordan tilgangene fungerer.
| Faktor | One-shot LLM | Fuld pipeline |
|---|---|---|
| Auditabilitet | Svag – citationer inde i modeloutput | Stærk — fastgjorte logs pr. trin |
| Adversarial robusthed | Lav — modellen kan kapres af vildledende beviser | Højere — ranker filtrerer marginale kilder |
| Gennemstrømningsloft | LLM-udbyderens grænser | Horisontalt skalerbar pr. trin |
Vælg one-shot, hvis: du er under 1K krav/dag, dit inputdomæne er harmløst (ingen modstridende misinformationkampagner), og dine revisorer ikke har brug for fastlåste logfiler pr. trin. Vælg den fulde pipeline, hvis: gennemløbet overstiger 10K krav/dag, du står over for modstridende input, eller du sender til en reguleret kontekst, hvor hver dom kræver et revisionsspor.
Ofte stillede spørgsmål
Hvordan fungerer AI-faktatjek?
AI-faktatjek fungerer ved at nedbryde en påstand til strukturerede atomare fakta, indhente relevante beviser fra et korpus (Wikipedia eller live nyheder), rangere disse beviser efter semantisk relevans og kildekredibilitet, køre en model for naturlig sproglig inferens for at bestemme, om beviserne understøtter eller afviser påstanden, og generere en begrundelse med citater. Nøjagtigheden afhænger af hvert trin; et svagt indhentningstrin vil kollapse resten af pipelinen.
Hvad er en faktatjek-pipeline?
En faktatjek-pipeline er en sekvens af NLP-komponenter – typisk ekstraktion af påstande, indhentning af beviser, rangering af beviser, naturlig sproglig inferens og generering af begrundelse – der tilsammen verificerer en frit-tekst-påstand mod et korpus af betroede kilder. Hvert trin har målbare input og output, hvilket lader ingeniører udskifte modeller, debugge fejlmodes og auditere domme uafhængigt.
Kan LLM'er faktatjekke nyhedsartikler?
En sprogmodel kan hjælpe med at tjekke nyhedspåstande, når den har relevante beviser. Alene kan den opfinde citater og kan ikke verificere begivenheder ud over dens træningsafslutning. Giv den aktuelle artikler og tjek dommen mod de citerede kilder.
Hvad er claim-ekstraktion i NLP?
Påstandsekstraktion er NLP-opgaven med at identificere faktuelle udsagn, der er værd at tjekke, inden for et længere tekststykke og konvertere hver enkelt til en atomar, verificerbar form med strukturerede felter: entitet, prædikat, objekt eller værdi og tidsanker. Moderne systemer bruger instruktions-tunede LLM'er med begrænset JSON-output, der filtrerer meninger, forudsigelser og vage forbehold fra, som ikke er verificerbare.
Hvordan hentes beviser til faktatjek?
Bevis hentes ved at forespørge et korpus — normalt Wikipedia til akademiske benchmarks, eller en live nyheds-API til produktionsfaktatjek — ved hjælp af kravets opløste enheder og tidsvindue som filtre. APITube og lignende API'er eksponerer entity.id, published_at og kilde-kredibilitetsfiltre, der lader hentningstrinnet returnere en lille, højpræcisions kandidatpulje, som en downstream-rangering derefter indsnævrer til 3-7 beviseksempler til NLI-fasen.
Prøv APITube Gratis
Pipen ovenfor bruger APITube som live-nyhedsbeviskilde. Gratis-niveauet returnerer 30 anmodninger pr. 30 minutter mod /v1/news/everything – nok til at validere hentning mod dit eget kravsæt, før du forpligter dig. Prøv APITube gratis på apitube.io; numeriske enheds-ID'er (med Wikidata links under entities[].links.wikidata), kilde-bias-etiketter og kilde-troværdighedsscore returneres på hver artikel uden separate berigelsesanmodninger.
Ressourcer
- APITube—apitube.io—prøv det gratis, sentiment og entiteter inkluderet på hver artikel
- Dokumentation—docs.apitube.io—endpoints, parametre, svarsstruktur, integrationer
- Prissætning—apitube.io/pricing—alle niveauer
- APITube blog—apitube.io/blog—flere guides og sammenligninger


