dhisana 0.0.1.dev23__tar.gz → 0.0.1.dev24__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.dev23 → dhisana-0.0.1.dev24}/PKG-INFO +1 -1
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/setup.py +1 -1
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/compose_cadence.py +31 -24
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/compose_three_step_workflow.py +1 -1
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/compose_workflow.py +4 -4
- dhisana-0.0.1.dev24/src/dhisana/utils/generate_linkedin_response_message.py +160 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/sales_navigator_crawler.py +3 -3
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana.egg-info/PKG-INFO +1 -1
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana.egg-info/SOURCES.txt +1 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/README.md +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/pyproject.toml +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/setup.cfg +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/__init__.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/cli/__init__.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/cli/cli.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/cli/datasets.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/cli/models.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/cli/predictions.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/schemas/__init__.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/schemas/common.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/schemas/sales.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/ui/__init__.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/ui/components.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/__init__.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/agent_task.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/agent_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/apollo_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/assistant_tool_tag.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/built_with_api_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/cache_output_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/check_email_validity_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/check_for_intent_signal.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/clay_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/company_utils.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/compose_salesnav_query.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/compose_search_query.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/composite_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/create_list_from_sales_navigator.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/create_smart_list.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/dataframe_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/domain_parser.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/enrich_lead_information.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/g2_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/generate_email.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/generate_email_response.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/generate_flow.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/google_custom_search.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/google_workspace_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/hubspot_clearbit.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/instantly_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/linkedin_crawler.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/lusha_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/openai_helpers.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/proxy_curl_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/python_function_to_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/research_lead.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/sendgrid_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/serpapi_search_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/trasform_json.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/web_download_parse_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/workflow_code_model.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/zoominfo_tools.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/workflow/__init__.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/workflow/agent.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/workflow/flow.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/workflow/task.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/workflow/test.py +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana.egg-info/dependency_links.txt +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana.egg-info/entry_points.txt +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana.egg-info/requires.txt +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana.egg-info/top_level.txt +0 -0
- {dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/tests/test_agent_tools.py +0 -0
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Any, Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
|
+
from pydantic import BaseModel
|
|
5
6
|
|
|
6
7
|
from dhisana.utils.generate_structured_output_internal import get_structured_output_with_o1
|
|
7
8
|
from dhisana.utils.workflow_code_model import WorkflowPythonCode
|
|
@@ -10,7 +11,7 @@ from dhisana.utils.workflow_code_model import WorkflowPythonCode
|
|
|
10
11
|
logging.basicConfig(level=logging.INFO)
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
async def generate_code_for_campaign_cadence(
|
|
15
16
|
english_description: str,
|
|
16
17
|
tool_config: Optional[List[Dict[str, Any]]] = None
|
|
@@ -19,25 +20,25 @@ async def generate_code_for_campaign_cadence(
|
|
|
19
20
|
Generate a workflow code (Python code) from an English description.
|
|
20
21
|
|
|
21
22
|
The function produced will be named:
|
|
22
|
-
async def run_campaing_cadence(input_leads, tool_config) -> str
|
|
23
|
+
async def run_campaing_cadence(input_leads, campaign_context, tool_config) -> str
|
|
23
24
|
Returns: (response_dict, status_string)
|
|
24
25
|
"""
|
|
25
26
|
system_message = (
|
|
26
27
|
"You are a helpful AI assistant and an expert Python coder. "
|
|
27
28
|
"Convert the English description provided by the user into an executable Python function named "
|
|
28
29
|
"'run_campaing_cadence', with the signature:\n"
|
|
29
|
-
" async def run_campaing_cadence(input_leads, tool_config) -> str\n"
|
|
30
|
+
" async def run_campaing_cadence(input_leads, campaign_context, tool_config) -> str\n"
|
|
30
31
|
"The function should iterate over 'input_leads' and perform the required LinkedIn actions via execute_task. "
|
|
31
32
|
"Valid commands include: "
|
|
32
|
-
" view_linked_in_profile, send_connection_request, like_recent_post,
|
|
33
|
+
" view_linked_in_profile, send_connection_request, like_recent_post, send_linkedin_message, get_current_messages, save_lead.\n"
|
|
33
34
|
"Return 'SUCCESS' or 'ERROR' as a string."
|
|
34
35
|
)
|
|
35
36
|
|
|
36
37
|
example_of_workflow_code = (
|
|
37
38
|
'''
|
|
38
|
-
async def run_campaing_cadence(input_leads, tool_config):
|
|
39
|
+
async def run_campaing_cadence(input_leads, campaign_context, tool_config):
|
|
39
40
|
"""
|
|
40
|
-
Example skeleton. Run a linkedin campaign cadence with actions like view_profile,
|
|
41
|
+
Example skeleton. Run a linkedin campaign cadence with actions like view_profile, send_linkedin_message etc.
|
|
41
42
|
Returns "SUCCESS" or "ERROR".
|
|
42
43
|
"""
|
|
43
44
|
|
|
@@ -56,7 +57,7 @@ async def generate_code_for_campaign_cadence(
|
|
|
56
57
|
MAX_ACTIONS = {
|
|
57
58
|
"view_profile": 30,
|
|
58
59
|
"send_connection_request": 10,
|
|
59
|
-
"
|
|
60
|
+
"send_linkedin_message": 10,
|
|
60
61
|
"like_post": 10
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -73,23 +74,23 @@ async def generate_code_for_campaign_cadence(
|
|
|
73
74
|
for lead in input_leads[:MAX_ACTIONS["send_connection_request"]]:
|
|
74
75
|
await execute_task(
|
|
75
76
|
command_name="send_connection_request",
|
|
76
|
-
command_args={
|
|
77
|
+
command_args={
|
|
78
|
+
"salesnav_url_profile": lead.get("lead_salesnav_url"),
|
|
79
|
+
"lead_info": lead,
|
|
80
|
+
"context": campaign_context
|
|
81
|
+
},
|
|
77
82
|
tool_config=tool_config
|
|
78
83
|
)
|
|
79
84
|
logger.debug("Sent connection request: %s", lead.get("full_name"))
|
|
80
85
|
|
|
81
86
|
# Send messages
|
|
82
|
-
for lead in input_leads[:MAX_ACTIONS["
|
|
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
|
-
)
|
|
87
|
+
for lead in input_leads[:MAX_ACTIONS["send_linkedin_message"]]:
|
|
88
88
|
await execute_task(
|
|
89
|
-
command_name="
|
|
89
|
+
command_name="send_linkedin_message",
|
|
90
90
|
command_args={
|
|
91
91
|
"salesnav_url_profile": lead.get("lead_salesnav_url"),
|
|
92
|
-
"
|
|
92
|
+
"lead_info": lead,
|
|
93
|
+
"context": campaign_context
|
|
93
94
|
},
|
|
94
95
|
tool_config=tool_config
|
|
95
96
|
)
|
|
@@ -115,7 +116,7 @@ async def generate_code_for_campaign_cadence(
|
|
|
115
116
|
|
|
116
117
|
user_prompt = f"""
|
|
117
118
|
{system_message}
|
|
118
|
-
"Make sure the imports are present within the function definition itself. Make sure the logging library is imported and logger defined
|
|
119
|
+
"Make sure the imports are present within the function definition itself. Make sure the logging library is imported and logger defined within the function. "
|
|
119
120
|
The user wants to generate Python code that does this:
|
|
120
121
|
|
|
121
122
|
\"{english_description}\"
|
|
@@ -124,7 +125,7 @@ async def generate_code_for_campaign_cadence(
|
|
|
124
125
|
{example_of_workflow_code}
|
|
125
126
|
|
|
126
127
|
The final code must be valid Python, and must produce:
|
|
127
|
-
async def run_campaing_cadence(input_leads, tool_config) -> str
|
|
128
|
+
async def run_campaing_cadence(input_leads, campaign_context, tool_config) -> str
|
|
128
129
|
|
|
129
130
|
Return your final output as valid JSON with:
|
|
130
131
|
{{
|
|
@@ -136,8 +137,13 @@ async def generate_code_for_campaign_cadence(
|
|
|
136
137
|
)
|
|
137
138
|
return response.model_dump(), status
|
|
138
139
|
|
|
140
|
+
|
|
141
|
+
class CampaignContext(BaseModel):
|
|
142
|
+
instructions: str
|
|
143
|
+
|
|
144
|
+
|
|
139
145
|
async def generate_campaign_cadence_workflow_and_execute(
|
|
140
|
-
|
|
146
|
+
instructions: str,
|
|
141
147
|
input_leads: List[Dict[str, Any]],
|
|
142
148
|
tool_config: Optional[List[Dict[str, Any]]] = None
|
|
143
149
|
) -> str:
|
|
@@ -149,7 +155,7 @@ async def generate_campaign_cadence_workflow_and_execute(
|
|
|
149
155
|
JSON string describing success or error.
|
|
150
156
|
"""
|
|
151
157
|
response, status = await generate_code_for_campaign_cadence(
|
|
152
|
-
|
|
158
|
+
instructions, tool_config=tool_config
|
|
153
159
|
)
|
|
154
160
|
|
|
155
161
|
# Check if we have a successful code generation
|
|
@@ -170,12 +176,13 @@ async def generate_campaign_cadence_workflow_and_execute(
|
|
|
170
176
|
if campaign_fn is None:
|
|
171
177
|
raise RuntimeError("No 'run_campaing_cadence' function found in generated code.")
|
|
172
178
|
|
|
173
|
-
#
|
|
174
|
-
async def run_campaign(leads, cfg):
|
|
175
|
-
return await campaign_fn(leads, cfg)
|
|
179
|
+
# Helper to call the newly generated async function
|
|
180
|
+
async def run_campaign(leads, campaign_context, cfg):
|
|
181
|
+
return await campaign_fn(leads, campaign_context, cfg)
|
|
176
182
|
|
|
177
183
|
try:
|
|
178
|
-
|
|
184
|
+
campaign_context = CampaignContext(instructions=instructions)
|
|
185
|
+
result = await run_campaign(input_leads, campaign_context, tool_config)
|
|
179
186
|
except Exception as e:
|
|
180
187
|
logger.exception("Error occurred while running run_campaing_cadence.")
|
|
181
188
|
return json.dumps({"status": "ERROR", "error": str(e)})
|
{dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/compose_three_step_workflow.py
RENAMED
|
@@ -196,7 +196,7 @@ async def generate_three_step_workflow_execute(
|
|
|
196
196
|
# generate_campaign_cadence_workflow_and_execute might return JSON:
|
|
197
197
|
# { "status": "SUCCESS", "result": "Campaign done" } or similar
|
|
198
198
|
result_str = await generate_campaign_cadence_workflow_and_execute(
|
|
199
|
-
|
|
199
|
+
instructions=step_3_instructions,
|
|
200
200
|
input_leads=leads_list,
|
|
201
201
|
tool_config=tool_config
|
|
202
202
|
)
|
|
@@ -42,7 +42,7 @@ async def generate_workflow_code(
|
|
|
42
42
|
"view_linked_in_profile",
|
|
43
43
|
"send_connection_request",
|
|
44
44
|
"like_recent_post",
|
|
45
|
-
"
|
|
45
|
+
"send_linkedin_message",
|
|
46
46
|
"get_current_messages",
|
|
47
47
|
]
|
|
48
48
|
|
|
@@ -128,7 +128,7 @@ async def custom_workflow(tool_config):
|
|
|
128
128
|
MAX_ACTIONS = {
|
|
129
129
|
"view_profile": 30,
|
|
130
130
|
"send_connection_request": 10,
|
|
131
|
-
"
|
|
131
|
+
"send_linkedin_message": 10,
|
|
132
132
|
"like_post": 10
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -146,11 +146,11 @@ async def custom_workflow(tool_config):
|
|
|
146
146
|
tool_config=tool_config
|
|
147
147
|
)
|
|
148
148
|
|
|
149
|
-
for lead in qualified_leads[:MAX_ACTIONS["
|
|
149
|
+
for lead in qualified_leads[:MAX_ACTIONS["send_linkedin_message"]]:
|
|
150
150
|
context = "<< Fill in context that can be used to generate message>>" # Fill in campaign context in which the message is being sent
|
|
151
151
|
message_to_send = await get_personalized_linkedin_message(lead_info=lead, outreach_context=context, tool_config=tool_config)
|
|
152
152
|
await execute_task(
|
|
153
|
-
command_name="
|
|
153
|
+
command_name="send_linkedin_message",
|
|
154
154
|
command_args={"salesnav_url_profile": lead.get("lead_salesnav_url"), "message": message_to_send},
|
|
155
155
|
tool_config=tool_config
|
|
156
156
|
)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from dhisana.utils.generate_structured_output_internal import (
|
|
4
|
+
get_structured_output_with_o1
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
class LinkedInTriageResponse(BaseModel):
|
|
8
|
+
"""
|
|
9
|
+
Model representing the structured response for a LinkedIn conversation triage.
|
|
10
|
+
- triage_status: "AUTOMATIC" or "REQUIRES_APPROVAL"
|
|
11
|
+
- triage_reason: Optional reason text if triage_status == "REQUIRES_APPROVAL"
|
|
12
|
+
- response_action_to_take: The recommended next action (e.g., SEND_REPLY, WAIT_TO_SEND, STOP_SENDING, etc.)
|
|
13
|
+
- response_message: The actual message (body) to be sent or used for approval.
|
|
14
|
+
"""
|
|
15
|
+
triage_status: str # "AUTOMATIC" or "REQUIRES_APPROVAL"
|
|
16
|
+
triage_reason: Optional[str]
|
|
17
|
+
response_action_to_take: str
|
|
18
|
+
response_message: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cleanup_reply_linkedin_context(lead_info: Dict[str, Any]) -> Dict[str, Any]:
|
|
22
|
+
"""
|
|
23
|
+
Clone or sanitize the lead info to remove unneeded or sensitive fields.
|
|
24
|
+
"""
|
|
25
|
+
cleaned_lead_info = dict(lead_info) # Shallow copy
|
|
26
|
+
|
|
27
|
+
# Example of removing or masking unwanted fields
|
|
28
|
+
cleaned_lead_info.pop("task_ids", None)
|
|
29
|
+
cleaned_lead_info.pop("research_status", None)
|
|
30
|
+
# Add more fields you want to remove as needed
|
|
31
|
+
|
|
32
|
+
return cleaned_lead_info
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def generate_linkedin_response_message(
|
|
36
|
+
lead_info: Dict[str, Any],
|
|
37
|
+
outreach_context: str,
|
|
38
|
+
connect_template: str,
|
|
39
|
+
current_conversations: str,
|
|
40
|
+
additional_instructions: str,
|
|
41
|
+
tool_config: Optional[List[Dict]] = None
|
|
42
|
+
) -> Dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Generate a triaged LinkedIn message, deciding if it can be sent automatically
|
|
45
|
+
or requires human approval. The final JSON structure must conform to
|
|
46
|
+
LinkedInTriageResponse (triage_status, triage_reason, response_action_to_take, response_message).
|
|
47
|
+
|
|
48
|
+
The content should be genuine, authentic, and professional, avoiding clichés
|
|
49
|
+
and heavy sales pushes. It should be respectful of user preferences (e.g., not
|
|
50
|
+
spamming a user who has shown disinterest).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# Possible actions for spam-avoidance, scheduling, and response
|
|
54
|
+
allowed_actions = [
|
|
55
|
+
"SEND_REPLY", # Normal: send reply
|
|
56
|
+
"WAIT_TO_SEND", # Wait because user was recently messaged
|
|
57
|
+
"STOP_SENDING", # No more messages to user
|
|
58
|
+
"SCHEDULE_MEETING", # If user requests a meeting
|
|
59
|
+
"FOLLOW_UP_LATER",
|
|
60
|
+
"NEED_MORE_INFO",
|
|
61
|
+
"OTHER"
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
cleaned_lead_info = cleanup_reply_linkedin_context(lead_info)
|
|
65
|
+
|
|
66
|
+
# Incorporate the outreach context and guidelines directly in the prompt
|
|
67
|
+
prompt = f"""
|
|
68
|
+
You are a specialized LinkedIn assistant.
|
|
69
|
+
|
|
70
|
+
Your task:
|
|
71
|
+
1. Analyze the current LinkedIn conversation.
|
|
72
|
+
2. Inspect the user (lead) info, outreach campaign context, and any additional instructions.
|
|
73
|
+
3. Decide whether to automatically send a reply or if human approval is needed (triage).
|
|
74
|
+
4. If approval is needed, provide the reason.
|
|
75
|
+
5. Choose one recommended next action from: {allowed_actions}.
|
|
76
|
+
6. Provide a short LinkedIn message body that addresses the lead's conversation.
|
|
77
|
+
|
|
78
|
+
Connect Template (for reference):
|
|
79
|
+
{connect_template}
|
|
80
|
+
|
|
81
|
+
Outreach (Campaign) Context:
|
|
82
|
+
{outreach_context}
|
|
83
|
+
|
|
84
|
+
Additional Instructions (Triage Guidelines):
|
|
85
|
+
{additional_instructions}
|
|
86
|
+
|
|
87
|
+
Current Conversations:
|
|
88
|
+
{current_conversations}
|
|
89
|
+
|
|
90
|
+
Lead Info:
|
|
91
|
+
{cleaned_lead_info}
|
|
92
|
+
|
|
93
|
+
=== IMPORTANT ANTI-SPAM AND RESPECT RULES ===
|
|
94
|
+
1. If we have sent a message to the user within the past 24 hours and the user has not responded,
|
|
95
|
+
do NOT send another message right now. Instead, triage with "WAIT_TO_SEND".
|
|
96
|
+
2. If we have sent more than 3 messages in total without any user response, do NOT send another message.
|
|
97
|
+
Instead, triage with "STOP_SENDING".
|
|
98
|
+
3. If the user explicitly says "don't reply", "not interested", or any equivalent,
|
|
99
|
+
do NOT continue the thread. Triage with "STOP_SENDING".
|
|
100
|
+
4. If the user has requested a meeting, triage as "AUTOMATIC" or "REQUIRES_APPROVAL"
|
|
101
|
+
(depending on complexity), and set response_action_to_take to "SCHEDULE_MEETING".
|
|
102
|
+
Craft a helpful response for scheduling.
|
|
103
|
+
|
|
104
|
+
=== TONE AND STYLE RULES ===
|
|
105
|
+
- Your message must be genuine, authentic, and professional.
|
|
106
|
+
- Avoid clichés and spammy or overly aggressive sales pitches.
|
|
107
|
+
- Do not push sales in an unnatural way.
|
|
108
|
+
|
|
109
|
+
=== OUTPUT FORMAT ===
|
|
110
|
+
Your final output must be valid JSON in this exact format:
|
|
111
|
+
{{
|
|
112
|
+
"triage_status": "AUTOMATIC" or "REQUIRES_APPROVAL",
|
|
113
|
+
"triage_reason": "<reason if REQUIRES_APPROVAL; else empty or null>",
|
|
114
|
+
"response_action_to_take": "<one of {allowed_actions}>",
|
|
115
|
+
"response_message": "<the new or reply message>"
|
|
116
|
+
}}
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# Decide how to call your structured output function (based on vector store or not).
|
|
120
|
+
# We'll assume there's no vector store ID for LinkedIn. If you do have one, adapt similarly.
|
|
121
|
+
response, status = await get_structured_output_with_o1(
|
|
122
|
+
prompt=prompt,
|
|
123
|
+
response_format=LinkedInTriageResponse,
|
|
124
|
+
tool_config=tool_config
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if status != 'SUCCESS':
|
|
128
|
+
raise Exception("Error in generating the triaged LinkedIn message.")
|
|
129
|
+
|
|
130
|
+
return response.model_dump()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def get_linkedin_response_message(
|
|
134
|
+
lead_info: Dict[str, Any],
|
|
135
|
+
outreach_context: str,
|
|
136
|
+
current_conversations: str,
|
|
137
|
+
additional_instructions: str,
|
|
138
|
+
tool_config: Optional[List[Dict]] = None
|
|
139
|
+
) -> str:
|
|
140
|
+
"""
|
|
141
|
+
Returns only the 'response_message' from the triaged LinkedIn response.
|
|
142
|
+
This includes usage of the outreach (campaign) context to better tailor
|
|
143
|
+
the message to the lead.
|
|
144
|
+
"""
|
|
145
|
+
connect_template = """
|
|
146
|
+
Hi <<first_name>>,
|
|
147
|
+
<< message >>
|
|
148
|
+
Thanks,
|
|
149
|
+
<< sender_name >>
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
triaged_response = await generate_linkedin_response_message(
|
|
153
|
+
lead_info=lead_info,
|
|
154
|
+
outreach_context=outreach_context,
|
|
155
|
+
connect_template=connect_template,
|
|
156
|
+
current_conversations=current_conversations,
|
|
157
|
+
additional_instructions=additional_instructions,
|
|
158
|
+
tool_config=tool_config
|
|
159
|
+
)
|
|
160
|
+
return triaged_response.get("response_message", "")
|
|
@@ -179,7 +179,7 @@ async def find_button_by_name(page: Page, button_name: str) -> Any:
|
|
|
179
179
|
return b
|
|
180
180
|
return None
|
|
181
181
|
|
|
182
|
-
async def
|
|
182
|
+
async def send_linkedin_message(page: Page, command_args: Dict[str, Any]) -> Dict[str, Any]:
|
|
183
183
|
await goto_url(page, command_args)
|
|
184
184
|
|
|
185
185
|
if "/sales/lead/" not in page.url:
|
|
@@ -787,7 +787,7 @@ command_to_function_mapping = {
|
|
|
787
787
|
"navigate_to_url": navigate_to_url,
|
|
788
788
|
"view_linked_in_profile": view_linked_in_profile,
|
|
789
789
|
"send_connection_request": send_connection_request,
|
|
790
|
-
"
|
|
790
|
+
"send_linkedin_message": send_linkedin_message,
|
|
791
791
|
"get_current_messages": get_current_messages,
|
|
792
792
|
"extract_leads_information": extract_leads,
|
|
793
793
|
"extract_lead_information": extract_lead,
|
|
@@ -989,7 +989,7 @@ def check_and_update_throttling_limits(task: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
989
989
|
"view_linked_in_profile": 500,
|
|
990
990
|
"navigate_to_url": 500,
|
|
991
991
|
"like_recent_post": 10,
|
|
992
|
-
"
|
|
992
|
+
"send_linkedin_message": 10
|
|
993
993
|
}
|
|
994
994
|
limit = daily_limits.get(cmd_id, 25)
|
|
995
995
|
|
|
@@ -47,6 +47,7 @@ src/dhisana/utils/generate_email.py
|
|
|
47
47
|
src/dhisana/utils/generate_email_response.py
|
|
48
48
|
src/dhisana/utils/generate_flow.py
|
|
49
49
|
src/dhisana/utils/generate_linkedin_connect_message.py
|
|
50
|
+
src/dhisana/utils/generate_linkedin_response_message.py
|
|
50
51
|
src/dhisana/utils/generate_structured_output_internal.py
|
|
51
52
|
src/dhisana/utils/google_custom_search.py
|
|
52
53
|
src/dhisana/utils/google_workspace_tools.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/check_linkedin_url_validity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/create_list_from_sales_navigator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/extract_email_content_for_llm.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/generate_linkedin_connect_message.py
RENAMED
|
File without changes
|
{dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/generate_structured_output_internal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev23 → dhisana-0.0.1.dev24}/src/dhisana/utils/openai_assistant_and_file_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|