feat(api): añade persistencia JSONL append-only y DI con factories cacheadas

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Juan
2026-05-10 15:29:23 +02:00
co-authored by Claude Opus 4.7
parent de738e5f15
commit 38d81252c6
3 changed files with 200 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
"""Dependencias compartidas inyectadas en los routers FastAPI.
Cada `get_*` está cacheada con `lru_cache(maxsize=1)`: la app construye registry,
policy store, proveedor LLM, motor de guardrails y orchestrator una sola vez. Los
tests llaman a `.cache_clear()` para reconstruirlos contra un `DATA_DIR` temporal.
"""
from __future__ import annotations
from functools import lru_cache
from agentforge_core.config import Settings
from agentforge_core.guardrails.base import GuardrailEngine
from agentforge_core.guardrails.factory import build_guardrail_engine
from agentforge_core.llm.base import LLMProvider
from agentforge_core.llm.factory import build_llm_provider
from agentforge_core.registry.factory import build_agent_registry, build_policy_store
from agentforge_core.registry.policy_store import FileSystemPolicyStore
from agentforge_core.registry.repository import FileSystemAgentRegistry
from agentforge_core.runtime.orchestrator import AgentOrchestrator
@lru_cache(maxsize=1)
def get_settings() -> Settings:
return Settings()
@lru_cache(maxsize=1)
def get_registry() -> FileSystemAgentRegistry:
return build_agent_registry(get_settings())
@lru_cache(maxsize=1)
def get_policy_store() -> FileSystemPolicyStore:
return build_policy_store(get_settings())
@lru_cache(maxsize=1)
def get_llm_provider() -> LLMProvider:
return build_llm_provider(get_settings())
@lru_cache(maxsize=1)
def get_guardrail_engine() -> GuardrailEngine:
return build_guardrail_engine(get_settings())
@lru_cache(maxsize=1)
def get_orchestrator() -> AgentOrchestrator:
settings = get_settings()
return AgentOrchestrator(
provider=get_llm_provider(),
engine=get_guardrail_engine(),
data_dir=settings.data_dir,
)
@@ -0,0 +1,63 @@
"""Helpers de persistencia append-only en JSONL para auditoría.
`executions.jsonl` y `violations.jsonl` son logs append-only: una línea JSON por
evento, nunca se reescriben. Sirven para reconstruir el historial y los listados.
"""
from __future__ import annotations
from pathlib import Path
from agentforge_core.domain.execution import AgentExecution, AgentExecutionSummary
from agentforge_core.domain.guardrail import GuardrailViolation
def append_violation(data_dir: Path, violation: GuardrailViolation) -> None:
data_dir.mkdir(parents=True, exist_ok=True)
with (data_dir / "violations.jsonl").open("a", encoding="utf-8") as f:
f.write(violation.model_dump_json() + "\n")
def append_execution(data_dir: Path, execution: AgentExecution) -> None:
data_dir.mkdir(parents=True, exist_ok=True)
with (data_dir / "executions.jsonl").open("a", encoding="utf-8") as f:
f.write(execution.model_dump_json() + "\n")
def read_violations(data_dir: Path) -> list[GuardrailViolation]:
path = data_dir / "violations.jsonl"
if not path.exists():
return []
out: list[GuardrailViolation] = []
with path.open(encoding="utf-8") as f:
for raw in f:
line = raw.strip()
if line:
out.append(GuardrailViolation.model_validate_json(line))
return out
def read_execution_summaries(data_dir: Path) -> list[AgentExecutionSummary]:
path = data_dir / "executions.jsonl"
if not path.exists():
return []
out: list[AgentExecutionSummary] = []
with path.open(encoding="utf-8") as f:
for raw in f:
line = raw.strip()
if not line:
continue
execution = AgentExecution.model_validate_json(line)
out.append(
AgentExecutionSummary(
trace_id=execution.trace_id,
agent_name=execution.agent_name,
agent_version=execution.agent_version,
status=execution.status,
started_at=execution.started_at,
finished_at=execution.finished_at,
n_violations=len(execution.violations),
n_proposed_actions=len(execution.proposed_actions),
)
)
return out