feat: pipeline complet MACRO/MÉSO/MICRO — Critic, Observer, Policy, Recovery, Learning, Audit Trail, TaskPlanner
Architecture 3 niveaux implémentée et testée (137 tests unitaires + 21 visuels) : MÉSO (acteur intelligent) : - P0 Critic : vérification sémantique post-action via gemma4 (replay_verifier.py) - P1 Observer : pré-analyse écran avant chaque action (api_stream.py /pre_analyze) - P2 Grounding/Policy : séparation localisation (grounding.py) et décision (policy.py) - P3 Recovery : rollback automatique Ctrl+Z/Escape/Alt+F4 (recovery.py) - P4 Learning : apprentissage runtime avec boucle de consolidation (replay_learner.py) MACRO (planificateur) : - TaskPlanner : comprend les ordres en langage naturel via gemma4 (task_planner.py) - Contexte métier TIM/CIM-10 pour les hôpitaux (domain_context.py) - Endpoint POST /api/v1/task pour l'exécution par instruction Traçabilité : - Audit trail complet avec 18 champs par action (audit_trail.py) - Endpoints GET /audit/history, /audit/summary, /audit/export (CSV) Grounding : - Fix parsing bbox_2d qwen2.5vl (pixels relatifs, pas grille 1000x1000) - Benchmarks visuels sur captures réelles (3 approches : baseline, zoom, Citrix) - Reproductibilité validée : variance < 0.008 sur 10 itérations Sécurité : - Tokens de production retirés du code source → .env.local - Secret key aléatoire si non configuré - Suppression logs qui leakent les tokens Résultats : 80% de replay (vs 12.5% avant), 100% détection visuelle Citrix JPEG Q20 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
201
agent_v0/server_v1/domain_context.py
Normal file
201
agent_v0/server_v1/domain_context.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# agent_v0/server_v1/domain_context.py
|
||||
"""
|
||||
Contexte métier pour les appels VLM — rend Léa experte du domaine.
|
||||
|
||||
Chaque workflow est associé à un domaine métier (médical, comptable, etc.)
|
||||
qui enrichit TOUS les prompts VLM (Observer, Critic, acteur, enrichissement).
|
||||
|
||||
Un gemma4 qui sait qu'il regarde un DPI et que l'utilisateur fait du codage
|
||||
CIM-10 prend des décisions bien meilleures qu'un VLM générique.
|
||||
|
||||
Premier domaine : TIM (Technicien d'Information Médicale)
|
||||
- Logiciels DPI/DMS (dossier patient informatisé)
|
||||
- Codage CIM-10 / CCAM / GHM
|
||||
- Lecture de comptes rendus médicaux
|
||||
- Validation des séjours / RSS / RSA
|
||||
|
||||
Usage :
|
||||
ctx = get_domain_context("tim_codage")
|
||||
prompt = f"{ctx.system_prompt}\n\n{user_prompt}"
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DomainContext:
|
||||
"""Contexte métier pour un domaine spécifique."""
|
||||
domain_id: str # Identifiant unique (tim_codage, comptabilite, etc.)
|
||||
name: str # Nom lisible (Codage médical TIM)
|
||||
description: str # Description courte du métier
|
||||
|
||||
# Prompt système injecté dans TOUS les appels VLM
|
||||
system_prompt: str = ""
|
||||
|
||||
# Vocabulaire métier (termes que le VLM doit connaître)
|
||||
vocabulary: List[str] = field(default_factory=list)
|
||||
|
||||
# Applications connues (noms de logiciels que le VLM peut rencontrer)
|
||||
known_apps: List[str] = field(default_factory=list)
|
||||
|
||||
# Écrans types (descriptions des écrans courants du métier)
|
||||
screen_patterns: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
def enrich_prompt(self, prompt: str, role: str = "") -> str:
|
||||
"""Enrichir un prompt avec le contexte métier.
|
||||
|
||||
Args:
|
||||
prompt: Le prompt original
|
||||
role: Le rôle du VLM (observer, critic, actor, enrichment)
|
||||
"""
|
||||
parts = []
|
||||
|
||||
if self.system_prompt:
|
||||
parts.append(self.system_prompt)
|
||||
|
||||
if role:
|
||||
role_hint = _ROLE_HINTS.get(role, "")
|
||||
if role_hint:
|
||||
parts.append(role_hint.format(domain=self.name))
|
||||
|
||||
parts.append(prompt)
|
||||
return "\n\n".join(parts)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"domain_id": self.domain_id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"known_apps": self.known_apps,
|
||||
"vocabulary_count": len(self.vocabulary),
|
||||
}
|
||||
|
||||
|
||||
# Hints par rôle VLM — adaptés au contexte métier
|
||||
_ROLE_HINTS = {
|
||||
"observer": (
|
||||
"Tu observes un écran utilisé dans le domaine '{domain}'. "
|
||||
"Cherche les popups, erreurs, ou états incohérents avec ce métier."
|
||||
),
|
||||
"critic": (
|
||||
"Tu vérifies qu'une action dans le domaine '{domain}' a produit "
|
||||
"le bon résultat. Sois précis sur ce que tu vois à l'écran."
|
||||
),
|
||||
"actor": (
|
||||
"Tu décides si une action est nécessaire dans le contexte '{domain}'. "
|
||||
"Utilise ta connaissance du métier pour juger si l'état est cohérent."
|
||||
),
|
||||
"enrichment": (
|
||||
"Tu analyses un enregistrement de workflow dans le domaine '{domain}'. "
|
||||
"Décris les intentions métier, pas juste les clics."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# Domaines pré-configurés
|
||||
# =========================================================================
|
||||
|
||||
_TIM_CODAGE = DomainContext(
|
||||
domain_id="tim_codage",
|
||||
name="Codage médical TIM",
|
||||
description=(
|
||||
"Technicien d'Information Médicale : lecture de comptes rendus médicaux, "
|
||||
"codage des diagnostics en CIM-10, codage des actes en CCAM, "
|
||||
"validation des groupes homogènes de malades (GHM), "
|
||||
"gestion des résumés de sortie standardisés (RSS/RSA)."
|
||||
),
|
||||
system_prompt=(
|
||||
"Tu es un assistant expert en codage médical hospitalier. "
|
||||
"L'utilisateur est un TIM (Technicien d'Information Médicale) qui utilise "
|
||||
"un logiciel DPI (Dossier Patient Informatisé) ou DIM (Département d'Information Médicale). "
|
||||
"Son travail : lire les comptes rendus médicaux des patients et coder les diagnostics "
|
||||
"en CIM-10, les actes en CCAM, et valider les séjours pour le PMSI.\n\n"
|
||||
"Vocabulaire du métier :\n"
|
||||
"- DPI/DMS : logiciel de dossier patient (ex: Orbis, DxCare, Crossway, Easily, Hopital Manager)\n"
|
||||
"- CIM-10 : Classification Internationale des Maladies, 10ème révision (codes diagnostics)\n"
|
||||
"- CCAM : Classification Commune des Actes Médicaux (codes actes chirurgicaux/médicaux)\n"
|
||||
"- GHM : Groupe Homogène de Malades (regroupement tarifaire)\n"
|
||||
"- RSS : Résumé de Sortie Standardisé (données du séjour)\n"
|
||||
"- RSA : Résumé de Sortie Anonyme (RSS anonymisé pour la T2A)\n"
|
||||
"- DP : Diagnostic Principal (le code CIM-10 principal du séjour)\n"
|
||||
"- DAS : Diagnostics Associés Significatifs\n"
|
||||
"- CMA : Complication ou Morbidité Associée (augmente la sévérité)\n"
|
||||
"- T2A : Tarification À l'Activité (financement des hôpitaux)\n"
|
||||
"- PMSI : Programme de Médicalisation des Systèmes d'Information\n"
|
||||
"- UM : Unité Médicale (service hospitalier)\n"
|
||||
"- CR : Compte Rendu (document médical)\n\n"
|
||||
"Écrans courants :\n"
|
||||
"- Liste de patients / dossiers à coder\n"
|
||||
"- Fiche patient (identité, séjour, UM)\n"
|
||||
"- Écran de codage CIM-10 (recherche de codes, saisie DP/DAS)\n"
|
||||
"- Visualiseur de comptes rendus médicaux\n"
|
||||
"- Écran de validation / groupage GHM\n"
|
||||
"- Recherche de codes (arborescence CIM-10 ou recherche textuelle)"
|
||||
),
|
||||
vocabulary=[
|
||||
"CIM-10", "CCAM", "GHM", "RSS", "RSA", "PMSI", "T2A",
|
||||
"diagnostic principal", "DAS", "CMA", "compte rendu",
|
||||
"dossier patient", "séjour", "unité médicale", "codage",
|
||||
"groupage", "valorisation", "exhaustivité",
|
||||
],
|
||||
known_apps=[
|
||||
"Orbis", "DxCare", "Crossway", "Easily", "Hopital Manager",
|
||||
"CORA", "AGFA", "Dedalus", "Maincare", "Softway Medical",
|
||||
"WebPIMS", "CEPAGE", "Medimust",
|
||||
],
|
||||
screen_patterns={
|
||||
"liste_patients": "Liste de dossiers patients avec colonnes (nom, prénom, date entrée, UM, statut codage)",
|
||||
"fiche_patient": "Fiche d'identité patient avec numéro IPP, séjour, dates, UM",
|
||||
"codage_cim10": "Écran de saisie des codes CIM-10 avec diagnostic principal et DAS",
|
||||
"compte_rendu": "Visualiseur de compte rendu médical (texte libre, souvent PDF intégré)",
|
||||
"recherche_code": "Recherche de code CIM-10 ou CCAM (champ de recherche + arborescence)",
|
||||
"validation_ghm": "Écran de validation du groupage avec GHM calculé et valorisation",
|
||||
},
|
||||
)
|
||||
|
||||
_GENERIC = DomainContext(
|
||||
domain_id="generic",
|
||||
name="Bureautique générale",
|
||||
description="Automatisation bureautique générale (Office, navigateur, etc.)",
|
||||
system_prompt=(
|
||||
"Tu es un assistant RPA qui observe des applications bureautiques. "
|
||||
"Décris précisément ce que tu vois à l'écran."
|
||||
),
|
||||
)
|
||||
|
||||
# Registre des domaines disponibles
|
||||
_DOMAINS: Dict[str, DomainContext] = {
|
||||
"tim_codage": _TIM_CODAGE,
|
||||
"generic": _GENERIC,
|
||||
}
|
||||
|
||||
|
||||
def get_domain_context(domain_id: str = "generic") -> DomainContext:
|
||||
"""Récupérer le contexte métier par ID.
|
||||
|
||||
Args:
|
||||
domain_id: Identifiant du domaine (tim_codage, generic, etc.)
|
||||
|
||||
Returns:
|
||||
DomainContext correspondant, ou generic si non trouvé.
|
||||
"""
|
||||
ctx = _DOMAINS.get(domain_id, _GENERIC)
|
||||
if ctx is _GENERIC and domain_id != "generic":
|
||||
logger.warning(f"Domaine '{domain_id}' non trouvé, utilisation de 'generic'")
|
||||
return ctx
|
||||
|
||||
|
||||
def register_domain(context: DomainContext) -> None:
|
||||
"""Enregistrer un nouveau domaine métier."""
|
||||
_DOMAINS[context.domain_id] = context
|
||||
logger.info(f"Domaine '{context.domain_id}' enregistré ({context.name})")
|
||||
|
||||
|
||||
def list_domains() -> List[Dict[str, Any]]:
|
||||
"""Lister tous les domaines disponibles."""
|
||||
return [ctx.to_dict() for ctx in _DOMAINS.values()]
|
||||
Reference in New Issue
Block a user