| """CLI du fact-checker JDM. |
| |
| Trois modes : |
| 1. Texte → LLM extrait les claims, puis vérification |
| python -m jdm_agent.apps.factcheck --text "La baleine est un poisson." |
| |
| 2. Claim unique directe (sans LLM, instantanée) |
| python -m jdm_agent.apps.factcheck --claim "baleine r_isa poisson" |
| python -m jdm_agent.apps.factcheck --claim "sang r_has_color rouge" |
| |
| 3. Stdin : un claim "subject r_xxx object" par ligne |
| echo "baleine r_isa poisson" | python -m jdm_agent.apps.factcheck --stdin |
| """ |
| from __future__ import annotations |
|
|
| from jdm_agent.apps import _console |
|
|
| import argparse |
| import json |
| import sys |
| from typing import Iterable |
|
|
| from jdm_agent.client import JDMClient |
| from jdm_agent.factcheck import Claim, Report, factcheck, factcheck_claims |
| from jdm_agent.factcheck.models import Status |
|
|
|
|
| GREEN = "\033[92m" |
| RED = "\033[91m" |
| YELLOW = "\033[93m" |
| GRAY = "\033[90m" |
| RESET = "\033[0m" |
|
|
| STATUS_COLOR = { |
| Status.SUPPORTED: GREEN, |
| Status.CONTRADICTED: RED, |
| Status.UNKNOWN: YELLOW, |
| } |
| STATUS_ICON = { |
| Status.SUPPORTED: "✓", |
| Status.CONTRADICTED: "✗", |
| Status.UNKNOWN: "?", |
| } |
|
|
|
|
| def parse_claim_line(line: str) -> Claim: |
| """Parse 'subject r_xxx object' (séparateurs espace OU pipe).""" |
| line = line.strip().replace("|", " ") |
| parts = line.split() |
| if len(parts) < 3: |
| raise ValueError(f"Format invalide : {line!r}. Attendu : 'subject r_xxx object'") |
| |
| subject = parts[0] |
| relation = parts[1] |
| obj = " ".join(parts[2:]) |
| if not relation.startswith("r_"): |
| raise ValueError(f"Le 2e champ doit être une relation JDM (`r_xxx`) : {relation!r}") |
| return Claim(text=line, subject=subject, relation=relation, object=obj) |
|
|
|
|
| def print_report(report: Report, as_json: bool = False) -> None: |
| if as_json: |
| print(report.model_dump_json(indent=2)) |
| return |
|
|
| summary = report.summary() |
| print(f"\n=== Rapport ({summary['total']} claim(s)) ===") |
| print(f" {GREEN}✓ supported{RESET} : {summary['supported']}") |
| print(f" {RED}✗ contradicted{RESET} : {summary['contradicted']}") |
| print(f" {YELLOW}? unknown{RESET} : {summary['unknown']}") |
| print() |
|
|
| for v in report.verdicts: |
| color = STATUS_COLOR[v.status] |
| icon = STATUS_ICON[v.status] |
| print(f"{color}{icon} [{v.status.value.upper()}]{RESET} " |
| f"`{v.claim.subject} | {v.claim.relation} | {v.claim.object}`" |
| f" {GRAY}(conf={v.confidence:.2f}){RESET}") |
| if v.claim.text and v.claim.text != f"{v.claim.subject} {v.claim.relation} {v.claim.object}": |
| print(f" {GRAY}texte : {v.claim.text!r}{RESET}") |
| if v.explanation: |
| print(f" → {v.explanation}") |
| for e in v.evidence_for: |
| print(f" {GREEN}+ {e.source} | {e.relation} | {e.target} (w={e.w:.0f}){RESET}") |
| for e in v.evidence_against: |
| print(f" {RED}- {e.source} | {e.relation} | {e.target} (w={e.w:.0f}){RESET}") |
| print() |
|
|
|
|
| def main() -> int: |
| p = argparse.ArgumentParser(description="Fact-checker JeuxDeMots.") |
| p.add_argument("--text", help="Texte libre à analyser (LLM extrait les claims).") |
| p.add_argument("--claim", action="append", |
| help="Claim direct au format 'subject r_xxx object' (répétable).") |
| p.add_argument("--stdin", action="store_true", |
| help="Lit un claim par ligne sur stdin.") |
| p.add_argument("--provider", default=None, help="LLM provider (pour --text)") |
| p.add_argument("--model", default=None, help="LLM model (pour --text)") |
| p.add_argument("--json", action="store_true", help="Sortie en JSON brut.") |
| args = p.parse_args() |
|
|
| if not (args.text or args.claim or args.stdin): |
| p.print_help() |
| return 1 |
|
|
| client = JDMClient() |
|
|
| if args.text: |
| try: |
| from jdm_agent.tools.llm_factory import get_llm |
| llm = get_llm(provider=args.provider, model=args.model) |
| except Exception as e: |
| print(f"[erreur] init LLM : {e}", file=sys.stderr) |
| return 2 |
| print(f"[extraction] {args.text!r} → claims via LLM…", file=sys.stderr) |
| report = factcheck(args.text, client=client, llm=llm) |
| else: |
| claims_lines: list[str] = [] |
| if args.claim: |
| claims_lines.extend(args.claim) |
| if args.stdin: |
| claims_lines.extend(l for l in sys.stdin if l.strip() and not l.startswith("#")) |
| try: |
| claims = [parse_claim_line(l) for l in claims_lines] |
| except ValueError as e: |
| print(f"[erreur] {e}", file=sys.stderr) |
| return 1 |
| report = factcheck_claims(claims, client=client) |
|
|
| print_report(report, as_json=args.json) |
| client.close() |
|
|
| |
| n_contradicted = sum(1 for v in report.verdicts if v.status == Status.CONTRADICTED) |
| return 0 if n_contradicted == 0 else 3 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|