pagina unica

This commit is contained in:
2026-06-10 18:21:33 +02:00
parent 7d0f10d21f
commit 37633920ce
108 changed files with 3874 additions and 1350 deletions
+53 -53
View File
@@ -1,9 +1,9 @@
# Forja explicado de principio a fin
> **Nota de estado (mayo 2026):** Este documento fue escrito para la arquitectura
> anterior (dos servicios + Streamlit/NiceGUI). Forja ahora es un **único servicio
> FastAPI** con UI HTMX embebida. El núcleo (runtime, guardrails, HITL, versionado)
> sigue siendo el mismo. Algunas secciones de UI y despliegue están desactualizadas.
> **Nota de estado (junio 2026):** Forja es un **único servicio FastAPI** con UI
> HTMX embebida. Además del runtime gobernado, incluye la capa MLOps: datasets y
> modelos versionados, training runs contra backends externos, evaluación
> post-training y promoción gobernada draft → active.
>
> Si solo quieres arrancarlo, ve al [`README.md`](../README.md).
@@ -49,37 +49,33 @@ Si entiendes estas seis ideas, entiendes el proyecto. Todo lo demás son detalle
---
## 3. Vista de pájaro: dos servicios
## 3. Vista de pájaro: un único servicio
Forja son **dos procesos** que se hablan por HTTP/JSON:
Forja es **un solo proceso** FastAPI (puerto 8000) con dos caras:
```
┌───────────────────────────── docker-compose ──────────────────────────────┐
│ ┌──────────────────────┐ HTTP/JSON ┌────────────────────────┐ │
│ │ forja-dashboard │ ───────────────► │ forja-core │ │
│ Streamlit :8501 │ ◄─────────────── │ FastAPI :8000
│ (la "consola") (el cerebro)
└──────────────────────┘ └───────────┬────────────┘
┌────────────────┬────────────────────┼───────────────┤
▼ ▼
capa LLM capa Guardrails runtime LangGraph
(Strategy+factory) (Strategy+factory) (grafo + checkpointer)
│ │ │ │ │
│ └────────────────┴──────────┬──────────┘ │
│ ▼ │
│ Persistencia: YAML · JSON · JSONL · SQLite │
forja-core :8000
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ UI HTMX (humanos, Jinja2) │ API REST (/api/*)
└────────────────────────────┬────────────────────────────────────┘
capa LLM capa Guardrails runtime LangGraph
(Strategy+factory) (Strategy+factory) (grafo + checkpointer)
│ │ │
└──────────────────┴───────────┬──────────┘
Persistencia: YAML · JSON · JSONL · SQLite
└────────────────────────────────────────────────────────────────────────────┘
```
- **`forja-core`** (FastAPI, puerto 8000) — todo el dominio: registry de
agentes, motor de guardrails, runtime de ejecución, persistencia. No tiene UI.
- **`forja-dashboard`** (Streamlit, puerto 8501) — una consola visual. **No
contiene lógica de negocio**: es un cliente HTTP del core con cinco páginas.
La separación importa: el core podría servir a una CLI, a otro servicio, a un
pipeline... el dashboard es solo una de las caras posibles.
- La **API REST** (bajo `/api`) es el contrato para máquinas: CLI, pipelines,
otros servicios.
- La **UI HTMX** es **una sola página** (`/`) organizada como las seis etapas
del ciclo de vida (Define → Run → Approve → Train → Promote → Audit). Se
sirve desde el mismo proceso y llama a los mismos servicios internos — no hay
un cliente HTTP intermedio ni lógica de negocio duplicada. Las rutas antiguas
(`/agents`, `/run`, ...) redirigen a su etapa.
### Las capas del core (de fuera hacia dentro)
@@ -285,25 +281,20 @@ inyectan). Dependen de él: la API (`api/executions.py` solo conoce el
Y la **raíz de la app**: `core/src/ forja_core/main.py``create_app()` instancia
`FastAPI`, añade el `TraceIdMiddleware`, monta los routers, y expone `/health`.
Depende de: todo lo de arriba (vía `deps.py`). Dependen de él: el dashboard (por
HTTP) y los tests de integración (`tests/integration/`, vía `TestClient`).
Depende de: todo lo de arriba (vía `deps.py`). Dependen de él: la UI HTMX
(mismo proceso) y los tests (`TestClient`).
### 4.8 `dashboard/` — la consola Streamlit
### 4.8 `web/` — la UI HTMX embebida (una página)
| Fichero | Rol |
|---------|-----|
| `client.py` | `CoreClient`: un cliente HTTP síncrono (httpx, con reintentos) que envuelve **todos** los endpoints del core y mapea 404/409/422 a `{"error": ...}`. Es lo único que sabe hablar con el core. |
| `app.py` | La página raíz: sidebar de branding + un health-check del core. |
| `pages/1_🏛️_Registro.py` | Catálogo de agentes: detalle (prompt, esquema, LLM, guardrails), tabla de versiones, **diff coloreado v1↔v2**. |
| `pages/2_▶️_Ejecutar.py` | Lanza un agente: botones con los escenarios pregrabados, textarea, `invoke`, y render del status + output + violaciones + *timeline* del `decision_path`. Avisa si quedó en `awaiting_approval`. |
| `pages/3_🤝_Aprobaciones.py` | La cola de HITL: lista las ejecuciones `awaiting_approval`, muestra cada acción propuesta (risk_score coloreado, target, rollback_plan) con un checkbox, y aprueba el subconjunto elegido o rechaza con motivo. |
| `pages/4_📜_Historial.py` | Pestaña de ejecuciones (tabla + detalle por `trace_id`) y pestaña de violaciones (filtrable por severidad). |
| `pages/5_📐_Politicas.py` | Inventario de políticas: validadores de entrada/salida (cada uno expandible con su config) y versiones. |
| `components/` | Trozos reutilizables de UI: `diff_view` (pinta `+`/`-`/`@@`), `trace_view` (el timeline del `decision_path`), `violation_view` (badges de severidad). |
| `ui.py` | `GET /` (la página completa), `POST /run`, `GET /fragments/{agents,approvals,training,promotions,history}` y redirects 301 de las rutas antiguas. Llama a los mismos singletons de `deps.py` que la API; no hay cliente HTTP intermedio. |
| `templates/index.html` | La página única: seis etapas (Define → Run → Approve → Train → Promote → Audit), cada una con su explicación y su panel. |
| `templates/_*.html` | Fragmentos por etapa. Cada panel lleva `hx-get="/fragments/X" hx-trigger="forja-refresh from:body"`: cualquier botón de acción dispara el evento `forja-refresh` al terminar y **todos los paneles se re-renderizan** — el ciclo entero se recorre con clicks, sin recargar la página. |
Depende de: el `forja-core` por HTTP (vía `FORJA_CORE_URL`). **Nadie del
core depende del dashboard.** No tiene tests unitarios (mal coste/beneficio para
Streamlit); su verificación es la checklist manual de [`docs/manual_qa.md`](manual_qa.md).
Los botones de acción (approve/reject/train/evaluate/promote) hacen `hx-post`
con la extensión `json-enc` directamente contra los endpoints `/api/*`; el botón
**Run demo incident** lanza un incidente pregrabado que pausa en HITL.
### 4.9 Lo que no es código: `agents/`, `policies/`, `data/`
@@ -348,7 +339,7 @@ Esta es la sección que conviene leer despacio: aquí se ve cómo encaja todo. S
### Acto 1 — la petición entra y se ejecuta el grafo
```
Cliente (dashboard o curl)
Cliente (UI o curl)
│ POST /agents/incident_analyzer/invoke { "input": "<texto del incidente SIP>" }
TraceIdMiddleware ............ genera trace_id = UUID, lo bind-ea al log
@@ -391,10 +382,10 @@ router: status no es terminal → NO se escribe en executions.jsonl,
respuesta 200 { status: "awaiting_approval", trace_id, needs_human_for: [act-1], decision_path: [...], ... }
```
En el dashboard, la página **Ejecutar** muestra el timeline y un aviso "ve a
Aprobaciones". La página **Aprobaciones** hace `GET /executions`, encuentra esta
ejecución (el endpoint reconstruye las `awaiting_approval` desde el índice + el
checkpointer) y muestra `act-1` con su risk_score, target y rollback_plan.
En la UI, la página **Runs** muestra el timeline y un aviso "Open Approvals".
La página **Approvals** reconstruye las ejecuciones `awaiting_approval` desde el
índice + el checkpointer y muestra `act-1` con su risk_score, target y
rollback_plan.
### Acto 2 — el humano decide; el grafo se reanuda
@@ -431,7 +422,7 @@ respuesta 200 { status: "completed", final_output: { ..., approved_actions: [ac
```mermaid
sequenceDiagram
participant U as Cliente / Dashboard
participant U as Cliente / UI
participant MW as TraceIdMiddleware
participant API as router (api/executions.py)
participant ORCH as AgentOrchestrator
@@ -477,6 +468,7 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
| Mapa `trace_id → agente/versión` | **JSON** (`execution_index.json`) | Necesario para reanudar un HITL sabiendo qué configuración lo ejecutó; debe sobrevivir a reinicios. |
| Log de ejecuciones terminales y de violaciones | **JSONL append-only** | Inmutable, auditable, trivial de "shipear" a un sistema de logs. |
| Estado intermedio del grafo (incl. pausas HITL) | **SQLite** (`checkpoints.sqlite`, vía LangGraph) | Es lo que LangGraph espera; permite reanudar una ejecución pausada entre reinicios del proceso. |
| Training runs, evaluaciones y promociones | **JSON por entidad** bajo `data/` (+ JSONL para promociones aprobadas) | Estado mutable consultable por id; la auditoría de promociones es append-only. |
---
@@ -497,6 +489,9 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
| **Factory** | Función `build_X(settings)` que monta la implementación de `X` adecuada según la configuración. |
| **Status de ejecución** | `running`, `awaiting_approval` (pausada en HITL), `blocked_by_guardrail` (la frenó un validador), `completed`, `failed`. |
| **Estados de un agente** | `draft`, `active`, `deprecated` (metadato en su YAML). |
| **Training run** | Job de fine-tuning enviado a un backend externo (mock o Azure ML) con dataset, modelo base y versión candidata del agente. |
| **Evaluación post-training** | Ejecutar los escenarios canónicos del agente (`examples/*.txt`) por el runtime gobernado; pasa si no hay violaciones bloqueantes. Es el gate de promoción. |
| **Promoción** | Petición de gobernanza para pasar una versión `draft` a `active`; requiere run `succeeded` + evaluación `passed` de esa misma versión + aprobación humana. |
---
@@ -508,18 +503,22 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
│ ├── src/ forja_core/
│ │ ├── domain/ ← los modelos de datos (empieza por execution.py)
│ │ ├── config.py · observability/ ← cimientos transversales
│ │ ├── llm/ ← proveedores LLM (Protocol + impls + factory)
│ │ ├── registry/ ← catálogo y versionado (YAML/JSON, hash, diff)
│ │ ├── llm/ ← proveedores LLM (Protocol + impls + fallback + factory)
│ │ ├── registry/ ← catálogo y versionado (base yaml_store.py + 4 stores)
│ │ ├── guardrails/ ← motor de validación (Protocol + composite + validators)
│ │ ├── runtime/ ← el grafo LangGraph (state, nodes, graph, checkpointer, orchestrator)
│ │ ├── training/ ← backends de fine-tuning (mock, Azure ML)
│ │ ├── evaluation/ ← escenarios canónicos + evaluación post-training
│ │ ├── api/ ← FastAPI (middlewares, deps, routers, persistence)
│ │ ├── web/ ← UI HTMX (ui.py + templates/)
│ │ └── main.py ← create_app(): ensambla la app
│ ├── Dockerfile · requirements.txt
├── agents/incident_analyzer/ ← el agente de ejemplo (YAMLs + escenarios .txt)
├── policies/default/ ← la política de guardrails de ejemplo
├── datasets/ · models/ ← datasets y modelos base versionados (YAML)
├── data/ ← estado runtime (gitignored)
├── tests/ ← pytest: tests/unit/ y tests/integration/
├── docs/ ← este documento, manual_qa.md, futuro.md, superpowers/
├── docs/ ← este documento, componentes.md, futuro.md, product-voice.md
├── docker-compose.yml ← levanta core (API + UI HTMX en puerto 8000)
├── Makefile ← install / test / test-all / lint / smoke / up / down
├── README.md · ARCHITECTURE.md
@@ -531,7 +530,8 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
3. `runtime/nodes.py` y `runtime/graph.py` — el flujo de ejecución.
4. `api/executions.py` — cómo se expone (invoke / approve / reject).
5. `tests/integration/test_invoke_hitl.py` — el ciclo completo, en ~40 líneas.
6. Levanta `docker compose up` y haz clic por las cinco páginas del dashboard.
6. Levanta `docker compose up` y recorre la página única de arriba abajo:
los 6 clicks de la demo del README cubren el ciclo completo.
---
@@ -540,4 +540,4 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
Es un MVP. La detección de PII más fina (modelos grandes, español), autenticación,
OpenTelemetry, multi-tenant, persistencia en Postgres, evaluadores LLM-as-judge,
una cola de aprobaciones con SLA... están en el roadmap: [`docs/futuro.md`](futuro.md).
El estado actual y cómo verificarlo: [`README.md`](../README.md) y [`docs/manual_qa.md`](manual_qa.md).
El estado actual y cómo verificarlo: [`README.md`](../README.md).