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,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'