Files
nemo_guardrails_ci_cd/README.md
2026-04-29 20:45:00 -03:00

18 KiB

CI/CD para Agentes de IA com NeMo Guardrails como Package Python no Azure DevOps e Deploy em OCI OKE

1. Introdução

Este documento apresenta uma estratégia de CI/CD para projetos de Agentes de IA que consomem uma arquitetura compartilhada de guardrails baseada em NeMo Guardrails.

A referência técnica e conceitual deste material é o projeto:

Tutorial: NeMo Guardrails com Python, Proxy OpenAI-Compatible e Tracing

A decisão arquitetural considerada neste documento é:

O projeto NeMo Guardrails não será implantado como um serviço/server separado.
Ele será empacotado como uma biblioteca Python versionada, publicada por um pipeline próprio, e instalada pelos projetos de Agente de IA durante o build da imagem Docker.

Essa decisão reduz a complexidade operacional, preserva a cadência de desenvolvimento dos times de agentes e mantém a governança dos artefatos de guardrails por meio de versionamento, testes e controle de publicação no pipeline do projeto NeMo.


2. Visão geral da arquitetura

2.1 Fluxo funcional dos guardrails

User Input
  ↓
Input Rails
  ├─ Regex: PII Masking
  ├─ LLM: Toxicidade
  └─ LLM: Out-of-Scope
  ↓
LLM principal via NeMo Guardrails
  ↓
Output Rails
  ├─ Compliance Anatel
  ├─ Verbalização Prematura
  └─ Groundedness
  ↓
Python Rules
  ├─ Alçada de Ajuste
  └─ Consistência Histórica
  ↓
Execução de API / Backend
  ↓
Supervisor VAS Avulso
  ↓
Curadoria / Métricas
  ├─ TCR
  ├─ Fallback
  ├─ Tokens
  ├─ Tamanho de mensagem
  └─ Eficiência RAG
  ↓
Resposta final

Esse fluxo representa a função do projeto NeMo dentro da arquitetura: ele atua como uma camada de segurança, validação, controle de resposta, curadoria e métricas para agentes de IA.


3. Decisão arquitetural: NeMo como package, não como serviço

3.1 Modelo descartado: NeMo como serviço central

Uma alternativa seria implantar o projeto NeMo como um serviço separado:

Agente de IA
  ↓ HTTP
Serviço NeMo Guardrails
  ↓
LLM / Tools / Backend

Esse modelo centralizaria o runtime dos guardrails, mas traria custos e impactos:

  • necessidade de manter um serviço adicional;
  • aumento de latência;
  • criação de dependência runtime entre agente e serviço de guardrails;
  • maior complexidade de deploy;
  • possível gargalo de aprovação e governança;
  • risco de interromper a cadência dos times de desenvolvimento.

3.2 Modelo adotado: NeMo como biblioteca versionada

Pipeline NeMo
  ↓
gera package Python versionado
  ↓
publica em um feed privado
  ↓
Pipeline Agente
  ↓
pip install company-nemo-guardrails==x.y.z
  ↓
build da imagem final do agente
  ↓
deploy no OCI OKE

No runtime, o Pod terá um único container, contendo:

Pod Kubernetes
 └── Container do Agente
       ├── código do agente de IA
       ├── package NeMo Guardrails instalado
       ├── configs de guardrails
       ├── actions
       ├── deterministic rails
       └── llm rails

Portanto, o deploy no OKE é sempre da imagem final do agente, e não de uma imagem separada do NeMo.


4. Separação de responsabilidades

4.1 Projeto NeMo Guardrails

O projeto NeMo é responsável por:

  • manter os artefatos de guardrails;
  • implementar rails determinísticos;
  • implementar rails baseados em LLM;
  • registrar actions;
  • manter prompts;
  • manter catálogo de regras;
  • executar testes unitários e de regressão;
  • gerar package Python versionado;
  • publicar o package em um feed privado.

4.2 Projeto Agente de IA

Cada projeto de Agente de IA é responsável por:

  • implementar sua lógica conversacional;
  • implementar integrações com APIs, backend, ferramentas e RAG;
  • declarar a dependência do package NeMo;
  • executar testes de integração com os guardrails;
  • construir a imagem Docker final;
  • publicar a imagem no OCIR;
  • fazer deploy no OKE.

4.3 Azure DevOps

O Azure DevOps é responsável por orquestrar:

  • pipeline do NeMo;
  • pipeline dos agentes;
  • publicação do package;
  • build Docker;
  • push para OCIR;
  • deploy no OKE;
  • controle de variáveis e secrets;
  • permissões entre repositórios, feeds e pipelines.

5. Artefatos envolvidos no projeto NeMo

O projeto nemo_guardrails_configuration contém artefatos que devem ser tratados como componentes de uma biblioteca compartilhada.

Estrutura esperada:

nemo_guardrails_tracing_project/
├── config/
│   ├── config.yml
│   ├── config.py
│   ├── guardrails.yaml
│   ├── guardrails_catalog.json
│   └── rails/
│       ├── input.co
│       └── output.co
├── src/
│   ├── app.py
│   ├── app_nemo.py
│   ├── actions.py
│   ├── deterministic_rails.py
│   ├── judges.py
│   ├── llm_client.py
│   ├── llm_rails.py
│   ├── models.py
│   ├── registry.py
│   └── prompts/
├── tests/
│   └── test_guardrails.py
├── scripts/
│   └── run_tests.sh
├── requirements.txt
├── pyproject.toml
├── .env.example
└── README.md

5.1 config/config.yml

Arquivo principal de configuração do NeMo Guardrails. Define modelos, engine, parâmetros, input rails, output rails e fluxos habilitados.

Em ambientes corporativos, o engine: openai pode apontar para um proxy OpenAI-compatible, inclusive um proxy interno para OCI Generative AI.

5.2 config/rails/input.co

Define fluxos de entrada, como PII, toxicidade, prompt injection, out-of-scope, bloqueio de intenção e normalização de input.

5.3 config/rails/output.co

Define fluxos de saída, como resposta segura, groundedness, ausência de vazamento de dados, aderência ao tom, compliance e prevenção de verbalização prematura.

5.4 src/actions.py

Arquivo de registro das actions expostas ao NeMo. Ele conecta os fluxos .co com funções Python.

5.5 src/deterministic_rails.py

Contém rails determinísticos, como máscara de CPF, validação de alçada, validação de protocolo, consistência entre valores e regras de negócio com retorno previsível.

5.6 src/llm_rails.py

Contém rails que dependem de LLM ou judge, como toxicidade, out-of-scope, groundedness, qualidade de resposta, tom de voz e avaliação semântica.

5.7 src/judges.py

Contém avaliadores usados para medir qualidade ou segurança das respostas.

5.8 src/models.py

Contém classes de retorno padronizadas, por exemplo:

class RailResult:
    allowed: bool
    reason: str
    sanitized_text: str | None
    code: str
    mechanism: str
    data: dict

5.9 src/registry.py

Contém o registry dos guardrails, mapeando código da regra, nome, tipo, mecanismo, severidade e comportamento esperado.

5.10 src/prompts/

Contém prompts usados por rails baseados em LLM. Esses prompts devem ser tratados como artefatos sensíveis.

5.11 tests/test_guardrails.py

Contém a suíte de testes do projeto NeMo. O pipeline apenas executa os testes versionados no repositório.

5.12 pyproject.toml

Arquivo necessário para transformar o projeto NeMo em package Python instalável.


6. Segurança e governança dos artefatos NeMo

Como o projeto NeMo contém artefatos de segurança, ele deve ter governança própria.

6.1 O time do agente não deve ter acesso aos steps internos do pipeline NeMo

O time do agente deve consumir apenas o resultado aprovado:

company-nemo-guardrails==1.2.0

Ele não precisa acessar YAML do pipeline NeMo, steps de teste, lógica de aprovação, scripts internos ou secrets do pipeline NeMo.

6.2 Permissões recomendadas no Azure DevOps

Recurso Time NeMo Time Agente
Repo NeMo Leitura/escrita Sem acesso ou leitura restrita
Pipeline NeMo Administração Sem acesso
Feed do package Publicação Leitura
Repo Agente Sem acesso ou leitura Leitura/escrita
Pipeline Agente Sem acesso ou leitura Administração
Service Connection OCI/OKE Uso controlado Uso conforme necessidade

6.3 Segurança por contrato

O contrato entre NeMo e Agente é o package.

Package Python versionado = contrato técnico e de segurança

7. Fluxo de CI/CD completo

7.1 Pipeline do NeMo

Commit no repo NeMo
  ↓
Pipeline NeMo
  ↓
instala dependências
  ↓
executa testes
  ↓
gera package Python
  ↓
publica no Azure Artifacts

7.2 Pipeline do Agente

Commit no repo Agente
  ↓
Pipeline Agente
  ↓
instala dependências
  ↓
instala package NeMo
  ↓
executa testes do agente
  ↓
build Docker
  ↓
push OCIR
  ↓
deploy OKE

7.3 Quem chama quem?

Nenhum pipeline precisa chamar o outro diretamente. O acoplamento ocorre via package:

Pipeline NeMo publica package
Pipeline Agente instala package

8. Criando o package Python do projeto NeMo

8.1 Estrutura sugerida

nemo_guardrails_configuration/
├── company_nemo_guardrails/
│   ├── __init__.py
│   ├── config/
│   │   ├── config.yml
│   │   └── rails/
│   │       ├── input.co
│   │       └── output.co
│   ├── actions.py
│   ├── deterministic_rails.py
│   ├── llm_rails.py
│   ├── judges.py
│   ├── models.py
│   ├── registry.py
│   └── prompts/
├── tests/
├── pyproject.toml
└── README.md

8.2 Exemplo de pyproject.toml

[build-system]
requires = ["setuptools>=68", "wheel", "build"]
build-backend = "setuptools.build_meta"

[project]
name = "company-nemo-guardrails"
version = "1.0.0"
description = "Biblioteca corporativa de guardrails baseada em NeMo Guardrails"
requires-python = ">=3.11"
dependencies = [
  "nemoguardrails[openai]",
  "pydantic>=2",
  "httpx"
]

[tool.setuptools.packages.find]
where = ["."]

[tool.setuptools.package-data]
company_nemo_guardrails = [
  "config/*.yml",
  "config/**/*.co",
  "prompts/*.txt",
  "prompts/*.md",
  "*.json"
]

8.3 Função utilitária para carregar config do package

from importlib.resources import files
from nemoguardrails import RailsConfig, LLMRails

def create_rails():
    config_path = files("company_nemo_guardrails").joinpath("config")
    config = RailsConfig.from_path(str(config_path))
    return LLMRails(config)

Uso no agente:

from company_nemo_guardrails import create_rails

rails = create_rails()

9. Pipeline Azure DevOps do NeMo para publicar package

trigger:
- main

pool:
  vmImage: 'ubuntu-latest'

variables:
  PYTHON_VERSION: '3.11'

steps:
- checkout: self

- task: UsePythonVersion@0
  inputs:
    versionSpec: '$(PYTHON_VERSION)'
  displayName: 'Use Python $(PYTHON_VERSION)'

- script: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
    pip install build twine pytest
  displayName: 'Install dependencies'

- script: |
    export OPENAI_API_KEY="${OPENAI_API_KEY:-sk-fake}"
    pytest -v --junitxml=test-results.xml
  displayName: 'Run NeMo Guardrails tests'

- task: PublishTestResults@2
  inputs:
    testResultsFormat: 'JUnit'
    testResultsFiles: 'test-results.xml'
    failTaskOnFailedTests: true
  condition: succeededOrFailed()
  displayName: 'Publish test results'

- script: |
    python -m build
    ls -la dist
  displayName: 'Build Python package'

- task: TwineAuthenticate@1
  inputs:
    artifactFeed: 'company-python-feed'
  displayName: 'Authenticate Azure Artifacts feed'

- script: |
    python -m twine upload       --repository company-python-feed       --config-file $(PYPIRC_PATH)       dist/*
  displayName: 'Publish package to Azure Artifacts'

10. Projeto Agente consumindo o package NeMo

10.1 Estrutura do projeto Agente

ai_agent_project/
├── app/
│   ├── main.py
│   └── agent.py
├── tests/
│   ├── test_agent.py
│   └── test_guardrails_integration.py
├── k8s/
│   ├── deployment.yaml
│   └── service.yaml
├── Dockerfile
├── requirements.txt
├── azure-pipelines.yml
└── README.md

10.2 requirements.txt do Agente

fastapi
uvicorn[standard]
company-nemo-guardrails==1.0.0

10.3 Uso no código do Agente

from company_nemo_guardrails import create_rails

rails = create_rails()

def process_message(message: str):
    response = rails.generate(
        messages=[{"role": "user", "content": message}],
        options={"log": {"activated_rails": True}}
    )
    return response.output_text

11. Pipeline Azure DevOps do Agente

trigger:
- main

pool:
  vmImage: 'ubuntu-latest'

variables:
  PYTHON_VERSION: '3.11'
  IMAGE_NAME: 'ai-agent'
  IMAGE_TAG: '$(Build.SourceVersion)'
  K8S_NAMESPACE: 'ai-agent'

steps:
- checkout: self

- task: UsePythonVersion@0
  inputs:
    versionSpec: '$(PYTHON_VERSION)'
  displayName: 'Use Python $(PYTHON_VERSION)'

- task: PipAuthenticate@1
  inputs:
    artifactFeeds: 'company-python-feed'
  displayName: 'Authenticate Python feed'

- script: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
  displayName: 'Install agent dependencies with NeMo package'

- script: |
    export OPENAI_API_KEY="${OPENAI_API_KEY:-sk-fake}"
    pytest -v --junitxml=test-results.xml
  displayName: 'Run agent tests'

- task: PublishTestResults@2
  inputs:
    testResultsFormat: 'JUnit'
    testResultsFiles: 'test-results.xml'
    failTaskOnFailedTests: true
  condition: succeededOrFailed()
  displayName: 'Publish test results'

- script: |
    IMAGE_URI="$(OCIR_REGISTRY)/$(OCI_NAMESPACE)/$(OCIR_REPOSITORY):$(IMAGE_TAG)"
    echo "##vso[task.setvariable variable=IMAGE_URI]$IMAGE_URI"
    docker build -t "$IMAGE_URI" .
  displayName: 'Build Docker image'

- script: |
    echo "$(OCIR_PASSWORD)" | docker login "$(OCIR_REGISTRY)"       -u "$(OCIR_USER)"       --password-stdin
    docker push "$(IMAGE_URI)"
  displayName: 'Push image to OCIR'

- script: |
    mkdir -p ~/.kube
    echo "$(OKE_KUBECONFIG_B64)" | base64 -d > ~/.kube/config
    chmod 600 ~/.kube/config

    kubectl apply -f k8s/namespace.yaml
    sed "s|REPLACE_IMAGE_URI|$(IMAGE_URI)|g" k8s/deployment.yaml | kubectl apply -f -
    kubectl apply -f k8s/service.yaml

    kubectl rollout status deployment/ai-agent -n $(K8S_NAMESPACE) --timeout=180s
  displayName: 'Deploy to OCI OKE'

12. Dockerfile do Agente

FROM python:3.11-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir --upgrade pip     && pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]

13. Variáveis e secrets no Azure DevOps

13.1 Variáveis do pipeline do Agente

Variável Exemplo Tipo
OCIR_REGISTRY gru.ocir.io normal
OCI_NAMESPACE meunamespace normal
OCIR_REPOSITORY agents/ai-agent normal
OCIR_USER tenancy/user secret
OCIR_PASSWORD auth token OCI secret
OKE_KUBECONFIG_B64 kubeconfig em base64 secret
OPENAI_API_KEY chave ou fake/proxy secret

13.2 Variáveis do pipeline NeMo

Variável Exemplo Tipo
OPENAI_API_KEY sk-fake secret
Feed credentials gerenciado pelo Azure Artifacts service/task

14. OCI Vault

14.1 Papel do OCI Vault

O OCI Vault deve ser usado para proteger secrets em runtime:

  • chaves de LLM;
  • credenciais de API;
  • tokens de backend;
  • senhas de integração.

14.2 Estratégia simples

Azure DevOps secret → Kubernetes Secret → Pod

14.3 Estratégia recomendada

OCI Vault → External Secrets Operator / CSI Driver → Kubernetes Secret → Pod

Esse modelo evita manter secrets de runtime no Azure DevOps.


15. Segurança: impedindo acesso indevido ao pipeline NeMo

15.1 Separação de repositórios

Repo NeMo        → time plataforma/guardrails
Repo Agente      → time aplicação/agente

15.2 Separação de pipelines

Pipeline NeMo    → administrado pelo time NeMo
Pipeline Agente  → administrado pelo time Agente

15.3 Compartilhamento apenas do package

O time do agente recebe permissão de leitura no feed:

company-python-feed

Ele não precisa acessar repo NeMo, pipeline NeMo, steps de teste, scripts internos ou secrets do NeMo.


16. Testes no modelo com package

16.1 Testes do NeMo

Executados no pipeline NeMo:

  • testes determinísticos;
  • testes de segurança;
  • testes de regressão;
  • testes de catálogo;
  • testes de actions;
  • testes de judge.

16.2 Testes do Agente

Executados no pipeline do agente:

  • testes do código do agente;
  • teste de carregamento do package NeMo;
  • teste de integração mínima;
  • teste E2E do fluxo conversacional;
  • teste de contrato de API.

16.3 Exemplo

def test_agent_uses_guardrails():
    from company_nemo_guardrails import create_rails

    rails = create_rails()
    assert rails is not None

17. Versionamento

Recomenda-se versionamento semântico:

MAJOR.MINOR.PATCH

Exemplos:

1.0.0
1.1.0
1.1.1
2.0.0
Tipo de mudança Versão
Correção sem mudança de comportamento esperado PATCH
Nova regra compatível MINOR
Mudança que pode bloquear novos casos MINOR ou MAJOR
Mudança incompatível MAJOR

No agente:

company-nemo-guardrails==1.2.0

Ou aceitando patches:

company-nemo-guardrails~=1.2.0

18. Conclusão

A estratégia recomendada para este cenário é:

NeMo Guardrails = package Python versionado
Agente de IA = aplicação final
Pipeline NeMo = publica package aprovado
Pipeline Agente = instala package, gera imagem e faz deploy
Runtime = um container com agente + guardrails

Esse modelo equilibra segurança, governança, velocidade, simplicidade operacional, reutilização entre múltiplos agentes e deploy único no OCI OKE.

A governança deixa de depender de um serviço central em runtime e passa a acontecer no ciclo de CI/CD, por meio de testes, versionamento, publicação controlada e permissões no feed de packages.