feat(dashboard): página Ejecutar con trace timeline y violations rendering
- components/trace_view.render_trace: decision_path como timeline con duración. - components/violation_view.render_violations: violaciones con badge de severidad. - pages/2_▶️_Ejecutar.py: selector de agente, botones de escenarios pregrabados, textarea, invoke y render de status/output/violations/traza. Aviso si queda en awaiting_approval. (Se omite la variable muerta chosen_example del plan.) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
"""Renderiza el decision_path como timeline."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
|
||||||
|
def render_trace(decision_path: list[dict]) -> None:
|
||||||
|
if not decision_path:
|
||||||
|
st.info("Aún no hay traza disponible.")
|
||||||
|
return
|
||||||
|
st.subheader("📜 Decision path")
|
||||||
|
for step in decision_path:
|
||||||
|
with st.container(border=True):
|
||||||
|
cols = st.columns([2, 1, 1])
|
||||||
|
with cols[0]:
|
||||||
|
st.markdown(f"**{step['step']}**")
|
||||||
|
with cols[1]:
|
||||||
|
st.caption(step.get("timestamp", "—"))
|
||||||
|
with cols[2]:
|
||||||
|
st.metric("ms", step.get("duration_ms", 0), label_visibility="collapsed")
|
||||||
|
if step.get("detail"):
|
||||||
|
with st.expander("detalle"):
|
||||||
|
st.json(step["detail"])
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""Renderiza violaciones de guardrails con badges de severidad."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
_SEV_BADGE = {
|
||||||
|
"info": "🟦",
|
||||||
|
"warning": "🟧",
|
||||||
|
"block": "🟥",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def render_violations(violations: list[dict]) -> None:
|
||||||
|
if not violations:
|
||||||
|
st.success("Sin violaciones.")
|
||||||
|
return
|
||||||
|
st.subheader("🛡️ Violaciones de guardrails")
|
||||||
|
for v in violations:
|
||||||
|
with st.container(border=True):
|
||||||
|
badge = _SEV_BADGE.get(v["severity"], "⬜")
|
||||||
|
st.markdown(
|
||||||
|
f"{badge} **{v['validator']}** — `{v['stage']}` — "
|
||||||
|
f"severity=`{v['severity']}` — blocked=`{v['blocked']}`"
|
||||||
|
)
|
||||||
|
st.caption(v["message"])
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"""Página para invocar un agente y visualizar la traza completa."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@st.cache_resource
|
||||||
|
def get_client() -> CoreClient:
|
||||||
|
return CoreClient()
|
||||||
|
|
||||||
|
|
||||||
|
client = get_client()
|
||||||
|
st.title("▶️ Ejecutar Agente")
|
||||||
|
st.caption("Lanza una ejecución con la cadena completa de gobierno.")
|
||||||
|
|
||||||
|
agents = client.list_agents()
|
||||||
|
if not agents:
|
||||||
|
st.warning("No hay agentes registrados.")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
names = [a["name"] for a in agents]
|
||||||
|
agent_name = st.selectbox("Agente", names)
|
||||||
|
|
||||||
|
# Cargar escenarios pregrabados si existen en el disco montado.
|
||||||
|
examples_dir = Path("agents") / agent_name / "examples"
|
||||||
|
example_files = sorted(examples_dir.glob("*.txt")) if examples_dir.exists() else []
|
||||||
|
|
||||||
|
col_l, col_r = st.columns([3, 1])
|
||||||
|
with col_r:
|
||||||
|
st.markdown("**Escenarios**")
|
||||||
|
for ef in example_files:
|
||||||
|
if st.button(ef.stem, use_container_width=True):
|
||||||
|
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...",
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🚀 Invocar agente", type="primary", disabled=not user_input):
|
||||||
|
with st.spinner("Ejecutando..."):
|
||||||
|
result = client.invoke_agent(agent_name, {"input": user_input})
|
||||||
|
st.session_state["last_execution"] = result
|
||||||
|
|
||||||
|
execution = st.session_state.get("last_execution")
|
||||||
|
if execution and "error" not in execution:
|
||||||
|
st.divider()
|
||||||
|
st.markdown(f"**trace_id:** `{execution['trace_id']}`")
|
||||||
|
st.markdown(f"**Status:** `{execution['status']}`")
|
||||||
|
|
||||||
|
if execution["status"] == "awaiting_approval":
|
||||||
|
st.warning(
|
||||||
|
"Esta ejecución requiere aprobación humana. "
|
||||||
|
"Ve a la página **Aprobaciones** para revisar y decidir."
|
||||||
|
)
|
||||||
|
if execution.get("final_output"):
|
||||||
|
st.subheader("📦 Output final")
|
||||||
|
st.json(execution["final_output"])
|
||||||
|
render_violations(execution.get("violations", []))
|
||||||
|
render_trace(execution.get("decision_path", []))
|
||||||
|
elif execution and "error" in execution:
|
||||||
|
st.error(f"Error de la API: {execution['error']}")
|
||||||
Reference in New Issue
Block a user