system-prompts-and-models-o.../salesflow-saas/backend/app/api/v1/hermes.py

286 lines
10 KiB
Python

"""
Hermes API -- Dealix AI Revenue OS -- واجهة هيرمس البرمجية
Orchestrator endpoints: execute tasks, manage profiles, view costs,
run security scans, approve improvements, and generate executive summaries.
"""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from app.services.hermes_orchestrator import hermes_orchestrator, HermesProfile
from app.services.execution_router import execution_router, TaskClass
from app.services.shannon_security import shannon_security, ShannonScope
from app.services.self_improvement import self_improvement_engine
from app.services.observability import observability_service
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/hermes", tags=["Hermes Orchestrator"])
# ---------------------------------------------------------------------------
# Request / response schemas
# ---------------------------------------------------------------------------
class ExecuteRequest(BaseModel):
profile_id: str = "founder"
task: str
params: Dict[str, Any] = Field(default_factory=dict)
user_context: Dict[str, Any] = Field(default_factory=lambda: {
"user_id": "api_user", "tenant_id": "default", "role": "owner",
})
class ExecuteResponse(BaseModel):
run_id: str
profile_id: str
task: str
status: str
backend: str = ""
data: Dict[str, Any] = {}
evidence: List[str] = []
receipt_id: Optional[str] = None
cost_usd: float = 0.0
duration_ms: int = 0
error: Optional[str] = None
message_ar: str = ""
class ScanRequest(BaseModel):
environment: str = "staging"
scopes: List[str] = Field(
default_factory=lambda: ["auth", "injection", "pdpl"],
)
base_url: str = "https://staging.dealix.sa"
class ApproveRequest(BaseModel):
user_id: str = "founder"
# ---------------------------------------------------------------------------
# Execute
# ---------------------------------------------------------------------------
@router.post("/execute", response_model=ExecuteResponse)
async def execute_task(req: ExecuteRequest) -> ExecuteResponse:
"""Execute a task via the Hermes orchestrator."""
result = await hermes_orchestrator.execute(
profile_id=req.profile_id,
task=req.task,
params=req.params,
user_context=req.user_context,
)
return ExecuteResponse(**result.model_dump())
# ---------------------------------------------------------------------------
# Profiles
# ---------------------------------------------------------------------------
@router.get("/profiles")
async def list_profiles() -> JSONResponse:
"""List all available Hermes profiles."""
profiles = await hermes_orchestrator.list_profiles()
return JSONResponse(content={
"profiles": [p.model_dump() for p in profiles],
"count": len(profiles),
"message_ar": f"{len(profiles)} ملفات شخصية متاحة",
})
@router.get("/profiles/{profile_id}")
async def get_profile(profile_id: str) -> JSONResponse:
"""Get details for a specific profile."""
profile = await hermes_orchestrator.get_profile(profile_id)
if not profile:
raise HTTPException(status_code=404, detail=f"الملف الشخصي غير موجود: {profile_id}")
return JSONResponse(content=profile.model_dump())
# ---------------------------------------------------------------------------
# Cost
# ---------------------------------------------------------------------------
@router.get("/cost")
async def cost_report(period: str = "daily") -> JSONResponse:
"""Get cost report from the orchestrator and observability service."""
hermes_cost = await hermes_orchestrator.get_cost_report(period)
obs_cost = await observability_service.get_cost_report(period)
return JSONResponse(content={
"hermes": hermes_cost,
"observability": obs_cost,
})
# ---------------------------------------------------------------------------
# Health
# ---------------------------------------------------------------------------
@router.get("/health")
async def health_report() -> JSONResponse:
"""System health report across all backends and workflows."""
obs_health = await observability_service.get_health_report()
backend_health = await execution_router.get_backend_health()
anomalies = await observability_service.detect_anomalies()
return JSONResponse(content={
"system": obs_health,
"backends": {k: v.model_dump() for k, v in backend_health.items()},
"anomalies": [a.model_dump(mode="json") for a in anomalies],
"anomaly_count": len(anomalies),
})
# ---------------------------------------------------------------------------
# Runs
# ---------------------------------------------------------------------------
@router.get("/runs")
async def list_active_runs() -> JSONResponse:
"""List currently active runs."""
runs = await hermes_orchestrator.get_active_runs()
return JSONResponse(content={
"active_runs": [r.model_dump(mode="json") for r in runs],
"count": len(runs),
"message_ar": f"{len(runs)} عمليات نشطة حالياً",
})
@router.post("/runs/{run_id}/abort")
async def abort_run(run_id: str) -> JSONResponse:
"""Abort an active run."""
success = await hermes_orchestrator.abort_run(run_id)
if not success:
raise HTTPException(status_code=404, detail=f"التشغيل غير موجود أو اكتمل بالفعل: {run_id}")
return JSONResponse(content={
"run_id": run_id,
"status": "aborted",
"message_ar": f"تم إلغاء التشغيل: {run_id}",
})
# ---------------------------------------------------------------------------
# Self-improvement
# ---------------------------------------------------------------------------
@router.get("/improvements")
async def list_improvements(status: Optional[str] = None) -> JSONResponse:
"""List self-improvement proposals."""
proposals = self_improvement_engine.list_proposals(status)
report = await self_improvement_engine.report()
return JSONResponse(content={
"proposals": [p.model_dump(mode="json") for p in proposals],
"count": len(proposals),
"summary": report,
})
@router.post("/improvements/{proposal_id}/approve")
async def approve_improvement(proposal_id: str, req: ApproveRequest) -> JSONResponse:
"""Approve a self-improvement proposal."""
proposal = await self_improvement_engine.approve(proposal_id, req.user_id)
if not proposal:
raise HTTPException(
status_code=404,
detail=f"المقترح غير موجود أو ليس في حالة 'مقترح': {proposal_id}",
)
return JSONResponse(content={
"proposal_id": proposal.id,
"status": proposal.status,
"approved_by": proposal.approved_by,
"message_ar": f"تمت الموافقة على المقترح: {proposal.title_ar}",
})
@router.post("/improvements/cycle")
async def run_improvement_cycle(tenant_id: Optional[str] = None) -> JSONResponse:
"""Trigger a self-improvement cycle."""
cycle = await self_improvement_engine.run_cycle(tenant_id)
return JSONResponse(content=cycle.model_dump(mode="json"))
# ---------------------------------------------------------------------------
# Shannon security
# ---------------------------------------------------------------------------
@router.get("/security/report")
async def latest_security_report() -> JSONResponse:
"""Get the latest Shannon security scan report."""
report = shannon_security.get_latest_report()
if not report:
return JSONResponse(content={
"report": None,
"message_ar": "لا يوجد تقرير أمني بعد. قم بتشغيل فحص أولاً.",
})
return JSONResponse(content=report.model_dump(mode="json"))
@router.post("/security/scan")
async def trigger_security_scan(req: ScanRequest) -> JSONResponse:
"""Trigger a Shannon security scan (staging only)."""
scopes = []
for s in req.scopes:
try:
scopes.append(ShannonScope(s))
except ValueError:
raise HTTPException(
status_code=400,
detail=f"نطاق غير صالح: {s}. المتاحة: {[x.value for x in ShannonScope]}",
)
report = await shannon_security.run_scan(
environment=req.environment,
scopes=scopes,
base_url=req.base_url,
)
return JSONResponse(content=report.model_dump(mode="json"))
# ---------------------------------------------------------------------------
# Executive summary
# ---------------------------------------------------------------------------
@router.get("/executive-summary")
async def executive_summary(period: str = "weekly") -> JSONResponse:
"""Arabic executive summary for leadership."""
summary = await observability_service.get_executive_summary(period)
improvement_report = await self_improvement_engine.report()
security_report = shannon_security.get_latest_report()
full_summary = summary
if improvement_report.get("total_proposals", 0) > 0:
applied = improvement_report.get("by_status", {}).get("applied", 0)
pending = improvement_report.get("by_status", {}).get("proposed", 0)
full_summary += f"، {applied} تحسين مطبق، {pending} تحسين معلق"
if security_report:
full_summary += (
f"، آخر فحص أمني: {security_report.critical_count} حرجة، "
f"{security_report.high_count} عالية"
)
else:
full_summary += "، لا يوجد فحص أمني بعد"
return JSONResponse(content={
"summary_ar": full_summary,
"period": period,
"improvements": improvement_report,
"security_status": security_report.model_dump(mode="json") if security_report else None,
})
# ---------------------------------------------------------------------------
# Routing stats
# ---------------------------------------------------------------------------
@router.get("/routing-stats")
async def routing_stats() -> JSONResponse:
"""Execution routing statistics."""
stats = await execution_router.get_routing_stats()
return JSONResponse(content=stats)