Merge PR #369: Standardize agent activity events and update timeline UI
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -9,6 +10,73 @@ from sqlalchemy.orm import Session
|
||||
from models.agent_activity_models import AgentAlert, AgentApprovalRequest, AgentEvent, AgentRun
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentEventPayload:
|
||||
"""Shared schema for agent activity event payloads."""
|
||||
|
||||
phase: Optional[str] = None
|
||||
step: Optional[str] = None
|
||||
tool_name: Optional[str] = None
|
||||
progress_percent: Optional[float] = None
|
||||
input_summary: Optional[str] = None
|
||||
output_summary: Optional[str] = None
|
||||
decision_reason: Optional[str] = None
|
||||
evidence_refs: List[str] = field(default_factory=list)
|
||||
safe_debug: bool = True
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
def build_agent_event_payload(
|
||||
*,
|
||||
phase: Optional[str] = None,
|
||||
step: Optional[str] = None,
|
||||
tool_name: Optional[str] = None,
|
||||
progress_percent: Optional[float] = None,
|
||||
input_summary: Optional[str] = None,
|
||||
output_summary: Optional[str] = None,
|
||||
decision_reason: Optional[str] = None,
|
||||
evidence_refs: Optional[List[str]] = None,
|
||||
safe_debug: bool = True,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
return asdict(
|
||||
AgentEventPayload(
|
||||
phase=phase,
|
||||
step=step,
|
||||
tool_name=tool_name,
|
||||
progress_percent=progress_percent,
|
||||
input_summary=input_summary,
|
||||
output_summary=output_summary,
|
||||
decision_reason=decision_reason,
|
||||
evidence_refs=list(evidence_refs or []),
|
||||
safe_debug=bool(safe_debug),
|
||||
metadata=dict(metadata or {}),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _normalize_event_payload(payload: Optional[Union[Dict[str, Any], AgentEventPayload]]) -> Dict[str, Any]:
|
||||
if payload is None:
|
||||
return build_agent_event_payload()
|
||||
if isinstance(payload, AgentEventPayload):
|
||||
return asdict(payload)
|
||||
if not isinstance(payload, dict):
|
||||
return build_agent_event_payload(output_summary=str(payload)[:2000], safe_debug=False)
|
||||
|
||||
return build_agent_event_payload(
|
||||
phase=payload.get("phase"),
|
||||
step=payload.get("step"),
|
||||
tool_name=payload.get("tool_name"),
|
||||
progress_percent=payload.get("progress_percent"),
|
||||
input_summary=payload.get("input_summary"),
|
||||
output_summary=payload.get("output_summary"),
|
||||
decision_reason=payload.get("decision_reason"),
|
||||
evidence_refs=payload.get("evidence_refs") if isinstance(payload.get("evidence_refs"), list) else [],
|
||||
safe_debug=bool(payload.get("safe_debug", True)),
|
||||
metadata=payload.get("metadata") if isinstance(payload.get("metadata"), dict) else {},
|
||||
)
|
||||
|
||||
|
||||
class AgentActivityService:
|
||||
def __init__(self, db: Session, user_id: str):
|
||||
self.db = db
|
||||
@@ -51,10 +119,11 @@ class AgentActivityService:
|
||||
event_type: str,
|
||||
severity: str = "info",
|
||||
message: Optional[str] = None,
|
||||
payload: Optional[Dict[str, Any]] = None,
|
||||
payload: Optional[Union[Dict[str, Any], AgentEventPayload]] = None,
|
||||
run_id: Optional[int] = None,
|
||||
agent_type: Optional[str] = None,
|
||||
) -> AgentEvent:
|
||||
normalized_payload = _normalize_event_payload(payload)
|
||||
evt = AgentEvent(
|
||||
run_id=run_id,
|
||||
user_id=self.user_id,
|
||||
@@ -62,7 +131,7 @@ class AgentActivityService:
|
||||
event_type=event_type,
|
||||
severity=severity,
|
||||
message=message,
|
||||
payload=payload,
|
||||
payload=normalized_payload,
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
self.db.add(evt)
|
||||
|
||||
@@ -38,7 +38,7 @@ from utils.logger_utils import get_service_logger
|
||||
from services.database import get_session_for_user
|
||||
from services.intelligence.monitoring.semantic_dashboard import RealTimeSemanticMonitor
|
||||
from services.intelligence.agents.safety_framework import get_safety_framework
|
||||
from services.agent_activity_service import AgentActivityService
|
||||
from services.agent_activity_service import AgentActivityService, build_agent_event_payload
|
||||
from services.intelligence.agents.agent_usage_tracking import track_agent_usage_sync
|
||||
import time
|
||||
|
||||
@@ -504,7 +504,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="plan",
|
||||
severity="info",
|
||||
message=(prompt[:2000] if prompt else None),
|
||||
payload={"kind": "prompt"},
|
||||
payload=build_agent_event_payload(phase="planning", step="run_started", tool_name="agent_run", progress_percent=0, input_summary=prompt[:250], output_summary="Agent run initialized", decision_reason="Received run prompt", safe_debug=False, metadata={"kind": "prompt"}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -531,7 +531,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="final_summary",
|
||||
severity="info",
|
||||
message=(str(result)[:2000] if result is not None else None),
|
||||
payload={"kind": "result"},
|
||||
payload=build_agent_event_payload(phase="execution", step="run_completed", tool_name="agent_run", progress_percent=100, output_summary=str(result)[:400] if result is not None else "No output", decision_reason="Run completed", safe_debug=True, metadata={"kind": "result"}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -545,7 +545,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="error",
|
||||
severity="error",
|
||||
message=str(e)[:2000],
|
||||
payload={"kind": "exception"},
|
||||
payload=build_agent_event_payload(phase="execution", step="run_error", tool_name="agent_runtime", output_summary=str(e)[:400], decision_reason="Unhandled exception during run", safe_debug=False, metadata={"kind": "exception"}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -591,7 +591,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="plan",
|
||||
severity="info",
|
||||
message=f"{action.action_type} -> {action.target_resource}",
|
||||
payload={"action": asdict(action)},
|
||||
payload=build_agent_event_payload(phase="planning", step="action_received", tool_name=action.action_type, progress_percent=5, input_summary=f"target={action.target_resource}", output_summary="Action accepted for execution", decision_reason="Start run lifecycle", safe_debug=True, metadata={"action": asdict(action)}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -606,7 +606,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="decision",
|
||||
severity="warning",
|
||||
message="Action failed safety validation",
|
||||
payload={"action_id": action.action_id, "action_type": action.action_type},
|
||||
payload=build_agent_event_payload(phase="validation", step="safety_blocked", tool_name="safety_framework", progress_percent=10, input_summary=action.action_type, output_summary="Action blocked by safety validation", decision_reason="Safety framework rejected action", safe_debug=True, metadata={"action_id": action.action_id, "action_type": action.action_type}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -646,7 +646,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="decision",
|
||||
severity="info",
|
||||
message="Action requires approval",
|
||||
payload={"approval_id": req.id, "action_id": action.action_id},
|
||||
payload=build_agent_event_payload(phase="approval", step="awaiting_user_decision", tool_name=action.action_type, progress_percent=20, input_summary=action.target_resource, output_summary="Approval request created", decision_reason="Action requires human approval", safe_debug=True, metadata={"approval_id": req.id, "action_id": action.action_id}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -671,7 +671,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="progress",
|
||||
severity="info",
|
||||
message="Rollback checkpoint created",
|
||||
payload={"checkpoint_id": checkpoint_id},
|
||||
payload=build_agent_event_payload(phase="safety", step="checkpoint_created", tool_name="rollback_manager", progress_percent=35, output_summary="Rollback checkpoint created", decision_reason="Prepare rollback safety net", safe_debug=True, metadata={"checkpoint_id": checkpoint_id}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -682,7 +682,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="warning",
|
||||
severity="warning",
|
||||
message=str(e)[:2000],
|
||||
payload={"checkpoint": "failed"},
|
||||
payload=build_agent_event_payload(phase="safety", step="checkpoint_failed", tool_name="rollback_manager", progress_percent=30, output_summary="Checkpoint creation failed", decision_reason="Proceeding without checkpoint", safe_debug=False, metadata={"checkpoint": "failed"}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -717,7 +717,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="final_summary",
|
||||
severity="info",
|
||||
message=str(result)[:2000] if result is not None else None,
|
||||
payload={"action_id": action.action_id},
|
||||
payload=build_agent_event_payload(phase="execution", step="completed", tool_name=action.action_type, progress_percent=100, output_summary=str(result)[:400] if result is not None else "No output", decision_reason="Action execution completed", safe_debug=True, metadata={"action_id": action.action_id}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
@@ -768,7 +768,7 @@ class BaseALwrityAgent(ABC):
|
||||
event_type="error",
|
||||
severity="error",
|
||||
message=str(e)[:2000],
|
||||
payload={"action_id": action.action_id, "checkpoint_id": checkpoint_id},
|
||||
payload=build_agent_event_payload(phase="execution", step="failed", tool_name=action.action_type, progress_percent=100, output_summary=str(e)[:400], decision_reason="Exception during action execution", safe_debug=False, metadata={"action_id": action.action_id, "checkpoint_id": checkpoint_id}),
|
||||
run_id=run_record.id,
|
||||
agent_type=self.agent_type,
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from models.daily_workflow_models import DailyWorkflowPlan, DailyWorkflowTask
|
||||
from models.agent_activity_models import AgentAlert
|
||||
from services.agent_activity_service import AgentActivityService
|
||||
from services.agent_activity_service import AgentActivityService, build_agent_event_payload
|
||||
from services.llm_providers.main_text_generation import llm_text_gen
|
||||
from loguru import logger
|
||||
|
||||
@@ -430,7 +430,7 @@ async def generate_agent_enhanced_plan(db: Session, user_id: str, date: str) ->
|
||||
event_type="plan",
|
||||
severity="info",
|
||||
message="Building grounded daily workflow plan",
|
||||
payload={"grounding": grounding},
|
||||
payload=build_agent_event_payload(phase="planning", step="build_grounded_plan", tool_name="llm_text_gen", progress_percent=10, input_summary="Grounding data assembled from onboarding + alerts", output_summary="Preparing daily workflow generation", decision_reason="Need context-aware workflow", evidence_refs=["onboarding_data","recent_agent_alerts"], safe_debug=True, metadata={"grounding": grounding}),
|
||||
run_id=run.id,
|
||||
agent_type="TodayWorkflowGenerator",
|
||||
)
|
||||
@@ -449,7 +449,7 @@ async def generate_agent_enhanced_plan(db: Session, user_id: str, date: str) ->
|
||||
event_type="warning",
|
||||
severity="warning",
|
||||
message=str(e)[:2000],
|
||||
payload={"fallback": True},
|
||||
payload=build_agent_event_payload(phase="generation", step="llm_failed_fallback", tool_name="llm_text_gen", progress_percent=70, output_summary="LLM generation failed, using fallback tasks", decision_reason="Exception during workflow generation", safe_debug=False, metadata={"fallback": True}),
|
||||
run_id=run.id,
|
||||
agent_type="TodayWorkflowGenerator",
|
||||
)
|
||||
@@ -467,7 +467,7 @@ async def generate_agent_enhanced_plan(db: Session, user_id: str, date: str) ->
|
||||
event_type="final_summary",
|
||||
severity="info",
|
||||
message="Daily workflow plan generated",
|
||||
payload={"date": date, "task_count": len(result.get("tasks", []))},
|
||||
payload=build_agent_event_payload(phase="generation", step="workflow_generated", tool_name="llm_text_gen", progress_percent=100, output_summary=f"Generated {len(result.get('tasks', []))} tasks", decision_reason="Workflow assembled successfully", evidence_refs=[date], safe_debug=True, metadata={"date": date, "task_count": len(result.get("tasks", []))}),
|
||||
run_id=run.id,
|
||||
agent_type="TodayWorkflowGenerator",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user