File size: 5,121 Bytes
7e3f910 ad1f162 7e3f910 ad1f162 7e3f910 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | """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 # noqa: F401 — UTF-8 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] (commence par r_), object = reste
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()
# Exit code basé sur les contradictions trouvées (utile en CI).
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())
|