stackConsult commited on
Commit
c33f0cb
·
verified ·
1 Parent(s): c75a06a

Create outreach_agent.py

Browse files
Files changed (1) hide show
  1. outreach_agent.py +136 -0
outreach_agent.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ enriched_profile = self.validate_and_enrich_profile(lead_profile)
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:
24
+ profile[field] = 'unspecified'
25
+ # Simulate enrichment logic
26
+ profile['validation'] = 'success'
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')
34
+ buyer_signals = profile.get('buyer_signals', [])
35
+ channel = profile.get('channel_fit', 'email')
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"),
43
+ "persona": profile.get("persona", "Professional"),
44
+ "pain": profile.get("pain_opportunity", "key considerations"),
45
+ "buyer_signals": ', '.join(profile.get("buyer_signals", [])),
46
+ "channel": profile.get("channel_fit", "email"),
47
+ "context": profile.get("context_snippets", ""),
48
+ "recent_posts": '\n'.join(profile.get("recent_posts", [])),
49
+ "complaints": ', '.join(profile.get("customer_complaint_tags", [])),
50
+ "hooks": ', '.join(profile.get("competitor_industry_hooks", [])),
51
+ "urgency": profile.get("urgency_triggers", "")
52
+ }
53
+ message = template.format(**variables)
54
+ # Attach recent posts, urgency, hooks etc with nuance
55
+ if variables["context"]:
56
+ message += f"\nContext: {variables['context']}"
57
+ if variables["recent_posts"]:
58
+ message += f"\nRecent posts: {variables['recent_posts']}"
59
+ if variables["complaints"]:
60
+ message += f"\nCustomer complaints: {variables['complaints']}"
61
+ if variables["hooks"]:
62
+ message += f"\nIndustry/competitor hooks: {variables['hooks']}"
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)
70
+ result = handler(profile, message)
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"]
82
+ if action not in valid_actions:
83
+ raise ValueError(f"Unsupported social action: {action}")
84
+ self.logger.info(f"Performed {action} for {profile.get('name')}")
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"),
92
+ "channel": channel,
93
+ "result": result
94
+ }
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 {
109
+ "profile": asset,
110
+ "last_message": asset.get("last_engagement", {}).get("result", {}).get("message", "") if asset else "",
111
+ "engagement_stats": [a for a in self.analytics if a.get("lead_id") == profile_id]
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}
125
+
126
+ def sms_handler(profile, message):
127
+ # Integrate real SMS API calls here
128
+ return {"status": "sent_via_sms", "recipient": profile.get("phone", "n/a"), "message": message}
129
+
130
+ # Usage:
131
+ # agent = OutreachAgent(channel_handlers={"email": email_handler, "linkedin": linkedin_handler, "sms": sms_handler})
132
+ # lead = agent.intake_lead({...})
133
+ # template = agent.select_template(lead)
134
+ # personalized_msg = agent.tailor_message(lead, template)
135
+ # agent.deliver(lead, personalized_msg, channel="email")
136
+ # agent.browser_social_actions(lead, "connect")