mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 23:39:34 +00:00
345 lines
12 KiB
Python
345 lines
12 KiB
Python
"""
|
|
Orchestrator — THE BRAIN of Dealix.
|
|
Controls the full lead lifecycle: Lead → Qualify → Nurture → Book → Close.
|
|
Decides when to use which agent, when to escalate to humans, and when to move stages.
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
from typing import Optional
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.ai.agent_router import AgentRouter
|
|
from app.ai.llm_provider import LLMProvider
|
|
from app.services.lead_service import LeadService
|
|
from app.services.deal_service import DealService
|
|
from app.services.meeting_service import MeetingService
|
|
from app.services.notification_service import NotificationService
|
|
from app.services.trust_score_service import TrustScoreService
|
|
|
|
|
|
# Lead lifecycle state machine
|
|
LEAD_STATES = {
|
|
"new": {
|
|
"actions": ["qualify", "enrich"],
|
|
"next_states": ["contacted", "lost"],
|
|
"auto_agent": "lead_qualification",
|
|
},
|
|
"contacted": {
|
|
"actions": ["nurture", "follow_up", "qualify"],
|
|
"next_states": ["qualified", "lost"],
|
|
"auto_agent": "outreach_writer",
|
|
},
|
|
"qualified": {
|
|
"actions": ["book_meeting", "send_proposal"],
|
|
"next_states": ["converted", "contacted", "lost"],
|
|
"auto_agent": "meeting_booking",
|
|
},
|
|
"converted": {
|
|
"actions": ["create_deal", "prepare_presentation"],
|
|
"next_states": [],
|
|
"auto_agent": None,
|
|
},
|
|
"lost": {
|
|
"actions": ["re_engage"],
|
|
"next_states": ["new"],
|
|
"auto_agent": None,
|
|
},
|
|
}
|
|
|
|
|
|
class Orchestrator:
|
|
"""
|
|
Central orchestration engine that automates the Lead-to-Meeting pipeline.
|
|
|
|
The Orchestrator:
|
|
1. Receives events (new lead, message, call, etc.)
|
|
2. Determines the current state of the lead
|
|
3. Decides which agent(s) to invoke
|
|
4. Executes the appropriate action
|
|
5. Moves the lead to the next state
|
|
6. Notifies humans when needed
|
|
"""
|
|
|
|
def __init__(self, db: AsyncSession, llm: LLMProvider = None):
|
|
self.db = db
|
|
self.llm = llm or LLMProvider()
|
|
self.router = AgentRouter(db=db, llm=self.llm)
|
|
self.leads = LeadService(db)
|
|
self.deals = DealService(db)
|
|
self.meetings = MeetingService(db)
|
|
self.notifications = NotificationService(db)
|
|
self.trust_scores = TrustScoreService(db)
|
|
|
|
# ── Process New Lead ──────────────────────────
|
|
|
|
async def process_new_lead(self, tenant_id: str, lead_id: str) -> dict:
|
|
"""
|
|
Full automated pipeline for a new lead:
|
|
1. Calculate trust score
|
|
2. AI qualification
|
|
3. If qualified → auto-assign + outreach
|
|
4. If hot → book meeting immediately
|
|
"""
|
|
actions_taken = []
|
|
|
|
# Step 1: Trust Score
|
|
trust = await self.trust_scores.calculate_lead_score(tenant_id, lead_id)
|
|
actions_taken.append({"action": "trust_score", "result": trust})
|
|
|
|
lead = await self.leads.get_lead(tenant_id, lead_id)
|
|
if not lead:
|
|
return {"error": "Lead not found", "actions": actions_taken}
|
|
|
|
# Step 2: AI Qualification
|
|
qual_result = await self.router.route(
|
|
event_type="lead.created",
|
|
event_data={
|
|
"lead": lead,
|
|
"trust_score": trust,
|
|
},
|
|
tenant_id=tenant_id,
|
|
lead_id=lead_id,
|
|
)
|
|
actions_taken.append({"action": "ai_qualification", "result": qual_result})
|
|
|
|
# Extract score from AI response
|
|
ai_score = 50 # default
|
|
if qual_result.get("results"):
|
|
output = qual_result["results"][0].get("output", {})
|
|
ai_score = output.get("qualification_score", output.get("score", 50))
|
|
if isinstance(ai_score, str):
|
|
try:
|
|
ai_score = int(ai_score)
|
|
except (ValueError, TypeError):
|
|
ai_score = 50
|
|
|
|
# Step 3: Update lead
|
|
await self.leads.qualify_lead(tenant_id, lead_id, ai_score)
|
|
|
|
# Step 4: Auto-assign
|
|
if ai_score >= 40:
|
|
assign_result = await self.leads.auto_assign_round_robin(tenant_id, lead_id)
|
|
actions_taken.append({"action": "auto_assign", "result": assign_result})
|
|
|
|
if assign_result and assign_result.get("assigned_to"):
|
|
await self.notifications.notify_new_lead(
|
|
tenant_id, assign_result["assigned_to"], lead["full_name"]
|
|
)
|
|
|
|
# Step 5: Hot lead → immediate meeting booking attempt
|
|
if ai_score >= 80 and trust.get("trust_score", 0) >= 60:
|
|
outreach = await self.router.route(
|
|
event_type="lead.meeting_ready",
|
|
event_data={"lead": lead, "score": ai_score},
|
|
tenant_id=tenant_id,
|
|
lead_id=lead_id,
|
|
)
|
|
actions_taken.append({"action": "meeting_booking_attempt", "result": outreach})
|
|
|
|
# Step 6: Warm lead → nurture sequence
|
|
elif ai_score >= 40:
|
|
nurture = await self.router.route(
|
|
event_type="lead.qualified",
|
|
event_data={"lead": lead, "score": ai_score},
|
|
tenant_id=tenant_id,
|
|
lead_id=lead_id,
|
|
)
|
|
actions_taken.append({"action": "nurture_outreach", "result": nurture})
|
|
|
|
return {
|
|
"lead_id": lead_id,
|
|
"trust_score": trust.get("trust_score", 0),
|
|
"ai_score": ai_score,
|
|
"classification": trust.get("classification", "cold"),
|
|
"actions_taken": actions_taken,
|
|
"next_state": LEAD_STATES.get(lead.get("status", "new"), {}),
|
|
}
|
|
|
|
# ── Handle Inbound Message ────────────────────
|
|
|
|
async def handle_inbound_message(
|
|
self,
|
|
tenant_id: str,
|
|
lead_id: str,
|
|
message: str,
|
|
channel: str = "whatsapp",
|
|
language: str = "ar",
|
|
) -> dict:
|
|
"""
|
|
Process an inbound message from a lead:
|
|
1. Detect language and intent
|
|
2. Route to appropriate conversation agent
|
|
3. Check for buying signals
|
|
4. Auto-escalate if needed
|
|
"""
|
|
lead = await self.leads.get_lead(tenant_id, lead_id)
|
|
if not lead:
|
|
return {"error": "Lead not found"}
|
|
|
|
# Determine event type based on language and channel
|
|
if language == "ar":
|
|
event_type = "message.inbound.whatsapp.ar"
|
|
else:
|
|
event_type = "message.inbound.whatsapp.en"
|
|
|
|
# Execute conversation agent
|
|
result = await self.router.route(
|
|
event_type=event_type,
|
|
event_data={
|
|
"lead": lead,
|
|
"message": message,
|
|
"channel": channel,
|
|
"language": language,
|
|
},
|
|
tenant_id=tenant_id,
|
|
lead_id=lead_id,
|
|
)
|
|
|
|
# Check for meeting readiness in response
|
|
if result.get("results"):
|
|
output = result["results"][0].get("output", {})
|
|
intent = output.get("intent", output.get("detected_intent", ""))
|
|
|
|
if intent in ["book_meeting", "schedule", "meeting", "demo"]:
|
|
# Trigger meeting booking
|
|
booking = await self.router.route(
|
|
event_type="meeting.requested",
|
|
event_data={"lead": lead, "conversation_output": output},
|
|
tenant_id=tenant_id,
|
|
lead_id=lead_id,
|
|
)
|
|
result["meeting_booking"] = booking
|
|
|
|
elif intent in ["pricing", "quote", "proposal"]:
|
|
# Trigger proposal generation
|
|
proposal = await self.router.route(
|
|
event_type="deal.proposal_needed",
|
|
event_data={"lead": lead, "conversation_output": output},
|
|
tenant_id=tenant_id,
|
|
lead_id=lead_id,
|
|
)
|
|
result["proposal"] = proposal
|
|
|
|
# Handle escalations
|
|
if result.get("escalations"):
|
|
for esc in result["escalations"]:
|
|
if lead.get("assigned_to"):
|
|
await self.notifications.notify_escalation(
|
|
tenant_id,
|
|
lead["assigned_to"],
|
|
f"تصعيد من {lead['full_name']}: {esc['reason']}",
|
|
)
|
|
|
|
return result
|
|
|
|
# ── Process Deal Stage Change ─────────────────
|
|
|
|
async def process_deal_update(
|
|
self, tenant_id: str, deal_id: str, new_stage: str
|
|
) -> dict:
|
|
"""Handle deal stage transitions with automated actions."""
|
|
deal = await self.deals.get_deal(tenant_id, deal_id)
|
|
if not deal:
|
|
return {"error": "Deal not found"}
|
|
|
|
actions = []
|
|
|
|
if new_stage == "proposal":
|
|
# Auto-generate proposal
|
|
result = await self.router.route(
|
|
event_type="deal.proposal_needed",
|
|
event_data={"deal": deal},
|
|
tenant_id=tenant_id,
|
|
)
|
|
actions.append({"action": "generate_proposal", "result": result})
|
|
|
|
elif new_stage == "closed_won":
|
|
# Revenue attribution + commission
|
|
result = await self.router.route(
|
|
event_type="deal.closed_won",
|
|
event_data={"deal": deal},
|
|
tenant_id=tenant_id,
|
|
)
|
|
actions.append({"action": "revenue_attribution", "result": result})
|
|
|
|
# Notify
|
|
if deal.get("assigned_to"):
|
|
await self.notifications.notify_deal_won(
|
|
tenant_id,
|
|
deal["assigned_to"],
|
|
deal["title"],
|
|
deal.get("value", 0),
|
|
)
|
|
|
|
await self.deals.move_stage(tenant_id, deal_id, new_stage)
|
|
return {"deal_id": deal_id, "new_stage": new_stage, "actions": actions}
|
|
|
|
# ── Prepare Meeting ───────────────────────────
|
|
|
|
async def prepare_meeting(self, tenant_id: str, meeting_id: str) -> dict:
|
|
"""
|
|
AI-powered meeting preparation:
|
|
1. Company research
|
|
2. Sector strategy
|
|
3. Talking points
|
|
4. Predicted objections
|
|
5. Recommended presentation
|
|
"""
|
|
package = await self.meetings.prepare_meeting_package(tenant_id, meeting_id)
|
|
if not package or not package.get("lead"):
|
|
return {"error": "Meeting or lead not found"}
|
|
|
|
lead = package["lead"]
|
|
|
|
# Get sector strategy
|
|
strategy = await self.router.route(
|
|
event_type="meeting.prep_needed",
|
|
event_data={
|
|
"lead": lead,
|
|
"meeting": package,
|
|
},
|
|
tenant_id=tenant_id,
|
|
lead_id=lead.get("id"),
|
|
)
|
|
|
|
package["ai_preparation"] = strategy
|
|
package["status"] = "ready"
|
|
|
|
return package
|
|
|
|
# ── Daily Automation ──────────────────────────
|
|
|
|
async def run_daily_automation(self, tenant_id: str) -> dict:
|
|
"""
|
|
Daily automated tasks:
|
|
1. Score unscored leads
|
|
2. Follow up on stale leads
|
|
3. Remind about upcoming meetings
|
|
4. Generate management summary
|
|
"""
|
|
results = {}
|
|
|
|
# Score all unscored leads
|
|
score_result = await self.trust_scores.score_all_leads(tenant_id)
|
|
results["scoring"] = score_result
|
|
|
|
# Generate daily summary
|
|
summary = await self.router.route(
|
|
event_type="report.daily",
|
|
event_data={"tenant_id": tenant_id, "type": "daily"},
|
|
tenant_id=tenant_id,
|
|
)
|
|
results["summary"] = summary
|
|
|
|
return results
|
|
|
|
# ── Status ────────────────────────────────────
|
|
|
|
def get_lifecycle_states(self) -> dict:
|
|
return LEAD_STATES
|
|
|
|
def get_supported_events(self) -> list:
|
|
return self.router.get_event_types()
|