Spaces:
Sleeping
Sleeping
Integrate lead_scoring_agent into outreach_agent.py - Add score_lead() as first logic step in intake_lead()
Browse files- outreach_agent.py +28 -13
outreach_agent.py
CHANGED
|
@@ -1,23 +1,40 @@
|
|
| 1 |
import logging
|
| 2 |
from typing import Dict, Any, Optional, List, Callable
|
|
|
|
|
|
|
| 3 |
|
| 4 |
class OutreachAgent:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
def __init__(self, channel_handlers: Optional[Dict[str, Callable]]=None):
|
| 6 |
self.logger = logging.getLogger("OutreachAgent")
|
| 7 |
self.lead_assets = {}
|
| 8 |
self.analytics = []
|
| 9 |
self.channel_handlers = channel_handlers or {}
|
| 10 |
-
|
| 11 |
def intake_lead(self, lead_profile: Dict[str, Any]) -> Dict[str, Any]:
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
self.logger.info(f"Intake for {enriched_profile.get('name', 'Unknown Lead')}")
|
|
|
|
| 14 |
asset_id = enriched_profile.get('id')
|
| 15 |
if asset_id:
|
| 16 |
self.lead_assets[asset_id] = enriched_profile
|
| 17 |
return enriched_profile
|
| 18 |
-
|
| 19 |
def validate_and_enrich_profile(self, profile: Dict[str, Any]) -> Dict[str, Any]:
|
| 20 |
# Enrich with historical, persona, engagements, etc.
|
|
|
|
| 21 |
required_fields = ['persona', 'channel_fit']
|
| 22 |
for field in required_fields:
|
| 23 |
if field not in profile:
|
|
@@ -27,7 +44,7 @@ class OutreachAgent:
|
|
| 27 |
if 'recent_posts' not in profile:
|
| 28 |
profile['recent_posts'] = []
|
| 29 |
return profile
|
| 30 |
-
|
| 31 |
def select_template(self, profile: Dict[str, Any]) -> str:
|
| 32 |
persona = profile.get('persona', 'Generic')
|
| 33 |
pain = profile.get('pain_opportunity', 'N/A')
|
|
@@ -36,7 +53,7 @@ class OutreachAgent:
|
|
| 36 |
# Template selection logic (expandable)
|
| 37 |
template = f"Hi {{name}}, as a {{persona}}, we noticed: {{pain}}. {{buyer_signals}}. Ready to discuss via {{channel}}?"
|
| 38 |
return template
|
| 39 |
-
|
| 40 |
def tailor_message(self, profile: Dict[str, Any], template: str) -> str:
|
| 41 |
variables = {
|
| 42 |
"name": profile.get("name", "there"),
|
|
@@ -63,7 +80,7 @@ class OutreachAgent:
|
|
| 63 |
if variables["urgency"]:
|
| 64 |
message += f"\nUrgency triggers: {variables['urgency']}"
|
| 65 |
return message
|
| 66 |
-
|
| 67 |
def deliver(self, profile: Dict[str, Any], message: str, channel: str) -> Dict[str, Any]:
|
| 68 |
# Channel handler pattern for extensibility
|
| 69 |
handler = self.channel_handlers.get(channel, self.default_handler)
|
|
@@ -71,11 +88,11 @@ class OutreachAgent:
|
|
| 71 |
self.logger.info(f"Delivered to {profile.get('name')} via {channel}")
|
| 72 |
self.update_analytics(profile, channel, result)
|
| 73 |
return result
|
| 74 |
-
|
| 75 |
def default_handler(self, profile: Dict[str, Any], message: str) -> Dict[str, Any]:
|
| 76 |
# Simulate sending logic
|
| 77 |
return {"status": "sent", "channel": profile.get('channel_fit', 'email'), "message": message}
|
| 78 |
-
|
| 79 |
def browser_social_actions(self, profile: Dict[str, Any], action: str, extra: Optional[Dict[str, Any]]=None) -> Dict[str, Any]:
|
| 80 |
# Simulate browser agent: connect, DM, comment, like, milestone outreach
|
| 81 |
valid_actions = ["connect", "dm", "comment", "like", "milestone"]
|
|
@@ -85,7 +102,7 @@ class OutreachAgent:
|
|
| 85 |
result = {"action": action, "status": "success", "lead": profile.get('id')}
|
| 86 |
self.update_analytics(profile, "social_action", result)
|
| 87 |
return result
|
| 88 |
-
|
| 89 |
def update_analytics(self, profile: Dict[str, Any], channel: str, result: Dict[str, Any]):
|
| 90 |
entry = {
|
| 91 |
"lead_id": profile.get("id"),
|
|
@@ -95,14 +112,14 @@ class OutreachAgent:
|
|
| 95 |
self.analytics.append(entry)
|
| 96 |
if profile.get("id") in self.lead_assets:
|
| 97 |
self.lead_assets[profile.get("id")]["last_engagement"] = entry
|
| 98 |
-
|
| 99 |
def feedback_loop(self, profile: Dict[str, Any], feedback: str):
|
| 100 |
# Store feedback and auto-update asset
|
| 101 |
pid = profile.get("id")
|
| 102 |
if pid and pid in self.lead_assets:
|
| 103 |
self.lead_assets[pid]["feedback"] = feedback
|
| 104 |
self.logger.info(f"Feedback for {profile.get('name')}: {feedback}")
|
| 105 |
-
|
| 106 |
def get_transcript(self, profile_id: str) -> Dict[str, Any]:
|
| 107 |
asset = self.lead_assets.get(profile_id)
|
| 108 |
return {
|
|
@@ -112,13 +129,11 @@ class OutreachAgent:
|
|
| 112 |
}
|
| 113 |
|
| 114 |
# Example usage handler for email
|
| 115 |
-
|
| 116 |
def email_handler(profile, message):
|
| 117 |
# Integrate real email API here
|
| 118 |
return {"status": "sent_via_email", "recipient": profile.get("email", "n/a"), "message": message}
|
| 119 |
|
| 120 |
# Example handlers for expansion for LinkedIn, SMS, browser agent, etc.
|
| 121 |
-
|
| 122 |
def linkedin_handler(profile, message):
|
| 123 |
# Integrate real LinkedIn API calls here
|
| 124 |
return {"status": "sent_via_linkedin", "recipient": profile.get("linkedin", "n/a"), "message": message}
|
|
|
|
| 1 |
import logging
|
| 2 |
from typing import Dict, Any, Optional, List, Callable
|
| 3 |
+
# Import lead_scoring_agent for lead scoring integration
|
| 4 |
+
from lead_scoring_agent import score_lead
|
| 5 |
|
| 6 |
class OutreachAgent:
|
| 7 |
+
"""
|
| 8 |
+
OutreachAgent handles lead intake, message tailoring, and multi-channel delivery.
|
| 9 |
+
Integrates lead_scoring_agent to score leads at intake for prioritization and routing.
|
| 10 |
+
"""
|
| 11 |
def __init__(self, channel_handlers: Optional[Dict[str, Callable]]=None):
|
| 12 |
self.logger = logging.getLogger("OutreachAgent")
|
| 13 |
self.lead_assets = {}
|
| 14 |
self.analytics = []
|
| 15 |
self.channel_handlers = channel_handlers or {}
|
| 16 |
+
|
| 17 |
def intake_lead(self, lead_profile: Dict[str, Any]) -> Dict[str, Any]:
|
| 18 |
+
"""
|
| 19 |
+
Intake lead with scoring integration as first logic step.
|
| 20 |
+
Calls score_lead(profile) to enrich profile with scoring metadata.
|
| 21 |
+
"""
|
| 22 |
+
# FIRST STEP: Score the lead using lead_scoring_agent
|
| 23 |
+
scored_profile = score_lead(lead_profile)
|
| 24 |
+
self.logger.info(f"Lead scored: {scored_profile.get('name', 'Unknown')} - Score: {scored_profile.get('lead_score', 'N/A')}")
|
| 25 |
+
|
| 26 |
+
# Enrich profile with additional data
|
| 27 |
+
enriched_profile = self.validate_and_enrich_profile(scored_profile)
|
| 28 |
self.logger.info(f"Intake for {enriched_profile.get('name', 'Unknown Lead')}")
|
| 29 |
+
|
| 30 |
asset_id = enriched_profile.get('id')
|
| 31 |
if asset_id:
|
| 32 |
self.lead_assets[asset_id] = enriched_profile
|
| 33 |
return enriched_profile
|
| 34 |
+
|
| 35 |
def validate_and_enrich_profile(self, profile: Dict[str, Any]) -> Dict[str, Any]:
|
| 36 |
# Enrich with historical, persona, engagements, etc.
|
| 37 |
+
# Preserve scoring metadata from lead_scoring_agent
|
| 38 |
required_fields = ['persona', 'channel_fit']
|
| 39 |
for field in required_fields:
|
| 40 |
if field not in profile:
|
|
|
|
| 44 |
if 'recent_posts' not in profile:
|
| 45 |
profile['recent_posts'] = []
|
| 46 |
return profile
|
| 47 |
+
|
| 48 |
def select_template(self, profile: Dict[str, Any]) -> str:
|
| 49 |
persona = profile.get('persona', 'Generic')
|
| 50 |
pain = profile.get('pain_opportunity', 'N/A')
|
|
|
|
| 53 |
# Template selection logic (expandable)
|
| 54 |
template = f"Hi {{name}}, as a {{persona}}, we noticed: {{pain}}. {{buyer_signals}}. Ready to discuss via {{channel}}?"
|
| 55 |
return template
|
| 56 |
+
|
| 57 |
def tailor_message(self, profile: Dict[str, Any], template: str) -> str:
|
| 58 |
variables = {
|
| 59 |
"name": profile.get("name", "there"),
|
|
|
|
| 80 |
if variables["urgency"]:
|
| 81 |
message += f"\nUrgency triggers: {variables['urgency']}"
|
| 82 |
return message
|
| 83 |
+
|
| 84 |
def deliver(self, profile: Dict[str, Any], message: str, channel: str) -> Dict[str, Any]:
|
| 85 |
# Channel handler pattern for extensibility
|
| 86 |
handler = self.channel_handlers.get(channel, self.default_handler)
|
|
|
|
| 88 |
self.logger.info(f"Delivered to {profile.get('name')} via {channel}")
|
| 89 |
self.update_analytics(profile, channel, result)
|
| 90 |
return result
|
| 91 |
+
|
| 92 |
def default_handler(self, profile: Dict[str, Any], message: str) -> Dict[str, Any]:
|
| 93 |
# Simulate sending logic
|
| 94 |
return {"status": "sent", "channel": profile.get('channel_fit', 'email'), "message": message}
|
| 95 |
+
|
| 96 |
def browser_social_actions(self, profile: Dict[str, Any], action: str, extra: Optional[Dict[str, Any]]=None) -> Dict[str, Any]:
|
| 97 |
# Simulate browser agent: connect, DM, comment, like, milestone outreach
|
| 98 |
valid_actions = ["connect", "dm", "comment", "like", "milestone"]
|
|
|
|
| 102 |
result = {"action": action, "status": "success", "lead": profile.get('id')}
|
| 103 |
self.update_analytics(profile, "social_action", result)
|
| 104 |
return result
|
| 105 |
+
|
| 106 |
def update_analytics(self, profile: Dict[str, Any], channel: str, result: Dict[str, Any]):
|
| 107 |
entry = {
|
| 108 |
"lead_id": profile.get("id"),
|
|
|
|
| 112 |
self.analytics.append(entry)
|
| 113 |
if profile.get("id") in self.lead_assets:
|
| 114 |
self.lead_assets[profile.get("id")]["last_engagement"] = entry
|
| 115 |
+
|
| 116 |
def feedback_loop(self, profile: Dict[str, Any], feedback: str):
|
| 117 |
# Store feedback and auto-update asset
|
| 118 |
pid = profile.get("id")
|
| 119 |
if pid and pid in self.lead_assets:
|
| 120 |
self.lead_assets[pid]["feedback"] = feedback
|
| 121 |
self.logger.info(f"Feedback for {profile.get('name')}: {feedback}")
|
| 122 |
+
|
| 123 |
def get_transcript(self, profile_id: str) -> Dict[str, Any]:
|
| 124 |
asset = self.lead_assets.get(profile_id)
|
| 125 |
return {
|
|
|
|
| 129 |
}
|
| 130 |
|
| 131 |
# Example usage handler for email
|
|
|
|
| 132 |
def email_handler(profile, message):
|
| 133 |
# Integrate real email API here
|
| 134 |
return {"status": "sent_via_email", "recipient": profile.get("email", "n/a"), "message": message}
|
| 135 |
|
| 136 |
# Example handlers for expansion for LinkedIn, SMS, browser agent, etc.
|
|
|
|
| 137 |
def linkedin_handler(profile, message):
|
| 138 |
# Integrate real LinkedIn API calls here
|
| 139 |
return {"status": "sent_via_linkedin", "recipient": profile.get("linkedin", "n/a"), "message": message}
|