Guardrails¶
El orchestrator incluye un sistema de guardrails en capas, siguiendo el patrón
de los agentes de clase mundial: el mecanismo vive en la librería, la
política la define quien usa el bot. Todo es opt-in y no cambia el
comportamiento existente, con una excepción: el hardening anti
prompt-injection (injection_hardening=True por defecto).
input_guards ──► [grafo] agent ──► tool_guards ──► tools ──► agent ─┐
(goal) (veto real) ▼
verify ──► output_guard ──► fin
Motor de políticas de tool calls (tool_guards)¶
Cada tool call que el modelo propone pasa por una cadena de guards antes de
ejecutarse — en todos los modos, incluido mode="auto". Un guard es
cualquier callable (sync o async) que recibe un GuardContext y devuelve una
GuardDecision:
| Acción | Efecto |
|---|---|
allow |
Continúa con el siguiente guard; si todos permiten, se ejecuta |
deny |
La tool no se ejecuta; el modelo recibe un ToolMessage de error con la razón y puede replantear |
ask |
Escala al interrupt de aprobación humana aunque el modo sea auto |
rewrite |
Se ejecuta con args_override en lugar de los args originales |
La primera decisión terminal gana (fail-closed: un guard que lanza excepción o
devuelve algo inválido deniega). Cada decisión distinta de allow queda
registrada en result.guard_events, en el stream "updates" y en
node_trace.
from sonika_ai_toolkit import OrchestratorBot
from sonika_ai_toolkit.guards import (
GuardDecision, RiskThresholdGuard, ToolAllowlistGuard, BudgetGuard, ArgPatternGuard,
)
def no_prod_db(ctx):
# ctx: tool_name, args, risk_level, goal, mode, tool_calls_so_far, ...
if ctx.tool_name == "postgresql" and "prod" in str(ctx.args):
return GuardDecision.deny("Base de datos de producción prohibida")
return GuardDecision.allow()
bot = OrchestratorBot(
strong_model, fast_model, "instrucciones...", tools=[...],
tool_guards=[
RiskThresholdGuard(max_risk=1), # riesgo 2 (bash/python/delete) → deny
ToolAllowlistGuard(["read_file", "search_web", "call_api"]),
BudgetGuard(50, per_tool={"run_bash": 3}),
ArgPatternGuard([r"drop\s+table"], tools=["postgresql", "mysql"]),
no_prod_db, # tu propia política
],
)
Guards built-in: RiskThresholdGuard, ToolAllowlistGuard,
ToolDenylistGuard, BudgetGuard, ArgPatternGuard. El kwarg
risk_threshold=N del constructor es un atajo que antepone
RiskThresholdGuard(max_risk=N) a la cadena (su default es None: sin guard).
Los args reescritos y los registrados se redactan igual que siempre
(password/token/secret/… → "***"); la tool recibe los valores reales.
Lista de prohibiciones del operador (prohibited)¶
Pasa una lista de reglas en lenguaje natural y se cumplen de verdad, no solo como instrucción:
bot = OrchestratorBot(
strong_model, fast_model, "instrucciones...", tools=[...],
prohibited=[
"No tocar la base de datos de producción",
"No enviar emails a direcciones fuera de @miempresa.com",
"No borrar archivos",
],
)
Doble enforcement:
- Prompt — la lista se inyecta numerada en el system prompt, así el modelo la conoce de antemano.
- Juez LLM (
ProhibitedActionsGuard, añadido automáticamente al final de la cadena) — antes de ejecutar cada tool call con riesgo ≥ 1, elfast_modelevalúa la acción concreta contra la lista y deniega si la viola, indicando la regla. Ante ambigüedad o fallo del juez, bloquea (fail-closed). Los args llegan al juez redactados y truncados.
Detalles operativos: las tools de riesgo 0 (read-only) no pagan el juez
(scope="risky"); el veredicto se memoiza por tool call, así los replays de
interrupts no re-facturan; el costo típico es ~300-700 tokens de prompt en el
modelo rápido por call arriesgada.
Hardening anti prompt-injection (injection_hardening)¶
Por defecto (True) el system prompt incluye una sección de seguridad: el
contenido de tools/web/archivos es dato y no instrucciones, nunca exfiltrar
secretos, etc. Es texto estático (~150 tokens) y compatible con el prompt
caching de Anthropic. injection_hardening=False lo desactiva.
Spotlighting (opcional): marca tools cuyos outputs vienen de fuentes no confiables y sus resultados se envuelven en delimitadores explícitos antes de llegar al modelo (cualquier delimitador falso embebido en el contenido se neutraliza):
Moderación de entrada/salida (input_guards / output_guards)¶
Un guard de I/O es un callable (text) -> GuardDecision. SafetyInputGuard/
SafetyOutputGuard adaptan el SafetyClassifier de la librería; cualquier
función propia sirve.
from sonika_ai_toolkit.guards import SafetyInputGuard, SafetyOutputGuard
bot = OrchestratorBot(
...,
input_guards=[SafetyInputGuard(fast_model)],
output_guards=[SafetyOutputGuard(fast_model)],
output_guard_action="annotate", # "annotate" | "block" | "retry"
)
- Input: se evalúa el
goalantes de invocar el grafo. Si deniega,arun()devuelvesuccess=Falsecon el motivo (el LLM nunca se llama) yastream_events()emite un único evento("graph", {"type": "guard_rejected", ...}). - Output: un nodo
output_guardmodera elfinal_reportal final del run (después del nodoverifysi está activo). En violación:"annotate"(default) registra el evento y deja el reporte intacto — en streaming los tokens ya salieron, por eso es el default honesto;"block"reemplaza el contenido por un aviso de política;"retry"devuelve el motivo al agente para UNA reescritura y luego acepta.
Auditoría¶
Toda intervención queda en BotResponse.guard_events:
result = bot.run("...")
for ev in result.guard_events:
print(ev["guard"], ev["action"], ev["tool_name"], ev["reason"])
Y si un run quedó pausado por una escalada ask (o mode="ask"),
result.pending_approval trae el payload del interrupt (tool,
tool_call_id, params redactados, guard_reason); se reanuda con
set_resume_command() + astream_events(None).
Qué capa usar¶
| Amenaza | Capa |
|---|---|
| El modelo llama una tool que no debería | tool_guards (allowlist/riesgo) |
| Reglas de negocio en lenguaje natural | prohibited |
| Contenido web malicioso manipula al agente | injection_hardening + untrusted_tool_outputs |
| Usuarios envían peticiones tóxicas/abusivas | input_guards |
| El reporte final filtra contenido no permitido | output_guards |
| Runs desbocados / costo | BudgetGuard + recursion_limit |
| SSRF, path traversal, bash destructivo | Ya integrado en las tools core (net guard, root=, BashSafeTool) |