dhisana 0.0.1.dev20__tar.gz → 0.0.1.dev23__tar.gz

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 (86) hide show
  1. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/PKG-INFO +1 -1
  2. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/setup.py +1 -1
  3. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/agent_task.py +29 -51
  4. dhisana-0.0.1.dev23/src/dhisana/utils/check_for_intent_signal.py +94 -0
  5. dhisana-0.0.1.dev23/src/dhisana/utils/compose_cadence.py +194 -0
  6. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/compose_salesnav_query.py +166 -63
  7. dhisana-0.0.1.dev23/src/dhisana/utils/compose_search_query.py +242 -0
  8. dhisana-0.0.1.dev23/src/dhisana/utils/compose_three_step_workflow.py +234 -0
  9. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/compose_workflow.py +2 -2
  10. dhisana-0.0.1.dev23/src/dhisana/utils/create_list_from_sales_navigator.py +265 -0
  11. dhisana-0.0.1.dev23/src/dhisana/utils/create_smart_list.py +219 -0
  12. dhisana-0.0.1.dev23/src/dhisana/utils/extract_email_content_for_llm.py +101 -0
  13. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/generate_email.py +29 -58
  14. dhisana-0.0.1.dev23/src/dhisana/utils/generate_email_response.py +144 -0
  15. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/generate_linkedin_connect_message.py +4 -5
  16. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/generate_structured_output_internal.py +98 -3
  17. dhisana-0.0.1.dev23/src/dhisana/utils/google_workspace_tools.py +821 -0
  18. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/research_lead.py +3 -3
  19. dhisana-0.0.1.dev23/src/dhisana/utils/workflow_code_model.py +5 -0
  20. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/PKG-INFO +1 -1
  21. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/SOURCES.txt +7 -0
  22. dhisana-0.0.1.dev20/src/dhisana/utils/check_for_intent_signal.py +0 -56
  23. dhisana-0.0.1.dev20/src/dhisana/utils/generate_email_response.py +0 -99
  24. dhisana-0.0.1.dev20/src/dhisana/utils/google_workspace_tools.py +0 -1023
  25. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/README.md +0 -0
  26. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/pyproject.toml +0 -0
  27. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/setup.cfg +0 -0
  28. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/__init__.py +0 -0
  29. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/__init__.py +0 -0
  30. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/cli.py +0 -0
  31. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/datasets.py +0 -0
  32. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/models.py +0 -0
  33. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/predictions.py +0 -0
  34. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/schemas/__init__.py +0 -0
  35. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/schemas/common.py +0 -0
  36. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/schemas/sales.py +0 -0
  37. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/ui/__init__.py +0 -0
  38. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/ui/components.py +0 -0
  39. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/__init__.py +0 -0
  40. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/agent_tools.py +0 -0
  41. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/apollo_tools.py +0 -0
  42. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  43. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/built_with_api_tools.py +0 -0
  44. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/cache_output_tools.py +0 -0
  45. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/check_email_validity_tools.py +0 -0
  46. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
  47. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/clay_tools.py +0 -0
  48. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/company_utils.py +0 -0
  49. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/composite_tools.py +0 -0
  50. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/dataframe_tools.py +0 -0
  51. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/domain_parser.py +0 -0
  52. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/enrich_lead_information.py +0 -0
  53. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/g2_tools.py +0 -0
  54. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/generate_flow.py +0 -0
  55. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/google_custom_search.py +0 -0
  56. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/hubspot_clearbit.py +0 -0
  57. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
  58. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/instantly_tools.py +0 -0
  59. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/linkedin_crawler.py +0 -0
  60. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/lusha_tools.py +0 -0
  61. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
  62. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openai_helpers.py +0 -0
  63. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
  64. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  65. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  66. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  67. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  68. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/proxy_curl_tools.py +0 -0
  69. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/python_function_to_tools.py +0 -0
  70. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
  71. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
  72. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/sendgrid_tools.py +0 -0
  73. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/serpapi_search_tools.py +0 -0
  74. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/trasform_json.py +0 -0
  75. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/web_download_parse_tools.py +0 -0
  76. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/zoominfo_tools.py +0 -0
  77. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/__init__.py +0 -0
  78. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/agent.py +0 -0
  79. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/flow.py +0 -0
  80. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/task.py +0 -0
  81. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/test.py +0 -0
  82. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/dependency_links.txt +0 -0
  83. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/entry_points.txt +0 -0
  84. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/requires.txt +0 -0
  85. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/top_level.txt +0 -0
  86. {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/tests/test_agent_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dhisana
3
- Version: 0.0.1.dev20
3
+ Version: 0.0.1.dev23
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='dhisana',
5
- version='0.0.1-dev20',
5
+ version='0.0.1-dev23',
6
6
  description='A Python SDK for Dhisana AI Platform',
7
7
  author='Admin',
8
8
  author_email='contact@dhisana.ai',
@@ -13,7 +13,7 @@ def get_task_agent_access_token(tool_config: Optional[List[Dict]] = None) -> str
13
13
  """
14
14
  if tool_config:
15
15
  task_agent_config = next(
16
- (item for item in tool_config if item.get("name") == "dhisana_task_api_key"), None
16
+ (item for item in tool_config if item.get("name") == "dhisana_workflow_tools"), None
17
17
  )
18
18
  if task_agent_config:
19
19
  config_map = {
@@ -32,38 +32,11 @@ def get_task_agent_access_token(tool_config: Optional[List[Dict]] = None) -> str
32
32
  raise ValueError("TASK_AGENT_API_KEY not found in config or env.")
33
33
  return TASK_AGENT_API_KEY
34
34
 
35
- def get_agent_id(tool_config: Optional[List[Dict]] = None) -> str:
36
- """
37
- Retrieves the AGENT_ID from the provided tool configuration or environment.
38
-
39
- Raises:
40
- ValueError: If the AGENT_ID is not found.
41
- """
42
- if tool_config:
43
- agent_id_config = next(
44
- (item for item in tool_config if item.get("name") == "agent_id"), None
45
- )
46
- if agent_id_config:
47
- config_map = {
48
- c["name"]: c["value"]
49
- for c in agent_id_config.get("configuration", [])
50
- if c
51
- }
52
- AGENT_ID = config_map.get("id")
53
- else:
54
- AGENT_ID = None
55
- else:
56
- AGENT_ID = None
57
-
58
- AGENT_ID = AGENT_ID or os.getenv("AGENT_ID", "agent_123")
59
- if not AGENT_ID:
60
- raise ValueError("AGENT_ID not found in config or env.")
61
- return AGENT_ID
62
35
 
63
36
  async def execute_task(
64
37
  command_name: str,
65
38
  command_args: Dict[str, Any],
66
- max_timeout: float = 300,
39
+ max_timeout: float = 2400,
67
40
  tool_config: Optional[List[Dict]] = None
68
41
  ) -> Dict[str, Any]:
69
42
  """
@@ -71,13 +44,14 @@ async def execute_task(
71
44
  2. Polls /get_agent_task_result until completion or until max_timeout has passed.
72
45
  3. Removes task from status queue and pending queue.
73
46
  4. Returns the command result.
47
+
48
+ This function is asynchronous and will yield control during sleep intervals,
49
+ allowing other tasks to run concurrently.
74
50
  """
75
51
  # ----------------------------------------------------------------
76
52
  # CONFIG & PREP
77
53
  # ----------------------------------------------------------------
78
54
  TASK_AGENT_API_KEY = get_task_agent_access_token(tool_config)
79
- # Hardcode or load from environment if desired
80
- agent_id = get_agent_id(tool_config)
81
55
 
82
56
  # Unique ID for this request
83
57
  request_id = str(uuid.uuid4())
@@ -96,18 +70,21 @@ async def execute_task(
96
70
  remove_task_url = f"{api_base_url}/remove_agent_task"
97
71
  remove_task_result_url = f"{api_base_url}/remove_agent_task_result"
98
72
 
99
- # This will hold the final result
73
+ headers = {
74
+ "Authorization": f"Bearer {TASK_AGENT_API_KEY}",
75
+ "Content-Type": "application/json"
76
+ }
77
+
100
78
  final_result: Dict[str, Any] = {}
101
79
 
102
- # ----------------------------------------------------------------
103
- # 1) ENQUEUE THE TASK
104
- # ----------------------------------------------------------------
105
80
  async with aiohttp.ClientSession() as session:
106
- # Add the task
81
+ # ----------------------------------------------------------------
82
+ # 1) ENQUEUE THE TASK
83
+ # ----------------------------------------------------------------
107
84
  async with session.post(
108
85
  add_task_url,
109
- params={"agent_id": agent_id, "api_key": TASK_AGENT_API_KEY},
110
- json=payload
86
+ json=payload,
87
+ headers=headers
111
88
  ) as response:
112
89
  if response.status != 200:
113
90
  raise Exception(f"Failed to add task. Status code: {response.status}")
@@ -119,17 +96,14 @@ async def execute_task(
119
96
  # 2) POLL UNTIL THE TASK IS COMPLETED OR TIMEOUT
120
97
  # ----------------------------------------------------------------
121
98
  start_time = time.time()
122
- while True:
123
- if (time.time() - start_time) >= max_timeout:
124
- # Timed out
125
- raise TimeoutError(
126
- f"Task {request_id} did not complete within {max_timeout} seconds."
127
- )
99
+ end_time = start_time + max_timeout
128
100
 
101
+ while time.time() < end_time:
129
102
  # Poll for the result
130
103
  async with session.post(
131
104
  get_task_result_url,
132
- params={"request_id": request_id, "agent_id": agent_id}
105
+ params={"request_id": request_id},
106
+ headers=headers
133
107
  ) as poll_response:
134
108
  if poll_response.status != 200:
135
109
  raise Exception(
@@ -149,15 +123,21 @@ async def execute_task(
149
123
  final_result = poll_data.get("result", {})
150
124
  break
151
125
 
152
- # Task not yet completed; sleep before next poll
126
+ # Not yet completed; wait a bit before polling again
153
127
  await asyncio.sleep(20)
128
+ else:
129
+ # If we exit the while-loop normally (no break), it's a timeout
130
+ raise TimeoutError(
131
+ f"Task {request_id} did not complete within {max_timeout} seconds."
132
+ )
154
133
 
155
134
  # ----------------------------------------------------------------
156
135
  # 3) REMOVE THE TASK RESULTS FROM THE STATUS QUEUE
157
136
  # ----------------------------------------------------------------
158
137
  async with session.delete(
159
138
  remove_task_result_url,
160
- params={"request_id": request_id, "agent_id": agent_id}
139
+ params={"request_id": request_id},
140
+ headers=headers
161
141
  ) as remove_res_status:
162
142
  if remove_res_status.status != 200:
163
143
  raise Exception(
@@ -166,12 +146,10 @@ async def execute_task(
166
146
  )
167
147
  remove_status_resp = await remove_res_status.json()
168
148
  if remove_status_resp.get("status") != "OK":
169
- # Not a fatal error, but we raise to be safe
149
+ # Not a fatal error, but raise to be safe
170
150
  raise Exception(f"Error removing task result: {remove_status_resp}")
171
151
 
172
152
  # ----------------------------------------------------------------
173
153
  # 4) RETURN THE RESULT
174
154
  # ----------------------------------------------------------------
175
- return final_result
176
-
177
-
155
+ return final_result
@@ -0,0 +1,94 @@
1
+ import logging
2
+ from typing import Any, Dict, List, Optional, cast
3
+
4
+ from pydantic import BaseModel
5
+ from dhisana.utils.generate_structured_output_internal import get_structured_output_with_o1
6
+ from dhisana.utils.compose_search_query import (
7
+ generate_google_search_queries,
8
+ get_search_results_for_insights
9
+ )
10
+
11
+ logger = logging.getLogger(__name__)
12
+ logging.basicConfig(level=logging.INFO)
13
+
14
+
15
+ class IntentSignalScoring(BaseModel):
16
+ score_based_on_intent_signal: int
17
+
18
+
19
+ async def check_for_intent_signal(
20
+ lead: Dict[str, Any],
21
+ signal_to_look_for_in_plan_english: str,
22
+ intent_signal_type: str,
23
+ add_search_results: Optional[bool] = False,
24
+ tool_config: Optional[List[Dict[str, Any]]] = None
25
+ ) -> int:
26
+ """
27
+ Evaluate a 'lead' for a specific intent signal and return an integer score from 0–5.
28
+ """
29
+
30
+ search_results_text = ""
31
+ if add_search_results:
32
+ # Correctly call get_search_results_for_insights with intent_signal_type
33
+ search_results = await get_search_results_for_insights(
34
+ lead=lead,
35
+ english_description=signal_to_look_for_in_plan_english,
36
+ intent_signal_type=intent_signal_type,
37
+ tool_config=tool_config
38
+ )
39
+
40
+ # Build a readable string from returned queries and results
41
+ # Each item in 'search_results' is a dict: {"query": str, "results": <json-encoded list of SERP results>}
42
+ for item in search_results:
43
+ query_str = item.get("query", "")
44
+ results_str = item.get("results", "")
45
+ search_results_text += f"Query: {query_str}\nResults: {results_str}\n\n"
46
+
47
+ user_prompt = f"""
48
+ Hi AI Assistant,
49
+ You are an expert in scoring leads based on intent signals.
50
+ You have the following lead and user requirements to provide a qulifying lead score score between 0 and 5
51
+ based on the intent signal the user is looking for.
52
+ Do the following step by step:
53
+ 1. This about the summary of the lead and the company lead is working for.
54
+ 2. Create a summary of the search results obtained.
55
+ 3. Think about the signal user is looking for to qualify and score the lead.
56
+ 4. Use the lead information, summary of search results and signal user is looking for to score the lead.
57
+ 5. Go back and check if the score makes sense. Score between 0-5 based on the confidence of the signal.
58
+
59
+ Lead Data:
60
+ {lead}
61
+
62
+ Description of the signal user is looking for:
63
+ {signal_to_look_for_in_plan_english}
64
+
65
+ Following is some search results I found online. Use them if they are relevant for scoring:
66
+ {search_results_text}
67
+
68
+ Return your answer in valid JSON with the key 'score_based_on_intent_signal'.
69
+ Make sure it is an integer between 0 and 5.
70
+ """
71
+
72
+ logger.info("Scoring intent signal '%s' for lead: %s", intent_signal_type, lead.get("full_name", "Unknown"))
73
+
74
+ # The helper returns (model_instance or None, status_str)
75
+ response_any, status = await get_structured_output_with_o1(
76
+ user_prompt,
77
+ IntentSignalScoring,
78
+ tool_config=tool_config
79
+ )
80
+
81
+ if status != "SUCCESS" or response_any is None:
82
+ raise Exception("Failed to generate an intent signal score from the LLM.")
83
+
84
+ # Cast to your specific model so the type checker is satisfied
85
+ response = cast(IntentSignalScoring, response_any)
86
+ score = response.score_based_on_intent_signal
87
+
88
+ logger.info(
89
+ "Lead '%s' scored %d for intent signal '%s'.",
90
+ lead.get("full_name", "Unknown"),
91
+ score,
92
+ intent_signal_type
93
+ )
94
+ return score
@@ -0,0 +1,194 @@
1
+ import json
2
+ import logging
3
+ from typing import Any, Dict, List, Optional, Tuple
4
+
5
+
6
+ from dhisana.utils.generate_structured_output_internal import get_structured_output_with_o1
7
+ from dhisana.utils.workflow_code_model import WorkflowPythonCode
8
+
9
+ # Initialize logger
10
+ logging.basicConfig(level=logging.INFO)
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ async def generate_code_for_campaign_cadence(
15
+ english_description: str,
16
+ tool_config: Optional[List[Dict[str, Any]]] = None
17
+ ) -> Tuple[Dict[str, Any], str]:
18
+ """
19
+ Generate a workflow code (Python code) from an English description.
20
+
21
+ The function produced will be named:
22
+ async def run_campaing_cadence(input_leads, tool_config) -> str
23
+ Returns: (response_dict, status_string)
24
+ """
25
+ system_message = (
26
+ "You are a helpful AI assistant and an expert Python coder. "
27
+ "Convert the English description provided by the user into an executable Python function named "
28
+ "'run_campaing_cadence', with the signature:\n"
29
+ " async def run_campaing_cadence(input_leads, tool_config) -> str\n"
30
+ "The function should iterate over 'input_leads' and perform the required LinkedIn actions via execute_task. "
31
+ "Valid commands include: "
32
+ " view_linked_in_profile, send_connection_request, like_recent_post, send_message, get_current_messages, save_lead.\n"
33
+ "Return 'SUCCESS' or 'ERROR' as a string."
34
+ )
35
+
36
+ example_of_workflow_code = (
37
+ '''
38
+ async def run_campaing_cadence(input_leads, tool_config):
39
+ """
40
+ Example skeleton. Run a linkedin campaign cadence with actions like view_profile, send_message etc.
41
+ Returns "SUCCESS" or "ERROR".
42
+ """
43
+
44
+ # Make sure required imports are there within the function definition itself.
45
+ import asyncio
46
+ import logging
47
+ from typing import Any, Dict, List, Optional, Tuple
48
+ from dhisana.utils.agent_task import execute_task
49
+ from dhisana.utils.generate_linkedin_connect_message import get_personalized_linkedin_message
50
+
51
+ # Make sure logger is defined and the logging library is imported within the function.
52
+ logger = logging.getLogger(__name__)
53
+ logging.basicConfig(level=logging.INFO)
54
+
55
+ try:
56
+ MAX_ACTIONS = {
57
+ "view_profile": 30,
58
+ "send_connection_request": 10,
59
+ "send_message": 10,
60
+ "like_post": 10
61
+ }
62
+
63
+ # View profiles
64
+ for lead in input_leads[:MAX_ACTIONS["view_profile"]]:
65
+ await execute_task(
66
+ command_name="view_linked_in_profile",
67
+ command_args={"salesnav_url_profile": lead.get("lead_salesnav_url")},
68
+ tool_config=tool_config
69
+ )
70
+ logger.debug("Viewed profile: %s", lead.get("full_name"))
71
+
72
+ # Send connection requests
73
+ for lead in input_leads[:MAX_ACTIONS["send_connection_request"]]:
74
+ await execute_task(
75
+ command_name="send_connection_request",
76
+ command_args={"salesnav_url_profile": lead.get("lead_salesnav_url")},
77
+ tool_config=tool_config
78
+ )
79
+ logger.debug("Sent connection request: %s", lead.get("full_name"))
80
+
81
+ # Send messages
82
+ for lead in input_leads[:MAX_ACTIONS["send_message"]]:
83
+ message_to_send = await get_personalized_linkedin_message(
84
+ lead_info=lead,
85
+ outreach_context="Hello from my LinkedIn automation!",
86
+ tool_config=tool_config
87
+ )
88
+ await execute_task(
89
+ command_name="send_message",
90
+ command_args={
91
+ "salesnav_url_profile": lead.get("lead_salesnav_url"),
92
+ "message": message_to_send
93
+ },
94
+ tool_config=tool_config
95
+ )
96
+ logger.debug("Sent message to: %s", lead.get("full_name"))
97
+
98
+ # Like recent posts
99
+ for lead in input_leads[:MAX_ACTIONS["like_post"]]:
100
+ await execute_task(
101
+ command_name="like_recent_post",
102
+ command_args={"salesnav_url_profile": lead.get("lead_salesnav_url")},
103
+ tool_config=tool_config
104
+ )
105
+ logger.debug("Liked recent post for lead: %s", lead.get("full_name"))
106
+
107
+ logger.info("Completed run_campaing_cadence successfully.")
108
+ return "SUCCESS"
109
+
110
+ except Exception as e:
111
+ logger.exception("Exception in run_campaing_cadence: %s", e)
112
+ return "ERROR"
113
+ '''
114
+ )
115
+
116
+ user_prompt = f"""
117
+ {system_message}
118
+ "Make sure the imports are present within the function definition itself. Make sure the logging library is imported and logger defined withing the function. "
119
+ The user wants to generate Python code that does this:
120
+
121
+ \"{english_description}\"
122
+
123
+ Here is an example workflow code:
124
+ {example_of_workflow_code}
125
+
126
+ The final code must be valid Python, and must produce:
127
+ async def run_campaing_cadence(input_leads, tool_config) -> str
128
+
129
+ Return your final output as valid JSON with:
130
+ {{
131
+ "workflow_python_code": "<the python code here>"
132
+ }}
133
+ """
134
+ response, status = await get_structured_output_with_o1(
135
+ user_prompt, WorkflowPythonCode, tool_config=tool_config
136
+ )
137
+ return response.model_dump(), status
138
+
139
+ async def generate_campaign_cadence_workflow_and_execute(
140
+ user_query: str,
141
+ input_leads: List[Dict[str, Any]],
142
+ tool_config: Optional[List[Dict[str, Any]]] = None
143
+ ) -> str:
144
+ """
145
+ Generate the campaign cadence workflow code from the user query,
146
+ then execute it on the provided input_leads.
147
+
148
+ Returns:
149
+ JSON string describing success or error.
150
+ """
151
+ response, status = await generate_code_for_campaign_cadence(
152
+ user_query, tool_config=tool_config
153
+ )
154
+
155
+ # Check if we have a successful code generation
156
+ if status == "SUCCESS" and response and response.get("workflow_python_code"):
157
+ code = response["workflow_python_code"]
158
+ if not code:
159
+ return json.dumps({"error": "No workflow code generated.", "status": status})
160
+
161
+ logger.info("Generated workflow code:\n%s", code)
162
+
163
+ local_vars = {}
164
+ global_vars = {}
165
+
166
+ try:
167
+ # Execute the generated code; expect a function named `run_campaing_cadence`
168
+ exec(code, global_vars, local_vars)
169
+ campaign_fn = local_vars.get("run_campaing_cadence", None)
170
+ if campaign_fn is None:
171
+ raise RuntimeError("No 'run_campaing_cadence' function found in generated code.")
172
+
173
+ # Now run the async function with the leads
174
+ async def run_campaign(leads, cfg):
175
+ return await campaign_fn(leads, cfg)
176
+
177
+ try:
178
+ result = await run_campaign(input_leads, tool_config)
179
+ except Exception as e:
180
+ logger.exception("Error occurred while running run_campaing_cadence.")
181
+ return json.dumps({"status": "ERROR", "error": str(e)})
182
+
183
+ # If it's not "SUCCESS", treat it as an error
184
+ if not result or result != "SUCCESS":
185
+ return json.dumps({"error": "Error running the workflow code.", "status": "ERROR"})
186
+
187
+ return json.dumps({"status": "SUCCESS"})
188
+
189
+ except Exception as e:
190
+ logger.exception("Exception occurred while executing generated code.")
191
+ return json.dumps({"status": "ERROR", "error": str(e)})
192
+
193
+ # If code generation failed or no code snippet was returned
194
+ return json.dumps({"error": "No valid workflow code generated.", "status": status})