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>
202 lines
8.0 KiB
Python
202 lines
8.0 KiB
Python
# 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()]
|