Boilerplate completo para construir soluciones agénticas sobre IBM watsonx Orchestrate (ADK 2.x) con una capa web encima. Destilado de cotemar-poc-n1 y dun-casos-prueba. Incluye: - 11 docs (best practices, architecture patterns, ADK cheatsheet, observability, tool authoring, deployment, RUNBOOK, DEPLOY_TO_NEW_WOX, known-issues con 16 errores reales y fix, eval strategy, INDEX) - 13 templates WxO (orchestrator/specialist/single-meta agents, 3 connections, KB + runbook, observable_tool decorator, coercion helpers, Python tools, OpenAPI spec, backend filter endpoint, webhook validator HMAC, MCP connection) - 6 scripts (deploy idempotente con fallback ADK, undeploy, reset, check-adk-version, new-specialist scaffold, eval-agents runner) - 4 eval pieces (linter de best practices, runner, smoke-test, direct backend probe) + scenario templates - 8 subagentes Claude (.claude/agents/) — wxo-architect, wxo-agent-author, wxo-tool-author, runbook-author, mock-builder, backend-tool-builder, eval-author, web-layer-builder - Skill bundle fit-wxo-bootstrap/ (SKILL.md + 3 templates) listo para copiar a ~/.claude/skills/ - Web layer default FastAPI+HTMX con vista timeline observable + endpoint receptor de trazas - Docker compose Coolify-ready (healthcheck wget -qO-, sin labels Traefik, network split internal:true) - CI Gitea workflow con lint + smoke Best practices enforced por evals/lint_wxo_yaml.py: - A1: máx 10 tools por agente - A3: orchestrator sin tools de remediación - A6: agente sin propósito (react sin tools ni KB) - T1: @observable_tool obligatorio, no @tool directo - T3: _compat shim inline en tools.py - D1: docker-compose sin wget --spider - I-005: OpenAPI con description per-operation - I-009: sin labels Traefik manuales Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
248 lines
6.4 KiB
Markdown
248 lines
6.4 KiB
Markdown
# Eval Strategy
|
|
|
|
Cómo validar que tu solución hace lo que debe. **Cuatro capas**, cada una
|
|
respondiendo una pregunta distinta.
|
|
|
|
---
|
|
|
|
## Capa 1 — Native (ADK)
|
|
|
|
**Pregunta:** ¿El agente responde algo razonable al input?
|
|
|
|
**Herramienta:** `orchestrate agents test`
|
|
|
|
```bash
|
|
orchestrate agents test mi_agente --input-file evals/scenarios/test1.json
|
|
```
|
|
|
|
Output:
|
|
- La respuesta del agente
|
|
- La traza de tool calls
|
|
- El timing
|
|
|
|
**Cobertura:**
|
|
- Happy path
|
|
- Casos límite (input ambiguo, datos faltantes)
|
|
- Casos de error (sistema externo caído, datos inválidos)
|
|
|
|
**Limitación:** no compara contra expected. Solo te muestra qué pasó.
|
|
|
|
**Fixture format** (`evals/scenarios/test1.json`):
|
|
```json
|
|
{
|
|
"messages": [
|
|
{ "role": "user", "content": "Necesito resetear la contraseña de juan.perez@cotemar.com" }
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Capa 2 — Agent behavior (custom)
|
|
|
|
**Pregunta:** ¿El agente llamó las tools correctas en el orden correcto?
|
|
|
|
**Herramienta:** `evals/runner.py` que consume trazas observables.
|
|
|
|
**Fixture format** (`evals/scenarios/scenario_reset.yaml`):
|
|
```yaml
|
|
name: reset_password_happy_path
|
|
input: "Necesito resetear la contraseña de juan.perez@cotemar.com"
|
|
expect:
|
|
agent_response_contains: ["TKT-"]
|
|
tool_calls_in_order:
|
|
- tool: lookup_user
|
|
inputs.username: "juan.perez@cotemar.com"
|
|
- tool: reset_password
|
|
- tool: create_ticket
|
|
inputs.status: "RESOLVED"
|
|
no_tool_calls:
|
|
- escalate_to_n2
|
|
```
|
|
|
|
**Cómo corre:**
|
|
1. Manda el input al agente vía WxO Runs API
|
|
2. Espera la respuesta
|
|
3. Lee las trazas emitidas durante el run (capa observability)
|
|
4. Compara contra `expect`
|
|
5. Pass/fail
|
|
|
|
**Por qué importa:** un agente puede dar la respuesta correcta llamando
|
|
las tools incorrectas (ej: dijo `RESOLVED` sin llamar `reset_password`).
|
|
Esto detecta esos casos.
|
|
|
|
---
|
|
|
|
## Capa 3 — Runbook compliance (custom)
|
|
|
|
**Pregunta:** ¿El estado final del sistema coincide con lo que el runbook
|
|
dice que debe pasar?
|
|
|
|
**Herramienta:** `evals/runner.py` con asserts sobre la DB del backend.
|
|
|
|
**Fixture format** (`evals/scenarios/scenario_reset.yaml` — sección
|
|
`final_state`):
|
|
```yaml
|
|
final_state:
|
|
query: "SELECT * FROM tickets WHERE created_at > $START_TIME"
|
|
expect_count: 1
|
|
expect_fields:
|
|
status: "RESOLVED"
|
|
category: "password_reset"
|
|
assigned_group: "Capital Humano"
|
|
extra.runbook: "01"
|
|
```
|
|
|
|
**Cómo corre:**
|
|
1. Captura timestamp inicial
|
|
2. Ejecuta el scenario (manda input al agente)
|
|
3. Query a la DB para ver qué se creó/actualizó
|
|
4. Asserts contra `final_state`
|
|
|
|
**Por qué importa:** la traza puede decir "todo bien" pero el ticket
|
|
final puede tener el grupo equivocado, falta de metadata, etc.
|
|
|
|
---
|
|
|
|
## Capa 4 — Web layer (custom, opcional)
|
|
|
|
**Pregunta:** ¿La UI muestra correctamente el resultado?
|
|
|
|
**Herramienta:** Playwright o `requests` + parsing HTML.
|
|
|
|
**Cuándo aplica:** si la solución incluye un control plane / kanban /
|
|
dashboard que el usuario consume directamente.
|
|
|
|
**Fixture format** (`evals/scenarios/scenario_reset.yaml` — sección `ui`):
|
|
```yaml
|
|
ui:
|
|
visit: "/insights"
|
|
expect_text:
|
|
- "Resueltos: 1"
|
|
- "Runbook 01"
|
|
visit: "/"
|
|
expect_selector: ".ticket-card--RESOLVED"
|
|
expect_count_selector:
|
|
selector: ".ticket-card"
|
|
count: ">=1"
|
|
```
|
|
|
|
---
|
|
|
|
## Runner
|
|
|
|
```bash
|
|
./evals/eval-agents.sh
|
|
```
|
|
|
|
Hace en orden:
|
|
1. `lint_wxo_yaml.py` — best practices estáticas (issue check antes de runtime)
|
|
2. Native eval por cada `evals/scenarios/*.json`
|
|
3. Behavior eval por cada `evals/scenarios/*.yaml`
|
|
4. Final state eval (parte del mismo YAML)
|
|
5. UI eval si existe sección `ui`
|
|
|
|
Reporta:
|
|
```
|
|
[lint] PASS
|
|
[native] PASS (5/5)
|
|
[behavior] PASS (4/5) — scenario "edge_case_inactive_user" FAILED
|
|
[final_state] PASS (4/5)
|
|
[ui] SKIP (no fixtures)
|
|
```
|
|
|
|
Exit code 0 si todo pasa, 1 si alguno falla.
|
|
|
|
---
|
|
|
|
## Smoke test (CI nightly)
|
|
|
|
`evals/smoke-test.sh` es el subset mínimo que debe pasar **siempre**:
|
|
|
|
1. Health endpoint responde 200
|
|
2. Un caso simple end-to-end (login → create → execute → poll → assert)
|
|
3. El audit log tiene las entries esperadas
|
|
4. La UI muestra el resultado
|
|
|
|
Si falla, algo de la infraestructura está mal (deploy, DNS, healthcheck,
|
|
auth, etc.). NO mira el agente en profundidad — para eso están las
|
|
capas 2/3.
|
|
|
|
```bash
|
|
./evals/smoke-test.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Direct backend probe (diagnóstico)
|
|
|
|
`evals/direct-backend-probe.sh` golpea el backend directamente con
|
|
curl + el token de WxO. Para aislar problemas:
|
|
|
|
```bash
|
|
$ ./evals/direct-backend-probe.sh
|
|
[1/3] Healthcheck OK (200)
|
|
[2/3] Direct tool call OK (200) — got expected "case not found"
|
|
[3/3] Auth test OK (200 con token, 401 sin)
|
|
✓ Backend OK from WxO side. Si la solución falla, es el agente o el LLM.
|
|
```
|
|
|
|
Si los 3 pasan pero el agente igual falla, el problema está en:
|
|
- El prompt del agente
|
|
- El modelo (issue I-002)
|
|
- El recursion_limit (issue I-001)
|
|
- Los schemas (issue I-003)
|
|
|
|
---
|
|
|
|
## Estrategia recomendada por fase del proyecto
|
|
|
|
### Fase 1 — Desarrollo
|
|
- **Capa 1** (native) frecuente, manualmente
|
|
- **Lint** antes de cada commit (pre-commit hook)
|
|
|
|
### Fase 2 — Estabilización
|
|
- Agregar **Capa 2** (behavior) para cada scenario
|
|
- Agregar **Capa 3** (final_state) para cada scenario
|
|
- CI corre las 3 en cada PR
|
|
|
|
### Fase 3 — Demo / handoff
|
|
- Agregar **Capa 4** (UI) para los 2-3 paths críticos
|
|
- **Smoke test nightly** en CI
|
|
|
|
### Fase 4 — Producción
|
|
- Smoke test cada 15 min (cron)
|
|
- Direct backend probe en healthcheck
|
|
- Behavior eval suite semanal
|
|
|
|
---
|
|
|
|
## Archivos a crear por scenario
|
|
|
|
```
|
|
evals/scenarios/
|
|
├── scenario_reset_password.yaml ← Capas 2, 3, 4 unified
|
|
├── scenario_reset_password.input.json ← Capa 1 (input para `agents test`)
|
|
├── scenario_restart_service.yaml
|
|
├── scenario_restart_service.input.json
|
|
└── ...
|
|
```
|
|
|
|
El subagente `eval-author` (en `.claude/agents/eval-author.md`) genera
|
|
estos pares automáticamente cuando le das un runbook o un agente nuevo.
|
|
|
|
---
|
|
|
|
## Cuando una eval falla
|
|
|
|
1. Mirá `evals/last_run.log` — qué fixture, qué assert
|
|
2. Si es **Capa 1**: el agente respondió mal o no respondió. Ver prompt.
|
|
3. Si es **Capa 2**: el agente respondió bien pero llamó las tools mal.
|
|
Ver instructions del agente.
|
|
4. Si es **Capa 3**: tools se llamaron pero el resultado final está
|
|
incorrecto. Ver el handler del backend.
|
|
5. Si es **Capa 4**: backend OK pero UI no muestra. Ver el view / HTMX template.
|
|
|
|
Repará, corré la eval específica (`./evals/eval-agents.sh -s
|
|
scenario_x`), commit.
|