chore(plan): marca Phase E (Tasks 17-20) completada
Incluye nota de desviación: SqliteSaver → AsyncSqliteSaver (async CM), aiosqlite<0.21, orchestrator abre el checkpointer por llamada, +tests del orchestrator. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2728,6 +2728,13 @@ git commit -m "feat(policies): añade política default v1 con guardrails comple
|
|||||||
|
|
||||||
## Phase E — LangGraph Runtime (Tasks 17–20)
|
## Phase E — LangGraph Runtime (Tasks 17–20)
|
||||||
|
|
||||||
|
> **Nota de implementación (desviación del plan):** los nodos del grafo son `async def`, así que se invoca con `await graph.ainvoke(...)`, que el `SqliteSaver` síncrono no soporta. El checkpointing se cambió a **`AsyncSqliteSaver`** expuesto como context manager asíncrono (`async with build_checkpointer(data_dir) as cp: ...`). Consecuencias:
|
||||||
|
> - `core/requirements.txt` fija `aiosqlite>=0.20,<0.21` (la 0.21 eliminó `Connection.is_alive()`, usado por `langgraph-checkpoint-sqlite` 2.x).
|
||||||
|
> - `AgentOrchestrator` no guarda el checkpointer en `__init__`; abre uno por llamada sobre `self._data_dir`. El estado igual persiste en `data_dir/checkpoints.sqlite` y un `awaiting_approval` sobrevive a un reinicio.
|
||||||
|
> - Se añadió `tests/unit/test_runtime_orchestrator.py` (5 tests; el plan no incluía tests del orchestrator).
|
||||||
|
> - Los bloques de código de Tasks 18-20 abajo son la versión original del plan; la **versión autoritativa es la committeada**. Fases siguientes que usen `build_checkpointer` deben usar el patrón `async with`.
|
||||||
|
|
||||||
|
|
||||||
### Task 17: AgentState y nodos de validación
|
### Task 17: AgentState y nodos de validación
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
@@ -2737,7 +2744,7 @@ git commit -m "feat(policies): añade política default v1 con guardrails comple
|
|||||||
|
|
||||||
LangGraph define el estado como `TypedDict`. El reducer `operator.add` sobre `decision_path` permite que cada nodo acumule pasos sin pisar lo previo.
|
LangGraph define el estado como `TypedDict`. El reducer `operator.add` sobre `decision_path` permite que cada nodo acumule pasos sin pisar lo previo.
|
||||||
|
|
||||||
- [ ] **Step 1: Test**
|
- [x] **Step 1: Test**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Tests de los nodos del grafo (lógica pura, sin compilación de grafo)."""
|
"""Tests de los nodos del grafo (lógica pura, sin compilación de grafo)."""
|
||||||
@@ -2818,7 +2825,7 @@ async def test_validate_input_pasa_sin_pii(
|
|||||||
assert out["status"] == "running"
|
assert out["status"] == "running"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Implementar `state.py`**
|
- [x] **Step 2: Implementar `state.py`**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Estado tipado del grafo LangGraph."""
|
"""Estado tipado del grafo LangGraph."""
|
||||||
@@ -2844,7 +2851,7 @@ class AgentState(TypedDict, total=False):
|
|||||||
human_decision: dict | None
|
human_decision: dict | None
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: Implementar `nodes.py`**
|
- [x] **Step 3: Implementar `nodes.py`**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Factory de nodos LangGraph parametrizados por engine, policy, llm, agent_def."""
|
"""Factory de nodos LangGraph parametrizados por engine, policy, llm, agent_def."""
|
||||||
@@ -3038,7 +3045,7 @@ def build_node_finalize() -> NodeFn:
|
|||||||
return finalize
|
return finalize
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Verificar y commit**
|
- [x] **Step 4: Verificar y commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pytest tests/unit/test_runtime_nodes.py -v
|
pytest tests/unit/test_runtime_nodes.py -v
|
||||||
@@ -3053,35 +3060,39 @@ git commit -m "feat(runtime): añade AgentState y nodos LangGraph (validate, llm
|
|||||||
**Files:**
|
**Files:**
|
||||||
- Create: `core/src/agentforge_core/runtime/checkpointer.py`
|
- Create: `core/src/agentforge_core/runtime/checkpointer.py`
|
||||||
|
|
||||||
- [ ] **Step 1: Implementar**
|
- [x] **Step 1: Implementar**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Wrapper sobre SqliteSaver de LangGraph. Centraliza el path y el ciclo de vida."""
|
"""Wrapper sobre AsyncSqliteSaver de LangGraph. Centraliza el path y el ciclo de vida."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sqlite3
|
from contextlib import AbstractAsyncContextManager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
|
||||||
|
|
||||||
|
|
||||||
def build_checkpointer(data_dir: Path) -> SqliteSaver:
|
def checkpointer_path(data_dir: Path) -> Path:
|
||||||
"""Devuelve un SqliteSaver apuntando a data_dir/checkpoints.sqlite.
|
"""Ruta al fichero SQLite de checkpoints, creando data_dir si no existe."""
|
||||||
|
|
||||||
La base se crea si no existe. La conexión usa `check_same_thread=False`
|
|
||||||
porque LangGraph ejecuta nodos en hilos del executor.
|
|
||||||
"""
|
|
||||||
data_dir.mkdir(parents=True, exist_ok=True)
|
data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
db_path = data_dir / "checkpoints.sqlite"
|
return data_dir / "checkpoints.sqlite"
|
||||||
conn = sqlite3.connect(str(db_path), check_same_thread=False)
|
|
||||||
return SqliteSaver(conn)
|
|
||||||
|
def build_checkpointer(data_dir: Path) -> AbstractAsyncContextManager[AsyncSqliteSaver]:
|
||||||
|
"""Context manager async con un AsyncSqliteSaver sobre data_dir/checkpoints.sqlite.
|
||||||
|
|
||||||
|
Uso: ``async with build_checkpointer(dir) as cp: ...``. La conexión aiosqlite se
|
||||||
|
abre al entrar y se cierra al salir; LangGraph crea las tablas (setup()) de forma
|
||||||
|
perezosa. El estado persiste, así que un awaiting_approval sobrevive a un reinicio.
|
||||||
|
"""
|
||||||
|
return AsyncSqliteSaver.from_conn_string(str(checkpointer_path(data_dir)))
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
- [x] **Step 2: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add core/src/agentforge_core/runtime/checkpointer.py
|
git add core/src/agentforge_core/runtime/checkpointer.py
|
||||||
git commit -m "feat(runtime): añade build_checkpointer wrapper sobre SqliteSaver"
|
# (committeado como) git commit -m "fix(runtime): build_checkpointer asíncrono con AsyncSqliteSaver"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -3092,7 +3103,7 @@ git commit -m "feat(runtime): añade build_checkpointer wrapper sobre SqliteSave
|
|||||||
- Create: `core/src/agentforge_core/runtime/graph.py`
|
- Create: `core/src/agentforge_core/runtime/graph.py`
|
||||||
- Create: `tests/unit/test_runtime_graph.py`
|
- Create: `tests/unit/test_runtime_graph.py`
|
||||||
|
|
||||||
- [ ] **Step 1: Test (lógica de routing post-validate_input)**
|
- [x] **Step 1: Test (lógica de routing post-validate_input)**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Tests del grafo compilado: happy path con MockProvider y guardrails básicos."""
|
"""Tests del grafo compilado: happy path con MockProvider y guardrails básicos."""
|
||||||
@@ -3196,7 +3207,7 @@ async def test_grafo_pausa_en_hitl_si_riesgo_alto(tmp_path: Path) -> None:
|
|||||||
assert state.next # tiene siguiente paso pendiente (interrupted)
|
assert state.next # tiene siguiente paso pendiente (interrupted)
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Implementar `graph.py`**
|
- [x] **Step 2: Implementar `graph.py`**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Compilación del grafo LangGraph para un AgentDefinition concreto."""
|
"""Compilación del grafo LangGraph para un AgentDefinition concreto."""
|
||||||
@@ -3266,7 +3277,7 @@ def build_graph(
|
|||||||
return g.compile(checkpointer=checkpointer)
|
return g.compile(checkpointer=checkpointer)
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: Verificar y commit**
|
- [x] **Step 3: Verificar y commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pytest tests/unit/test_runtime_graph.py -v
|
pytest tests/unit/test_runtime_graph.py -v
|
||||||
@@ -3284,7 +3295,7 @@ git commit -m "feat(runtime): añade build_graph (LangGraph StateGraph + interru
|
|||||||
|
|
||||||
El orchestrator encapsula el ciclo de vida (build graph → invoke → leer estado → serializar a `AgentExecution`). El router de FastAPI lo llamará.
|
El orchestrator encapsula el ciclo de vida (build graph → invoke → leer estado → serializar a `AgentExecution`). El router de FastAPI lo llamará.
|
||||||
|
|
||||||
- [ ] **Step 1: Implementar**
|
- [x] **Step 1: Implementar**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Orchestrator que envuelve build_graph + ainvoke + serialización a AgentExecution."""
|
"""Orchestrator que envuelve build_graph + ainvoke + serialización a AgentExecution."""
|
||||||
@@ -3432,7 +3443,7 @@ class AgentOrchestrator:
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
- [x] **Step 2: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add core/src/agentforge_core/runtime/orchestrator.py core/src/agentforge_core/runtime/__init__.py
|
git add core/src/agentforge_core/runtime/orchestrator.py core/src/agentforge_core/runtime/__init__.py
|
||||||
|
|||||||
Reference in New Issue
Block a user