agent-governance

Par github · awesome-copilot

Modèles et techniques pour ajouter des contrôles de gouvernance, de sécurité et de confiance aux systèmes d'agents IA. Utilisez cette skill lorsque : - Vous construisez des agents IA qui appellent des outils externes (APIs, bases de données, systèmes de fichiers) - Vous implémentez des contrôles d'accès basés sur des politiques pour l'utilisation des outils par les agents - Vous ajoutez une classification sémantique des intentions pour détecter les prompts dangereux - Vous créez des systèmes de scoring de confiance pour les workflows multi-agents - Vous construisez des pistes d'audit pour les actions et décisions des agents - Vous appliquez des rate limits, des filtres de contenu ou des restrictions d'outils sur les agents - Vous travaillez avec n'importe quel framework d'agents (PydanticAI, CrewAI, OpenAI Agents, LangChain, AutoGen)

npx skills add https://github.com/github/awesome-copilot --skill agent-governance

Modèles de Gouvernance des Agents

Modèles pour ajouter la sécurité, la confiance et l'application des politiques aux systèmes d'agents IA.

Vue d'ensemble

Les modèles de gouvernance garantissent que les agents IA opèrent dans des limites définies — en contrôlant les outils qu'ils peuvent appeler, le contenu qu'ils peuvent traiter, ce qu'ils peuvent faire, et en maintenant la responsabilité grâce aux pistes d'audit.

Requête utilisateur → Classification d'intention → Vérification de politique → Exécution d'outil → Journal d'audit
                            ↓                            ↓                          ↓
                    Détection de menace         Autoriser/Refuser      Mise à jour de confiance

Quand utiliser

  • Agents avec accès aux outils : Tout agent qui appelle des outils externes (APIs, bases de données, commandes shell)
  • Systèmes multi-agents : Les agents qui délèguent à d'autres agents ont besoin de limites de confiance
  • Déploiements en production : Exigences de conformité, d'audit et de sécurité
  • Opérations sensibles : Transactions financières, accès aux données, gestion d'infrastructure

Modèle 1 : Politique de Gouvernance

Définissez ce qu'un agent est autorisé à faire comme un objet de politique composable et sérialisable.

from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
import re

class PolicyAction(Enum):
    ALLOW = "allow"
    DENY = "deny"
    REVIEW = "review"  # drapeau pour examen humain

@dataclass
class GovernancePolicy:
    """Politique déclarative contrôlant le comportement de l'agent."""
    name: str
    allowed_tools: list[str] = field(default_factory=list)       # liste blanche
    blocked_tools: list[str] = field(default_factory=list)       # liste noire
    blocked_patterns: list[str] = field(default_factory=list)    # filtres de contenu
    max_calls_per_request: int = 100                             # limite de débit
    require_human_approval: list[str] = field(default_factory=list)  # outils nécessitant une approbation

    def check_tool(self, tool_name: str) -> PolicyAction:
        """Vérifier si un outil est autorisé par cette politique."""
        if tool_name in self.blocked_tools:
            return PolicyAction.DENY
        if tool_name in self.require_human_approval:
            return PolicyAction.REVIEW
        if self.allowed_tools and tool_name not in self.allowed_tools:
            return PolicyAction.DENY
        return PolicyAction.ALLOW

    def check_content(self, content: str) -> Optional[str]:
        """Vérifier le contenu par rapport aux modèles bloqués. Retourne le modèle correspondant ou None."""
        for pattern in self.blocked_patterns:
            if re.search(pattern, content, re.IGNORECASE):
                return pattern
        return None

Composition de Politiques

Combinez plusieurs politiques (par ex., à l'échelle de l'organisation + équipe + spécifique à l'agent) :

def compose_policies(*policies: GovernancePolicy) -> GovernancePolicy:
    """Fusionner les politiques avec une sémantique « la plus restrictive gagne »."""
    combined = GovernancePolicy(name="composed")

    for policy in policies:
        combined.blocked_tools.extend(policy.blocked_tools)
        combined.blocked_patterns.extend(policy.blocked_patterns)
        combined.require_human_approval.extend(policy.require_human_approval)
        combined.max_calls_per_request = min(
            combined.max_calls_per_request,
            policy.max_calls_per_request
        )
        if policy.allowed_tools:
            if combined.allowed_tools:
                combined.allowed_tools = [
                    t for t in combined.allowed_tools if t in policy.allowed_tools
                ]
            else:
                combined.allowed_tools = list(policy.allowed_tools)

    return combined


# Utilisation : superposer les politiques du large au spécifique
org_policy = GovernancePolicy(
    name="org-wide",
    blocked_tools=["shell_exec", "delete_database"],
    blocked_patterns=[r"(?i)(api[_-]?key|secret|password)\s*[:=]"],
    max_calls_per_request=50
)
team_policy = GovernancePolicy(
    name="data-team",
    allowed_tools=["query_db", "read_file", "write_report"],
    require_human_approval=["write_report"]
)
agent_policy = compose_policies(org_policy, team_policy)

Politique en YAML

Stockez les politiques comme configuration, pas comme code :

# governance-policy.yaml
name: production-agent
allowed_tools:
  - search_documents
  - query_database
  - send_email
blocked_tools:
  - shell_exec
  - delete_record
blocked_patterns:
  - "(?i)(api[_-]?key|secret|password)\\s*[:=]"
  - "(?i)(drop|truncate|delete from)\\s+\\w+"
max_calls_per_request: 25
require_human_approval:
  - send_email
import yaml

def load_policy(path: str) -> GovernancePolicy:
    with open(path) as f:
        data = yaml.safe_load(f)
    return GovernancePolicy(**data)

Modèle 2 : Classification d'Intention Sémantique

Détectez l'intention dangereuse dans les prompts avant qu'elles n'atteignent l'agent, à l'aide de signaux basés sur des modèles.

from dataclasses import dataclass

@dataclass
class IntentSignal:
    category: str       # par ex., "data_exfiltration", "privilege_escalation"
    confidence: float   # 0,0 à 1,0
    evidence: str       # ce qui a déclenché la détection

# Modèles de signaux pondérés pour la détection des menaces
THREAT_SIGNALS = [
    # Exfiltration de données
    (r"(?i)send\s+(all|every|entire)\s+\w+\s+to\s+", "data_exfiltration", 0.8),
    (r"(?i)export\s+.*\s+to\s+(external|outside|third.?party)", "data_exfiltration", 0.9),
    (r"(?i)curl\s+.*\s+-d\s+", "data_exfiltration", 0.7),

    # Escalade de privilèges
    (r"(?i)(sudo|as\s+root|admin\s+access)", "privilege_escalation", 0.8),
    (r"(?i)chmod\s+777", "privilege_escalation", 0.9),

    # Modification du système
    (r"(?i)(rm\s+-rf|del\s+/[sq]|format\s+c:)", "system_destruction", 0.95),
    (r"(?i)(drop\s+database|truncate\s+table)", "system_destruction", 0.9),

    # Injection de prompt
    (r"(?i)ignore\s+(previous|above|all)\s+(instructions?|rules?)", "prompt_injection", 0.9),
    (r"(?i)you\s+are\s+now\s+(a|an)\s+", "prompt_injection", 0.7),
]

def classify_intent(content: str) -> list[IntentSignal]:
    """Classer le contenu en fonction des signaux de menace."""
    signals = []
    for pattern, category, weight in THREAT_SIGNALS:
        match = re.search(pattern, content)
        if match:
            signals.append(IntentSignal(
                category=category,
                confidence=weight,
                evidence=match.group()
            ))
    return signals

def is_safe(content: str, threshold: float = 0.7) -> bool:
    """Vérification rapide : le contenu est-il sûr au-dessus du seuil donné ?"""
    signals = classify_intent(content)
    return not any(s.confidence >= threshold for s in signals)

Élément clé : La classification d'intention se fait avant l'exécution de l'outil, agissant comme une vérification de sécurité préalable. C'est fondamentalement différent des garde-fous de sortie qui ne vérifient qu'après la génération.


Modèle 3 : Décorateur de Gouvernance au Niveau de l'Outil

Enveloppez les fonctions d'outil individuelles avec des vérifications de gouvernance :

import functools
import time
from collections import defaultdict

_call_counters: dict[str, int] = defaultdict(int)

def govern(policy: GovernancePolicy, audit_trail=None):
    """Décorateur qui applique la politique de gouvernance à une fonction d'outil."""
    def decorator(func):
        @functools.wraps(func)
        async def wrapper(*args, **kwargs):
            tool_name = func.__name__

            # 1. Vérifier la liste blanche/noire des outils
            action = policy.check_tool(tool_name)
            if action == PolicyAction.DENY:
                raise PermissionError(f"La politique '{policy.name}' bloque l'outil '{tool_name}'")
            if action == PolicyAction.REVIEW:
                raise PermissionError(f"L'outil '{tool_name}' nécessite une approbation humaine")

            # 2. Vérifier la limite de débit
            _call_counters[policy.name] += 1
            if _call_counters[policy.name] > policy.max_calls_per_request:
                raise PermissionError(f"Limite de débit dépassée : {policy.max_calls_per_request} appels")

            # 3. Vérifier le contenu dans les arguments
            for arg in list(args) + list(kwargs.values()):
                if isinstance(arg, str):
                    matched = policy.check_content(arg)
                    if matched:
                        raise PermissionError(f"Modèle bloqué détecté : {matched}")

            # 4. Exécuter et auditer
            start = time.monotonic()
            try:
                result = await func(*args, **kwargs)
                if audit_trail is not None:
                    audit_trail.append({
                        "tool": tool_name,
                        "action": "allowed",
                        "duration_ms": (time.monotonic() - start) * 1000,
                        "timestamp": time.time()
                    })
                return result
            except Exception as e:
                if audit_trail is not None:
                    audit_trail.append({
                        "tool": tool_name,
                        "action": "error",
                        "error": str(e),
                        "timestamp": time.time()
                    })
                raise

        return wrapper
    return decorator


# Utilisation avec n'importe quel framework d'agent
audit_log = []
policy = GovernancePolicy(
    name="search-agent",
    allowed_tools=["search", "summarize"],
    blocked_patterns=[r"(?i)password"],
    max_calls_per_request=10
)

@govern(policy, audit_trail=audit_log)
async def search(query: str) -> str:
    """Rechercher des documents — gouverné par la politique."""
    return f"Results for: {query}"

# Passe : search("latest quarterly report")
# Bloqué : search("show me the admin password")

Modèle 4 : Score de Confiance

Suivez la fiabilité de l'agent au fil du temps avec des scores de confiance basés sur la décroissance :

from dataclasses import dataclass, field
import math
import time

@dataclass
class TrustScore:
    """Score de confiance avec décroissance temporelle."""
    score: float = 0.5          # 0,0 (non fiable) à 1,0 (entièrement fiable)
    successes: int = 0
    failures: int = 0
    last_updated: float = field(default_factory=time.time)

    def record_success(self, reward: float = 0.05):
        self.successes += 1
        self.score = min(1.0, self.score + reward * (1 - self.score))
        self.last_updated = time.time()

    def record_failure(self, penalty: float = 0.15):
        self.failures += 1
        self.score = max(0.0, self.score - penalty * self.score)
        self.last_updated = time.time()

    def current(self, decay_rate: float = 0.001) -> float:
        """Obtenir le score avec décroissance temporelle — la confiance s'érode sans activité."""
        elapsed = time.time() - self.last_updated
        decay = math.exp(-decay_rate * elapsed)
        return self.score * decay

    @property
    def reliability(self) -> float:
        total = self.successes + self.failures
        return self.successes / total if total > 0 else 0.0


# Utilisation dans les systèmes multi-agents
trust = TrustScore()

# L'agent complète les tâches avec succès
trust.record_success()  # 0,525
trust.record_success()  # 0,549

# L'agent fait une erreur
trust.record_failure()  # 0,467

# Limiter les opérations sensibles en fonction de la confiance
if trust.current() >= 0.7:
    # Autoriser l'opération autonome
    pass
elif trust.current() >= 0.4:
    # Autoriser avec surveillance humaine
    pass
else:
    # Refuser ou exiger une approbation explicite
    pass

Confiance multi-agents : Dans les systèmes où les agents délèguent à d'autres agents, chaque agent maintient des scores de confiance pour ses délégués :

class AgentTrustRegistry:
    def __init__(self):
        self.scores: dict[str, TrustScore] = {}

    def get_trust(self, agent_id: str) -> TrustScore:
        if agent_id not in self.scores:
            self.scores[agent_id] = TrustScore()
        return self.scores[agent_id]

    def most_trusted(self, agents: list[str]) -> str:
        return max(agents, key=lambda a: self.get_trust(a).current())

    def meets_threshold(self, agent_id: str, threshold: float) -> bool:
        return self.get_trust(agent_id).current() >= threshold

Modèle 5 : Piste d'Audit

Journal d'audit en ajout uniquement pour toutes les actions de l'agent — critique pour la conformité et le débogage :

from dataclasses import dataclass, field
import json
import time

@dataclass
class AuditEntry:
    timestamp: float
    agent_id: str
    tool_name: str
    action: str           # "allowed", "denied", "error"
    policy_name: str
    details: dict = field(default_factory=dict)

class AuditTrail:
    """Piste d'audit en ajout uniquement pour les événements de gouvernance des agents."""
    def __init__(self):
        self._entries: list[AuditEntry] = []

    def log(self, agent_id: str, tool_name: str, action: str,
            policy_name: str, **details):
        self._entries.append(AuditEntry(
            timestamp=time.time(),
            agent_id=agent_id,
            tool_name=tool_name,
            action=action,
            policy_name=policy_name,
            details=details
        ))

    def denied(self) -> list[AuditEntry]:
        """Obtenir toutes les actions refusées — utile pour l'examen de sécurité."""
        return [e for e in self._entries if e.action == "denied"]

    def by_agent(self, agent_id: str) -> list[AuditEntry]:
        return [e for e in self._entries if e.agent_id == agent_id]

    def export_jsonl(self, path: str):
        """Exporter au format JSON Lines pour les systèmes d'agrégation de journaux."""
        with open(path, "w") as f:
            for entry in self._entries:
                f.write(json.dumps({
                    "timestamp": entry.timestamp,
                    "agent_id": entry.agent_id,
                    "tool": entry.tool_name,
                    "action": entry.action,
                    "policy": entry.policy_name,
                    **entry.details
                }) + "\n")

Modèle 6 : Intégration du Framework

PydanticAI

from pydantic_ai import Agent

policy = GovernancePolicy(
    name="support-bot",
    allowed_tools=["search_docs", "create_ticket"],
    blocked_patterns=[r"(?i)(ssn|social\s+security|credit\s+card)"],
    max_calls_per_request=20
)

agent = Agent("openai:gpt-4o", system_prompt="You are a support assistant.")

@agent.tool
@govern(policy)
async def search_docs(ctx, query: str) -> str:
    """Rechercher dans la base de connaissances — gouverné."""
    return await kb.search(query)

@agent.tool
@govern(policy)
async def create_ticket(ctx, title: str, body: str) -> str:
    """Créer un ticket de support — gouverné."""
    return await tickets.create(title=title, body=body)

CrewAI

from crewai import Agent, Task, Crew

policy = GovernancePolicy(
    name="research-crew",
    allowed_tools=["search", "analyze"],
    max_calls_per_request=30
)

# Appliquer la gouvernance au niveau de l'équipe
def governed_crew_run(crew: Crew, policy: GovernancePolicy):
    """Envelopper l'exécution de l'équipe avec des vérifications de gouvernance."""
    audit = AuditTrail()
    for agent in crew.agents:
        for tool in agent.tools:
            original = tool.func
            tool.func = govern(policy, audit_trail=audit)(original)
    result = crew.kickoff()
    return result, audit

OpenAI Agents SDK

from agents import Agent, function_tool

policy = GovernancePolicy(
    name="coding-agent",
    allowed_tools=["read_file", "write_file", "run_tests"],
    blocked_tools=["shell_exec"],
    max_calls_per_request=50
)

@function_tool
@govern(policy)
async def read_file(path: str) -> str:
    """Lire le contenu du fichier — gouverné."""
    import os
    safe_path = os.path.realpath(path)
    if not safe_path.startswith(os.path.realpath(".")):
        raise ValueError("Path traversal blocked by governance")
    with open(safe_path) as f:
        return f.read()

Niveaux de Gouvernance

Adaptez la rigueur de la gouvernance au niveau de risque :

Niveau Contrôles Cas d'Usage
Ouvert Audit uniquement, aucune restriction Dev/tests internes
Standard Liste blanche des outils + filtres de contenu Agents en production générale
Strict Tous les contrôles + approbation humaine pour les opérations sensibles Financier, santé, légal
Verrouillé Liste blanche uniquement, pas d'outils dynamiques, audit complet Systèmes critiques pour la conformité

Bonnes Pratiques

Pratique Justification
Politique comme configuration Stockez les politiques en YAML/JSON, pas en dur — permet les changements sans redéploiement
La plus restrictive gagne Lors de la composition de politiques, refuser a toujours priorité sur autoriser
Vérification d'intention préalable Classez l'intention avant l'exécution de l'outil, pas après
Décroissance de confiance Les scores de confiance doivent décroître au fil du temps — requièrent un bon comportement continu
Audit en ajout uniquement Ne jamais modifier ni supprimer les entrées d'audit — l'immuabilité permet la conformité
Fermer par défaut Si la vérification de gouvernance échoue, refusez l'action plutôt que de l'autoriser
Séparer la politique de la logique L'application de la gouvernance doit être indépendante de la logique métier de l'agent

Liste de Contrôle de Démarrage Rapide

## Liste de Contrôle de Mise en Œuvre de la Gouvernance des Agents

### Configuration
- [ ] Définir la politique de gouvernance (outils autorisés, modèles bloqués, limites de débit)
- [ ] Choisir le niveau de gouvernance (ouvert/standard/strict/verrouillé)
- [ ] Configurer le stockage de la piste d'audit

### Implémentation
- [ ] Ajouter le décorateur @govern à toutes les fonctions d'outil
- [ ] Ajouter la classification d'intention au traitement des entrées utilisateur
- [ ] Implémenter le score de confiance pour les interactions multi-agents
- [ ] Intégrer l'export de la piste d'audit

### Validation
- [ ] Vérifier que les outils bloqués sont correctement refusés
- [ ] Vérifier que les filtres de contenu capturent les modèles sensibles
- [ ] Vérifier le comportement de la limitation de débit
- [ ] Vérifier que la piste d'audit capture tous les événements
- [ ] Tester la composition de politiques (la plus restrictive gagne)

Ressources Connexes

Skills similaires