fix(.enrich): SKIP les triplets non-inférés au lieu d'écrire un placeholder
Browse filesÉnorme bug logique de la version précédente : écrire un triplet
dans un .enrich avec « non inférable depuis JDM » comme explication
= soumettre du garbage à JDM ! Le triplet aurait quand même atterri
dans le fichier de soumission.
Maintenant : pour un fichier .enrich, si le triplet n'est PAS dans le
registry de consolidation (= pas passé par consolidate_candidate ou
pas inféré), on le SKIP entièrement. Pas d'écriture, pas de garbage.
Les skipped sont remontés à l'agent via `skipped_no_inference_proof`
+ `skipped_count` + `skipped_note` (« re-passe par consolidate_candidate
avant »). L'agent peut soit ré-inférer, soit retirer ces triplets.
Tests : passé de .enrich à .txt pour les tests d'upload qui ne sont
pas dans un contexte agent (pas de registry peuplé).
183 tests verts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- src/jdm_agent/tools/jdm_tools.py +37 -26
- tests/test_tools.py +3 -3
|
@@ -1792,35 +1792,39 @@ def write_submission_file(
|
|
| 1792 |
# Mode TRIPLETS canonique (que des dicts).
|
| 1793 |
# Pour .enrich : l'explication DOIT venir du registry de
|
| 1794 |
# consolidation (mise là par consolidate_candidate après inférence).
|
| 1795 |
-
#
|
| 1796 |
-
#
|
| 1797 |
-
#
|
| 1798 |
-
#
|
| 1799 |
-
#
|
| 1800 |
-
#
|
| 1801 |
-
# (fallback registry → LLM text).
|
| 1802 |
from jdm_agent.enrich.validators import get_consolidation
|
| 1803 |
is_enrich_file = str(path).lower().endswith(".enrich")
|
| 1804 |
-
|
| 1805 |
-
|
| 1806 |
-
|
| 1807 |
-
)
|
|
|
|
|
|
|
|
|
|
| 1808 |
if from_registry and from_registry.get("explanation"):
|
| 1809 |
-
|
| 1810 |
-
|
| 1811 |
-
|
| 1812 |
-
|
| 1813 |
-
|
| 1814 |
-
|
| 1815 |
-
|
| 1816 |
-
|
| 1817 |
-
|
| 1818 |
-
|
| 1819 |
-
|
| 1820 |
-
|
| 1821 |
-
|
| 1822 |
-
|
| 1823 |
-
|
|
|
|
|
|
|
| 1824 |
out = {
|
| 1825 |
"path": path, "count": n,
|
| 1826 |
"lines": [
|
|
@@ -1830,6 +1834,13 @@ def write_submission_file(
|
|
| 1830 |
],
|
| 1831 |
"mode": "triplets",
|
| 1832 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1833 |
|
| 1834 |
if upload:
|
| 1835 |
from jdm_agent.enrich.uploader import submit_to_jdm
|
|
|
|
| 1792 |
# Mode TRIPLETS canonique (que des dicts).
|
| 1793 |
# Pour .enrich : l'explication DOIT venir du registry de
|
| 1794 |
# consolidation (mise là par consolidate_candidate après inférence).
|
| 1795 |
+
# Si le triplet N'EST PAS dans le registry → on le SKIP entièrement.
|
| 1796 |
+
# Un .enrich ne doit contenir QUE des triplets prouvés par
|
| 1797 |
+
# inférence — pas de garbage à JDM. Les skipped sont remontés
|
| 1798 |
+
# dans `skipped_no_inference_proof` pour que l'agent les voie
|
| 1799 |
+
# et puisse soit les ré-inférer, soit les retirer de sa pile.
|
| 1800 |
+
# Pour .audit / .err / .stat (si schéma triplet) : texte libre OK.
|
|
|
|
| 1801 |
from jdm_agent.enrich.validators import get_consolidation
|
| 1802 |
is_enrich_file = str(path).lower().endswith(".enrich")
|
| 1803 |
+
cands: list[Candidate] = []
|
| 1804 |
+
skipped_no_proof: list[dict] = []
|
| 1805 |
+
for t in dict_items:
|
| 1806 |
+
term_v = str(t.get("term") or "")
|
| 1807 |
+
rel_v = str(t.get("relation") or "")
|
| 1808 |
+
tgt_v = str(t.get("target") or "")
|
| 1809 |
+
from_registry = get_consolidation(term_v, rel_v, tgt_v)
|
| 1810 |
if from_registry and from_registry.get("explanation"):
|
| 1811 |
+
explanation = from_registry["explanation"]
|
| 1812 |
+
elif is_enrich_file:
|
| 1813 |
+
# Pas de preuve d'inférence pour un .enrich → SKIP.
|
| 1814 |
+
skipped_no_proof.append({
|
| 1815 |
+
"term": term_v, "relation": rel_v, "target": tgt_v,
|
| 1816 |
+
})
|
| 1817 |
+
continue
|
| 1818 |
+
else:
|
| 1819 |
+
explanation = str(t.get("explanation") or "")
|
| 1820 |
+
cands.append(Candidate(
|
| 1821 |
+
term=term_v, relation=rel_v, target=tgt_v,
|
| 1822 |
+
annotation=str(t.get("annotation") or ""),
|
| 1823 |
+
consolidation_explanation=explanation,
|
| 1824 |
+
confidence=0.7, source="agent",
|
| 1825 |
+
validation_status="ok", consolidation_status="consolidated",
|
| 1826 |
+
))
|
| 1827 |
+
n = _write_sub(path, cands, client=c) if cands else 0
|
| 1828 |
out = {
|
| 1829 |
"path": path, "count": n,
|
| 1830 |
"lines": [
|
|
|
|
| 1834 |
],
|
| 1835 |
"mode": "triplets",
|
| 1836 |
}
|
| 1837 |
+
if skipped_no_proof:
|
| 1838 |
+
out["skipped_no_inference_proof"] = skipped_no_proof
|
| 1839 |
+
out["skipped_count"] = len(skipped_no_proof)
|
| 1840 |
+
out["skipped_note"] = (
|
| 1841 |
+
"Triplets non écrits car absents du registry d'inférence. "
|
| 1842 |
+
"Re-passe-les par consolidate_candidate avant write_submission_file."
|
| 1843 |
+
)
|
| 1844 |
|
| 1845 |
if upload:
|
| 1846 |
from jdm_agent.enrich.uploader import submit_to_jdm
|
|
@@ -381,7 +381,7 @@ def test_write_submission_file_local_only_default(tmp_path, monkeypatch):
|
|
| 381 |
"term": "chat", "relation": "r_isa", "target": "mammifère",
|
| 382 |
"annotation": "constitutif", "explanation": "trivialement",
|
| 383 |
}],
|
| 384 |
-
"path": str(tmp_path / "sub.
|
| 385 |
})
|
| 386 |
assert out["count"] == 1
|
| 387 |
assert "upload" not in out # pas tenté
|
|
@@ -402,7 +402,7 @@ def test_write_submission_file_with_upload_success(tmp_path, monkeypatch):
|
|
| 402 |
"term": "chat", "relation": "r_isa", "target": "mammifère",
|
| 403 |
"annotation": "", "explanation": "trivialement",
|
| 404 |
}],
|
| 405 |
-
"path": str(tmp_path / "sub.
|
| 406 |
"upload": True,
|
| 407 |
"model_name": "claude-sonnet-4-7",
|
| 408 |
"api_key": "explicit-key",
|
|
@@ -426,7 +426,7 @@ def test_write_submission_file_upload_without_api_key(tmp_path, monkeypatch):
|
|
| 426 |
"term": "chat", "relation": "r_isa", "target": "mammifère",
|
| 427 |
"annotation": "", "explanation": "trivialement",
|
| 428 |
}],
|
| 429 |
-
"path": str(tmp_path / "sub.
|
| 430 |
"upload": True,
|
| 431 |
"model_name": "claude-haiku",
|
| 432 |
})
|
|
|
|
| 381 |
"term": "chat", "relation": "r_isa", "target": "mammifère",
|
| 382 |
"annotation": "constitutif", "explanation": "trivialement",
|
| 383 |
}],
|
| 384 |
+
"path": str(tmp_path / "sub.txt"),
|
| 385 |
})
|
| 386 |
assert out["count"] == 1
|
| 387 |
assert "upload" not in out # pas tenté
|
|
|
|
| 402 |
"term": "chat", "relation": "r_isa", "target": "mammifère",
|
| 403 |
"annotation": "", "explanation": "trivialement",
|
| 404 |
}],
|
| 405 |
+
"path": str(tmp_path / "sub.txt"),
|
| 406 |
"upload": True,
|
| 407 |
"model_name": "claude-sonnet-4-7",
|
| 408 |
"api_key": "explicit-key",
|
|
|
|
| 426 |
"term": "chat", "relation": "r_isa", "target": "mammifère",
|
| 427 |
"annotation": "", "explanation": "trivialement",
|
| 428 |
}],
|
| 429 |
+
"path": str(tmp_path / "sub.txt"),
|
| 430 |
"upload": True,
|
| 431 |
"model_name": "claude-haiku",
|
| 432 |
})
|