dhisana 0.0.1.dev243__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dhisana/__init__.py +1 -0
- dhisana/cli/__init__.py +1 -0
- dhisana/cli/cli.py +20 -0
- dhisana/cli/datasets.py +27 -0
- dhisana/cli/models.py +26 -0
- dhisana/cli/predictions.py +20 -0
- dhisana/schemas/__init__.py +1 -0
- dhisana/schemas/common.py +399 -0
- dhisana/schemas/sales.py +965 -0
- dhisana/ui/__init__.py +1 -0
- dhisana/ui/components.py +472 -0
- dhisana/utils/__init__.py +1 -0
- dhisana/utils/add_mapping.py +352 -0
- dhisana/utils/agent_tools.py +51 -0
- dhisana/utils/apollo_tools.py +1597 -0
- dhisana/utils/assistant_tool_tag.py +4 -0
- dhisana/utils/built_with_api_tools.py +282 -0
- dhisana/utils/cache_output_tools.py +98 -0
- dhisana/utils/cache_output_tools_local.py +78 -0
- dhisana/utils/check_email_validity_tools.py +717 -0
- dhisana/utils/check_for_intent_signal.py +107 -0
- dhisana/utils/check_linkedin_url_validity.py +209 -0
- dhisana/utils/clay_tools.py +43 -0
- dhisana/utils/clean_properties.py +135 -0
- dhisana/utils/company_utils.py +60 -0
- dhisana/utils/compose_salesnav_query.py +259 -0
- dhisana/utils/compose_search_query.py +759 -0
- dhisana/utils/compose_three_step_workflow.py +234 -0
- dhisana/utils/composite_tools.py +137 -0
- dhisana/utils/dataframe_tools.py +237 -0
- dhisana/utils/domain_parser.py +45 -0
- dhisana/utils/email_body_utils.py +72 -0
- dhisana/utils/email_parse_helpers.py +132 -0
- dhisana/utils/email_provider.py +375 -0
- dhisana/utils/enrich_lead_information.py +933 -0
- dhisana/utils/extract_email_content_for_llm.py +101 -0
- dhisana/utils/fetch_openai_config.py +129 -0
- dhisana/utils/field_validators.py +426 -0
- dhisana/utils/g2_tools.py +104 -0
- dhisana/utils/generate_content.py +41 -0
- dhisana/utils/generate_custom_message.py +271 -0
- dhisana/utils/generate_email.py +278 -0
- dhisana/utils/generate_email_response.py +465 -0
- dhisana/utils/generate_flow.py +102 -0
- dhisana/utils/generate_leads_salesnav.py +303 -0
- dhisana/utils/generate_linkedin_connect_message.py +224 -0
- dhisana/utils/generate_linkedin_response_message.py +317 -0
- dhisana/utils/generate_structured_output_internal.py +462 -0
- dhisana/utils/google_custom_search.py +267 -0
- dhisana/utils/google_oauth_tools.py +727 -0
- dhisana/utils/google_workspace_tools.py +1294 -0
- dhisana/utils/hubspot_clearbit.py +96 -0
- dhisana/utils/hubspot_crm_tools.py +2440 -0
- dhisana/utils/instantly_tools.py +149 -0
- dhisana/utils/linkedin_crawler.py +168 -0
- dhisana/utils/lusha_tools.py +333 -0
- dhisana/utils/mailgun_tools.py +156 -0
- dhisana/utils/mailreach_tools.py +123 -0
- dhisana/utils/microsoft365_tools.py +455 -0
- dhisana/utils/openai_assistant_and_file_utils.py +267 -0
- dhisana/utils/openai_helpers.py +977 -0
- dhisana/utils/openapi_spec_to_tools.py +45 -0
- dhisana/utils/openapi_tool/__init__.py +1 -0
- dhisana/utils/openapi_tool/api_models.py +633 -0
- dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +271 -0
- dhisana/utils/openapi_tool/openapi_tool.py +319 -0
- dhisana/utils/parse_linkedin_messages_txt.py +100 -0
- dhisana/utils/profile.py +37 -0
- dhisana/utils/proxy_curl_tools.py +1226 -0
- dhisana/utils/proxycurl_search_leads.py +426 -0
- dhisana/utils/python_function_to_tools.py +83 -0
- dhisana/utils/research_lead.py +176 -0
- dhisana/utils/sales_navigator_crawler.py +1103 -0
- dhisana/utils/salesforce_crm_tools.py +477 -0
- dhisana/utils/search_router.py +131 -0
- dhisana/utils/search_router_jobs.py +51 -0
- dhisana/utils/sendgrid_tools.py +162 -0
- dhisana/utils/serarch_router_local_business.py +75 -0
- dhisana/utils/serpapi_additional_tools.py +290 -0
- dhisana/utils/serpapi_google_jobs.py +117 -0
- dhisana/utils/serpapi_google_search.py +188 -0
- dhisana/utils/serpapi_local_business_search.py +129 -0
- dhisana/utils/serpapi_search_tools.py +852 -0
- dhisana/utils/serperdev_google_jobs.py +125 -0
- dhisana/utils/serperdev_local_business.py +154 -0
- dhisana/utils/serperdev_search.py +233 -0
- dhisana/utils/smtp_email_tools.py +582 -0
- dhisana/utils/test_connect.py +2087 -0
- dhisana/utils/trasform_json.py +173 -0
- dhisana/utils/web_download_parse_tools.py +189 -0
- dhisana/utils/workflow_code_model.py +5 -0
- dhisana/utils/zoominfo_tools.py +357 -0
- dhisana/workflow/__init__.py +1 -0
- dhisana/workflow/agent.py +18 -0
- dhisana/workflow/flow.py +44 -0
- dhisana/workflow/task.py +43 -0
- dhisana/workflow/test.py +90 -0
- dhisana-0.0.1.dev243.dist-info/METADATA +43 -0
- dhisana-0.0.1.dev243.dist-info/RECORD +102 -0
- dhisana-0.0.1.dev243.dist-info/WHEEL +5 -0
- dhisana-0.0.1.dev243.dist-info/entry_points.txt +2 -0
- dhisana-0.0.1.dev243.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
|
+
from dhisana.schemas.sales import MessageItem
|
|
6
|
+
from dhisana.utils.cache_output_tools import (
|
|
7
|
+
retrieve_output,
|
|
8
|
+
cache_output
|
|
9
|
+
)
|
|
10
|
+
from dhisana.utils.field_validators import normalize_linkedin_url, normalize_salesnav_url
|
|
11
|
+
from dhisana.utils.parse_linkedin_messages_txt import parse_conversation
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
def get_short_id_from_salesnav_url(sn_url: str) -> str:
|
|
16
|
+
import re
|
|
17
|
+
if not sn_url:
|
|
18
|
+
return ""
|
|
19
|
+
if "/sales/lead/" in sn_url:
|
|
20
|
+
pattern = r"linkedin\.com/sales/lead/([^/?#,]+)"
|
|
21
|
+
match = re.search(pattern, sn_url, re.IGNORECASE)
|
|
22
|
+
if not match:
|
|
23
|
+
return ""
|
|
24
|
+
sales_nav_id = re.sub(r"[^\w-]", "", match.group(1))
|
|
25
|
+
# Arbitrary example logic: if it starts with ACw or ACo, strip those and return 8 chars
|
|
26
|
+
if sales_nav_id.startswith("ACw") or sales_nav_id.startswith("ACo"):
|
|
27
|
+
return sales_nav_id[3:11]
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
async def add_mapping_tool(mapping: dict) -> dict:
|
|
31
|
+
"""
|
|
32
|
+
Create a two-way (forward & reverse) cache mapping between:
|
|
33
|
+
- user_linkedin_url (normalized)
|
|
34
|
+
- user_linkedin_salesnav_url (normalized, with short ID extracted if possible)
|
|
35
|
+
|
|
36
|
+
Also store single-direction entries for easy lookup:
|
|
37
|
+
- LN→SN (key: "mapping_ln:<sha_of_normalized_ln>")
|
|
38
|
+
- SN→LN (key: "mapping_sn:<sha_of_short_id_or_full_sn>")
|
|
39
|
+
|
|
40
|
+
The cache content has the actual (raw) user-provided URLs,
|
|
41
|
+
only the keys are lowercased/hashed.
|
|
42
|
+
|
|
43
|
+
Returns a dict with status, message, and data.
|
|
44
|
+
"""
|
|
45
|
+
user_linkedin_url = mapping.get("user_linkedin_url", "").strip()
|
|
46
|
+
user_linkedin_salesnav_url = mapping.get("user_linkedin_salesnav_url", "").strip()
|
|
47
|
+
|
|
48
|
+
if not user_linkedin_url or not user_linkedin_salesnav_url:
|
|
49
|
+
return {
|
|
50
|
+
"status": "ERROR",
|
|
51
|
+
"message": "Both user_linkedin_url and user_linkedin_salesnav_url must be provided."
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Normalize
|
|
55
|
+
ln_url_norm = normalize_linkedin_url(user_linkedin_url)
|
|
56
|
+
sn_url_norm = normalize_salesnav_url(user_linkedin_salesnav_url)
|
|
57
|
+
salesnav_short_id = get_short_id_from_salesnav_url(sn_url_norm)
|
|
58
|
+
|
|
59
|
+
# We'll use short_id in the forward/reverse *mapping strings*
|
|
60
|
+
forward_str = f"{ln_url_norm}→{salesnav_short_id}"
|
|
61
|
+
reverse_str = f"{salesnav_short_id}→{ln_url_norm}"
|
|
62
|
+
|
|
63
|
+
forward_key = "mapping:" + hashlib.sha256(forward_str.encode("utf-8")).hexdigest()
|
|
64
|
+
reverse_key = "mapping:" + hashlib.sha256(reverse_str.encode("utf-8")).hexdigest()
|
|
65
|
+
|
|
66
|
+
# Use the same namespace used later by the single-direction logic
|
|
67
|
+
forward_cached = retrieve_output("tool_mappings_linkedin_id", forward_key)
|
|
68
|
+
if forward_cached:
|
|
69
|
+
# If we found an identical forward mapping, return success
|
|
70
|
+
if (
|
|
71
|
+
forward_cached.get("user_linkedin_url") == ln_url_norm
|
|
72
|
+
and forward_cached.get("user_linkedin_salesnav_url") == sn_url_norm
|
|
73
|
+
):
|
|
74
|
+
return {
|
|
75
|
+
"status": "SUCCESS",
|
|
76
|
+
"message": "Mapping already exists (forward).",
|
|
77
|
+
"data": forward_cached
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
reverse_cached = retrieve_output("tool_mappings_linkedin_id", reverse_key)
|
|
81
|
+
if reverse_cached:
|
|
82
|
+
# If we found an identical reverse mapping, return success
|
|
83
|
+
if (
|
|
84
|
+
reverse_cached.get("user_linkedin_url") == ln_url_norm
|
|
85
|
+
and reverse_cached.get("salesnav_short_id") == salesnav_short_id
|
|
86
|
+
):
|
|
87
|
+
return {
|
|
88
|
+
"status": "SUCCESS",
|
|
89
|
+
"message": "Mapping already exists (reverse).",
|
|
90
|
+
"data": reverse_cached
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Create object for storing in forward & reverse
|
|
94
|
+
serialized_results = {
|
|
95
|
+
"user_linkedin_url": ln_url_norm,
|
|
96
|
+
"user_linkedin_salesnav_url": sn_url_norm,
|
|
97
|
+
"salesnav_short_id": salesnav_short_id
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Store them in the same place we retrieve them from
|
|
101
|
+
cache_output("tool_mappings_linkedin_id", forward_key, serialized_results)
|
|
102
|
+
cache_output("tool_mappings_linkedin_id", reverse_key, serialized_results)
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------------------
|
|
105
|
+
# Additional single-direction keys to enable easy lookups:
|
|
106
|
+
# LN key: "mapping_ln:<sha_of_normalized_ln>"
|
|
107
|
+
# SN key: "mapping_sn:<sha_of_short_id_or_full_sn>"
|
|
108
|
+
# ------------------------------------------------------------------------
|
|
109
|
+
ln_lower = ln_url_norm.lower()
|
|
110
|
+
|
|
111
|
+
# For SN, we prefer the short ID if it's not empty; otherwise fall back to the full URL.
|
|
112
|
+
sn_lookup_value = salesnav_short_id if salesnav_short_id else sn_url_norm
|
|
113
|
+
ln_key = "mapping_ln:" + hashlib.sha256(ln_lower.encode("utf-8")).hexdigest()
|
|
114
|
+
sn_key = "mapping_sn:" + hashlib.sha256(sn_lookup_value.lower().encode("utf-8")).hexdigest()
|
|
115
|
+
|
|
116
|
+
# Cache the raw user-provided (normalized) URLs
|
|
117
|
+
single_direction_data = {
|
|
118
|
+
"raw_linkedin_url": ln_url_norm, # no forced lowercasing in the stored data
|
|
119
|
+
"raw_salesnav_url": sn_url_norm,
|
|
120
|
+
"salesnav_short_id": salesnav_short_id
|
|
121
|
+
}
|
|
122
|
+
logger.info("mapping data cached: %s", single_direction_data)
|
|
123
|
+
|
|
124
|
+
cache_output("tool_mappings_linkedin_id", ln_key, single_direction_data)
|
|
125
|
+
cache_output("tool_mappings_linkedin_id", sn_key, single_direction_data)
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
"status": "SUCCESS",
|
|
129
|
+
"message": "Mapping saved successfully in both directions.",
|
|
130
|
+
"data": serialized_results
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async def get_salesnav_url_for_linkedin_url(raw_ln_url: str) -> Optional[str]:
|
|
134
|
+
"""
|
|
135
|
+
Given a LinkedIn URL, normalize it and look up the corresponding
|
|
136
|
+
*raw* sales nav URL from the single-direction cache.
|
|
137
|
+
Returns None if not found.
|
|
138
|
+
"""
|
|
139
|
+
ln_norm = normalize_linkedin_url(raw_ln_url).lower()
|
|
140
|
+
ln_key = "mapping_ln:" + hashlib.sha256(ln_norm.encode("utf-8")).hexdigest()
|
|
141
|
+
|
|
142
|
+
# Must retrieve from the same namespace we used when caching
|
|
143
|
+
record = retrieve_output("tool_mappings_linkedin_id", ln_key)
|
|
144
|
+
if not record:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
return record.get("raw_salesnav_url")
|
|
148
|
+
|
|
149
|
+
async def get_linkedin_url_for_salesnav_url(raw_sn_url: str) -> Optional[str]:
|
|
150
|
+
"""
|
|
151
|
+
Given a Sales Navigator URL, normalize it and look up the corresponding
|
|
152
|
+
*raw* LinkedIn URL from the single-direction cache.
|
|
153
|
+
Uses the short ID if available; otherwise falls back to the full SN URL.
|
|
154
|
+
Returns None if not found in cache.
|
|
155
|
+
"""
|
|
156
|
+
sn_norm = normalize_salesnav_url(raw_sn_url)
|
|
157
|
+
salesnav_short_id = get_short_id_from_salesnav_url(sn_norm)
|
|
158
|
+
|
|
159
|
+
# Use short ID if present, else the normalized URL
|
|
160
|
+
sn_lookup_value = salesnav_short_id if salesnav_short_id else sn_norm
|
|
161
|
+
sn_key = "mapping_sn:" + hashlib.sha256(sn_lookup_value.lower().encode("utf-8")).hexdigest()
|
|
162
|
+
|
|
163
|
+
record = retrieve_output("tool_mappings_linkedin_id", sn_key)
|
|
164
|
+
if not record:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
return record.get("raw_linkedin_url")
|
|
168
|
+
|
|
169
|
+
async def cache_enriched_lead_info_from_salesnav(lead_info: dict, agent_id: str) -> Dict[str, Any]:
|
|
170
|
+
"""
|
|
171
|
+
Cache lead information using the old SHA-256 approach if and only if:
|
|
172
|
+
- command_name is "get_current_messages" or "send_linkedin_message"
|
|
173
|
+
- user_linkedin_url is present
|
|
174
|
+
Then store a *parsed/serialized* version of the conversation data under:
|
|
175
|
+
"salesnav_lead_messages_raw:<sha256(normalized_url + agent_id)>".
|
|
176
|
+
For other commands, do nothing (ignore).
|
|
177
|
+
"""
|
|
178
|
+
if not lead_info:
|
|
179
|
+
return {"status": "ERROR", "message": "No lead_info provided."}
|
|
180
|
+
|
|
181
|
+
command_name = lead_info.get("command_name", "")
|
|
182
|
+
if command_name not in ["get_current_messages", "send_linkedin_message"]:
|
|
183
|
+
return {"status": "IGNORED", "message": f"No caching for command_name={command_name}"}
|
|
184
|
+
|
|
185
|
+
user_linkedin_url = lead_info.get("user_linkedin_url", "").strip()
|
|
186
|
+
if not user_linkedin_url:
|
|
187
|
+
return {
|
|
188
|
+
"status": "ERROR",
|
|
189
|
+
"message": "Missing user_linkedin_url for caching messages."
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
data_to_store = lead_info.get("data", "")
|
|
193
|
+
if not isinstance(data_to_store, str):
|
|
194
|
+
return {
|
|
195
|
+
"status": "ERROR",
|
|
196
|
+
"message": "Invalid data format; must be raw text for parsing."
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
parsed_messages = parse_conversation(data_to_store)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
return {
|
|
203
|
+
"status": "ERROR",
|
|
204
|
+
"message": f"Failed to parse conversation: {e}"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
sn_norm = normalize_linkedin_url(user_linkedin_url)
|
|
208
|
+
sn_key = "salesnav_lead_messages_raw:" + hashlib.sha256(
|
|
209
|
+
(sn_norm.lower() + agent_id).encode("utf-8")
|
|
210
|
+
).hexdigest()
|
|
211
|
+
|
|
212
|
+
list_of_dicts = [m.dict() for m in parsed_messages]
|
|
213
|
+
cache_output("lead_linkedin_messages_", sn_key, list_of_dicts)
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"status": "SUCCESS",
|
|
217
|
+
"message": f"Cached parsed messages for command={command_name}, url={user_linkedin_url}",
|
|
218
|
+
"parsed_count": len(list_of_dicts),
|
|
219
|
+
"data": list_of_dicts
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async def retrieve_enriched_lead_info_from_salesnav(user_linkedin_url: str, agent_id: str) -> Optional[dict]:
|
|
223
|
+
"""
|
|
224
|
+
Retrieve the data previously cached for "get_current_messages" or
|
|
225
|
+
"send_linkedin_message" under the key:
|
|
226
|
+
"salesnav_lead_messages_raw:<sha256(normalized_url + agent_id)>".
|
|
227
|
+
Returns None if not found.
|
|
228
|
+
"""
|
|
229
|
+
if not user_linkedin_url:
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
sn_norm = normalize_linkedin_url(user_linkedin_url)
|
|
233
|
+
sn_key = "salesnav_lead_messages_raw:" + hashlib.sha256(
|
|
234
|
+
(sn_norm.lower() + agent_id).encode("utf-8")
|
|
235
|
+
).hexdigest()
|
|
236
|
+
|
|
237
|
+
return retrieve_output("tool_mappings", sn_key)
|
|
238
|
+
|
|
239
|
+
async def cache_lead_html_from_salesnav(lead_info: dict, agent_id: str) -> dict:
|
|
240
|
+
"""
|
|
241
|
+
Cache enriched lead information from Sales Navigator for a given lead.
|
|
242
|
+
The cache key is generated from the SHA-256 hash of: normalized(Sales Navigator URL) + agent_id.
|
|
243
|
+
"""
|
|
244
|
+
if not lead_info or not lead_info.get("user_linkedin_url"):
|
|
245
|
+
return {
|
|
246
|
+
"status": "ERROR",
|
|
247
|
+
"message": "Lead information or user_linkedin_url is missing."
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
user_linkedin_url = lead_info.get("user_linkedin_url", "").strip()
|
|
251
|
+
sn_norm = normalize_linkedin_url(user_linkedin_url)
|
|
252
|
+
sn_key = "lead_html_from_salesnav:" + hashlib.sha256(
|
|
253
|
+
(sn_norm.lower() + agent_id).encode("utf-8")
|
|
254
|
+
).hexdigest()
|
|
255
|
+
|
|
256
|
+
cache_output("tool_mappings", sn_key, lead_info)
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
"status": "SUCCESS",
|
|
260
|
+
"message": "Lead information cached successfully.",
|
|
261
|
+
"data": lead_info
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async def retrieve_lead_html_from_salesnav(user_linkedin_url: str, agent_id: str) -> Optional[dict]:
|
|
265
|
+
"""
|
|
266
|
+
Retrieve enriched lead information from Sales Navigator for a given user LinkedIn URL
|
|
267
|
+
using the key: "lead_html_from_salesnav:<sha256(normalized_url + agent_id)>".
|
|
268
|
+
"""
|
|
269
|
+
sn_norm = normalize_linkedin_url(user_linkedin_url)
|
|
270
|
+
sn_key = "lead_html_from_salesnav:" + hashlib.sha256(
|
|
271
|
+
(sn_norm.lower() + agent_id).encode("utf-8")
|
|
272
|
+
).hexdigest()
|
|
273
|
+
|
|
274
|
+
return retrieve_output("tool_mappings", sn_key)
|
|
275
|
+
|
|
276
|
+
async def cache_touchpoint_status(
|
|
277
|
+
lead_info: Dict[str, Any],
|
|
278
|
+
agent_id: str,
|
|
279
|
+
touchpoint_data: Dict[str, Any]
|
|
280
|
+
) -> Dict[str, Any]:
|
|
281
|
+
"""
|
|
282
|
+
Cache TouchPointStatus data for a given lead.
|
|
283
|
+
The cache key is generated from the SHA-256 hash of the normalized LinkedIn URL
|
|
284
|
+
plus the agent_id.
|
|
285
|
+
"""
|
|
286
|
+
if not lead_info or not lead_info.get("user_linkedin_url"):
|
|
287
|
+
return {
|
|
288
|
+
"status": "ERROR",
|
|
289
|
+
"message": "Lead information or user_linkedin_url is missing."
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
user_linkedin_url = lead_info["user_linkedin_url"].strip()
|
|
293
|
+
sn_norm = normalize_linkedin_url(user_linkedin_url)
|
|
294
|
+
sn_key = "touchpoint_status:" + hashlib.sha256((sn_norm.lower() + agent_id).encode("utf-8")).hexdigest()
|
|
295
|
+
|
|
296
|
+
cache_output("touchpoint_status", sn_key, touchpoint_data)
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
"status": "SUCCESS",
|
|
300
|
+
"message": "TouchPointStatus cached successfully.",
|
|
301
|
+
"data": touchpoint_data
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async def retrieve_touchpoint_status(
|
|
305
|
+
user_linkedin_url: str,
|
|
306
|
+
agent_id: str
|
|
307
|
+
):
|
|
308
|
+
"""
|
|
309
|
+
Retrieve TouchPointStatus data from the cache for a given user LinkedIn URL
|
|
310
|
+
and agent_id.
|
|
311
|
+
"""
|
|
312
|
+
sn_norm = normalize_linkedin_url(user_linkedin_url.strip())
|
|
313
|
+
sn_key = "touchpoint_status:" + hashlib.sha256((sn_norm.lower() + agent_id).encode("utf-8")).hexdigest()
|
|
314
|
+
|
|
315
|
+
cached_data = retrieve_output("touchpoint_status", sn_key)
|
|
316
|
+
return cached_data
|
|
317
|
+
|
|
318
|
+
async def retrieve_connection_status(
|
|
319
|
+
user_linkedin_url: str,
|
|
320
|
+
agent_id: str
|
|
321
|
+
):
|
|
322
|
+
"""
|
|
323
|
+
Retrieve minimal "connection status" from the TouchPointStatus cache.
|
|
324
|
+
"""
|
|
325
|
+
sn_norm = normalize_linkedin_url(user_linkedin_url.strip())
|
|
326
|
+
sn_key = "touchpoint_status:" + hashlib.sha256((sn_norm.lower() + agent_id).encode("utf-8")).hexdigest()
|
|
327
|
+
|
|
328
|
+
cached_data = retrieve_output("touchpoint_status", sn_key) or {}
|
|
329
|
+
connection_degree = cached_data.get("connection_degree", "")
|
|
330
|
+
connection_status = {
|
|
331
|
+
"connection_degree": connection_degree,
|
|
332
|
+
"connection_request_status": cached_data.get("connection_request_status", ""),
|
|
333
|
+
"is_connected_on_linkedin": connection_degree == "1st"
|
|
334
|
+
}
|
|
335
|
+
return connection_status
|
|
336
|
+
|
|
337
|
+
async def get_lead_linkedin_messages(user_linkedin_url: str, agent_id: str) -> Optional[list]:
|
|
338
|
+
"""
|
|
339
|
+
Retrieve parsed LinkedIn messages for a given user LinkedIn URL.
|
|
340
|
+
The cache key includes agent_id in the SHA-256 hash.
|
|
341
|
+
"""
|
|
342
|
+
sn_norm = normalize_linkedin_url(user_linkedin_url)
|
|
343
|
+
sn_key = "salesnav_lead_messages_raw:" + hashlib.sha256(
|
|
344
|
+
(sn_norm + agent_id).encode("utf-8")
|
|
345
|
+
).hexdigest()
|
|
346
|
+
|
|
347
|
+
list_of_dicts = retrieve_output("lead_linkedin_messages_", sn_key) or []
|
|
348
|
+
messages: List[MessageItem] = []
|
|
349
|
+
for message in list_of_dicts:
|
|
350
|
+
message_item = MessageItem(**message)
|
|
351
|
+
messages.append(message_item)
|
|
352
|
+
return messages
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Global List of tools that can be used in the assistant
|
|
2
|
+
# Only functions marked with @assistant_tool will be available in the allowed list
|
|
3
|
+
# This is in addition to the tools from OpenAPI Spec add to allowed tools
|
|
4
|
+
# These tools are loaded in the agent like below
|
|
5
|
+
|
|
6
|
+
# async def load_global_tool_function():
|
|
7
|
+
# # Iterate over all modules in the package
|
|
8
|
+
# for loader, module_name, is_pkg in pkgutil.walk_packages(global_tools.__path__):
|
|
9
|
+
# module = importlib.import_module(f"{global_tools.__name__}.{module_name}")
|
|
10
|
+
# for name in dir(module):
|
|
11
|
+
# func = getattr(module, name)
|
|
12
|
+
# if callable(func) and getattr(func, 'is_assistant_tool', False):
|
|
13
|
+
# GLOBAL_TOOLS_FUNCTIONS[name] = func
|
|
14
|
+
|
|
15
|
+
# Global Data Models Used for Data Extraction. These can be referenced in workflows.
|
|
16
|
+
GLOBAL_DATA_MODELS = []
|
|
17
|
+
|
|
18
|
+
# Global Functions used in workflows. Like CRM, Email, etc. They can be invoked as Python functions.
|
|
19
|
+
GLOBAL_TOOLS_FUNCTIONS = {}
|
|
20
|
+
|
|
21
|
+
# GLOBAL_TOOLS_FUNCTIONS in OPENAI function spec format
|
|
22
|
+
# src/dhisana/utils/agent_tools.py
|
|
23
|
+
|
|
24
|
+
# Global List of tools that can be used in the assistant
|
|
25
|
+
# Only functions marked with @assistant_tool will be available in the allowed list
|
|
26
|
+
# This is in addition to the tools from OpenAPI Spec add to allowed tools
|
|
27
|
+
# These tools are loaded in the agent like below
|
|
28
|
+
|
|
29
|
+
# async def load_global_tool_function():
|
|
30
|
+
# # Iterate over all modules in the package
|
|
31
|
+
# for loader, module_name, is_pkg in pkgutil.walk_packages(global_tools.__path__):
|
|
32
|
+
# module = importlib.import_module(f"{global_tools.__name__}.{module_name}")
|
|
33
|
+
# for name in dir(module):
|
|
34
|
+
# func = getattr(module, name)
|
|
35
|
+
# if callable(func) and getattr(func, 'is_assistant_tool', False):
|
|
36
|
+
# GLOBAL_TOOLS_FUNCTIONS[name] = func
|
|
37
|
+
|
|
38
|
+
# Ensure GLOBAL_DATA_MODELS is only initialized once
|
|
39
|
+
if 'GLOBAL_DATA_MODELS' not in globals():
|
|
40
|
+
GLOBAL_DATA_MODELS = []
|
|
41
|
+
|
|
42
|
+
# Ensure GLOBAL_TOOLS_FUNCTIONS is only initialized once
|
|
43
|
+
if 'GLOBAL_TOOLS_FUNCTIONS' not in globals():
|
|
44
|
+
GLOBAL_TOOLS_FUNCTIONS = {}
|
|
45
|
+
|
|
46
|
+
# Ensure GLOBAL_OPENAI_ASSISTANT_TOOLS is only initialized once
|
|
47
|
+
if 'GLOBAL_OPENAI_ASSISTANT_TOOLS' not in globals():
|
|
48
|
+
GLOBAL_OPENAI_ASSISTANT_TOOLS = []
|
|
49
|
+
|
|
50
|
+
if 'GLOBAL_TOOLS_CACHE_PATH' not in globals():
|
|
51
|
+
GLOBAL_TOOLS_CACHE_PATH = '/tmp/dhisana_ai'
|