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.