From 4244b00ce71696a153529f4cc955614369d392ea Mon Sep 17 00:00:00 2001 From: Juan Marquez Date: Tue, 19 May 2026 18:13:13 +0200 Subject: [PATCH] =?UTF-8?q?a=C3=B1adido=20recorrido=20codificaci=C3=B3n=20?= =?UTF-8?q?a=20walkthrough?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/walkthrough.html | 123 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/docs/walkthrough.html b/docs/walkthrough.html index 45e0027..8937fe0 100644 --- a/docs/walkthrough.html +++ b/docs/walkthrough.html @@ -304,6 +304,7 @@ details.dd .body{padding:4px 16px 14px}
  • 20Glosario
  • 21Qué no hace (a propósito)
  • 22Por dónde empezar a leer
  • +
  • 23Ruta de codificación manual
  • @@ -1597,6 +1598,128 @@ open docs/walkthrough.html # macOS + +
    +

    23 Ruta de codificación manual — de cero a sistema funcionando

    +

    Si quieres entender el proyecto escribiéndolo, este es el orden que maximiza la comprensión. La lógica: sigue el grafo de dependencias hacia afuera desde el centro — empieza por lo que no depende de nada, termina por lo que depende de todo.

    + +
    +
    Principio rector
    +

    domain/ no importa nada del proyecto. El grafo no sabe que existe una API. La API no sabe que existe el dashboard. Escribe en esa dirección: adentro → afuera, abstracto → concreto, sin dependencias → con dependencias.

    +
    + +

    Fase 1 — El vocabulario: domain/ (~30 min)

    +

    Estos cuatro ficheros no importan nada del proyecto — solo Pydantic. Son el vocabulario de todo lo demás. Escribirlos primero te obliga a responder "¿qué maneja este sistema?" antes de pensar en cómo lo procesa.

    +
      +
    1. domain/agent.pyAgentDefinition, LLMConfig, AgentVersionMeta. Al terminar sabrás exactamente qué es "un agente" en este sistema: una declaración Pydantic, no código Python.
    2. +
    3. domain/guardrail.pyGuardrailViolation. Qué devuelve un guardrail cuando detecta un problema: una violación tipada, no una excepción.
    4. +
    5. domain/policy.pyPolicyDefinition. La lista de reglas que rigen un agente concreto.
    6. +
    7. domain/execution.py — el modelo más rico: AgentExecution, ProposedAction, ExecutionStatus, DecisionStep. Contiene el estado completo de una ejecución. Fíjate en que AgentExecution tiene definición del agente separada de su instancia en runtime.
    8. +
    +
    +
    Momento clave #1
    +

    Al escribir AgentExecution te das cuenta de que el sistema separa definir un agente (YAML → Pydantic) de ejecutarlo (runtime → JSONL). Son dos ciclos de vida distintos.

    +
    + +

    Fase 2 — Las costuras: los dos Protocols (~15 min)

    +

    Los Protocols son las interfaces estructurales del sistema — el contrato que separa "qué hace" de "cómo lo hace". El grafo solo importa estos dos ficheros, nunca OpenAI ni guardrails-ai directamente.

    +
      +
    1. llm/base.pyLLMProvider (un método async complete()), Message, CompletionResult. Dos clases Pydantic y un Protocol de cuatro líneas.
    2. +
    3. guardrails/base.pyGuardrailEngine: dos métodos (validate_input y validate_output), ambos devuelven list[GuardrailViolation].
    4. +
    +
    +
    Momento clave #2
    +

    Escribir estos dos Protocols antes que cualquier implementación te revela por qué cambiar el proveedor LLM de mock a azure no requiere tocar el grafo: el grafo nunca ve la implementación concreta.

    +
    + +

    Fase 3 — Primera ejecución posible: mocks + YAML del agente (~45 min)

    +

    Con dominios e interfaces ya puedes ejecutar el sistema sin ninguna clave de API. Los mocks son tu andamio.

    +
      +
    1. llm/mock.py — implementa LLMProvider devolviendo un JSON hardcodeado. ~30 líneas. Permite correr el grafo completo sin tocar OpenAI ni Azure.
    2. +
    3. guardrails/validators.py — validadores keyword-based simples. Implementan GuardrailEngine sin dependencias externas.
    4. +
    5. El primer YAML del agente: agents/incident_analyzer/versions/v1.yaml. Escribe la definición declarativa (nombre, owner, purpose, guardrails, config LLM, system_prompt, output_schema).
    6. +
    +
    +
    Momento clave #3
    +

    Al escribir el YAML del agente entiendes que un agente es pura configuración: no tiene código Python. Cambiar su comportamiento — modelo, temperatura, guardrails, threshold de HITL — es editar texto, no redeployar.

    +
    + +

    Fase 4 — El corazón del sistema: estado → nodos → grafo (~1.5 h)

    +

    Esta es la parte más densa e importante. El grafo LangGraph es la máquina de estados que orquesta todo. El orden dentro de la fase también importa.

    +
      +
    1. + runtime/state.py primero — AgentState: un TypedDict con todos los campos del estado mutable que fluye por el grafo. + El detalle a entender: decision_path: Annotated[list, operator.add] — no se sobreescribe, se acumula; LangGraph llama al reducer automáticamente cada vez que un nodo devuelve un decision_path. +
    2. +
    3. + runtime/nodes.py — los seis nodos, en el orden en que aparecerán en el grafo: +
        +
      • build_node_validate_input — llama al engine, acumula violations, decide si poner blocked_by_guardrail.
      • +
      • build_node_llm_reason — llama al provider, captura el fallo y lo transforma en status="failed".
      • +
      • build_node_validate_output — parsea el JSON del LLM y valida el output contra la política.
      • +
      • build_node_propose_actions — extrae proposed_actions del output parseado.
      • +
      • build_node_approve_gate — el nodo HITL: llama a interrupt({"awaiting_actions": risky}) si hay acciones con riesgo ≥ threshold. La ejecución se pausa aquí hasta que llegue un Command(resume=...).
      • +
      • build_node_finalize — filtra acciones por las aprobadas, construye final_output.
      • +
      +

      Nota el patrón: cada builder captura sus dependencias por clausura y devuelve una función async (state) → dict. Las dependencias no viajan en el estado — están fijas en el cierre.

      +
    4. +
    5. + runtime/graph.py — conecta los nodos. Los tres routers condicionales (_after_validate_input, _after_llm, _after_validate_output) hacen que cualquier error cortocircuite hacia END sin pasar por nodos posteriores. +
    6. +
    +
    +
    Momento clave #4
    +

    Al implementar approve_gate entiendes cómo funciona HITL en LangGraph: interrupt() no lanza excepción — serializa el estado en el checkpoint SQLite y devuelve el control. La ejecución se reanuda en el mismo nodo cuando llega Command(resume=...). El estado nunca se pierde entre las dos llamadas HTTP.

    +
    + +

    Fase 5 — Orquestador y checkpointer (~30 min)

    +
      +
    1. runtime/checkpointer.py — configura SQLite como backend de checkpoints LangGraph. Sin esto, interrupt() no puede persistir el estado entre la llamada a invoke y la llamada a approve.
    2. +
    3. runtime/orchestrator.py — el objeto que la API usará. Recibe AgentDefinition + PolicyDefinition + providers ya construidos, compila el grafo y expone dos métodos: invoke() (nueva ejecución) y approve() (reanudar tras HITL).
    4. +
    +

    Punto de control: en este momento tienes un sistema funcionando de extremo a extremo. Puedes llamar directamente al orquestador en un test y observar el decision_path completo, sin API ni dashboard.

    + +

    Fase 6 — La API: de adentro hacia afuera (~1 h)

    +

    Dentro de api/, el orden también importa.

    +
      +
    1. api/deps.py — el armario de cableado: los @lru_cache que construyen el registry, el policy store, el LLM provider y el guardrail engine. Escríbelo después de tener todo lo anterior — solo entonces sabrás exactamente qué estás conectando.
    2. +
    3. api/executions.py — los tres endpoints críticos: POST /executions/invoke, POST /executions/{trace_id}/approve, GET /executions/{trace_id}.
    4. +
    5. api/agents.py — CRUD del registry (listar agentes, leer versión concreta).
    6. +
    7. api/middlewares.py — inyecta X-Trace-Id y bind-ea el contexto de structlog.
    8. +
    9. main.py — monta los routers, configura CORS, arranca la app FastAPI.
    10. +
    + +

    Fase 7 — Registry y versionado (~45 min)

    +

    Si has estado usando el mock registry, ahora añades la persistencia real basada en YAML.

    +
      +
    1. registry/repository.py — carga definiciones de agentes desde YAML, cachea en memoria.
    2. +
    3. registry/versioning.py — hash SHA-256 + index.yaml: el sistema de versionado tipo-Git.
    4. +
    5. registry/policy_store.py — análogo al repository, pero para políticas.
    6. +
    7. registry/factory.py — el factory que elige qué implementación de registry usar según Settings.
    8. +
    + +

    Fase 8 — El dashboard (~1.5 h)

    +

    El dashboard es un cliente HTTP del core — puedes empezarlo en cualquier momento después de tener los endpoints.

    +
      +
    1. dashboard/client.py — primero. Te fuerza a pensar en qué necesita la UI antes de diseñar las páginas. Es el contrato del core visto desde el consumidor.
    2. +
    3. Las páginas por complejidad creciente: +
        +
      • pages/1_Registro.py — solo lectura: lista agentes y versiones.
      • +
      • pages/5_Politicas.py — solo lectura: muestra políticas activas.
      • +
      • pages/4_Historial.py — lectura + filtros: lista ejecuciones pasadas.
      • +
      • pages/2_Ejecutar.py — escritura: invoke + polling de estado + visualización del decision_path.
      • +
      • pages/3_Aprobaciones.py — la más compleja: muestra acciones pendientes, permite aprobar o rechazar individual o masivamente.
      • +
      +
    4. +
    + +
    +
    Punto de control mínimo viable (tras Fase 5)
    +

    Con las fases 1–5 completas y el mock LLM tienes un sistema funcionando sin Docker ni credenciales externas. Llama directamente al orquestador desde un test de integración, observa el decision_path completo, y pausa-reanuda en el nodo HITL. No necesitas la API ni el dashboard para validar el núcleo.

    +
    +
    + +