dhisana 0.0.1.dev116__py3-none-any.whl → 0.0.1.dev236__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 (69) hide show
  1. dhisana/schemas/common.py +10 -1
  2. dhisana/schemas/sales.py +203 -22
  3. dhisana/utils/add_mapping.py +0 -2
  4. dhisana/utils/apollo_tools.py +739 -119
  5. dhisana/utils/built_with_api_tools.py +4 -2
  6. dhisana/utils/check_email_validity_tools.py +35 -18
  7. dhisana/utils/check_for_intent_signal.py +1 -2
  8. dhisana/utils/check_linkedin_url_validity.py +34 -8
  9. dhisana/utils/clay_tools.py +3 -2
  10. dhisana/utils/clean_properties.py +1 -4
  11. dhisana/utils/compose_salesnav_query.py +0 -1
  12. dhisana/utils/compose_search_query.py +7 -3
  13. dhisana/utils/composite_tools.py +0 -1
  14. dhisana/utils/dataframe_tools.py +2 -2
  15. dhisana/utils/email_body_utils.py +72 -0
  16. dhisana/utils/email_provider.py +174 -35
  17. dhisana/utils/enrich_lead_information.py +183 -53
  18. dhisana/utils/fetch_openai_config.py +129 -0
  19. dhisana/utils/field_validators.py +1 -1
  20. dhisana/utils/g2_tools.py +0 -1
  21. dhisana/utils/generate_content.py +0 -1
  22. dhisana/utils/generate_email.py +68 -23
  23. dhisana/utils/generate_email_response.py +294 -46
  24. dhisana/utils/generate_flow.py +0 -1
  25. dhisana/utils/generate_linkedin_connect_message.py +9 -2
  26. dhisana/utils/generate_linkedin_response_message.py +137 -66
  27. dhisana/utils/generate_structured_output_internal.py +317 -164
  28. dhisana/utils/google_custom_search.py +150 -44
  29. dhisana/utils/google_oauth_tools.py +721 -0
  30. dhisana/utils/google_workspace_tools.py +278 -54
  31. dhisana/utils/hubspot_clearbit.py +3 -1
  32. dhisana/utils/hubspot_crm_tools.py +718 -272
  33. dhisana/utils/instantly_tools.py +3 -1
  34. dhisana/utils/lusha_tools.py +10 -7
  35. dhisana/utils/mailgun_tools.py +150 -0
  36. dhisana/utils/microsoft365_tools.py +447 -0
  37. dhisana/utils/openai_assistant_and_file_utils.py +121 -177
  38. dhisana/utils/openai_helpers.py +8 -6
  39. dhisana/utils/parse_linkedin_messages_txt.py +1 -3
  40. dhisana/utils/profile.py +37 -0
  41. dhisana/utils/proxy_curl_tools.py +377 -76
  42. dhisana/utils/proxycurl_search_leads.py +426 -0
  43. dhisana/utils/research_lead.py +3 -3
  44. dhisana/utils/sales_navigator_crawler.py +1 -6
  45. dhisana/utils/salesforce_crm_tools.py +323 -50
  46. dhisana/utils/search_router.py +131 -0
  47. dhisana/utils/search_router_jobs.py +51 -0
  48. dhisana/utils/sendgrid_tools.py +126 -91
  49. dhisana/utils/serarch_router_local_business.py +75 -0
  50. dhisana/utils/serpapi_additional_tools.py +290 -0
  51. dhisana/utils/serpapi_google_jobs.py +117 -0
  52. dhisana/utils/serpapi_google_search.py +188 -0
  53. dhisana/utils/serpapi_local_business_search.py +129 -0
  54. dhisana/utils/serpapi_search_tools.py +360 -432
  55. dhisana/utils/serperdev_google_jobs.py +125 -0
  56. dhisana/utils/serperdev_local_business.py +154 -0
  57. dhisana/utils/serperdev_search.py +233 -0
  58. dhisana/utils/smtp_email_tools.py +178 -18
  59. dhisana/utils/test_connect.py +1603 -130
  60. dhisana/utils/trasform_json.py +3 -3
  61. dhisana/utils/web_download_parse_tools.py +0 -1
  62. dhisana/utils/zoominfo_tools.py +2 -3
  63. dhisana/workflow/test.py +1 -1
  64. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/METADATA +1 -1
  65. dhisana-0.0.1.dev236.dist-info/RECORD +100 -0
  66. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/WHEEL +1 -1
  67. dhisana-0.0.1.dev116.dist-info/RECORD +0 -83
  68. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/entry_points.txt +0 -0
  69. {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/top_level.txt +0 -0
@@ -2,9 +2,11 @@ from typing import Any, Dict, List, Optional
2
2
  from pydantic import BaseModel
3
3
  from dhisana.schemas.sales import (
4
4
  ContentGenerationContext,
5
+ Lead,
5
6
  MessageItem,
6
7
  MessageResponse,
7
- MessageGenerationInstructions
8
+ MessageGenerationInstructions,
9
+ SenderInfo
8
10
  )
9
11
  from dhisana.utils.generate_structured_output_internal import (
10
12
  get_structured_output_internal,
@@ -28,6 +30,7 @@ class LinkedInTriageResponse(BaseModel):
28
30
  triage_reason: Optional[str]
29
31
  response_action_to_take: str
30
32
  response_message: str
33
+ meeting_offer_sent: Optional[bool]
31
34
 
32
35
 
33
36
  # ---------------------------------------------------------------------------------------
@@ -59,13 +62,16 @@ async def generate_linkedin_response_message_copy(
59
62
  Returns a structured result conforming to LinkedInTriageResponse.
60
63
  """
61
64
  allowed_actions = [
62
- "SEND_REPLY", # Normal: send reply
63
- "WAIT_TO_SEND", # Wait because user was recently messaged
64
- "STOP_SENDING", # No more messages to user
65
- "SCHEDULE_MEETING", # If user requests a meeting
66
- "FOLLOW_UP_LATER",
65
+ "SCHEDULE_MEETING",
66
+ "SEND_REPLY",
67
+ "UNSUBSCRIBE",
68
+ "OOF_MESSAGE",
69
+ "NOT_INTERESTED",
67
70
  "NEED_MORE_INFO",
68
- "OTHER"
71
+ "FORWARD_TO_OTHER_USER",
72
+ "NO_MORE_IN_ORGANIZATION",
73
+ "OBJECTION_RAISED",
74
+ "END_CONVERSATION"
69
75
  ]
70
76
 
71
77
  cleaned_context = cleanup_reply_linkedin_context(linkedin_context)
@@ -88,65 +94,126 @@ async def generate_linkedin_response_message_copy(
88
94
  conversation_thread_dump = []
89
95
 
90
96
  current_date_iso = datetime.datetime.now().isoformat()
97
+ lead_data = linkedin_context.lead_info or Lead()
98
+ sender_data = linkedin_context.sender_info or SenderInfo()
99
+ campaign_context = linkedin_context.campaign_context or CampaignContext()
100
+
91
101
  prompt = f"""
92
- You are a specialized LinkedIn assistant.
93
-
94
- Your task:
95
- 1. Analyze the current LinkedIn conversation.
96
- 2. Inspect the user (lead) info, outreach campaign context, and any additional instructions.
97
- 3. Decide whether to automatically send a reply or if human approval is needed (triage).
98
- 4. If approval is needed, provide the reason.
99
- 5. Choose one recommended next action from: {allowed_actions}.
100
- 6. Provide a short LinkedIn message body that addresses the lead's conversation.
101
- MAKE SURE the message is less than 300 words.
102
-
103
- Use the following instructions to generate message:
104
- {variation}
105
-
106
- 1. Understand the conversation thread:
107
- {conversation_thread_dump}
108
-
109
- 2. User & Company (Lead) Info:
110
- {cleaned_context.model_dump()}
111
-
112
- 3. Triage Guidelines:
113
- {cleaned_context.campaign_context.linkedin_triage_guidelines}
114
- - If the request is standard, simple, or obviously handled by standard processes,
115
- set triage_status to "AUTOMATIC".
116
- - If the request is complex, sensitive, or needs special input,
117
- set triage_status to "REQUIRES_APPROVAL" and provide triage_reason.
118
-
119
- 4. Choose one action from this list: {allowed_actions}
120
-
121
- Todays date is : {current_date_iso}
122
- === IMPORTANT ANTI-SPAM AND RESPECT RULES ===
123
- 1. If we have sent a message to the user within the past 24 hours and the user has not responded,
124
- do NOT send another message right now. Instead, triage with "WAIT_TO_SEND".
125
- 2. If we have sent more than 3 messages in total without any user response, do NOT send another message.
126
- Instead, triage with "STOP_SENDING".
127
- 3. If the user explicitly says "don't reply", "not interested", or any equivalent,
128
- do NOT continue the thread. Triage with "STOP_SENDING".
129
- 4. If the user has requested a meeting, triage as "AUTOMATIC" or "REQUIRES_APPROVAL"
130
- (depending on complexity), and set response_action_to_take to "SCHEDULE_MEETING".
131
- Craft a helpful response for scheduling.
132
-
133
- === TONE AND STYLE RULES ===
134
- - Your message must be genuine, authentic, and professional.
135
- - Avoid clichés and spammy or overly aggressive sales pitches.
136
- - Do not push sales in an unnatural way.
137
- - In the subject or body DO NOT include any HTML tags like <a>, <b>, <i>, etc.
138
- - The body message and subject should be in plain text.
139
- - If there is a link provided in message use it as is. dont wrap it in any HTML tags.
140
- - <First Name> is the first name of the lead. Its conversational name. It does not have any special characters or spaces.
141
-
142
- Make sure the message is less than 300 words.
143
- === OUTPUT FORMAT ===
144
- Your final output must be valid JSON in this exact format:
102
+ You are a specialized linkedin message reply assistant.
103
+ Your task is to analyze the user's linkedin message thread, the user/company info,
104
+ and the provided triage guidelines to craft an appropriate response.
105
+
106
+ Follow these instructions to generate the reply:
107
+ {variation}
108
+
109
+ 1. Message thread or conversation to respond to:
110
+ {conversation_thread_dump}
111
+
112
+ 2.
113
+ Lead Information:
114
+ {lead_data.dict()}
115
+
116
+ Sender Information:
117
+ Full Name: {sender_data.sender_full_name or ''}
118
+ First Name: {sender_data.sender_first_name or ''}
119
+ Last Name: {sender_data.sender_last_name or ''}
120
+ Bio: {sender_data.sender_bio or ''}
121
+
122
+ 3. Campaign-specific triage guidelines (user overrides always win):
123
+ {campaign_context.linkedin_triage_guidelines}
124
+
125
+ -----------------------------------------------------------------
126
+ Core decision logic
127
+ -----------------------------------------------------------------
128
+ • If the request is routine, non-sensitive, and clearly actionable
129
+ **triage_status = "AUTOMATIC"**.
130
+ • If the thread contains PII, finance, legal, or any sensitive/NSFW content
131
+ **triage_status = "END_CONVERSATION"** and give a concise **triage_reason**.
132
+
133
+ 4. Choose exactly ONE of: {allowed_actions}
134
+
135
+ -----------------------------------------------------------------
136
+ Response best practices
137
+ -----------------------------------------------------------------
138
+ MAX 150 words, friendly & concise, single clear CTA.
139
+ Begin with a thank-you, mirror the prospect’s wording briefly, then answer /
140
+ propose next step.
141
+ Never contradict, trash-talk, or disparage {lead_data.organization_name}.
142
+ • Plain-text only – NO HTML tags (<a>, <b>, <i>, etc.).
143
+ If a link already exists in the inbound message, include it verbatim—do not re-wrap or shorten.
144
+
145
+ Meeting & follow-up rules
146
+ -------------------------
147
+ 1. Let `meeting_offer_sent` = **true** if any earlier assistant message offered a
148
+ meeting.
149
+ 2. If First “Thanks / Sounds good” & *no* prior meeting offer
150
+ **SEND_REPLY** asking for a 15-min call (≤150 words).
151
+ 3. If Second non-committal reply *after* meeting_offer_sent, or explicit “not interested”
152
+ **END_CONVERSATION**.
153
+ 4. If prospect explicitly asks for times / requests your link
154
+ **SCHEDULE_MEETING** and confirm or propose times.
155
+ 5. If One unsolicited follow-up maximum; stop unless prospect re-engages.
156
+
157
+ If you have not proposed a meeting even once in the thread, and the user response is polite acknowledgment then you MUST request for a meeting.
158
+
159
+
160
+ Objections & info requests
161
+ --------------------------
162
+ • Pricing / docs / case-studies request → **NEED_MORE_INFO**.
163
+ • Budget, timing, or competitor concerns → **OBJECTION_RAISED**
164
+ (acknowledge + one clarifying Q or concise value point).
165
+ • “Loop in {{colleague_name}}” → **FORWARD_TO_OTHER_USER**.
166
+
167
+ Unsubscribe & priority handling
168
+ -------------------------------
169
+ 1. “Unsubscribe / Remove me” → **UNSUBSCRIBE**
170
+ 2. Clear lack of interest → **NOT_INTERESTED**
171
+ 3. Auto OOO reply → **OOF_MESSAGE**
172
+ 4. Explicit meeting request → **SCHEDULE_MEETING**
173
+ 5. Otherwise follow the Meeting & follow-up rules above
174
+ 6. Default → **END_CONVERSATION**
175
+
176
+ Style guard-rails
177
+ -----------------
178
+ • Plain language; no jargon or filler.
179
+ • Do **not** repeat previous messages verbatim.
180
+ • Signature must include sender_first_name exactly as provided.
181
+ • Check UNSUBSCRIBE / NOT_INTERESTED first before other triage.
182
+
183
+ If you have not proposed a meeting even once in the thread, and the user response is polite acknowledgment then you MUST request for a meeting.
184
+
185
+ • Meeting ask template example:
186
+ Hi {{lead_first_name}}, would you be open to a quick 15-min call to
187
+ understand your use-case and share notes?
188
+
189
+ • Competitor-stack mention template example:
190
+ Hi {{lead_first_name}}, thanks for sharing your current stack. Would you be
191
+ open to a 15-min call to explore where we can add value?
192
+
193
+ Use conversational name for company name.
194
+ Use conversational name when using lead first name.
195
+ Do not use special characters or spaces when using lead’s first name.
196
+ In the subject or body DO NOT include any HTML tags like <a>, <b>, <i>, etc.
197
+ The body and subject should be in plain text.
198
+ If there is a link provided in the message, use it as is; do not wrap it in any HTML tags.
199
+ DO NOT make up information. Use only the information provided in the context and instructions.
200
+ Do NOT repeat the same message sent to the user in the past.
201
+ Keep the thread conversational and friendly as a good account executive would respond.
202
+ Do NOT rehash/repeat the same previous message already sent. Keep the reply to the point.
203
+ DO NOT try to spam users with multiple messages.
204
+ Current date is: {current_date_iso}.
205
+ DO NOT share any link to internal or made up document. You can attach or send any document.
206
+ If the user is asking for any additional document END_CONVERSATION and let Account executive handle it.
207
+ Make sure the body text is well-formatted and that newline and carriage-return characters are correctly present and preserved in the message body.
208
+ - Do Not use em dash in the generated output.
209
+
210
+ Required JSON output
211
+ --------------------
145
212
  {{
146
- "triage_status": "AUTOMATIC" or "REQUIRES_APPROVAL",
147
- "triage_reason": "<reason if REQUIRES_APPROVAL; else empty or null>",
148
- "response_action_to_take": "<one of {allowed_actions}>",
149
- "response_message": "<the new or reply message>"
213
+ "triage_status": "AUTOMATIC" or "END_CONVERSATION",
214
+ "triage_reason": "<reason if END_CONVERSATION; otherwise null>",
215
+ "response_action_to_take": "one of {allowed_actions}",
216
+ "response_message": "<the reply body if response_action_to_take is SEND_REPLY or SCHEDULE_MEETING; otherwise empty>"
150
217
  }}
151
218
  """
152
219
 
@@ -158,14 +225,18 @@ async def generate_linkedin_response_message_copy(
158
225
  initial_response, status = await get_structured_output_with_assistant_and_vector_store(
159
226
  prompt=prompt,
160
227
  response_format=LinkedInTriageResponse,
228
+ model="gpt-5.1-chat",
161
229
  vector_store_id=cleaned_context.external_known_data.external_openai_vector_store_id,
162
- tool_config=tool_config
230
+ tool_config=tool_config,
231
+ use_cache=linkedin_context.message_instructions.use_cache if linkedin_context.message_instructions else True
163
232
  )
164
233
  else:
165
234
  initial_response, status = await get_structured_output_internal(
166
235
  prompt,
167
236
  LinkedInTriageResponse,
168
- tool_config=tool_config
237
+ model="gpt-5.1-chat",
238
+ tool_config=tool_config,
239
+ use_cache=linkedin_context.message_instructions.use_cache if linkedin_context.message_instructions else True
169
240
  )
170
241
 
171
242
  if status != 'SUCCESS':