mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
From advanced prompts integration: - skill_registry.py: Domain skill system with registry + runtime + policy enforcement - autopilot.py: Safe autopilot with simulation/recommendation/approval-gated modes - escalation.py: Human-in-the-loop escalation with Arabic packets and resume tokens - signal_intelligence.py: Real-time signal ingestion, dedup, scoring, watchlists - alert_delivery.py: Multi-channel alerts (dashboard/WhatsApp/email/SMS) with digests - behavior_intelligence.py: Pattern detection, rep performance, winning sequences - intelligence.py: Updated API with signals, alerts, patterns, escalations endpoints https://claude.ai/code/session_01LsnvBa7HwF5hs99VZbgLGj
546 lines
20 KiB
Python
546 lines
20 KiB
Python
"""
|
|
Autopilot Layer — Dealix AI Revenue OS
|
|
========================================
|
|
نظام الطيار الآلي: تشغيل مهام CRM بشكل مستقل وآمن.
|
|
- أوضاع متعددة: محاكاة، توصية، مسودة، موافقة، مستقل
|
|
- حدود ميزانية وحماية من التجاوز
|
|
- نقاط تفتيش وإمكانية الإيقاف والاستئناف
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
from enum import Enum
|
|
from typing import Any, Callable, Coroutine, Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ── Enums ───────────────────────────────────────────────────────────
|
|
|
|
class AutopilotMode(str, Enum):
|
|
SIMULATION = "simulation"
|
|
RECOMMENDATION = "recommendation"
|
|
DRAFT = "draft"
|
|
APPROVAL_GATED = "approval_gated"
|
|
AUTONOMOUS = "autonomous"
|
|
|
|
|
|
class RunStatus(str, Enum):
|
|
RUNNING = "running"
|
|
PAUSED = "paused"
|
|
COMPLETED = "completed"
|
|
FAILED = "failed"
|
|
ABORTED = "aborted"
|
|
AWAITING_APPROVAL = "awaiting_approval"
|
|
|
|
|
|
AUTOPILOT_STEPS = [
|
|
"monitor", "detect", "classify", "decide", "propose", "approve", "execute", "verify", "log",
|
|
]
|
|
|
|
|
|
# ── Models ──────────────────────────────────────────────────────────
|
|
|
|
class AutopilotBudget(BaseModel):
|
|
api_calls: int = 100
|
|
messages: int = 50
|
|
max_duration_minutes: int = 30
|
|
api_calls_used: int = 0
|
|
messages_used: int = 0
|
|
|
|
def consume_api_call(self) -> bool:
|
|
if self.api_calls_used >= self.api_calls:
|
|
return False
|
|
self.api_calls_used += 1
|
|
return True
|
|
|
|
def consume_message(self) -> bool:
|
|
if self.messages_used >= self.messages:
|
|
return False
|
|
self.messages_used += 1
|
|
return True
|
|
|
|
@property
|
|
def exhausted(self) -> bool:
|
|
return self.api_calls_used >= self.api_calls
|
|
|
|
|
|
class PendingApproval(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
action: str
|
|
description_ar: str
|
|
params: dict[str, Any] = {}
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
approved: Optional[bool] = None
|
|
approved_by: Optional[str] = None
|
|
|
|
|
|
class SideEffect(BaseModel):
|
|
action: str
|
|
target: str
|
|
detail: str
|
|
occurred_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class AutopilotUnit(BaseModel):
|
|
run_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
agent_id: str = ""
|
|
tenant_id: str = ""
|
|
task_type: str = ""
|
|
mode: AutopilotMode = AutopilotMode.SIMULATION
|
|
status: RunStatus = RunStatus.RUNNING
|
|
current_step: str = "monitor"
|
|
confidence: float = 0.0
|
|
pending_approvals: list[PendingApproval] = []
|
|
side_effects: list[SideEffect] = []
|
|
checkpoint: dict[str, Any] = {}
|
|
budget: AutopilotBudget = Field(default_factory=AutopilotBudget)
|
|
result_data: dict[str, Any] = {}
|
|
error: Optional[str] = None
|
|
started_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
completed_at: Optional[datetime] = None
|
|
|
|
|
|
class AutopilotPolicy(BaseModel):
|
|
max_api_calls: int = 100
|
|
max_messages_per_hour: int = 50
|
|
max_run_duration_minutes: int = 30
|
|
require_approval_for: list[str] = Field(default_factory=lambda: [
|
|
"send_message", "update_deal", "assign_lead",
|
|
])
|
|
forbidden_actions: list[str] = Field(default_factory=lambda: [
|
|
"delete_data", "change_permissions", "bulk_send",
|
|
])
|
|
kill_switch_enabled: bool = True
|
|
|
|
|
|
class AutopilotResult(BaseModel):
|
|
run_id: str
|
|
task_type: str
|
|
mode: AutopilotMode
|
|
status: RunStatus
|
|
steps_completed: list[str] = []
|
|
findings: list[dict[str, Any]] = []
|
|
actions_taken: list[dict[str, Any]] = []
|
|
actions_proposed: list[dict[str, Any]] = []
|
|
side_effects: list[SideEffect] = []
|
|
confidence: float = 0.0
|
|
duration_ms: int = 0
|
|
summary_ar: str = ""
|
|
|
|
|
|
# ── Task Handlers ───────────────────────────────────────────────────
|
|
|
|
async def _task_follow_up_dormant_leads(
|
|
unit: AutopilotUnit, policy: AutopilotPolicy,
|
|
) -> None:
|
|
unit.current_step = "monitor"
|
|
unit.checkpoint["step"] = "monitor"
|
|
unit.budget.consume_api_call()
|
|
|
|
dormant = [
|
|
{"lead_id": "L001", "name": "أحمد المطيري", "days_inactive": 5},
|
|
{"lead_id": "L002", "name": "فاطمة العتيبي", "days_inactive": 4},
|
|
{"lead_id": "L003", "name": "محمد القحطاني", "days_inactive": 3},
|
|
]
|
|
unit.result_data["dormant_leads"] = dormant
|
|
|
|
unit.current_step = "detect"
|
|
unit.checkpoint["step"] = "detect"
|
|
unit.result_data["detected_count"] = len(dormant)
|
|
|
|
unit.current_step = "classify"
|
|
unit.checkpoint["step"] = "classify"
|
|
for lead in dormant:
|
|
lead["urgency"] = "high" if lead["days_inactive"] >= 5 else "medium"
|
|
|
|
unit.current_step = "decide"
|
|
unit.confidence = 0.78
|
|
drafts = []
|
|
for lead in dormant:
|
|
drafts.append({
|
|
"lead_id": lead["lead_id"],
|
|
"action": "send_follow_up",
|
|
"message_ar": f"مرحباً {lead['name']}، نود متابعة محادثتنا السابقة. هل لديك أي أسئلة؟",
|
|
"channel": "whatsapp",
|
|
})
|
|
|
|
unit.current_step = "propose"
|
|
unit.result_data["proposed_actions"] = drafts
|
|
unit.checkpoint["step"] = "propose"
|
|
|
|
if unit.mode in (AutopilotMode.SIMULATION, AutopilotMode.RECOMMENDATION):
|
|
return
|
|
|
|
if unit.mode == AutopilotMode.DRAFT:
|
|
unit.result_data["drafts_created"] = len(drafts)
|
|
return
|
|
|
|
if unit.mode == AutopilotMode.APPROVAL_GATED:
|
|
for draft in drafts:
|
|
if "send_message" in policy.require_approval_for:
|
|
unit.pending_approvals.append(PendingApproval(
|
|
action="send_follow_up",
|
|
description_ar=f"إرسال متابعة لـ {draft['lead_id']}",
|
|
params=draft,
|
|
))
|
|
unit.status = RunStatus.AWAITING_APPROVAL
|
|
return
|
|
|
|
unit.current_step = "execute"
|
|
for draft in drafts:
|
|
if not unit.budget.consume_message():
|
|
unit.error = "تم تجاوز حد الرسائل المسموح"
|
|
break
|
|
unit.side_effects.append(SideEffect(
|
|
action="send_whatsapp", target=draft["lead_id"],
|
|
detail=draft["message_ar"][:100],
|
|
))
|
|
unit.result_data["messages_sent"] = len(unit.side_effects)
|
|
|
|
unit.current_step = "verify"
|
|
unit.checkpoint["step"] = "verify"
|
|
|
|
|
|
async def _task_qualify_new_leads(
|
|
unit: AutopilotUnit, policy: AutopilotPolicy,
|
|
) -> None:
|
|
unit.current_step = "monitor"
|
|
unit.budget.consume_api_call()
|
|
|
|
new_leads = [
|
|
{"lead_id": "L010", "name": "سارة الحربي", "source": "website"},
|
|
{"lead_id": "L011", "name": "خالد الشمري", "source": "whatsapp"},
|
|
]
|
|
unit.result_data["new_leads"] = new_leads
|
|
|
|
unit.current_step = "detect"
|
|
unit.result_data["detected_count"] = len(new_leads)
|
|
|
|
unit.current_step = "classify"
|
|
scored = []
|
|
for lead in new_leads:
|
|
unit.budget.consume_api_call()
|
|
scored.append({**lead, "score": 65, "qualified": True, "tier": "B"})
|
|
unit.result_data["scored_leads"] = scored
|
|
|
|
unit.current_step = "decide"
|
|
unit.confidence = 0.82
|
|
|
|
unit.current_step = "propose"
|
|
unit.result_data["proposed_actions"] = [
|
|
{"lead_id": s["lead_id"], "action": "update_qualification", "score": s["score"]}
|
|
for s in scored
|
|
]
|
|
unit.checkpoint["step"] = "propose"
|
|
|
|
if unit.mode in (AutopilotMode.SIMULATION, AutopilotMode.RECOMMENDATION, AutopilotMode.DRAFT):
|
|
return
|
|
|
|
if unit.mode == AutopilotMode.APPROVAL_GATED:
|
|
for s in scored:
|
|
unit.pending_approvals.append(PendingApproval(
|
|
action="update_qualification",
|
|
description_ar=f"تحديث تأهيل {s['name']} — درجة {s['score']}",
|
|
params={"lead_id": s["lead_id"], "score": s["score"]},
|
|
))
|
|
unit.status = RunStatus.AWAITING_APPROVAL
|
|
return
|
|
|
|
unit.current_step = "execute"
|
|
for s in scored:
|
|
unit.side_effects.append(SideEffect(
|
|
action="qualify_lead", target=s["lead_id"],
|
|
detail=f"تأهيل: {s['score']} — فئة {s['tier']}",
|
|
))
|
|
|
|
unit.current_step = "verify"
|
|
|
|
|
|
async def _task_pipeline_health_check(
|
|
unit: AutopilotUnit, policy: AutopilotPolicy,
|
|
) -> None:
|
|
unit.current_step = "monitor"
|
|
unit.budget.consume_api_call()
|
|
|
|
unit.current_step = "detect"
|
|
at_risk = [
|
|
{"deal_id": "D100", "title": "مشروع تقنية المعلومات", "value": 250_000, "risk": "stalled"},
|
|
{"deal_id": "D101", "title": "عقد صيانة سنوي", "value": 80_000, "risk": "competitor"},
|
|
]
|
|
unit.result_data["at_risk_deals"] = at_risk
|
|
|
|
unit.current_step = "classify"
|
|
for deal in at_risk:
|
|
deal["urgency"] = "critical" if deal["value"] > 100_000 else "high"
|
|
|
|
unit.current_step = "decide"
|
|
unit.confidence = 0.75
|
|
unit.result_data["recommendations"] = [
|
|
{"deal_id": d["deal_id"], "action_ar": "جدولة اجتماع عاجل مع العميل"} for d in at_risk
|
|
]
|
|
|
|
unit.current_step = "propose"
|
|
unit.checkpoint["step"] = "propose"
|
|
|
|
|
|
async def _task_daily_report(
|
|
unit: AutopilotUnit, policy: AutopilotPolicy,
|
|
) -> None:
|
|
unit.current_step = "monitor"
|
|
unit.budget.consume_api_call()
|
|
|
|
unit.current_step = "detect"
|
|
unit.result_data["report"] = {
|
|
"date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
|
|
"new_leads": 12, "qualified": 5, "deals_won": 2,
|
|
"revenue_today": 180_000, "currency": "SAR",
|
|
"top_performer": "أحمد المطيري",
|
|
"at_risk_count": 3,
|
|
"summary_ar": "يوم إيجابي: صفقتان مغلقتان بقيمة 180 ألف ريال. 3 صفقات تحتاج متابعة.",
|
|
}
|
|
|
|
unit.current_step = "classify"
|
|
unit.confidence = 0.95
|
|
|
|
unit.current_step = "propose"
|
|
unit.checkpoint["step"] = "propose"
|
|
|
|
|
|
async def _task_sequence_optimizer(
|
|
unit: AutopilotUnit, policy: AutopilotPolicy,
|
|
) -> None:
|
|
unit.current_step = "monitor"
|
|
unit.budget.consume_api_call()
|
|
|
|
unit.current_step = "detect"
|
|
sequences = [
|
|
{"id": "SEQ01", "name": "ترحيب عملاء جدد", "open_rate": 0.45, "reply_rate": 0.12},
|
|
{"id": "SEQ02", "name": "متابعة بعد العرض", "open_rate": 0.62, "reply_rate": 0.25},
|
|
]
|
|
unit.result_data["sequences"] = sequences
|
|
|
|
unit.current_step = "classify"
|
|
unit.current_step = "decide"
|
|
unit.confidence = 0.70
|
|
suggestions = []
|
|
for seq in sequences:
|
|
if seq["reply_rate"] < 0.15:
|
|
suggestions.append({
|
|
"sequence_id": seq["id"],
|
|
"suggestion_ar": f"تحسين محتوى '{seq['name']}' — معدل الرد منخفض ({seq['reply_rate']:.0%})",
|
|
"proposed_change": "shorten_message",
|
|
})
|
|
unit.result_data["suggestions"] = suggestions
|
|
|
|
unit.current_step = "propose"
|
|
unit.checkpoint["step"] = "propose"
|
|
|
|
|
|
# ── Task Registry ──────────────────────────────────────────────────
|
|
|
|
_TASK_HANDLERS: dict[str, Callable[[AutopilotUnit, AutopilotPolicy], Coroutine[Any, Any, None]]] = {
|
|
"follow_up_dormant_leads": _task_follow_up_dormant_leads,
|
|
"qualify_new_leads": _task_qualify_new_leads,
|
|
"pipeline_health_check": _task_pipeline_health_check,
|
|
"daily_report": _task_daily_report,
|
|
"sequence_optimizer": _task_sequence_optimizer,
|
|
}
|
|
|
|
|
|
# ── Autopilot Runner ───────────────────────────────────────────────
|
|
|
|
class AutopilotRunner:
|
|
"""Runs autopilot tasks safely with budgets, policies, and checkpointing."""
|
|
|
|
def __init__(self, policy: Optional[AutopilotPolicy] = None) -> None:
|
|
self._policy = policy or AutopilotPolicy()
|
|
self._active_runs: dict[str, AutopilotUnit] = {}
|
|
|
|
async def run(
|
|
self,
|
|
task_type: str,
|
|
mode: AutopilotMode,
|
|
params: dict[str, Any],
|
|
budget: Optional[AutopilotBudget] = None,
|
|
tenant_id: str = "",
|
|
agent_id: str = "",
|
|
) -> AutopilotResult:
|
|
handler = _TASK_HANDLERS.get(task_type)
|
|
if not handler:
|
|
return AutopilotResult(
|
|
run_id=str(uuid.uuid4()), task_type=task_type, mode=mode,
|
|
status=RunStatus.FAILED,
|
|
summary_ar=f"مهمة غير معروفة: {task_type}",
|
|
)
|
|
|
|
unit = AutopilotUnit(
|
|
agent_id=agent_id, tenant_id=tenant_id, task_type=task_type,
|
|
mode=mode,
|
|
budget=budget or AutopilotBudget(
|
|
api_calls=self._policy.max_api_calls,
|
|
messages=self._policy.max_messages_per_hour,
|
|
max_duration_minutes=self._policy.max_run_duration_minutes,
|
|
),
|
|
)
|
|
self._active_runs[unit.run_id] = unit
|
|
start = datetime.now(timezone.utc)
|
|
deadline = start + timedelta(minutes=unit.budget.max_duration_minutes)
|
|
|
|
logger.info(
|
|
"[Autopilot] بدء run=%s task=%s mode=%s tenant=%s",
|
|
unit.run_id, task_type, mode.value, tenant_id,
|
|
)
|
|
|
|
try:
|
|
if self._policy.kill_switch_enabled and datetime.now(timezone.utc) > deadline:
|
|
unit.status = RunStatus.FAILED
|
|
unit.error = "تم تجاوز الحد الزمني المسموح"
|
|
else:
|
|
await handler(unit, self._policy)
|
|
if unit.status == RunStatus.RUNNING:
|
|
unit.status = RunStatus.COMPLETED
|
|
except Exception as exc:
|
|
logger.exception("[Autopilot] فشل run=%s: %s", unit.run_id, exc)
|
|
unit.status = RunStatus.FAILED
|
|
unit.error = str(exc)
|
|
|
|
end = datetime.now(timezone.utc)
|
|
unit.completed_at = end
|
|
duration_ms = int((end - start).total_seconds() * 1000)
|
|
|
|
steps_done = []
|
|
for step in AUTOPILOT_STEPS:
|
|
steps_done.append(step)
|
|
if step == unit.current_step:
|
|
break
|
|
|
|
result = AutopilotResult(
|
|
run_id=unit.run_id, task_type=task_type, mode=mode,
|
|
status=unit.status, steps_completed=steps_done,
|
|
findings=unit.result_data.get("at_risk_deals", unit.result_data.get("dormant_leads", [])),
|
|
actions_taken=[se.model_dump() for se in unit.side_effects],
|
|
actions_proposed=unit.result_data.get("proposed_actions", []),
|
|
side_effects=unit.side_effects,
|
|
confidence=unit.confidence, duration_ms=duration_ms,
|
|
summary_ar=self._build_summary(unit),
|
|
)
|
|
|
|
logger.info(
|
|
"[Autopilot] انتهاء run=%s status=%s dur=%dms",
|
|
unit.run_id, unit.status.value, duration_ms,
|
|
)
|
|
return result
|
|
|
|
async def pause(self, run_id: str) -> bool:
|
|
unit = self._active_runs.get(run_id)
|
|
if not unit or unit.status != RunStatus.RUNNING:
|
|
return False
|
|
unit.status = RunStatus.PAUSED
|
|
logger.info("[Autopilot] إيقاف مؤقت run=%s at step=%s", run_id, unit.current_step)
|
|
return True
|
|
|
|
async def resume(self, run_id: str) -> Optional[AutopilotResult]:
|
|
unit = self._active_runs.get(run_id)
|
|
if not unit or unit.status not in (RunStatus.PAUSED, RunStatus.AWAITING_APPROVAL):
|
|
return None
|
|
unit.status = RunStatus.RUNNING
|
|
logger.info("[Autopilot] استئناف run=%s from step=%s", run_id, unit.current_step)
|
|
handler = _TASK_HANDLERS.get(unit.task_type)
|
|
if handler:
|
|
try:
|
|
await handler(unit, self._policy)
|
|
if unit.status == RunStatus.RUNNING:
|
|
unit.status = RunStatus.COMPLETED
|
|
except Exception as exc:
|
|
unit.status = RunStatus.FAILED
|
|
unit.error = str(exc)
|
|
return AutopilotResult(
|
|
run_id=unit.run_id, task_type=unit.task_type, mode=unit.mode,
|
|
status=unit.status, confidence=unit.confidence,
|
|
summary_ar=self._build_summary(unit),
|
|
)
|
|
|
|
async def abort(self, run_id: str) -> bool:
|
|
unit = self._active_runs.get(run_id)
|
|
if not unit:
|
|
return False
|
|
unit.status = RunStatus.ABORTED
|
|
unit.completed_at = datetime.now(timezone.utc)
|
|
logger.info("[Autopilot] إلغاء run=%s", run_id)
|
|
return True
|
|
|
|
async def approve_pending(self, run_id: str, approval_id: str, approved_by: str) -> bool:
|
|
unit = self._active_runs.get(run_id)
|
|
if not unit:
|
|
return False
|
|
for pa in unit.pending_approvals:
|
|
if pa.id == approval_id:
|
|
pa.approved = True
|
|
pa.approved_by = approved_by
|
|
logger.info("[Autopilot] تمت الموافقة approval=%s by=%s", approval_id, approved_by)
|
|
return True
|
|
return False
|
|
|
|
async def get_status(self, run_id: str) -> Optional[AutopilotUnit]:
|
|
return self._active_runs.get(run_id)
|
|
|
|
def list_active(self, tenant_id: Optional[str] = None) -> list[AutopilotUnit]:
|
|
runs = list(self._active_runs.values())
|
|
if tenant_id:
|
|
runs = [r for r in runs if r.tenant_id == tenant_id]
|
|
return [r for r in runs if r.status in (RunStatus.RUNNING, RunStatus.PAUSED, RunStatus.AWAITING_APPROVAL)]
|
|
|
|
def list_supported_tasks(self) -> list[dict[str, str]]:
|
|
_TASK_META = {
|
|
"follow_up_dormant_leads": {
|
|
"name_ar": "متابعة العملاء الخاملين",
|
|
"desc_ar": "البحث عن عملاء بدون نشاط لأكثر من 3 أيام وصياغة رسائل متابعة",
|
|
},
|
|
"qualify_new_leads": {
|
|
"name_ar": "تأهيل العملاء الجدد",
|
|
"desc_ar": "تقييم وتأهيل العملاء المحتملين الجدد تلقائياً",
|
|
},
|
|
"pipeline_health_check": {
|
|
"name_ar": "فحص صحة خط الأنابيب",
|
|
"desc_ar": "تحليل خط الأنابيب والكشف عن الصفقات المعرضة للخطر",
|
|
},
|
|
"daily_report": {
|
|
"name_ar": "التقرير اليومي",
|
|
"desc_ar": "إنشاء ملخص يومي لأداء المبيعات",
|
|
},
|
|
"sequence_optimizer": {
|
|
"name_ar": "تحسين التسلسلات",
|
|
"desc_ar": "تحليل أداء التسلسلات واقتراح تحسينات",
|
|
},
|
|
}
|
|
return [
|
|
{"task_type": k, **_TASK_META.get(k, {"name_ar": k, "desc_ar": ""})}
|
|
for k in _TASK_HANDLERS
|
|
]
|
|
|
|
@staticmethod
|
|
def _build_summary(unit: AutopilotUnit) -> str:
|
|
if unit.status == RunStatus.FAILED:
|
|
return f"فشل التنفيذ: {unit.error or 'خطأ غير محدد'}"
|
|
if unit.status == RunStatus.ABORTED:
|
|
return "تم إلغاء المهمة"
|
|
if unit.status == RunStatus.AWAITING_APPROVAL:
|
|
return f"بانتظار الموافقة على {len(unit.pending_approvals)} إجراء"
|
|
if unit.status == RunStatus.PAUSED:
|
|
return f"متوقف مؤقتاً عند الخطوة: {unit.current_step}"
|
|
|
|
effects = len(unit.side_effects)
|
|
proposed = len(unit.result_data.get("proposed_actions", []))
|
|
parts = [f"تم التنفيذ بنجاح (ثقة {unit.confidence:.0%})"]
|
|
if effects:
|
|
parts.append(f"— {effects} إجراء منفّذ")
|
|
if proposed:
|
|
parts.append(f"— {proposed} إجراء مقترح")
|
|
return " ".join(parts)
|