feat: timings granulaires appels LLM (RAG, DAS, DP, QC)
Ajoute des mesures time.monotonic() autour de chaque appel LLM dans
rag_search.py : enrich_diagnostic, enrich_acte, extract_das_llm.
Format de log : logger.info("⏱ [RAG-DP] 14.2s — texte")
Découpe enrich_dossier() en 2 fonctions exportées :
- enrich_dp() : enrichit seulement le DP (parallélisable)
- enrich_das_and_actes() : enrichit DAS + actes en parallèle
L'ancienne enrich_dossier() reste comme wrapper rétro-compatible.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
from ..config import (
|
||||
@@ -610,7 +611,9 @@ def enrich_diagnostic(
|
||||
|
||||
Modifie le diagnostic en place. Fallback gracieux si FAISS ou Ollama échouent.
|
||||
"""
|
||||
t0 = time.monotonic()
|
||||
diag_type = "dp" if est_dp else "das"
|
||||
label = f"RAG-{diag_type.upper()}"
|
||||
|
||||
# 1. Vérifier le cache
|
||||
cached = cache.get(diagnostic.texte, diag_type) if cache else None
|
||||
@@ -626,6 +629,8 @@ def enrich_diagnostic(
|
||||
if cached is not None:
|
||||
logger.info("Cache hit (sans sources FAISS) pour %s : « %s »", diag_type.upper(), diagnostic.texte)
|
||||
_apply_llm_result_diagnostic(diagnostic, cached)
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.info("⏱ [%s] %.1fs — %s (no FAISS)", label, elapsed, diagnostic.texte[:60])
|
||||
return
|
||||
|
||||
# 3. Stocker les sources RAG
|
||||
@@ -643,11 +648,15 @@ def enrich_diagnostic(
|
||||
if cached is not None:
|
||||
logger.info("Cache hit pour %s : « %s »", diag_type.upper(), diagnostic.texte)
|
||||
_apply_llm_result_diagnostic(diagnostic, cached)
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.info("⏱ [%s] %.1fs — %s (cache hit)", label, elapsed, diagnostic.texte[:60])
|
||||
return
|
||||
|
||||
# 5. Appel Ollama pour justification avec raisonnement structuré
|
||||
prompt = _build_prompt(diagnostic.texte, sources, contexte, est_dp=est_dp)
|
||||
t_llm = time.monotonic()
|
||||
llm_result = _call_ollama(prompt)
|
||||
llm_elapsed = time.monotonic() - t_llm
|
||||
|
||||
if llm_result:
|
||||
_apply_llm_result_diagnostic(diagnostic, llm_result)
|
||||
@@ -656,6 +665,9 @@ def enrich_diagnostic(
|
||||
else:
|
||||
logger.info("Ollama non disponible — sources FAISS conservées sans justification LLM")
|
||||
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.info("⏱ [%s] %.1fs (LLM %.1fs) — %s", label, elapsed, llm_elapsed, diagnostic.texte[:60])
|
||||
|
||||
|
||||
def _apply_llm_result_acte(acte: ActeCCAM, llm_result: dict) -> None:
|
||||
"""Applique un résultat LLM (frais ou caché) à un ActeCCAM."""
|
||||
@@ -687,6 +699,8 @@ def enrich_acte(acte: ActeCCAM, contexte: dict, cache: OllamaCache | None = None
|
||||
|
||||
Modifie l'acte en place. Fallback gracieux si FAISS ou Ollama échouent.
|
||||
"""
|
||||
t0 = time.monotonic()
|
||||
|
||||
# 1. Vérifier le cache
|
||||
cached = cache.get(acte.texte, "ccam") if cache else None
|
||||
|
||||
@@ -712,11 +726,15 @@ def enrich_acte(acte: ActeCCAM, contexte: dict, cache: OllamaCache | None = None
|
||||
if cached is not None:
|
||||
logger.info("Cache hit pour CCAM : « %s »", acte.texte)
|
||||
_apply_llm_result_acte(acte, cached)
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.info("⏱ [RAG-CCAM] %.1fs — %s (cache hit)", elapsed, acte.texte[:60])
|
||||
return
|
||||
|
||||
# 5. Appel Ollama pour justification avec raisonnement structuré
|
||||
prompt = _build_prompt_ccam(acte.texte, sources, contexte)
|
||||
t_llm = time.monotonic()
|
||||
llm_result = _call_ollama(prompt)
|
||||
llm_elapsed = time.monotonic() - t_llm
|
||||
|
||||
if llm_result:
|
||||
_apply_llm_result_acte(acte, llm_result)
|
||||
@@ -725,6 +743,9 @@ def enrich_acte(acte: ActeCCAM, contexte: dict, cache: OllamaCache | None = None
|
||||
else:
|
||||
logger.info("Ollama non disponible — sources FAISS CCAM conservées sans justification LLM")
|
||||
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.info("⏱ [RAG-CCAM] %.1fs (LLM %.1fs) — %s", elapsed, llm_elapsed, acte.texte[:60])
|
||||
|
||||
|
||||
def _smart_truncate(text: str, max_chars: int = 6000) -> str:
|
||||
"""Troncature intelligente : garde le début + les sections finales importantes.
|
||||
@@ -790,6 +811,8 @@ def extract_das_llm(
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
t0 = time.monotonic()
|
||||
|
||||
# Clé de cache basée sur le hash du texte
|
||||
text_hash = hashlib.md5(text[:4000].encode()).hexdigest()[:16]
|
||||
cache_key_text = f"das_extract::{text_hash}"
|
||||
@@ -798,15 +821,19 @@ def extract_das_llm(
|
||||
if cache is not None:
|
||||
cached = cache.get(cache_key_text, "das_llm")
|
||||
if cached is not None:
|
||||
logger.info("Cache hit pour extraction DAS LLM")
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.info("⏱ [DAS-LLM] %.1fs — cache hit", elapsed)
|
||||
return cached.get("diagnostics_supplementaires", [])
|
||||
|
||||
# Construire le prompt et appeler Ollama
|
||||
prompt = _build_prompt_das_extraction(text, contexte, existing_das, dp_texte)
|
||||
t_llm = time.monotonic()
|
||||
result = call_ollama(prompt, temperature=0.1, max_tokens=2000, role="coding")
|
||||
llm_elapsed = time.monotonic() - t_llm
|
||||
|
||||
if result is None:
|
||||
logger.warning("Extraction DAS LLM : Ollama non disponible")
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.warning("⏱ [DAS-LLM] %.1fs — Ollama non disponible", elapsed)
|
||||
return []
|
||||
|
||||
das_list = result.get("diagnostics_supplementaires", [])
|
||||
@@ -818,25 +845,52 @@ def extract_das_llm(
|
||||
if cache is not None:
|
||||
cache.put(cache_key_text, "das_llm", result)
|
||||
|
||||
logger.info("Extraction DAS LLM : %d diagnostics supplémentaires détectés", len(das_list))
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.info("⏱ [DAS-LLM] %.1fs (LLM %.1fs) — %d diagnostics détectés", elapsed, llm_elapsed, len(das_list))
|
||||
return das_list
|
||||
|
||||
|
||||
def enrich_dossier(dossier: DossierMedical) -> None:
|
||||
"""Enrichit le DP et tous les DAS d'un dossier via le RAG.
|
||||
def enrich_dp(dossier: DossierMedical, cache: OllamaCache | None = None) -> None:
|
||||
"""Enrichit SEULEMENT le DP d'un dossier via le RAG (Phase 1).
|
||||
|
||||
Utilise un cache persistant et parallélise les appels Ollama
|
||||
pour les DAS et actes CCAM (max_workers = OLLAMA_MAX_PARALLEL).
|
||||
Peut être exécuté en parallèle avec d'autres tâches (DAS LLM, DP selector)
|
||||
car il ne dépend que du DP existant.
|
||||
|
||||
Args:
|
||||
dossier: Dossier médical à enrichir (modifié en place).
|
||||
cache: Cache Ollama optionnel (créé si None).
|
||||
"""
|
||||
t0 = time.monotonic()
|
||||
if cache is None:
|
||||
cache = OllamaCache(OLLAMA_CACHE_PATH, get_model("coding"))
|
||||
|
||||
contexte = build_enriched_context(dossier)
|
||||
|
||||
# Phase 1 : DP seul (le contexte DAS en dépend)
|
||||
if dossier.diagnostic_principal:
|
||||
logger.info("RAG enrichissement DP : %s", dossier.diagnostic_principal.texte)
|
||||
enrich_diagnostic(dossier.diagnostic_principal, contexte, est_dp=True, cache=cache)
|
||||
|
||||
cache.save()
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.info("⏱ [RAG-DP-PHASE] %.1fs — enrichissement DP terminé", elapsed)
|
||||
|
||||
|
||||
def enrich_das_and_actes(dossier: DossierMedical, cache: OllamaCache | None = None) -> None:
|
||||
"""Enrichit les DAS et actes CCAM d'un dossier via le RAG (Phase 2).
|
||||
|
||||
Parallélise les appels Ollama (max_workers = OLLAMA_MAX_PARALLEL).
|
||||
Doit être appelé APRES enrich_dp() et après l'ajout des DAS LLM.
|
||||
|
||||
Args:
|
||||
dossier: Dossier médical à enrichir (modifié en place).
|
||||
cache: Cache Ollama optionnel (créé si None).
|
||||
"""
|
||||
t0 = time.monotonic()
|
||||
if cache is None:
|
||||
cache = OllamaCache(OLLAMA_CACHE_PATH, get_model("coding"))
|
||||
|
||||
contexte = build_enriched_context(dossier)
|
||||
|
||||
# Mettre à jour le contexte avec le DP pour les DAS
|
||||
if dossier.diagnostic_principal:
|
||||
contexte["dp_texte"] = dossier.diagnostic_principal.texte
|
||||
@@ -846,7 +900,6 @@ def enrich_dossier(dossier: DossierMedical) -> None:
|
||||
if d.cim10_suggestion
|
||||
]
|
||||
|
||||
# Phase 2 : DAS + Actes en parallèle
|
||||
das_list = dossier.diagnostics_associes
|
||||
actes_list = dossier.actes_ccam
|
||||
|
||||
@@ -863,3 +916,18 @@ def enrich_dossier(dossier: DossierMedical) -> None:
|
||||
f.result() # propage les exceptions
|
||||
|
||||
cache.save()
|
||||
elapsed = time.monotonic() - t0
|
||||
n_das = len(das_list) if das_list else 0
|
||||
n_actes = len(actes_list) if actes_list else 0
|
||||
logger.info("⏱ [RAG-DAS-PHASE] %.1fs — %d DAS + %d actes enrichis", elapsed, n_das, n_actes)
|
||||
|
||||
|
||||
def enrich_dossier(dossier: DossierMedical) -> None:
|
||||
"""Enrichit le DP et tous les DAS d'un dossier via le RAG.
|
||||
|
||||
Wrapper rétro-compatible qui appelle enrich_dp() puis enrich_das_and_actes().
|
||||
Utilise un cache persistant partagé entre les deux phases.
|
||||
"""
|
||||
cache = OllamaCache(OLLAMA_CACHE_PATH, get_model("coding"))
|
||||
enrich_dp(dossier, cache=cache)
|
||||
enrich_das_and_actes(dossier, cache=cache)
|
||||
|
||||
Reference in New Issue
Block a user