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.
Files changed (102) hide show
  1. dhisana/__init__.py +1 -0
  2. dhisana/cli/__init__.py +1 -0
  3. dhisana/cli/cli.py +20 -0
  4. dhisana/cli/datasets.py +27 -0
  5. dhisana/cli/models.py +26 -0
  6. dhisana/cli/predictions.py +20 -0
  7. dhisana/schemas/__init__.py +1 -0
  8. dhisana/schemas/common.py +399 -0
  9. dhisana/schemas/sales.py +965 -0
  10. dhisana/ui/__init__.py +1 -0
  11. dhisana/ui/components.py +472 -0
  12. dhisana/utils/__init__.py +1 -0
  13. dhisana/utils/add_mapping.py +352 -0
  14. dhisana/utils/agent_tools.py +51 -0
  15. dhisana/utils/apollo_tools.py +1597 -0
  16. dhisana/utils/assistant_tool_tag.py +4 -0
  17. dhisana/utils/built_with_api_tools.py +282 -0
  18. dhisana/utils/cache_output_tools.py +98 -0
  19. dhisana/utils/cache_output_tools_local.py +78 -0
  20. dhisana/utils/check_email_validity_tools.py +717 -0
  21. dhisana/utils/check_for_intent_signal.py +107 -0
  22. dhisana/utils/check_linkedin_url_validity.py +209 -0
  23. dhisana/utils/clay_tools.py +43 -0
  24. dhisana/utils/clean_properties.py +135 -0
  25. dhisana/utils/company_utils.py +60 -0
  26. dhisana/utils/compose_salesnav_query.py +259 -0
  27. dhisana/utils/compose_search_query.py +759 -0
  28. dhisana/utils/compose_three_step_workflow.py +234 -0
  29. dhisana/utils/composite_tools.py +137 -0
  30. dhisana/utils/dataframe_tools.py +237 -0
  31. dhisana/utils/domain_parser.py +45 -0
  32. dhisana/utils/email_body_utils.py +72 -0
  33. dhisana/utils/email_parse_helpers.py +132 -0
  34. dhisana/utils/email_provider.py +375 -0
  35. dhisana/utils/enrich_lead_information.py +933 -0
  36. dhisana/utils/extract_email_content_for_llm.py +101 -0
  37. dhisana/utils/fetch_openai_config.py +129 -0
  38. dhisana/utils/field_validators.py +426 -0
  39. dhisana/utils/g2_tools.py +104 -0
  40. dhisana/utils/generate_content.py +41 -0
  41. dhisana/utils/generate_custom_message.py +271 -0
  42. dhisana/utils/generate_email.py +278 -0
  43. dhisana/utils/generate_email_response.py +465 -0
  44. dhisana/utils/generate_flow.py +102 -0
  45. dhisana/utils/generate_leads_salesnav.py +303 -0
  46. dhisana/utils/generate_linkedin_connect_message.py +224 -0
  47. dhisana/utils/generate_linkedin_response_message.py +317 -0
  48. dhisana/utils/generate_structured_output_internal.py +462 -0
  49. dhisana/utils/google_custom_search.py +267 -0
  50. dhisana/utils/google_oauth_tools.py +727 -0
  51. dhisana/utils/google_workspace_tools.py +1294 -0
  52. dhisana/utils/hubspot_clearbit.py +96 -0
  53. dhisana/utils/hubspot_crm_tools.py +2440 -0
  54. dhisana/utils/instantly_tools.py +149 -0
  55. dhisana/utils/linkedin_crawler.py +168 -0
  56. dhisana/utils/lusha_tools.py +333 -0
  57. dhisana/utils/mailgun_tools.py +156 -0
  58. dhisana/utils/mailreach_tools.py +123 -0
  59. dhisana/utils/microsoft365_tools.py +455 -0
  60. dhisana/utils/openai_assistant_and_file_utils.py +267 -0
  61. dhisana/utils/openai_helpers.py +977 -0
  62. dhisana/utils/openapi_spec_to_tools.py +45 -0
  63. dhisana/utils/openapi_tool/__init__.py +1 -0
  64. dhisana/utils/openapi_tool/api_models.py +633 -0
  65. dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +271 -0
  66. dhisana/utils/openapi_tool/openapi_tool.py +319 -0
  67. dhisana/utils/parse_linkedin_messages_txt.py +100 -0
  68. dhisana/utils/profile.py +37 -0
  69. dhisana/utils/proxy_curl_tools.py +1226 -0
  70. dhisana/utils/proxycurl_search_leads.py +426 -0
  71. dhisana/utils/python_function_to_tools.py +83 -0
  72. dhisana/utils/research_lead.py +176 -0
  73. dhisana/utils/sales_navigator_crawler.py +1103 -0
  74. dhisana/utils/salesforce_crm_tools.py +477 -0
  75. dhisana/utils/search_router.py +131 -0
  76. dhisana/utils/search_router_jobs.py +51 -0
  77. dhisana/utils/sendgrid_tools.py +162 -0
  78. dhisana/utils/serarch_router_local_business.py +75 -0
  79. dhisana/utils/serpapi_additional_tools.py +290 -0
  80. dhisana/utils/serpapi_google_jobs.py +117 -0
  81. dhisana/utils/serpapi_google_search.py +188 -0
  82. dhisana/utils/serpapi_local_business_search.py +129 -0
  83. dhisana/utils/serpapi_search_tools.py +852 -0
  84. dhisana/utils/serperdev_google_jobs.py +125 -0
  85. dhisana/utils/serperdev_local_business.py +154 -0
  86. dhisana/utils/serperdev_search.py +233 -0
  87. dhisana/utils/smtp_email_tools.py +582 -0
  88. dhisana/utils/test_connect.py +2087 -0
  89. dhisana/utils/trasform_json.py +173 -0
  90. dhisana/utils/web_download_parse_tools.py +189 -0
  91. dhisana/utils/workflow_code_model.py +5 -0
  92. dhisana/utils/zoominfo_tools.py +357 -0
  93. dhisana/workflow/__init__.py +1 -0
  94. dhisana/workflow/agent.py +18 -0
  95. dhisana/workflow/flow.py +44 -0
  96. dhisana/workflow/task.py +43 -0
  97. dhisana/workflow/test.py +90 -0
  98. dhisana-0.0.1.dev243.dist-info/METADATA +43 -0
  99. dhisana-0.0.1.dev243.dist-info/RECORD +102 -0
  100. dhisana-0.0.1.dev243.dist-info/WHEEL +5 -0
  101. dhisana-0.0.1.dev243.dist-info/entry_points.txt +2 -0
  102. dhisana-0.0.1.dev243.dist-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+ # Read API keys from environment variables
2
+ def assistant_tool(func):
3
+ func.is_assistant_tool = True
4
+ return func
@@ -0,0 +1,282 @@
1
+ import json
2
+ import os
3
+ import aiohttp
4
+ from typing import Optional
5
+ import os
6
+ import aiohttp
7
+ import backoff
8
+ from dhisana.utils.cache_output_tools import cache_output,retrieve_output
9
+ from dhisana.utils.assistant_tool_tag import assistant_tool
10
+ from typing import Optional
11
+ from typing import Optional, List, Dict
12
+
13
+ def get_builtwith_api_key(tool_config: Optional[List[Dict]] = None) -> str:
14
+ """
15
+ Retrieves the BUILTWITH_API_KEY access token from the provided tool configuration.
16
+
17
+ Args:
18
+ tool_config (list): A list of dictionaries containing the tool configuration.
19
+ Each dictionary should have a "name" key and a "configuration" key,
20
+ where "configuration" is a list of dictionaries containing "name" and "value" keys.
21
+
22
+ Returns:
23
+ str: The BUILTWITH_API_KEY access token.
24
+
25
+ Raises:
26
+ ValueError: If the BuiltWith integration has not been configured.
27
+ """
28
+ if tool_config:
29
+ builtwith_config = next(
30
+ (item for item in tool_config if item.get("name") == "builtwith"), None
31
+ )
32
+ if builtwith_config:
33
+ config_map = {
34
+ item["name"]: item["value"]
35
+ for item in builtwith_config.get("configuration", [])
36
+ if item
37
+ }
38
+ BUILTWITH_API_KEY = config_map.get("apiKey")
39
+ else:
40
+ BUILTWITH_API_KEY = None
41
+ else:
42
+ BUILTWITH_API_KEY = None
43
+
44
+ BUILTWITH_API_KEY = BUILTWITH_API_KEY or os.getenv("BUILTWITH_API_KEY")
45
+ if not BUILTWITH_API_KEY:
46
+ raise ValueError(
47
+ "BuiltWith integration is not configured. Please configure the connection to BuiltWith in Integrations."
48
+ )
49
+ return BUILTWITH_API_KEY
50
+
51
+ # Use BuiltWith API to find tech stack and financials of a company
52
+ @assistant_tool
53
+ @backoff.on_exception(
54
+ backoff.expo,
55
+ aiohttp.ClientResponseError,
56
+ max_tries=2,
57
+ giveup=lambda e: e.status != 429,
58
+ factor=10,
59
+ )
60
+ async def get_company_info_from_builtwith(
61
+ company_domain: Optional[str] = None,
62
+ tool_config: Optional[List[Dict]] = None,
63
+ ):
64
+ """
65
+ Fetch a company's technology details from BuiltWith using the company domain.
66
+
67
+ Parameters:
68
+ - **company_domain** (*str*, optional): Domain of the company.
69
+
70
+ Returns:
71
+ - **dict**: JSON response containing technology information.
72
+ """
73
+ BUILTWITH_API_KEY = get_builtwith_api_key(tool_config=tool_config)
74
+
75
+ if not company_domain:
76
+ return {'error': "Company domain must be provided"}
77
+
78
+ headers = {
79
+ "Content-Type": "application/json",
80
+ "Cache-Control": "no-cache",
81
+ "accept": "application/json"
82
+ }
83
+
84
+ cached_response = retrieve_output("get_company_info_from_builtwith", company_domain) # Replace with your caching logic if needed
85
+ if cached_response is not None:
86
+ return cached_response
87
+
88
+ url = f'https://api.builtwith.com/v19/api.json?KEY={BUILTWITH_API_KEY}&LOOKUP={company_domain}'
89
+
90
+ async with aiohttp.ClientSession() as session:
91
+ async with session.get(url, headers=headers) as response:
92
+ if response.status == 200:
93
+ result = await response.json()
94
+ cache_output("get_company_info_from_builtwith", company_domain, result) # Replace with your caching logic if needed
95
+ return result
96
+ elif response.status == 429:
97
+ raise aiohttp.ClientResponseError(
98
+ request_info=response.request_info,
99
+ history=response.history,
100
+ status=response.status,
101
+ message="Rate limit exceeded",
102
+ headers=response.headers
103
+ )
104
+ else:
105
+ try:
106
+ result = await response.json()
107
+ return {'error': result}
108
+ except Exception as e:
109
+ return {'error': f"Unexpected error: {str(e)}"}
110
+
111
+ @assistant_tool
112
+ @backoff.on_exception(
113
+ backoff.expo,
114
+ aiohttp.ClientResponseError,
115
+ max_tries=2,
116
+ giveup=lambda e: e.status != 429,
117
+ factor=10,
118
+ )
119
+ async def get_company_financials_from_builtwith(
120
+ company_domain: Optional[str] = None,
121
+ tool_config: Optional[List[Dict]] = None,
122
+ ):
123
+ """
124
+ Fetch a company's financial details from BuiltWith using the company domain.
125
+
126
+ Parameters:
127
+ - **company_domain** (*str*, optional): Domain of the company.
128
+
129
+ Returns:
130
+ - **dict**: JSON response containing financial information.
131
+ """
132
+ BUILTWITH_API_KEY = get_builtwith_api_key(tool_config=tool_config)
133
+
134
+ if not company_domain:
135
+ return {'error': "Company domain must be provided"}
136
+
137
+ headers = {
138
+ "Content-Type": "application/json",
139
+ "Cache-Control": "no-cache",
140
+ "accept": "application/json"
141
+ }
142
+
143
+ cached_response = retrieve_output("get_company_financials_from_builtwith", company_domain) # Replace with your caching logic if needed
144
+ if cached_response is not None:
145
+ return cached_response
146
+
147
+ url = f'https://api.builtwith.com/v19/financial.json?KEY={BUILTWITH_API_KEY}&LOOKUP={company_domain}'
148
+
149
+ async with aiohttp.ClientSession() as session:
150
+ async with session.get(url, headers=headers) as response:
151
+ if response.status == 200:
152
+ result = await response.json()
153
+ cache_output("get_company_financials_from_builtwith", company_domain, result) # Replace with your caching logic if needed
154
+ return result
155
+ elif response.status == 429:
156
+ raise aiohttp.ClientResponseError(
157
+ request_info=response.request_info,
158
+ history=response.history,
159
+ status=response.status,
160
+ message="Rate limit exceeded",
161
+ headers=response.headers
162
+ )
163
+ else:
164
+ try:
165
+ result = await response.json()
166
+ return {'error': result}
167
+ except Exception as e:
168
+ return {'error': f"Unexpected error: {str(e)}"}
169
+
170
+ @assistant_tool
171
+ @backoff.on_exception(
172
+ backoff.expo,
173
+ aiohttp.ClientResponseError,
174
+ max_tries=3,
175
+ giveup=lambda e: e.status != 429,
176
+ factor=10,
177
+ )
178
+ async def get_company_info_from_builtwith_by_name(company_name: str, tool_config: Optional[List[Dict]] = None):
179
+ """
180
+ Fetch a company's technology details from BuiltWith using the company name.
181
+
182
+ Parameters:
183
+ - company_name (str): Name of the company.
184
+
185
+ Returns:
186
+ - dict: JSON response containing technology information or error details.
187
+ """
188
+ BUILTWITH_API_KEY = get_builtwith_api_key(tool_config=tool_config)
189
+
190
+ if not company_name:
191
+ return {'error': "Company name must be provided"}
192
+
193
+ headers = {
194
+ "Content-Type": "application/json",
195
+ "Cache-Control": "no-cache",
196
+ "Accept": "application/json"
197
+ }
198
+
199
+ # Step 1: Use Company To URL API to get the company's domain
200
+ company_to_url_api = f'https://ctu.builtwith.com/ctu2/api.json?KEY={BUILTWITH_API_KEY}&COMPANY={company_name}'
201
+ company_info = retrieve_output("get_company_info_from_builtwith_by_name", company_name)
202
+ if company_info:
203
+ company_domain = company_info.get('Domain')
204
+ return await get_company_info_from_builtwith(company_domain)
205
+
206
+ async with aiohttp.ClientSession() as session:
207
+ async with session.get(company_to_url_api, headers=headers) as response:
208
+ if response.status == 200:
209
+ company_data = await response.json()
210
+ if isinstance(company_data, list) and company_data:
211
+ company_info = company_data[0]
212
+ company_domain = company_info.get('Domain')
213
+ if not company_domain:
214
+ return {'error': "Domain not found for the given company name"}
215
+ cache_output("get_company_info_from_builtwith_by_name", company_name, company_info)
216
+ return await get_company_info_from_builtwith(company_domain)
217
+ else:
218
+ return {'error': "No results found for the given company name"}
219
+ elif response.status == 429:
220
+ raise aiohttp.ClientResponseError(
221
+ request_info=response.request_info,
222
+ history=response.history,
223
+ status=response.status,
224
+ message="Rate limit exceeded",
225
+ headers=response.headers
226
+ )
227
+ else:
228
+ try:
229
+ error_data = await response.json()
230
+ return {'error': error_data}
231
+ except Exception as e:
232
+ return {'error': f"Unexpected error: {str(e)}"}
233
+
234
+ @assistant_tool
235
+ @backoff.on_exception(
236
+ backoff.expo,
237
+ aiohttp.ClientResponseError,
238
+ max_tries=2,
239
+ giveup=lambda e: e.status != 429,
240
+ factor=10,
241
+ )
242
+ async def is_tech_used_in_company(
243
+ company_domain: Optional[str] = None,
244
+ company_name: Optional[str] = None,
245
+ keyword: Optional[str] = None,
246
+ tool_config: Optional[List[Dict]] = None
247
+ ) -> bool:
248
+ if not company_domain and not company_name:
249
+ return False
250
+
251
+ if not keyword:
252
+ return False
253
+
254
+ if company_domain:
255
+ company_data_buildwith = await get_company_info_from_builtwith(company_domain, tool_config=tool_config)
256
+ elif company_name:
257
+ company_data_buildwith = await get_company_info_from_builtwith_by_name(company_name, tool_config=tool_config)
258
+ company_domain = company_data_buildwith.get('Lookup', '')
259
+
260
+ data_str = json.dumps(company_data_buildwith).lower()
261
+ keyword_lower = keyword.lower()
262
+ return keyword_lower in data_str
263
+
264
+ @assistant_tool
265
+ @backoff.on_exception(
266
+ backoff.expo,
267
+ aiohttp.ClientResponseError,
268
+ max_tries=2,
269
+ giveup=lambda e: e.status != 429,
270
+ factor=10,
271
+ )
272
+ async def get_company_domain_from_name(
273
+ company_name: Optional[str] = None,
274
+ tool_config: Optional[List[Dict]] = None,
275
+ ) -> str:
276
+ if not company_name:
277
+ return ''
278
+
279
+ company_data_buildwith = await get_company_info_from_builtwith_by_name(company_name, tool_config=tool_config)
280
+ company_domain = company_data_buildwith.get('Lookup', '')
281
+ return company_domain
282
+
@@ -0,0 +1,98 @@
1
+ import os
2
+ import hashlib
3
+ import json
4
+ import logging
5
+
6
+ from azure.storage.blob import BlobServiceClient
7
+ from azure.core.exceptions import ResourceNotFoundError, AzureError
8
+
9
+ logger = logging.getLogger(__name__)
10
+ logging.getLogger("azure").setLevel(logging.CRITICAL)
11
+
12
+ CONTAINER_NAME = "cacheoutputs"
13
+
14
+ def _get_container_client():
15
+ """
16
+ Returns the container client for the cache container.
17
+ Ensures that the container is created if it doesn't exist.
18
+ """
19
+ connection_string = os.environ.get("AZURE_BLOB_CONNECTION_STRING")
20
+ if not connection_string:
21
+ raise ValueError("AZURE_BLOB_CONNECTION_STRING environment variable is not set")
22
+
23
+ blob_service_client = BlobServiceClient.from_connection_string(connection_string)
24
+ container_client = blob_service_client.get_container_client(CONTAINER_NAME)
25
+
26
+ # Ensure the container exists; if already created, the AzureError is ignored.
27
+ try:
28
+ container_client.create_container()
29
+ except AzureError:
30
+ pass
31
+
32
+ return container_client
33
+
34
+ def cache_output(tool_name: str, key: str, value, ttl: int = None) -> bool:
35
+ """
36
+ Cache the output of a function using Azure Blob Storage.
37
+
38
+ Parameters:
39
+ tool_name (str): Name of the tool whose output is being cached.
40
+ key (str): The cache key.
41
+ value (Any): The value to be cached.
42
+ ttl (int, optional): The time-to-live (TTL) for the cached value in seconds.
43
+
44
+ Returns:
45
+ bool: True if the value was successfully cached, False otherwise.
46
+ """
47
+ # Create a hash of the key for a consistent blob name
48
+ key_hash = hashlib.sha256(key.encode()).hexdigest()
49
+ # Construct the blob name using a virtual folder for the tool name
50
+ blob_name = f"{tool_name}/{key_hash}.json"
51
+
52
+ # Prepare the cache data
53
+ cache_data = {
54
+ "value": value,
55
+ "ttl": ttl
56
+ }
57
+ data = json.dumps(cache_data)
58
+
59
+ try:
60
+ container_client = _get_container_client()
61
+ blob_client = container_client.get_blob_client(blob=blob_name)
62
+ # Upload the blob content (overwrite if the blob already exists)
63
+ blob_client.upload_blob(data, overwrite=True)
64
+ return True
65
+ except Exception as e:
66
+ logger.error(f"Error uploading blob '{blob_name}': {e}")
67
+ return False
68
+
69
+ def retrieve_output(tool_name: str, key: str):
70
+ """
71
+ Retrieve the cached output for a given tool and cache key from Azure Blob Storage.
72
+
73
+ Parameters:
74
+ tool_name (str): Name of the tool whose output is being retrieved.
75
+ key (str): The cache key.
76
+
77
+ Returns:
78
+ Any: The cached value if found, None otherwise.
79
+ """
80
+ # Create a hash of the key to locate the blob
81
+ key_hash = hashlib.sha256(key.encode()).hexdigest()
82
+ # Construct the blob name using the tool name folder
83
+ blob_name = f"{tool_name}/{key_hash}.json"
84
+
85
+ try:
86
+ container_client = _get_container_client()
87
+ blob_client = container_client.get_blob_client(blob=blob_name)
88
+ download_stream = blob_client.download_blob()
89
+ content = download_stream.readall() # content is in bytes
90
+ cache_data = json.loads(content.decode("utf-8"))
91
+ return cache_data.get("value")
92
+ except ResourceNotFoundError:
93
+ # Blob does not exist
94
+ logger.info(f"Blob '{blob_name}' not found.")
95
+ return None
96
+ except Exception as e:
97
+ logger.error(f"Error retrieving blob '{blob_name}': {e}")
98
+ return None
@@ -0,0 +1,78 @@
1
+ import os
2
+ import hashlib
3
+ import json
4
+ import logging
5
+
6
+ CACHE_PATH = os.environ.get('AGENT_CACHE_PATH', '/tmp/dhisana_ai/cache_run_outputs/')
7
+
8
+ def cache_output(tool_name, key, value, ttl=None):
9
+ """
10
+ Cache the output of a function using the provided key and value.
11
+
12
+ Parameters:
13
+ tool_name (str): Name of the tool whose output is being cached.
14
+ key (str): The cache key.
15
+ value (Any): The value to be cached.
16
+ ttl (int, optional): The time-to-live (TTL) for the cached value in seconds.
17
+
18
+ Returns:
19
+ bool: True if the value was successfully cached, False otherwise.
20
+ """
21
+ # Ensure the cache directory exists
22
+ if not os.path.exists(CACHE_PATH):
23
+ os.makedirs(CACHE_PATH)
24
+
25
+ # Create a hash of the key
26
+ key_hash = hashlib.sha256(key.encode()).hexdigest()
27
+
28
+ # Create the cache file path
29
+ cache_file_path = os.path.join(CACHE_PATH, f"{tool_name}_{key_hash}.json")
30
+
31
+ # Create the cache data
32
+ cache_data = {
33
+ 'value': value,
34
+ 'ttl': ttl
35
+ }
36
+
37
+ # Write the cache data to the file
38
+ try:
39
+ os.makedirs(os.path.dirname(cache_file_path), exist_ok=True)
40
+ with open(cache_file_path, 'w') as cache_file:
41
+ json.dump(cache_data, cache_file)
42
+ return True
43
+ except IOError as e:
44
+ logging.error(f"IOError while writing to cache file {cache_file_path}: {e}")
45
+ return False
46
+ except Exception as e:
47
+ logging.error(f"Unexpected error while writing to cache file {cache_file_path}: {e}")
48
+ return False
49
+
50
+
51
+ def retrieve_output(tool_name, key):
52
+ """
53
+ Retrieve the cached output for a given tool and cache key.
54
+
55
+ Parameters:
56
+ tool_name (str): Name of the tool whose output is being retrieved.
57
+ key (str): The cache key.
58
+
59
+ Returns:
60
+ Any: The cached value if found, None otherwise.
61
+ """
62
+ # Create a hash of the key
63
+ key_hash = hashlib.sha256(key.encode()).hexdigest()
64
+
65
+ # Create the cache file path
66
+ cache_file_path = os.path.join(CACHE_PATH, f"{tool_name}_{key_hash}.json")
67
+
68
+ # Read the cache data from the file
69
+ if os.path.exists(cache_file_path):
70
+ try:
71
+ with open(cache_file_path, 'r') as cache_file:
72
+ cache_data = json.load(cache_file)
73
+ return cache_data['value']
74
+ except IOError as e:
75
+ logging.error(f"Error retrieving cache for tool '{tool_name}' with key '{key}': {e}")
76
+ return None
77
+ else:
78
+ return None