Spaces:
Sleeping
Sleeping
Create research_agent.py
Browse files- research_agent.py +104 -0
research_agent.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import csv
|
| 2 |
+
import json
|
| 3 |
+
import logging
|
| 4 |
+
from typing import Any, Dict, List
|
| 5 |
+
|
| 6 |
+
class ResearchAgent:
|
| 7 |
+
"""
|
| 8 |
+
Collects, augments, audits, and outputs lead and signal data for outreach,
|
| 9 |
+
with robust error/memory handling and dynamic variable mapping.
|
| 10 |
+
"""
|
| 11 |
+
def __init__(self, db: Any = None, schema: Dict[str, Any] = None):
|
| 12 |
+
self.db = db
|
| 13 |
+
self.schema = schema or {}
|
| 14 |
+
self.memory = []
|
| 15 |
+
self.errors = []
|
| 16 |
+
self.profiles = []
|
| 17 |
+
self.refined = []
|
| 18 |
+
self.mcp_updates = []
|
| 19 |
+
logging.basicConfig(level=logging.INFO)
|
| 20 |
+
|
| 21 |
+
def map_raw_data(self, raw_lead: Dict[str, Any], signals: Dict[str, Any], dynamic_vars: Dict[str, Any]) -> Dict[str, Any]:
|
| 22 |
+
mapped = {}
|
| 23 |
+
try:
|
| 24 |
+
for field, rule in (self.schema.get('lead', {})).items():
|
| 25 |
+
mapped[field] = raw_lead.get(field, rule.get('default'))
|
| 26 |
+
mapped.update(signals)
|
| 27 |
+
mapped.update(dynamic_vars)
|
| 28 |
+
except Exception as e:
|
| 29 |
+
self.errors.append(f"Mapping error: {e}")
|
| 30 |
+
logging.error(f"Mapping error: {e}")
|
| 31 |
+
return mapped
|
| 32 |
+
|
| 33 |
+
def handle_loop_errors(self, data_list: List[Dict[str, Any]]):
|
| 34 |
+
for idx, entry in enumerate(data_list):
|
| 35 |
+
try:
|
| 36 |
+
assert all(entry.values()), f"Missing value in {entry}"
|
| 37 |
+
except Exception as e:
|
| 38 |
+
self.errors.append(f"Loop error at {idx}: {e}")
|
| 39 |
+
logging.warning(f"Loop error at {idx}: {e}")
|
| 40 |
+
|
| 41 |
+
def memory_management(self, current: Dict[str, Any]):
|
| 42 |
+
self.memory.append(current)
|
| 43 |
+
if len(self.memory) > 50:
|
| 44 |
+
dropped = self.memory.pop(0)
|
| 45 |
+
logging.info(f"Dropped old memory: {dropped}")
|
| 46 |
+
|
| 47 |
+
def refine_profile(self, profile: Dict[str, Any], messaging_signals: Dict[str, Any]) -> Dict[str, Any]:
|
| 48 |
+
"""Output the most outreach-ready version of the profile."""
|
| 49 |
+
refined = {**profile, **messaging_signals}
|
| 50 |
+
self.refined.append(refined)
|
| 51 |
+
return refined
|
| 52 |
+
|
| 53 |
+
def handoff(self, profiles: List[Dict[str, Any]], target: str = 'csv', path: str = 'handoff.csv'):
|
| 54 |
+
"""Wire/export to csv/mcp/db."""
|
| 55 |
+
try:
|
| 56 |
+
if target == 'csv':
|
| 57 |
+
with open(path, 'w', newline='') as f:
|
| 58 |
+
writer = csv.DictWriter(f, fieldnames=profiles[0].keys())
|
| 59 |
+
writer.writeheader()
|
| 60 |
+
writer.writerows(profiles)
|
| 61 |
+
elif target == 'mcp' and self.db:
|
| 62 |
+
for p in profiles:
|
| 63 |
+
self.db.update_profile(p)
|
| 64 |
+
elif target == 'db' and self.db:
|
| 65 |
+
self.db.bulk_insert(profiles)
|
| 66 |
+
self.mcp_updates.extend(profiles)
|
| 67 |
+
logging.info(f"Profiles handed off to {target}")
|
| 68 |
+
except Exception as e:
|
| 69 |
+
self.errors.append(f"Handoff error: {e}")
|
| 70 |
+
logging.error(f"Handoff error: {e}")
|
| 71 |
+
|
| 72 |
+
def audit(self, profiles: List[Dict[str, Any]]):
|
| 73 |
+
"""Audit field completeness, error/correction, and handoff readiness."""
|
| 74 |
+
required_fields = self.schema.get('required', set())
|
| 75 |
+
for idx, profile in enumerate(profiles):
|
| 76 |
+
missing = [f for f in required_fields if not profile.get(f)]
|
| 77 |
+
if missing:
|
| 78 |
+
self.errors.append(f"Profile {idx} missing: {missing}")
|
| 79 |
+
logging.warning(f"Profile {idx} missing: {missing}")
|
| 80 |
+
|
| 81 |
+
def export_refined_for_outreach(self, out_path: str = 'refined_for_outreach.csv'):
|
| 82 |
+
try:
|
| 83 |
+
with open(out_path, 'w', newline='') as f:
|
| 84 |
+
writer = csv.DictWriter(f, fieldnames=self.refined[0].keys())
|
| 85 |
+
writer.writeheader()
|
| 86 |
+
writer.writerows(self.refined)
|
| 87 |
+
except Exception as e:
|
| 88 |
+
self.errors.append(f"Export error: {e}")
|
| 89 |
+
logging.error(f"Export error: {e}")
|
| 90 |
+
|
| 91 |
+
def get_messaging_recommendations(self, profile: Dict[str, Any]) -> Dict[str, Any]:
|
| 92 |
+
"""Stub: Replace with your ML/model recommendation logic."""
|
| 93 |
+
recs = {"recommendation": f"Personalize outreach for {profile.get('name', 'Unknown')}"}
|
| 94 |
+
return recs
|
| 95 |
+
|
| 96 |
+
def run_pipeline(self, leads, signals_list, dynamic_vars_list):
|
| 97 |
+
for raw_lead, signals, dynamic_vars in zip(leads, signals_list, dynamic_vars_list):
|
| 98 |
+
mapped = self.map_raw_data(raw_lead, signals, dynamic_vars)
|
| 99 |
+
self.memory_management(mapped)
|
| 100 |
+
self.profiles.append(mapped)
|
| 101 |
+
messaging = self.get_messaging_recommendations(mapped)
|
| 102 |
+
refined = self.refine_profile(mapped, messaging)
|
| 103 |
+
self.audit(self.profiles)
|
| 104 |
+
self.handoff(self.refined)
|