Capa do artigo Guardrails e avaliação de agentes: o que separa um demo de um sistema em produção

Guardrails e avaliação de agentes: o que separa um demo de um sistema em produção

A demo foi impecável. O agente entendeu o pedido, chamou a ferramenta certa, devolveu a resposta em oito segundos e a sala aplaudiu. Três semanas depois ele está em produção, alguém colou um e-mail encaminhado dentro do chat e o agente executou a instrução que estava escrita no rodapé daquele e-mail — não a que o usuário pediu. Ninguém consegue dizer se isso já tinha acontecido antes, porque não existe medida: existe a lembrança de que “na demo funcionava”. O problema não foi o modelo. Foi ter colocado em produção um sistema sobre o qual não havia nenhuma afirmação verificável.

As duas camadas Guardrail e avaliação resolvem problemas diferentes e são confundidos o tempo todo. O guardrail é contenção em runtime: ele decide um caso agora, bloqueia ou deixa passar, e não guarda opinião sobre a qualidade do sistema. A avaliação é medida ao longo do tempo: ela dá nota depois do fato, produz série histórica e sustenta um critério de aprovação — e não impede absolutamente nada enquanto a requisição acontece. Quem escolhe uma das duas leva um sistema torto para produção. Com guardrail e sem avaliação, você tem um agente contido que não sabe se melhorou. Com avaliação e sem guardrail, você tem um relatório bonito sobre um incidente que já chegou ao usuário.

O que é um guardrail — e o que é uma avaliação?

Guardrail é o conjunto de filtros que roda no caminho da requisição. No Azure, esse papel é do Azure AI Content Safety, um serviço que detecta conteúdo prejudicial gerado tanto por pessoas quanto por modelos, com APIs de texto e imagem. O sistema de filtragem integrado ao Microsoft Foundry processa os dois lados: o prompt de entrada e a completion de saída, por meio de modelos de classificação em ensemble.

Avaliação é o conjunto de evaluators — ferramentas especializadas que medem qualidade, segurança e confiabilidade das respostas ao longo do ciclo de vida. Elas atuam em três momentos distintos: escolha do modelo base, avaliação pré-produção e monitoramento pós-produção. E operam em dois níveis: turn, a resposta individual, que é o padrão, e conversation, a conversa multi-turn inteira.

O contraste que importa: o guardrail responde “deixo passar?”. A avaliação responde “está melhor ou pior do que na semana passada?”. Nenhuma das duas perguntas substitui a outra, e a segunda é a que autoriza um deploy.

O problema que resolve

Agentes quebraram a suposição em que quase toda a prática de teste de software se apoia: a de que o mesmo input produz o mesmo output. Um agente é não determinístico, tem estado, chama ferramentas com efeito colateral no mundo real e recebe texto de fontes que ninguém controla. Testar isso com um punhado de prompts manuais não é rigor — é anedota.

A urgência mudou de lugar, e o OWASP GenAI LLM Top 10 publicado em agosto de 2026 registra isso de forma clara: Excessive Agency, que era o oitavo item da lista de 2023, subiu para o terceiro lugar. Prompt Injection segue em primeiro. Traduzindo para a arquitetura: o risco dominante deixou de ser o modelo falar besteira e passou a ser o agente fazer besteira — com credencial válida, dentro do seu perímetro, porque leu uma instrução plantada num documento.

Os quatro erros que eu mais encontro em campo:

  1. Tratar o filtro de conteúdo como se fosse teste. Ele bloqueia categoria de dano. Não diz se o agente chamou a ferramenta certa, nem se resolveu o pedido do usuário.
  2. Avaliar só a resposta final. O texto pode estar impecável e o caminho até ele, desastroso: três ferramentas desnecessárias, uma chamada com parâmetro errado e um dado sensível que passou pelo contexto.
  3. Confiar no default e não olhar mais. Existe filtro padrão, e ele é bom — mas é um piso genérico, não a política do seu domínio.
  4. Não versionar o dataset de avaliação. Sem conjunto de casos fixo, “melhorou” vira percepção. Regressão só existe se houver linha de base.
Diagrama dos quatro pontos de controle de um agente de IA: na entrada em runtime, Prompt Shields verifica jailbreak direto e injeção indireta; na saída em runtime, groundedness e material protegido; em pré-produção, evaluators de agente medem intenção, aderência à tarefa e acerto de chamada de ferramenta sobre um dataset versionado; em pós-produção, avaliação contínua e red teaming agendado produzem defect rate e taxa de sucesso de ataque.
Quatro pontos de controle, duas camadas: guardrail contém em runtime, avaliação mede ao longo do tempo — e nenhuma das duas faz o trabalho da outra.

Como funciona — passo a passo

  1. Escreva o contrato do agente antes do código. O que ele pode fazer, o que nunca pode e o que só pode com confirmação humana. Sem essa lista, não há como avaliar aderência — não existe régua.
  2. Ative e ajuste o filtro de conteúdo. No Foundry, em Guardrails + controls > Content filters, defina os limiares por categoria e escolha entre Annotate only e Annotate + Block. Anotar sem bloquear é uma fase legítima de calibração; virar produção assim, não.
  3. Ligue Prompt Shields nos dois modos. Ataque direto — o usuário tentando subverter as regras — e ataque indireto, o XPIA, em que a instrução maliciosa vem dentro de um documento, e-mail ou retorno de ferramenta que o agente lê.
  4. Monte o dataset de avaliação. Casos reais do domínio mais casos adversariais, versionados no mesmo repositório do código. Esse arquivo é o ativo mais subestimado do projeto.
  5. Escolha os evaluators pelo que você precisa provar. Qualidade de resposta e qualidade de comportamento do agente são famílias diferentes, e a segunda é a que interessa aqui.
  6. Rode red teaming automatizado antes do deploy. Em ambiente separado, com recursos parecidos com os de produção.
  7. Instrumente e continue medindo. Tracing em OpenTelemetry para o Application Insights, avaliação contínua sobre amostra do tráfego e alertas do Azure Monitor quando a nota cair.

Os filtros que o Foundry já traz — um a um

Antes de escrever qualquer linha de código, vale saber o que já está ligado. É comum ver time construindo validação própria para algo que o Foundry aplica por padrão e, ao mesmo tempo, deixando desligado exatamente o filtro que importa para agente.

O piso: todo modelo implantado no Azure OpenAI in Foundry Models recebe políticas de segurança padrão — a única exceção documentada é o Whisper. O limiar padrão para texto é Medium nas quatro categorias de dano. Além disso, por padrão, entram detecção de jailbreak nos prompts e Protected Material para texto e código nas completions.

Agora, filtro a filtro:

Filtro Onde age Vem ligado? O que sobra para você decidir
Categorias de dano — Hate and Fairness, Sexual, Violence, Self-Harm Entrada e saída Sim, em Medium O limiar de cada categoria, separadamente
Prompt Shields — ataque direto (jailbreak) Entrada Sim, nos prompts Bloquear ou apenas anotar
Prompt Shields — ataque indireto (XPIA) Entrada Não Ativar e delimitar o conteúdo de terceiro
Protected material — texto Saída Sim Manter ligado
Protected material — código Saída Sim Manter: pode ser exigido para o Customer Copyright Commitment
Groundedness detection Saída Não Preview, só inglês, só em streaming no content filter
PII Saída Não O que fazer quando dado pessoal aparece na resposta
Blocklists Entrada, saída ou ambas Só as de profanidade Suas listas de termos
Custom categories Entrada e saída Não Preview; treinar a categoria que só existe no seu domínio
Task adherence Uso de ferramenta Não Preview; o guardrail que nasceu para agente
Filtro de imagem Entrada e saída Conforme o modelo Limiar nas mesmas quatro categorias

Quatro deles mudam o desenho de um agente, e merecem comentário:

  • Prompt Shields é uma API só, mas dois ataques diferentes. O user prompt attack cobre mudança de regras do sistema, substituição de persona, conversação simulada e saída codificada. O document attack — o XPIA — é o que pega agente: a instrução maliciosa chega dentro de um documento, e-mail ou retorno de ferramenta. E aqui está a pegadinha: a detecção indireta só funciona se você delimitar os documentos no prompt. Sem essa demarcação, o filtro não sabe onde termina a sua instrução e começa o texto de terceiro. Os limites são generosos, mas finitos: 10 mil caracteres de prompt e até cinco documentos somando outros 10 mil.
  • Groundedness detection tem modo non-reasoning, rápido e binário, e modo reasoning, que explica qual trecho não se sustenta. Há ainda um recurso de correção, que devolve correctedText reescrito sobre as fontes. Antes de contar com ele na arquitetura, leia as restrições: está em preview, atende só inglês e, dentro do content filter do Foundry, apenas em cenário de streaming, com disponibilidade regional limitada.
  • Protected material para código tem peso contratual, não só editorial: seu uso pode ser exigido para cobertura pelo Customer Copyright Commitment. Desligar isso para “reduzir latência” é uma decisão jurídica disfarçada de decisão técnica.
  • Task adherence, em preview dentro do Content Safety, detecta uso de ferramenta desalinhado, não intencional ou prematuro no contexto da interação. É o único da lista que olha comportamento, não conteúdo.

Os três interruptores que quase ninguém mexe

  1. Annotate only ou Annotate + Block. Anotar sem bloquear é uma fase legítima de calibração — você mede antes de restringir. Virar produção assim, não: o filtro passa a ser um relatório que ninguém lê.
  2. A combinação de severidades. A configuração não é um número solto, é um conjunto: Low + medium + high (o mais restritivo), Medium + high, High only — e duas opções que exigem aprovação para valer nas completions: No filters e Annotate only.
  3. Streaming. Existe modo de filtragem para saída em streaming. Se a sua UX transmite token a token e o filtro está configurado só para resposta completa, você está mostrando ao usuário texto que ainda não foi avaliado.

Um detalhe de escala fecha o quadro: a severidade vai de 0 a 7, normalmente devolvida em quatro degraus — 0, 2, 4 e 6. E o nível safe é anotado mas não é filtrável nem configurável. Ou seja, a anotação existe mesmo quando nada é bloqueado — e é dela que sai o seu telemétrico de risco, se você resolver capturá-la em vez de descartar.

Chamando o filtro fora do modelo

Tudo acima acontece sozinho no caminho do modelo. Mas num agente existe conteúdo que nunca passa pelo modelo antes de virar decisão: o retorno de uma ferramenta, o trecho recuperado de um índice, o corpo de um e-mail que o agente vai ler. Para esses, você chama o Content Safety diretamente, com o pacote azure-ai-contentsafety:

import os
from azure.ai.contentsafety import ContentSafetyClient
from azure.ai.contentsafety.models import AnalyzeTextOptions, TextCategory
from azure.identity import DefaultAzureCredential

client = ContentSafetyClient(
    os.environ["CONTENT_SAFETY_ENDPOINT"],
    DefaultAzureCredential(),
)

resultado = client.analyze_text(
    AnalyzeTextOptions(
        text=retorno_da_ferramenta,
        blocklist_names=["termos-proibidos-do-dominio"],
        halt_on_blocklist_hit=False,
    )
)

for item in resultado.categories_analysis:
    if item.category == TextCategory.VIOLENCE and item.severity >= 4:
        raise RuntimeError("conteúdo barrado antes de entrar no contexto")

for match in resultado.blocklists_match or []:
    print(f"lista {match.blocklist_name} pegou {match.blocklist_item_text!r}")

Duas observações honestas sobre esse SDK, porque elas mudam o desenho e quase nunca aparecem nos tutoriais:

  • O cliente estável expõe analyze_text e analyze_image. Prompt Shields e groundedness detection não têm método Python — são endpoints REST (text:shieldPrompt). Dentro do content filter do Foundry eles funcionam sem você escrever nada; fora dele, é requests na mão.
  • halt_on_blocklist_hit=False faz o serviço continuar a análise mesmo depois de bater na blocklist. Deixe assim enquanto estiver calibrando: você quer ver tudo que a chamada acionou, não só a primeira coisa.

As listas em si você cria uma vez, com o BlocklistClient:

from azure.ai.contentsafety import BlocklistClient
from azure.ai.contentsafety.models import (
    AddOrUpdateTextBlocklistItemsOptions,
    TextBlocklist,
    TextBlocklistItem,
)

blocklist_client = BlocklistClient(
    os.environ["CONTENT_SAFETY_ENDPOINT"], DefaultAzureCredential()
)

blocklist_client.create_or_update_text_blocklist(
    blocklist_name="termos-proibidos-do-dominio",
    options=TextBlocklist(
        blocklist_name="termos-proibidos-do-dominio",
        description="Termos que só fazem sentido bloquear neste negócio.",
    ),
)

blocklist_client.add_or_update_blocklist_items(
    blocklist_name="termos-proibidos-do-dominio",
    options=AddOrUpdateTextBlocklistItemsOptions(
        blocklist_items=[
            TextBlocklistItem(text="nome-de-projeto-confidencial"),
            TextBlocklistItem(text="codinome-interno"),
        ]
    ),
)

É aqui que mora o valor de uma blocklist: ela é o único filtro que sabe algo que a Microsoft não tem como saber — o vocabulário do seu negócio.

Custom filters: quando o catálogo não cobre o seu risco

Tudo o que vimos até aqui é catálogo: categorias que a Microsoft define e você calibra. Só que risco de negócio quase nunca cabe em ódio, sexual, violência e autoagressão. Recomendação de investimento sem disclaimer, orientação clínica, promessa de prazo que não está em contrato — nada disso tem categoria pronta.

Antes de sair criando filtro, vale a escada de decisão. Ela vai do mais barato para o mais caro, e a maioria dos casos morre no primeiro ou no segundo degrau:

  1. Ajuste a severidade. Se o conteúdo já cai numa categoria existente e só está passando, o problema é o limiar, não a falta de filtro. Descer violence de high para medium custa uma linha de configuração.
  2. Blocklist, se o risco for vocabulário. Termo fechado, nome próprio, codinome interno. Casamento exato de texto, sem semântica, sem treino.
  3. Custom category, se o risco for semântico. Quando o que você quer barrar é uma ideia recorrente e não uma palavra — aí a blocklist não alcança.
  4. Custom evaluator, se o risco for de comportamento. Quando a resposta não é nociva, é apenas errada para o seu domínio. Isso não se bloqueia: se mede.

As duas famílias de custom category

O Content Safety expõe duas implementações, e elas resolvem problemas diferentes:

Standard Rapid
Motor Modelo de ML treinado com os seus exemplos LLM aprendendo por amostra, sem treino
Tempo até valer Cinco a dez horas de treino Imediato — não há etapa de treino
Modalidade Só texto Texto e imagem
Idioma Só inglês Todos os idiomas do Content Safety
Amostras Mínimo de 50 positivas, até 5 mil Até mil por incidente
Feito para Política estável, escrita uma vez Incidente em curso, resposta em minutos

Dois detalhes que mudam a decisão. O primeiro: a documentação da Microsoft anuncia que custom categories (standard) será desativada em 1º de setembro de 2026, com migração para a Custom text API do Foundry Custom text classification. Começar projeto novo pelo standard hoje é comprar dívida com data marcada. O segundo: o standard tem limite de três categorias por recurso e só funciona em inglês — o que, para a maioria dos times no Brasil, já elimina a opção.

Na prática isso empurra quase todo mundo para o rapid, e ele é mesmo o caso de uso mais honesto: você descobre um padrão nocivo em produção, descreve o incidente em uma frase, sobe um punhado de exemplos e passa a detectar. É contenção de incidente, não política permanente.

Como se cria — e a pegadinha do SDK

Custom categories não existem no SDK Python. O pacote azure-ai-contentsafety expõe analyze_text, analyze_image e o gerenciamento de blocklists — mais nada. Não há CustomCategory, não há Incident, não há analyze_custom_category. Todos os exemplos oficiais usam requests direto contra a REST API, e é assim que o seu código vai ficar:

import os

import requests

CS = os.environ["CONTENT_SAFETY_ENDPOINT"]
API = "2024-02-15-preview"
HEAD = {
    "Ocp-Apim-Subscription-Key": os.environ["CONTENT_SAFETY_KEY"],
    "Content-Type": "application/json",
}
INCIDENTE = "promessa-de-prazo"

# 1. Cria o incidente. A definição é linguagem natural, não regex.
requests.patch(
    f"{CS}/contentsafety/text/incidents/{INCIDENTE}?api-version={API}",
    headers=HEAD,
    json={
        "incidentName": INCIDENTE,
        "incidentDefinition": (
            "Resposta que promete prazo de entrega, SLA ou data de "
            "disponibilidade sem que isso conste em contrato."
        ),
    },
)

# 2. Ensina por exemplo. Não há etapa de treino.
requests.post(
    f"{CS}/contentsafety/text/incidents/{INCIDENTE}"
    f":addIncidentSamples?api-version={API}",
    headers=HEAD,
    json={
        "IncidentSamples": [
            {"text": "Garanto que fica pronto até sexta."},
            {"text": "Seu pedido chega em no máximo 48 horas, pode confiar."},
        ]
    },
)

# 3. Publica o incidente.
requests.post(
    f"{CS}/contentsafety/text/incidents/{INCIDENTE}:deploy?api-version={API}",
    headers=HEAD,
)

# 4. Passa a valer como filtro adicional, na sua chamada.
def detectar(texto: str) -> dict:
    resposta = requests.post(
        f"{CS}/contentsafety/text:detectIncidents?api-version={API}",
        headers=HEAD,
        json={"text": texto, "incidentNames": [INCIDENTE]},
    )
    resposta.raise_for_status()
    return resposta.json()

Repare no incidentNames: é uma lista. Você avalia vários incidentes na mesma chamada, o que importa quando o time de conteúdo abre três incidentes na mesma semana. E repare que a detecção acontece num endpoint separado — ela não é um parâmetro do text:analyze normal. São duas chamadas, e o custo de latência é seu.

Sobre escrever a definição, a orientação da própria Microsoft é curta e vale repetir: dê um nome claro, escreva uma definição que descreva a característica do conteúdo, e monte um conjunto balanceado — exemplos positivos e, quando possível, negativos — que represente a variedade que o modelo vai encontrar no mundo real. Amostra enviesada vira filtro enviesado.

Custom evaluator: quando o risco é comportamento, não conteúdo

Nem todo risco se resolve barrando. Um agente que responde sobre investimento sem o aviso obrigatório não produziu conteúdo nocivo — produziu conteúdo fora da sua política. Isso é caso de avaliador, e um avaliador customizado é só uma classe que sabe ser chamada:

class DisclaimerEvaluator:
    """Verifica se a resposta financeira carrega o aviso obrigatório."""

    TERMOS = ("não constitui recomendação", "consulte seu assessor")

    def __call__(self, *, response: str, **kwargs):
        presente = any(t in response.lower() for t in self.TERMOS)
        return {
            "disclaimer": 1.0 if presente else 0.0,
            "motivo": "aviso presente" if presente else "aviso ausente",
        }

resultado = evaluate(
    data="conversas.jsonl",
    evaluators={
        "intent_resolution": intent_eval,
        "disclaimer": DisclaimerEvaluator(),
    },
    azure_ai_project=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
)

Custom e built-in entram no evaluate() exatamente do mesmo jeito — como valores do dicionário evaluators. É essa simetria que permite subir a régua do domínio sem montar um pipeline paralelo.

Quando o critério for subjetivo demais para código — tom, empatia, aderência a um manual de marca — o caminho é um avaliador baseado em prompt: um arquivo .prompty com a rubrica de notas, carregado por uma classe que faz a mesma coisa que a de cima. O julgamento vira texto versionado no repositório, e não uma regra escondida numa função.

Um aviso honesto para quem for implementar agora: a Microsoft mantém dois padrões vivos de avaliador customizado. O clássico, do pacote azure-ai-evaluation, é o do exemplo acima e é o que a maior parte do código existente usa. O novo, do azure-ai-projects, registra o avaliador num catálogo do projeto com uma função grade() e um schema de métricas, e é para lá que a documentação de avaliação em nuvem está migrando. Os dois estão documentados e funcionam. Escolha um por projeto e não misture — a assinatura da função muda entre eles.

A camada de medida: evaluators que enxergam o agente, não só a resposta

Aqui está a diferença real entre avaliar um chatbot e avaliar um agente. Os evaluators de qualidade clássicos — GroundednessEvaluator, com nota de 1 a 5, RelevanceEvaluator, CoherenceEvaluator, FluencyEvaluator — olham o texto que saiu. Os evaluators de agente olham o percurso:

Evaluator O que responde
IntentResolutionEvaluator O agente entendeu o que o usuário queria?
TaskAdherenceEvaluator Ele respeitou as regras, procedimentos e restrições que você definiu?
TaskCompletionEvaluator Entregou algo utilizável, ponta a ponta?
ToolCallAccuracyEvaluator Chamou as ferramentas certas, com os parâmetros certos, sem redundância?
ToolSelectionEvaluator / ToolInputAccuracyEvaluator Errou na escolha ou no preenchimento?
TaskNavigationEfficiencyEvaluator O caminho percorrido bate com o caminho ótimo?

Repare no que essa tabela permite: localizar a falha. “O agente foi mal” é inútil. “A intenção foi resolvida, a ferramenta escolhida estava certa e o parâmetro estava errado” é um bug com endereço. Vale notar o estado de maturidade: a família de tool e a de eficiência de navegação estão GA, enquanto aderência, conclusão e resolução de intenção ainda constam como preview — planeje com isso em mente.

Do lado de segurança, os evaluators de risco (builtin.hate_unfairness, builtin.violence, builtin.protected_material, builtin.code_vulnerability, builtin.indirect_attack, entre outros) têm duas propriedades práticas ótimas: não exigem um deployment seu, porque rodam contra os modelos de segurança hospedados pela Microsoft, e devolvem defect rate — taxa agregada de conteúdo indesejado. Isso é métrica de release, não impressão. Existem ainda dois que só fazem sentido para agentes e existem em preview: builtin.prohibited_actions e builtin.sensitive_data_leakage.

import os
from azure.ai.evaluation import (
    IntentResolutionEvaluator,
    TaskAdherenceEvaluator,
    ToolCallAccuracyEvaluator,
)

model_config = {
    "azure_endpoint": os.environ["AZURE_OPENAI_ENDPOINT"],
    "azure_deployment": os.environ["AZURE_OPENAI_DEPLOYMENT"],
    "api_key": os.environ["AZURE_OPENAI_API_KEY"],
}

intencao = IntentResolutionEvaluator(model_config=model_config)
aderencia = TaskAdherenceEvaluator(model_config=model_config)
ferramentas = ToolCallAccuracyEvaluator(model_config=model_config)

resultado = ferramentas(
    query=pergunta,
    response=resposta,
    tool_calls=chamadas,
    tool_definitions=definicoes,
)

Repare no que o model_config está dizendo: o judge é um modelo seu. Isso tem consequência de custo e de reprodutibilidade — trocar o deployment do judge muda a régua, então fixe-o e versione-o junto com o código.

Rodar um por vez serve para depurar. Para CI, o que você quer é o evaluate(), que aplica um conjunto de evaluators sobre um arquivo .jsonl e devolve o agregado:

from azure.ai.evaluation import evaluate

resultado = evaluate(
    data="casos-de-regressao.jsonl",
    evaluators={
        "intencao": intencao,
        "aderencia": aderencia,
        "ferramentas": ferramentas,
    },
    evaluator_config={
        "intencao": {
            "column_mapping": {
                "query": "${data.query}",
                "response": "${data.response}",
            }
        }
    },
    output_path="./resultado-avaliacao.json",
)

Esse .jsonl é o ativo mais subestimado do processo. Ele começa com dez casos escritos à mão e cresce com todo incidente que chegou em produção — cada bug vira uma linha, e a linha nunca sai. É assim que a avaliação deixa de ser um relatório e vira uma trava de regressão.

Se o seu agente roda no Foundry Agent Service, você não precisa montar esse arquivo à mão: o AIAgentConverter lê as threads e converte para o esquema que os evaluators esperam.

from azure.ai.evaluation import AIAgentConverter

converter = AIAgentConverter(project_client=project_client)

Vale entender como essa nota nasce. Os evaluators assistidos por IA usam um modelo como judge e devolvem um campo reason com a justificativa — o que os torna auditáveis, e também mais caros em tokens. Já as métricas de NLP (F1, BLEU, ROUGE, METEOR, GLEU) comparam sobreposição com um ground truth: são determinísticas, baratas e rápidas, mas exigem gabarito. Use as duas famílias por motivos diferentes; não peça à segunda que julgue comportamento.

Para rodar localmente, o pacote é azure-ai-evaluation. Para avaliação em nuvem e de agentes, o caminho atual é o azure-ai-projects, com openai_client.evals.create() e openai_client.evals.runs.create().

Red teaming: o ataque que você não escreve à mão

Você não vai imaginar sozinho as variações de ataque que importam — e não deveria tentar. A Microsoft mantém o PyRIT (Python Risk Identification Tool for generative AI), framework aberto para identificação proativa de risco em sistemas generativos. Dentro do Foundry, o AI Red Teaming Agent combina o PyRIT com os evaluators de risco e faz três coisas: varredura automatizada com probing adversarial, cálculo do Attack Success Rate — o percentual de ataques bem-sucedidos — e um scorecard por categoria de risco e técnica de ataque.

As técnicas vêm do PyRIT e cobrem desde codificação simples até escalada conversacional: Base64, Leetspeak, ROT13, Morse, ASCII art, Jailbreak (UPIA), Indirect Jailbreak (XPIA), multi turn e Crescendo, que sobe a temperatura gradualmente ao longo da conversa em vez de atacar de frente. Três categorias de risco existem apenas para agentes e apenas na execução em nuvem: prohibited actions, sensitive data leakage e task adherence.

Duas recomendações da própria documentação que eu repetiria em qualquer revisão de arquitetura. A primeira: rode em um ambiente separado, parecido com produção, nunca contra produção. A segunda é a taxonomia de ações, que é o antídoto direto contra excessive agency — classifique cada ação do agente como proibida (nunca), de alto risco (só com autorização humana explícita) ou irreversível (só com aviso e confirmação). A Microsoft recomenda manter as ações proibidas padrão derivadas de restrição regulatória, e desaconselha desmarcá-las.

Na prática, disparar uma varredura é curto — o trabalho está em ler o resultado:

import os
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
    AttackStrategy,
    AzureOpenAIModelConfiguration,
    RedTeam,
    RiskCategory,
)
from azure.identity import DefaultAzureCredential

with (
    DefaultAzureCredential() as credential,
    AIProjectClient(
        endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential
    ) as project_client,
):
    varredura = project_client.beta.red_teams.create(
        red_team=RedTeam(
            display_name="regressao-semanal",
            target=AzureOpenAIModelConfiguration(
                model_deployment_name=os.environ["FOUNDRY_MODEL_NAME"]
            ),
            attack_strategies=[AttackStrategy.BASE64],
            risk_categories=[RiskCategory.VIOLENCE],
        ),
        headers={
            "model-endpoint": os.environ["MODEL_ENDPOINT"],
            "model-api-key": os.environ["MODEL_API_KEY"],
        },
    )

    status = project_client.beta.red_teams.get(name=varredura.name).status
    print(f"varredura {varredura.name}: {status}")

O recurso está sob beta, e isso é um contrato: a superfície pode mudar. Prenda a versão do azure-ai-projects no seu requirements antes de colocar isso num pipeline que precisa ser estável.

Implementando: como conectar o seu projeto ao Azure AI Foundry

Tudo o que veio acima só vira defesa quando está ligado no seu projeto. Este é o caminho, na ordem em que ele realmente acontece.

1. Crie o projeto e anote o endpoint. No portal do Foundry, o endereço aparece na página inicial do projeto, na aba de visão geral. Ele tem esta forma:

https://<nome-da-conta>.services.ai.azure.com/api/projects/<nome-do-projeto>

Guarde-o como FOUNDRY_PROJECT_ENDPOINT. Não existe mais from_connection_string no SDK atual — a connection string do modelo antigo, com assinatura e grupo de recursos concatenados, saiu de cena. Se você encontrar um tutorial usando isso, ele é de outra geração da biblioteca.

2. Atribua a role certa — e não é a que parece. Autenticação é por Microsoft Entra ID: é o único método suportado pelo cliente. Para quem desenvolve e testa agentes, a role de menor privilégio é a Foundry User. Para uma identidade que só precisa chamar o agente, existe a Foundry Agent Consumer. E há uma armadilha explícita na documentação: não use a role Azure AI Developer — apesar do nome, ela pertence a workspaces do Azure Machine Learning e a hubs, não a projetos do Foundry. As roles do Foundry foram renomeadas recentemente, então você ainda vai encontrar os nomes antigos (Azure AI User, Azure AI Project Manager) circulando; os IDs e permissões não mudaram.

3. Instale os pacotes. Separe por função, para não arrastar dependência de avaliação para dentro do runtime:

azure-ai-projects        # projeto, agentes, avaliação em nuvem, red teaming
azure-identity           # DefaultAzureCredential
azure-ai-contentsafety   # chamada direta ao filtro
azure-ai-evaluation      # evaluators locais e no CI
azure-monitor-opentelemetry

4. Conecte. O cliente é um context manager, e a credencial também — usar os dois assim evita conexão pendurada em processo longo:

import os
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential

with (
    DefaultAzureCredential() as credential,
    AIProjectClient(
        endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential
    ) as project_client,
    project_client.get_openai_client() as openai_client,
):
    resposta = openai_client.responses.create(
        model=os.environ["FOUNDRY_MODEL_NAME"],
        input="Qual é a política de reembolso para pedidos acima de 30 dias?",
    )
    print(resposta.output_text)

O get_openai_client() é o detalhe que economiza um dia de trabalho: ele devolve um cliente compatível com o SDK da OpenAI já autenticado e apontando para o seu projeto. Você não monta URL, não gerencia token, não guarda chave.

5. Configure o content filter e conecte-o ao deployment. Isso é feito no portal, não em código: crie a política, defina limiar por categoria, ligue Prompt Shields para ataque indireto, decida entre anotar e bloquear — e então associe a política ao deployment. Um filtro criado e não associado protege exatamente nada. Confirme a associação com uma chamada de teste antes de considerar o passo concluído.

6. Rode avaliação no CI. Em nuvem, os evaluators são identificados por nome (builtin.*) e não exigem que você instancie nada localmente:

from azure.ai.projects.models import TestingCriterionAzureAIEvaluator

criterios = [
    TestingCriterionAzureAIEvaluator(
        type="azure_ai_evaluator",
        name="aderencia",
        evaluator_name="builtin.task_adherence",
        initialization_parameters={"deployment_name": os.environ["FOUNDRY_MODEL_NAME"]},
        data_mapping={
            "query": "{{item.query}}",
            "response": "{{item.response}}",
        },
    )
]

avaliacao = openai_client.evals.create(
    name="regressao-de-aderencia",
    data_source_config=configuracao_do_dataset,
    testing_criteria=criterios,
)

execucao = openai_client.evals.runs.create(
    eval_id=avaliacao.id, name="build-atual", data_source=fonte_de_dados
)
print(execucao.report_url)

Repare em initialization_parameters: os evaluators de qualidade precisam de um modelo judge; os de risco (builtin.violence e família) não precisam, porque rodam contra os modelos de segurança hospedados pela Microsoft. Isso muda o custo do seu pipeline — e é o motivo pelo qual dá para rodar a bateria de segurança com frequência muito maior que a de qualidade.

7. Ligue o tracing. Sem trace você tem nota, mas não tem a conversa que gerou a nota — e é a conversa que explica a falha:

import os

os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true"

from azure.monitor.opentelemetry import configure_azure_monitor
from opentelemetry import trace

connection_string = project_client.telemetry.get_application_insights_connection_string()
configure_azure_monitor(connection_string=connection_string)

tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("atendimento-reembolso"):
    ...

A ordem aqui não é estilo, é requisito: AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING precisa estar definida antes do import da instrumentação. Se estiver depois, a instrumentação não liga e você só descobre pelo warning no log — com o painel vazio e horas perdidas. O recurso é experimental, então os atributos dos spans podem mudar.

8. Agende o que ninguém vai lembrar de rodar. Avaliação contínua sobre uma amostra do tráfego real, avaliação programada com dataset fixo para detectar drift, red teaming recorrente e alerta no Azure Monitor quando a métrica cruzar o limiar. Guardrail que depende de alguém lembrar de executar não é guardrail — é intenção.

Boas práticas para produção

  • Trate defesa como camadas, não como produto. A documentação de IA responsável descreve quatro: o modelo e seu alinhamento, o sistema de segurança com os filtros, a aplicaçãosystem message e mitigação de UX — e o posicionamento, com transparência e educação do usuário. Nenhuma camada sozinha é suficiente.
  • Escreva o system message como artefato de segurança. Ele tem componentes definidos: papel e tarefa, público e tom, escopo e limites, diretrizes de segurança, ferramentas e dados. É código de produção, e merece revisão como tal.
  • Dê à ferramenta o menor privilégio possível. A pergunta certa não é “o agente consegue fazer isso?”, é “o que acontece se ele fizer isso na hora errada?”. Ação irreversível exige confirmação; ação de alto risco exige gente.
  • Delimite todo conteúdo de terceiro. Documento recuperado, e-mail e retorno de ferramenta entram no prompt como dado, nunca como instrução — e a detecção indireta depende dessa demarcação para funcionar.
  • Faça o dataset de avaliação crescer com os incidentes. Todo bug de produção vira caso de teste. É assim que a suíte para de ser sintética.
  • Instrumente antes de precisar. Tracing em OpenTelemetry para o Application Insights, com suporte a LangChain, LangGraph, OpenAI Agents SDK e Microsoft Agent Framework. Sem trace, avaliar comportamento de agente é adivinhação.
  • Agende o que você não vai lembrar de fazer. Avaliação contínua sobre amostra do tráfego, avaliação programada com dataset fixo para pegar drift, red teaming recorrente e alerta do Azure Monitor no limiar de qualidade.

O que precisa estar de pé

Três estágios, e a ordem entre eles é causal, não cronológica: cada um só se sustenta sobre o anterior.

Fundação — autoridade sobre o comportamento. Existe uma lista escrita do que o agente pode, não pode e só pode com humano no meio. O filtro de conteúdo está ativo com limiar decidido, não herdado. Todo conteúdo de terceiro entra delimitado.
Está de pé quando alguém de fora do time consegue ler o contrato do agente e prever o que ele recusaria.

Produção com contexto — a medida vira decisão. Existe dataset versionado com casos reais e adversariais. Os evaluators de agente rodam no pipeline e há um critério numérico de aprovação — não uma revisão por leitura. Red teaming roda antes do deploy e o Attack Success Rate é registrado por versão.
Está de pé quando um deploy é barrado por um número, e ninguém precisa discutir se a regressão é real.

Escala e eficiência — a plataforma como produto. Avaliação contínua sobre tráfego real, alertas ligados a limiar, red teaming agendado e governança sobre a frota inteira de agentes, não sobre um agente de cada vez.
Está de pé quando a pergunta “algum agente piorou esta semana?” tem resposta em um painel, não em uma reunião.

Perguntas frequentes (FAQ)

Guardrail e avaliação não são a mesma coisa com nomes diferentes?

Não. O guardrail age durante a requisição e decide um caso: passa ou não passa. A avaliação age fora do caminho crítico e produz nota, série histórica e critério de release. O guardrail não te diz se a versão nova ficou melhor; a avaliação não impede o incidente de hoje.

Se o Azure já aplica um filtro padrão, preciso configurar alguma coisa?

Precisa. O padrão é um piso genérico — limiar Medium nas quatro categorias, mais detecção de jailbreak e material protegido. Ele desconhece o seu domínio, não cobre as suas categorias próprias e não define o que é ação proibida para o seu agente.

Avaliar com um modelo como judge é confiável?

É útil e auditável, desde que você saiba o que está comprando. O evaluator assistido por IA devolve um campo reason com a justificativa, o que permite revisar a decisão em vez de aceitá-la. Para o que tem gabarito, prefira métrica determinística; para julgar comportamento, o modelo como judge é o que existe — e por isso a nota vale como tendência entre versões, mais do que como verdade absoluta em um caso isolado.

Qual a diferença entre GroundednessEvaluator e GroundednessProEvaluator?

O primeiro é baseado em modelo e devolve nota de 1 a 5. O GroundednessProEvaluator, em preview, usa o Azure AI Content Safety, devolve resultado binário de aprovação e não exige que você tenha um deployment de modelo para rodar.

Por onde eu começo se hoje não tenho nada disso?

Pelo dataset. Vinte casos reais do seu domínio, escritos à mão, versionados — mais valiosos que qualquer ferramenta. Sem conjunto de casos, você não tem o que medir, e nenhuma das camadas acima ganha significado.

Conclusão

Colocar um agente em produção não é um problema de modelo, é um problema de evidência. O guardrail é a contenção que impede o pior caso agora; a avaliação é a medida que autoriza a próxima versão. Quem só tem a primeira opera às cegas com rede de proteção. Quem só tem a segunda escreve relatórios elegantes sobre estragos já entregues.

O sinal de maturidade é simples e desconfortável: um deploy que é barrado por um número. Enquanto a decisão de subir depender de alguém dizer “testei aqui e pareceu bom”, o que existe é um demo com URL de produção.

👉 Se você está levando um agente do piloto para produção — especialmente em ambiente regulado, onde “pareceu bom” não é evidência aceitável, este é o ponto em que a arquitetura decide o resultado.
Quer trocar ideia sobre guardrails e avaliação de agentes? Me chama no LinkedIn.

Compartilhe:

Eron Cavalcante

Eron Cavalcante atua como Cloud Solution Architect na Microsoft, com foco em plataformas de dados, IA generativa e arquitetura de nuvem para o setor financeiro. Combina experiência técnica em Azure, Databricks e engenharia de dados com uma visão de negócio orientada a valor, apoiando clientes na adoção responsável de IA em escala.

Deixe um comentário