fix: réparation JSON tronqué + retry 429 + whitelist codes CPAM anti-hallucination
- parse_json_response : réparation JSON tronqué par max_tokens (fermeture auto des structures ouvertes), meilleur stripping des blocs fencés avec texte superflu après la fermeture ``` - call_ollama : retry avec backoff exponentiel (1s/2s/4s) pour les erreurs 429 rate limit, 3 tentatives au lieu de 2 - Validation adversariale : max_tokens 800 → 1500 - Prompt CPAM : whitelist PÉRIMÈTRE DE CODES AUTORISÉS (dossier DP+DAS + UCR) avec interdiction explicite des codes hors périmètre - Tests : 19 tests parse_json/_repair_truncated_json, 6 tests whitelist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -511,6 +511,40 @@ def _build_cpam_prompt(
|
||||
# Définitions CIM-10 déterministes (tous les codes en jeu)
|
||||
definitions_str = _get_cim10_definitions(dossier, controle)
|
||||
|
||||
# Whitelist explicite des codes autorisés (anti-hallucination)
|
||||
_all_codes: list[str] = []
|
||||
if dossier.diagnostic_principal and dossier.diagnostic_principal.cim10_suggestion:
|
||||
_all_codes.append(dossier.diagnostic_principal.cim10_suggestion)
|
||||
for das in dossier.diagnostics_associes:
|
||||
if das.cim10_suggestion:
|
||||
_all_codes.append(das.cim10_suggestion)
|
||||
for field in (controle.dp_ucr, controle.da_ucr, controle.dr_ucr):
|
||||
if field:
|
||||
for raw in re.split(r"[,;\s]+", field.strip()):
|
||||
raw = raw.strip()
|
||||
if raw:
|
||||
_all_codes.append(raw)
|
||||
# Dédupliquer en normalisant
|
||||
_seen_norm: set[str] = set()
|
||||
_unique_codes: list[str] = []
|
||||
for c in _all_codes:
|
||||
norm = normalize_code(c)
|
||||
if norm and norm not in _seen_norm:
|
||||
_seen_norm.add(norm)
|
||||
is_valid, label = validate_code(norm)
|
||||
_unique_codes.append(f"{norm} — {label}" if is_valid and label else norm)
|
||||
if _unique_codes:
|
||||
codes_autorises_str = (
|
||||
"\nPÉRIMÈTRE DE CODES AUTORISÉS (liste EXHAUSTIVE) :\n"
|
||||
+ "\n".join(f" {c}" for c in _unique_codes)
|
||||
+ "\n\nINTERDICTION : Ne mentionne AUCUN code CIM-10 qui ne figure pas "
|
||||
"dans cette liste. Si un code supplémentaire te semble cliniquement "
|
||||
"pertinent, signale-le en toutes lettres dans la conclusion SANS "
|
||||
"citer le code CIM-10."
|
||||
)
|
||||
else:
|
||||
codes_autorises_str = ""
|
||||
|
||||
# Contexte clinique tagué pour le grounding
|
||||
tagged_context, tag_map = _build_tagged_context(dossier)
|
||||
if tagged_context:
|
||||
@@ -591,6 +625,7 @@ def _build_cpam_prompt(
|
||||
decision_ucr=controle.decision_ucr,
|
||||
codes_str=codes_str,
|
||||
definitions_str=definitions_str,
|
||||
codes_autorises_str=codes_autorises_str,
|
||||
sources_text=sources_text,
|
||||
extraction_str=extraction_str,
|
||||
)
|
||||
|
||||
@@ -232,9 +232,9 @@ def _validate_adversarial(
|
||||
)
|
||||
|
||||
logger.debug(" Validation adversariale")
|
||||
result = call_ollama(prompt, temperature=0.0, max_tokens=800, role="validation")
|
||||
result = call_ollama(prompt, temperature=0.0, max_tokens=1500, role="validation")
|
||||
if result is None:
|
||||
result = call_anthropic(prompt, temperature=0.0, max_tokens=800)
|
||||
result = call_anthropic(prompt, temperature=0.0, max_tokens=1500)
|
||||
if result is None:
|
||||
logger.warning(" Validation adversariale échouée — LLM indisponible")
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user