Spaces:
Sleeping
Sleeping
File size: 8,306 Bytes
c33f0cb 566bdbd 97376e1 c33f0cb 566bdbd 97376e1 c33f0cb 97376e1 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd 97376e1 c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb 566bdbd c33f0cb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | import logging
from typing import Dict, Any, Optional, List, Callable
# Import lead_scoring_agent for lead scoring integration
from lead_scoring_agent import score_lead
from data_enrichment import DataEnrichmentManager
class OutreachAgent:
"""
OutreachAgent handles lead intake, message tailoring, and multi-channel delivery.
Integrates lead_scoring_agent to score leads at intake for prioritization and routing.
"""
def __init__(self, username: str, channel_handlers: Optional[Dict[str, Callable]]=None): self.logger = logging.getLogger("OutreachAgent")
self.lead_assets = {}
self.analytics = []
self.channel_handlers = channel_handlers or {}
self.username = username
self.enrichment_manager = DataEnrichmentManager(username)
def intake_lead(self, lead_profile: Dict[str, Any]) -> Dict[str, Any]:
"""
Intake lead with scoring integration as first logic step.
Calls score_lead(profile) to enrich profile with scoring metadata.
"""
# FIRST STEP: Score the lead using lead_scoring_agent
scored_profile = score_lead(lead_profile)
self.logger.info(f"Lead scored: {scored_profile.get('name', 'Unknown')} - Score: {scored_profile.get('lead_score', 'N/A')}")
# Enrich profile with additional data
enriched_profile = self.validate_and_enrich_profile(scored_profile)
self.logger.info(f"Intake for {enriched_profile.get('name', 'Unknown Lead')}")
asset_id = enriched_profile.get('id')
if asset_id:
self.lead_assets[asset_id] = enriched_profile
return enriched_profile
def validate_and_enrich_profile(self, profile: Dict[str, Any]) -> Dict[str, Any]:
# Enrich with historical, persona, engagements, etc.
# Preserve scoring metadata from lead_scoring_agent
# ENRICHMENT STEP: Use DataEnrichmentManager to enrich profile with 14 APIs
try:
enriched_data = self.enrichment_manager.enrich_contact_full(profile)
if enriched_data:
# Merge enriched data into profile (preserve existing fields)
for key, value in enriched_data.items():
if key not in profile or not profile[key]: # Only update if field is missing
profile[key] = value
self.logger.info(f"Profile enriched with {len(enriched_data)} data sources")
except Exception as e:
self.logger.warning(f"Enrichment failed: {e}. Continuing with available data.")
required_fields = ['persona', 'channel_fit']
for field in required_fields:
if field not in profile:
profile[field] = 'unspecified'
# Simulate enrichment logic
profile['validation'] = 'success'
if 'recent_posts' not in profile:
profile['recent_posts'] = []
return profile
def select_template(self, profile: Dict[str, Any]) -> str:
persona = profile.get('persona', 'Generic')
pain = profile.get('pain_opportunity', 'N/A')
buyer_signals = profile.get('buyer_signals', [])
channel = profile.get('channel_fit', 'email')
# Template selection logic (expandable)
template = f"Hi {{name}}, as a {{persona}}, we noticed: {{pain}}. {{buyer_signals}}. Ready to discuss via {{channel}}?"
return template
def tailor_message(self, profile: Dict[str, Any], template: str) -> str:
variables = {
"name": profile.get("name", "there"),
"persona": profile.get("persona", "Professional"),
"pain": profile.get("pain_opportunity", "key considerations"),
"buyer_signals": ', '.join(profile.get("buyer_signals", [])),
"channel": profile.get("channel_fit", "email"),
"context": profile.get("context_snippets", ""),
"recent_posts": '\n'.join(profile.get("recent_posts", [])),
"complaints": ', '.join(profile.get("customer_complaint_tags", [])),
"hooks": ', '.join(profile.get("competitor_industry_hooks", [])),
"urgency": profile.get("urgency_triggers", "")
}
message = template.format(**variables)
# Attach recent posts, urgency, hooks etc with nuance
if variables["context"]:
message += f"\nContext: {variables['context']}"
if variables["recent_posts"]:
message += f"\nRecent posts: {variables['recent_posts']}"
if variables["complaints"]:
message += f"\nCustomer complaints: {variables['complaints']}"
if variables["hooks"]:
message += f"\nIndustry/competitor hooks: {variables['hooks']}"
if variables["urgency"]:
message += f"\nUrgency triggers: {variables['urgency']}"
return message
def deliver(self, profile: Dict[str, Any], message: str, channel: str) -> Dict[str, Any]:
# Channel handler pattern for extensibility
handler = self.channel_handlers.get(channel, self.default_handler)
result = handler(profile, message)
self.logger.info(f"Delivered to {profile.get('name')} via {channel}")
self.update_analytics(profile, channel, result)
return result
def default_handler(self, profile: Dict[str, Any], message: str) -> Dict[str, Any]:
# Simulate sending logic
return {"status": "sent", "channel": profile.get('channel_fit', 'email'), "message": message}
def browser_social_actions(self, profile: Dict[str, Any], action: str, extra: Optional[Dict[str, Any]]=None) -> Dict[str, Any]:
# Simulate browser agent: connect, DM, comment, like, milestone outreach
valid_actions = ["connect", "dm", "comment", "like", "milestone"]
if action not in valid_actions:
raise ValueError(f"Unsupported social action: {action}")
self.logger.info(f"Performed {action} for {profile.get('name')}")
result = {"action": action, "status": "success", "lead": profile.get('id')}
self.update_analytics(profile, "social_action", result)
return result
def update_analytics(self, profile: Dict[str, Any], channel: str, result: Dict[str, Any]):
entry = {
"lead_id": profile.get("id"),
"channel": channel,
"result": result
}
self.analytics.append(entry)
if profile.get("id") in self.lead_assets:
self.lead_assets[profile.get("id")]["last_engagement"] = entry
def feedback_loop(self, profile: Dict[str, Any], feedback: str):
# Store feedback and auto-update asset
pid = profile.get("id")
if pid and pid in self.lead_assets:
self.lead_assets[pid]["feedback"] = feedback
self.logger.info(f"Feedback for {profile.get('name')}: {feedback}")
def get_transcript(self, profile_id: str) -> Dict[str, Any]:
asset = self.lead_assets.get(profile_id)
return {
"profile": asset,
"last_message": asset.get("last_engagement", {}).get("result", {}).get("message", "") if asset else "",
"engagement_stats": [a for a in self.analytics if a.get("lead_id") == profile_id]
}
# Example usage handler for email
def email_handler(profile, message):
# Integrate real email API here
return {"status": "sent_via_email", "recipient": profile.get("email", "n/a"), "message": message}
# Example handlers for expansion for LinkedIn, SMS, browser agent, etc.
def linkedin_handler(profile, message):
# Integrate real LinkedIn API calls here
return {"status": "sent_via_linkedin", "recipient": profile.get("linkedin", "n/a"), "message": message}
def sms_handler(profile, message):
# Integrate real SMS API calls here
return {"status": "sent_via_sms", "recipient": profile.get("phone", "n/a"), "message": message}
# Usage:
# agent = OutreachAgent(channel_handlers={"email": email_handler, "linkedin": linkedin_handler, "sms": sms_handler})
# lead = agent.intake_lead({...})
# template = agent.select_template(lead)
# personalized_msg = agent.tailor_message(lead, template)
# agent.deliver(lead, personalized_msg, channel="email")
# agent.browser_social_actions(lead, "connect") |