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.
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/PKG-INFO +1 -1
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/setup.py +1 -1
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/agent_task.py +29 -51
- dhisana-0.0.1.dev23/src/dhisana/utils/check_for_intent_signal.py +94 -0
- dhisana-0.0.1.dev23/src/dhisana/utils/compose_cadence.py +194 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/compose_salesnav_query.py +166 -63
- dhisana-0.0.1.dev23/src/dhisana/utils/compose_search_query.py +242 -0
- dhisana-0.0.1.dev23/src/dhisana/utils/compose_three_step_workflow.py +234 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/compose_workflow.py +2 -2
- dhisana-0.0.1.dev23/src/dhisana/utils/create_list_from_sales_navigator.py +265 -0
- dhisana-0.0.1.dev23/src/dhisana/utils/create_smart_list.py +219 -0
- dhisana-0.0.1.dev23/src/dhisana/utils/extract_email_content_for_llm.py +101 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/generate_email.py +29 -58
- dhisana-0.0.1.dev23/src/dhisana/utils/generate_email_response.py +144 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/generate_linkedin_connect_message.py +4 -5
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/generate_structured_output_internal.py +98 -3
- dhisana-0.0.1.dev23/src/dhisana/utils/google_workspace_tools.py +821 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/research_lead.py +3 -3
- dhisana-0.0.1.dev23/src/dhisana/utils/workflow_code_model.py +5 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/PKG-INFO +1 -1
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/SOURCES.txt +7 -0
- dhisana-0.0.1.dev20/src/dhisana/utils/check_for_intent_signal.py +0 -56
- dhisana-0.0.1.dev20/src/dhisana/utils/generate_email_response.py +0 -99
- dhisana-0.0.1.dev20/src/dhisana/utils/google_workspace_tools.py +0 -1023
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/README.md +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/pyproject.toml +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/setup.cfg +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/__init__.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/__init__.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/cli.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/datasets.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/models.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/cli/predictions.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/schemas/__init__.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/schemas/common.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/schemas/sales.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/ui/__init__.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/ui/components.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/__init__.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/agent_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/apollo_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/assistant_tool_tag.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/built_with_api_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/cache_output_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/check_email_validity_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/clay_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/company_utils.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/composite_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/dataframe_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/domain_parser.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/enrich_lead_information.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/g2_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/generate_flow.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/google_custom_search.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/hubspot_clearbit.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/instantly_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/linkedin_crawler.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/lusha_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openai_helpers.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/proxy_curl_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/python_function_to_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/sendgrid_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/serpapi_search_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/trasform_json.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/web_download_parse_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/utils/zoominfo_tools.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/__init__.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/agent.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/flow.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/task.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana/workflow/test.py +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/dependency_links.txt +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/entry_points.txt +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/requires.txt +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/src/dhisana.egg-info/top_level.txt +0 -0
- {dhisana-0.0.1.dev20 → dhisana-0.0.1.dev23}/tests/test_agent_tools.py +0 -0
|
@@ -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") == "
|
|
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 =
|
|
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
|
-
|
|
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
|
-
#
|
|
81
|
+
# ----------------------------------------------------------------
|
|
82
|
+
# 1) ENQUEUE THE TASK
|
|
83
|
+
# ----------------------------------------------------------------
|
|
107
84
|
async with session.post(
|
|
108
85
|
add_task_url,
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
#
|
|
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,
|
|
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
|
|
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})
|