proyecto generalizado

This commit is contained in:
2026-05-23 16:44:45 +02:00
parent 2f990ea636
commit b4964261ec
94 changed files with 1032 additions and 320 deletions
+1 -1
View File
@@ -26,6 +26,6 @@ EXPOSE 8501
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 \
CMD curl -fsS http://localhost:8501/_stcore/health || exit 1
CMD ["streamlit", "run", "/app/src/agentforge_dashboard/app.py", \
CMD ["streamlit", "run", "/app/src/forja_dashboard/app.py", \
"--server.address=0.0.0.0", "--server.port=8501", \
"--server.headless=true", "--browser.gatherUsageStats=false"]
-47
View File
@@ -1,47 +0,0 @@
"""Entry point del dashboard Streamlit con sidebar de branding y health."""
from __future__ import annotations
import streamlit as st
from agentforge_dashboard.client import CoreClient
@st.cache_resource
def get_client() -> CoreClient:
return CoreClient()
def main() -> None:
st.set_page_config(
page_title="AgentForge",
page_icon="🛡️",
layout="wide",
initial_sidebar_state="expanded",
)
st.sidebar.markdown("## 🛡️ AgentForge")
st.sidebar.caption("Plataforma de gobernanza de agentes IA")
client = get_client()
try:
client.health()
st.sidebar.success("Core API: OK")
except Exception as exc:
st.sidebar.error(f"Core API unreachable: {exc}")
st.title("🛡️ AgentForge")
st.markdown(
"""
Bienvenido al panel de gobernanza de agentes IA.
Usa la barra lateral para navegar:
- **Registro**: catálogo de agentes y versiones.
- **Ejecutar**: lanzar un agente con guardrails completos.
- **Aprobaciones**: ejecuciones pausadas a la espera de revisión humana.
- **Historial**: trazas, violaciones y resultados.
- **Politicas**: políticas de guardrails y diff entre versiones.
"""
)
if __name__ == "__main__":
main()
+47
View File
@@ -0,0 +1,47 @@
"""Entry point del dashboard Streamlit con sidebar de branding y health."""
from __future__ import annotations
import streamlit as st
from forja_dashboard.client import CoreClient
@st.cache_resource
def get_client() -> CoreClient:
return CoreClient()
def main() -> None:
st.set_page_config(
page_title="Forja",
page_icon="🔨",
layout="wide",
initial_sidebar_state="expanded",
)
st.sidebar.markdown("## 🔨 Forja")
st.sidebar.caption("Forja de agentes IA — gobernanza para cualquier proyecto")
client = get_client()
try:
client.health()
st.sidebar.success("Core API: OK")
except Exception as exc:
st.sidebar.error(f"Core API unreachable: {exc}")
st.title("🔨 Forja")
st.markdown(
"""
Bienvenido a la forja de agentes IA.
Navega por la barra lateral:
- **Registro**: catálogo y versiones.
- **Ejecutar**: invocar con gobierno completo.
- **Aprobaciones** / **Historial**.
- **Politicas**.
- **🔨 Forjar Agente** y **🛡️ Forjar Politica**: editores gráficos (nuevo).
"""
)
if __name__ == "__main__":
main()
@@ -12,7 +12,7 @@ class CoreClient:
"""Cliente sincrono (Streamlit es sync). 2 retries con httpx Transport."""
def __init__(self, base_url: str | None = None, timeout: float = 30.0) -> None:
self.base_url = base_url or os.getenv("AGENTFORGE_CORE_URL", "http://localhost:8000")
self.base_url = base_url or os.getenv("FORJA_CORE_URL", "http://localhost:8000")
transport = httpx.HTTPTransport(retries=2)
self._client = httpx.Client(base_url=self.base_url, timeout=timeout, transport=transport)
@@ -56,6 +56,17 @@ class CoreClient:
def list_policy_versions(self, name: str) -> list[dict]:
return self._get(f"/policies/{name}/versions")
def create_agent_version(self, name: str, agent_def: dict, message: str, author: str = "dashboard") -> dict:
body = {"agent": agent_def, "message": message, "author": author}
return self._post(f"/agents/{name}/versions", body)
def create_policy_version(self, name: str, policy_def: dict, message: str, author: str = "dashboard") -> dict:
body = {"policy": policy_def, "message": message, "author": author}
return self._post(f"/policies/{name}/versions", body)
def list_validators(self) -> dict:
return self._get("/policies/validators")
# ----- helpers privados -----
def _get(self, path: str, params: dict | None = None) -> Any:
@@ -4,8 +4,8 @@ from __future__ import annotations
import streamlit as st
from agentforge_dashboard.client import CoreClient
from agentforge_dashboard.components.diff_view import render_unified_diff
from forja_dashboard.client import CoreClient
from forja_dashboard.components.diff_view import render_unified_diff
@st.cache_resource
@@ -16,6 +16,7 @@ def get_client() -> CoreClient:
client = get_client()
st.title("🏛️ Registro de Agentes")
st.caption("Catálogo central de agentes con versionado tipo Git.")
st.info("Usa **🔨 Forjar Agente** (barra lateral) para crear o versionar gráficamente.")
agents = client.list_agents()
if not agents:
@@ -6,9 +6,9 @@ from pathlib import Path
import streamlit as st
from agentforge_dashboard.client import CoreClient
from agentforge_dashboard.components.trace_view import render_trace
from agentforge_dashboard.components.violation_view import render_violations
from forja_dashboard.client import CoreClient
from forja_dashboard.components.trace_view import render_trace
from forja_dashboard.components.violation_view import render_violations
@st.cache_resource
@@ -27,6 +27,7 @@ if not agents:
names = [a["name"] for a in agents]
agent_name = st.selectbox("Agente", names)
agent = client.get_agent(agent_name)
# Cargar escenarios pregrabados si existen en el disco montado.
examples_dir = Path("agents") / agent_name / "examples"
@@ -40,12 +41,9 @@ with col_r:
st.session_state["input_text"] = ef.read_text(encoding="utf-8")
with col_l:
user_input = st.text_area(
"Descripción del incidente",
height=240,
key="input_text",
placeholder="Pega aquí la descripción del incidente o usa un escenario...",
)
label = agent.get("input_label") or "Input para el agente"
ph = agent.get("input_placeholder") or "Escribe el input para el agente o usa un escenario..."
user_input = st.text_area(label, height=240, key="input_text", placeholder=ph)
if st.button("🚀 Invocar agente", type="primary", disabled=not user_input):
with st.spinner("Ejecutando..."):
@@ -4,7 +4,7 @@ from __future__ import annotations
import streamlit as st
from agentforge_dashboard.client import CoreClient
from forja_dashboard.client import CoreClient
@st.cache_resource
@@ -4,9 +4,9 @@ from __future__ import annotations
import streamlit as st
from agentforge_dashboard.client import CoreClient
from agentforge_dashboard.components.trace_view import render_trace
from agentforge_dashboard.components.violation_view import render_violations
from forja_dashboard.client import CoreClient
from forja_dashboard.components.trace_view import render_trace
from forja_dashboard.components.violation_view import render_violations
@st.cache_resource
@@ -4,7 +4,7 @@ from __future__ import annotations
import streamlit as st
from agentforge_dashboard.client import CoreClient
from forja_dashboard.client import CoreClient
@st.cache_resource
@@ -15,6 +15,7 @@ def get_client() -> CoreClient:
client = get_client()
st.title("📐 Politicas de guardrails")
st.caption("Inventario de políticas y sus versiones (estilo Git).")
st.info("Usa **🛡️ Forjar Politica** (barra lateral) para componer y versionar gráficamente.")
policies = client.list_policies()
if not policies:
@@ -0,0 +1,115 @@
"""Página de editor gráfico para crear o versionar agentes (Forja)."""
from __future__ import annotations
import json
import streamlit as st
from forja_dashboard.client import CoreClient
@st.cache_resource
def get_client() -> CoreClient:
return CoreClient()
client = get_client()
st.title("🔨 Forjar Agente")
st.caption("Editor gráfico. Crea un agente nuevo o una nueva versión de uno existente. Todo se guarda como YAML versionado.")
agents = client.list_agents()
agent_names = [a["name"] for a in agents] if agents else []
mode = st.radio("Modo", ["Nuevo agente", "Nueva versión de existente"], horizontal=True)
base_name = None
if mode == "Nueva versión de existente" and agent_names:
base_name = st.selectbox("Agente base", agent_names)
elif mode == "Nueva versión de existente":
st.warning("No hay agentes. Crea uno nuevo.")
st.stop()
with st.form("forge_agent", clear_on_submit=False):
col1, col2 = st.columns(2)
with col1:
name = st.text_input("Nombre del agente", value=base_name or "mi-nuevo-agente")
version = st.text_input("Versión (ej. v1)", value="v1")
owner = st.text_input("Owner", value="usuario")
state = st.selectbox("Estado", ["draft", "active", "deprecated"], index=1)
with col2:
purpose = st.text_area("Propósito / descripción", value="Agente gobernado por Forja", height=80)
category = st.text_input("Categoría (opcional)", value="")
tags_str = st.text_input("Tags (coma separadas)", value="")
icon = st.text_input("Icono emoji (opcional)", value="🤖")
st.subheader("LLM")
llm_col1, llm_col2 = st.columns(4)
with llm_col1:
provider = st.selectbox("Provider", ["mock", "azure", "openai"], index=0)
with llm_col2:
model = st.text_input("Modelo", value="gpt-4o")
with llm_col3:
temperature = st.number_input("Temperature", 0.0, 2.0, 0.2, 0.1)
with llm_col4:
max_tokens = st.number_input("Max tokens", 1, 128000, 2000)
st.subheader("Prompt y Schema")
system_prompt = st.text_area("System prompt", value="Eres un asistente útil. Responde en JSON según el schema.", height=160)
schema_text = st.text_area("Output schema (JSON)", value=json.dumps({"type": "object", "properties": {"result": {"type": "string"}}}, indent=2), height=120)
st.subheader("Guardrails y HITL")
policies = client.list_policies()
policy_names = [p["name"] for p in policies] if policies else ["default"]
guardrails = st.multiselect("Políticas de guardrails activas", policy_names, default=policy_names[:1] if policy_names else [])
risk = st.slider("Risk threshold para HITL", 1, 5, 4)
st.subheader("Metadatos de versión")
message = st.text_input("Mensaje de versión (como commit)", value="Creación vía editor gráfico de Forja")
author = st.text_input("Autor", value="dashboard")
submitted = st.form_submit_button("🔨 Forjar / Guardar versión", type="primary")
if submitted:
try:
output_schema = json.loads(schema_text)
except Exception as e:
st.error(f"Schema JSON inválido: {e}")
st.stop()
tags = [t.strip() for t in tags_str.split(",") if t.strip()] if tags_str else []
agent_def = {
"name": name,
"version": version,
"owner": owner,
"purpose": purpose,
"state": state,
"guardrails": guardrails or ["default"],
"llm": {
"provider": provider,
"model": model,
"temperature": float(temperature),
"max_tokens": int(max_tokens),
},
"system_prompt": system_prompt,
"output_schema": output_schema,
"risk_threshold_for_hitl": int(risk),
"updated_at": "2026-05-23T00:00:00Z",
"input_label": "Input para el agente",
"input_placeholder": "Escribe aquí el input...",
"category": category or None,
"tags": tags,
"icon": icon or None,
"template": "governed_llm",
}
try:
res = client.create_agent_version(name, agent_def, message, author)
if "api_error" in res:
st.error(f"Error API: {res['api_error']}")
else:
st.success(f"Versión {version} de {name} creada correctamente. Recarga Registro para verla.")
st.json(res)
except Exception as exc:
st.error(f"Error al forjar: {exc}")
@@ -0,0 +1,82 @@
"""Página de editor gráfico para políticas de guardrails (Forja)."""
from __future__ import annotations
import json
import streamlit as st
from forja_dashboard.client import CoreClient
@st.cache_resource
def get_client() -> CoreClient:
return CoreClient()
client = get_client()
st.title("🛡️ Forjar Política")
st.caption("Compositor gráfico de políticas. Añade validadores de entrada/salida, configura y versiona.")
policies = client.list_policies()
policy_names = [p["name"] for p in policies] if policies else []
mode = st.radio("Modo", ["Nueva política", "Nueva versión de existente"], horizontal=True)
base = None
if mode == "Nueva versión de existente" and policy_names:
base = st.selectbox("Política base", policy_names)
validators_meta = client.list_validators()
input_types = validators_meta.get("input", [])
output_types = validators_meta.get("output", [])
def validator_form(stage: str, types: list[str], key_prefix: str) -> list[dict]:
st.markdown(f"**Validadores de {stage}**")
result = []
num = st.number_input(f"Número de validadores {stage}", 0, 12, 2, key=f"num_{key_prefix}")
for i in range(int(num)):
with st.expander(f"Validador {i+1} ({stage})", expanded=True):
t = st.selectbox("Tipo", types, key=f"type_{key_prefix}_{i}")
cfg_text = st.text_area("Config (JSON)", value="{}", key=f"cfg_{key_prefix}_{i}", height=80)
try:
cfg = json.loads(cfg_text)
except Exception:
cfg = {}
result.append({"type": t, "config": cfg})
return result
with st.form("forge_policy"):
name = st.text_input("Nombre de la política", value=base or "mi-politica")
version = st.text_input("Versión", value="v1")
description = st.text_area("Descripción", value="Política creada con el editor gráfico de Forja")
on_error = st.selectbox("on_validator_error", ["fail_closed", "fail_open"], index=0)
st.divider()
input_validators = validator_form("input", input_types, "in")
output_validators = validator_form("output", output_types, "out")
st.subheader("Metadatos de versión")
message = st.text_input("Mensaje", value="Creada vía UI gráfica")
author = st.text_input("Autor", value="dashboard")
submitted = st.form_submit_button("🛡️ Guardar versión de política", type="primary")
if submitted:
policy_def = {
"name": name,
"version": version,
"description": description,
"input_validators": input_validators,
"output_validators": output_validators,
"on_validator_error": on_error,
}
try:
res = client.create_policy_version(name, policy_def, message, author)
if "api_error" in res:
st.error(res["api_error"])
else:
st.success(f"Política {name}@{version} guardada.")
st.json(res)
except Exception as e:
st.error(str(e))