system-prompts-and-models-o.../dealix/auto_client_acquisition/agents/followup.py
2026-05-01 14:03:52 +03:00

119 lines
3.6 KiB
Python

"""
Follow-up Agent — generates time-appropriate follow-up messages.
وكيل المتابعة — يُنشئ رسائل متابعة مناسبة لكل مرحلة.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Any
from auto_client_acquisition.agents.intake import Lead, LeadStatus
from core.agents.base import BaseAgent
from core.config.models import Task
from core.llm.base import Message
from core.prompts import get_prompt
from core.prompts.sales_scripts import get_sales_script
from core.utils import utcnow
@dataclass
class FollowUpPlan:
attempt: int
scheduled_for: datetime
channel: str
body: str
should_pause: bool = False
reason: str = ""
def to_dict(self) -> dict[str, Any]:
return {
"attempt": self.attempt,
"scheduled_for": self.scheduled_for.isoformat(),
"channel": self.channel,
"body": self.body,
"should_pause": self.should_pause,
"reason": self.reason,
}
# Cadence: [days_after_previous_touch] per attempt
DEFAULT_CADENCE_DAYS = [0, 3, 7, 14] # immediate, 3d, 7d, 14d
class FollowUpAgent(BaseAgent):
"""Plans and generates follow-up messages."""
name = "followup"
async def run(
self,
*,
lead: Lead,
attempt: int = 1,
last_touch: datetime | None = None,
history_summary: str = "",
channel: str = "email",
**_: Any,
) -> FollowUpPlan:
"""Generate a follow-up message for the given attempt."""
# Short-circuit if lead is closed/won/lost
if lead.status in (LeadStatus.WON, LeadStatus.LOST, LeadStatus.DISQUALIFIED):
return FollowUpPlan(
attempt=attempt,
scheduled_for=utcnow(),
channel=channel,
body="",
should_pause=True,
reason=f"Lead already in terminal status: {lead.status.value}",
)
# Determine schedule
last_touch = last_touch or utcnow()
days = DEFAULT_CADENCE_DAYS[min(attempt, len(DEFAULT_CADENCE_DAYS) - 1)]
scheduled = last_touch + timedelta(days=days)
# Use canned scripts for attempts 1-2, LLM for bespoke attempt 3+
if attempt == 1:
body = get_sales_script(
"follow_up_1",
locale=lead.locale,
name=lead.contact_name or "",
sector=lead.sector or ("قطاعكم" if lead.locale == "ar" else "your sector"),
)
elif attempt == 2:
body = get_sales_script(
"follow_up_2",
locale=lead.locale,
name=lead.contact_name or "",
)
else:
prompt = get_prompt(
"followup",
attempt=attempt,
history=history_summary or "No prior context",
status=lead.status.value,
locale=lead.locale,
)
response = await self.router.run(
task=Task.PAGE_COPY,
messages=[Message(role="user", content=prompt)],
max_tokens=300,
temperature=0.6,
)
body = response.content.strip()
self.log.info(
"followup_planned",
lead_id=lead.id,
attempt=attempt,
scheduled=scheduled.isoformat(),
)
return FollowUpPlan(
attempt=attempt,
scheduled_for=scheduled,
channel=channel,
body=body,
)