dhisana 0.0.1.dev272__py3-none-any.whl → 0.0.1.dev273__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.
@@ -15,7 +15,25 @@ from pydantic import BaseModel, ConfigDict
15
15
  # ---------------------------------------------------------------------------------------
16
16
  # CONSTANTS
17
17
  # ---------------------------------------------------------------------------------------
18
- DEFAULT_OPENAI_MODEL = "gpt-4.1"
18
+ DEFAULT_OPENAI_MODEL = "gpt-4.1" # Larger context length - used for generating instructions
19
+ DEFAULT_EMAIL_GEN_MODEL = "gpt-5.1-chat" # Better content generation - used for generating email
20
+
21
+ # -----------------------------------------------------------------------------
22
+ # Email Generation Instructions schema (intermediate step)
23
+ # -----------------------------------------------------------------------------
24
+ class EmailGenerationInstructions(BaseModel):
25
+ """Instructions generated by the first step to guide email generation."""
26
+ recipient_context: str # Key info about the lead/recipient
27
+ sender_context: str # Key info about the sender
28
+ campaign_context: str # Product, value prop, pain points, etc.
29
+ conversation_context: str # Summary of any existing conversation
30
+ tone_and_style: str # How the email should sound
31
+ key_points_to_include: str # Main points to cover in the email
32
+ call_to_action: str # What action to request
33
+ formatting_requirements: str # HTML vs plain text, signature format, etc.
34
+
35
+ model_config = ConfigDict(extra="forbid")
36
+
19
37
 
20
38
  # -----------------------------------------------------------------------------
21
39
  # Email Copy schema
@@ -68,28 +86,18 @@ FRAMEWORK_VARIATIONS = [
68
86
  ]
69
87
 
70
88
  # -----------------------------------------------------------------------------
71
- # Core function to generate an email copy
89
+ # Step 1: Generate email instructions using gpt-4.1 (larger context)
72
90
  # -----------------------------------------------------------------------------
73
- async def generate_personalized_email_copy(
91
+ async def generate_email_instructions(
74
92
  email_context: ContentGenerationContext,
75
93
  message_instructions: MessageGenerationInstructions,
76
94
  variation_text: str,
77
95
  tool_config: Optional[List[Dict]] = None,
78
- ) -> dict:
96
+ ) -> EmailGenerationInstructions:
79
97
  """
80
- Generate a personalized email using the provided context and instructions.
81
-
82
- Steps:
83
- 1. Build a prompt referencing 6 main info:
84
- (a) Lead Info
85
- (b) Sender Info
86
- (c) Campaign Info
87
- (d) Messaging Instructions
88
- (e) Additional Data (vector store) if any
89
- (f) Current Conversation Context
90
- 2. Generate an initial draft with or without vector store usage.
91
- 3. Optionally refine if a vector store was used and user instructions were not provided.
92
- 4. Return the final subject & body.
98
+ Step 1 of 2-step email generation process.
99
+ Uses gpt-4.1 (larger context length) to process all context and generate
100
+ concise instructions for email generation.
93
101
  """
94
102
  cleaned_context = cleanup_email_context(email_context)
95
103
 
@@ -110,110 +118,212 @@ async def generate_personalized_email_copy(
110
118
  conversation_data = cleaned_context.current_conversation_context or ConversationContext()
111
119
 
112
120
  html_note = (
113
- f"\n Provide the HTML body using this guidance/template when possible:\n {message_instructions.html_template}"
121
+ f"\n HTML Template to follow: {message_instructions.html_template}"
114
122
  if getattr(message_instructions, "html_template", None)
115
123
  else ""
116
124
  )
117
- important_requirements = """
118
- IMPORTANT REQUIREMENTS:
125
+
126
+ allow_html = getattr(message_instructions, "allow_html", False)
127
+ format_requirement = "HTML email with body_html field" if allow_html else "Plain text only (no HTML tags)"
128
+
129
+ # Construct the prompt for generating instructions
130
+ instructions_prompt = f"""
131
+ Generate email writing instructions based on the following business context.
132
+
133
+ LEAD INFORMATION:
134
+ {lead_data.dict()}
135
+
136
+ SENDER INFORMATION:
137
+ Full Name: {sender_data.sender_full_name or ''}
138
+ First Name: {sender_data.sender_first_name or ''}
139
+ Last Name: {sender_data.sender_last_name or ''}
140
+ Bio: {sender_data.sender_bio or ''}
141
+ Appointment Booking URL: {sender_data.sender_appointment_booking_url or ''}
142
+
143
+ CAMPAIGN INFORMATION:
144
+ Product Name: {campaign_data.product_name or ''}
145
+ Value Proposition: {campaign_data.value_prop or ''}
146
+ Call To Action: {campaign_data.call_to_action or ''}
147
+ Pain Points: {campaign_data.pain_points or []}
148
+ Proof Points: {campaign_data.proof_points or []}
149
+ Email Triage Guidelines: {campaign_data.email_triage_guidelines or ''}
150
+ LinkedIn Triage Guidelines: {campaign_data.linkedin_triage_guidelines or ''}
151
+
152
+ MESSAGING FRAMEWORK:
153
+ {selected_instructions}{html_note}
154
+
155
+ CONVERSATION HISTORY:
156
+ Email Thread: {conversation_data.current_email_thread or ''}
157
+ LinkedIn Thread: {conversation_data.current_linkedin_thread or ''}
158
+
159
+ OUTPUT FIELDS:
160
+ - recipient_context: Key details about the lead (name, company, role, relevant background)
161
+ - sender_context: Sender name and relevant info for signature
162
+ - campaign_context: Product or service being promoted, value proposition, key pain points
163
+ - conversation_context: Summary of any prior conversation if exists
164
+ - tone_and_style: Professional, friendly, etc.
165
+ - key_points_to_include: Specific points to mention in the email
166
+ - call_to_action: What action the recipient should take
167
+ - formatting_requirements: Format is {format_requirement}. Salutation format, signature requirements.
168
+ """
169
+
170
+ # Check if a vector store is available
171
+ vector_store_id = (email_context.external_known_data.external_openai_vector_store_id
172
+ if email_context.external_known_data else None)
173
+
174
+ instructions_response = None
175
+ instructions_status = ""
176
+
177
+ # Generate instructions using gpt-4.1 (larger context)
178
+ if vector_store_id:
179
+ instructions_response, instructions_status = await get_structured_output_with_assistant_and_vector_store(
180
+ prompt=instructions_prompt,
181
+ response_format=EmailGenerationInstructions,
182
+ vector_store_id=vector_store_id,
183
+ model=DEFAULT_OPENAI_MODEL,
184
+ tool_config=tool_config,
185
+ use_cache=email_context.message_instructions.use_cache if email_context.message_instructions else True
186
+ )
187
+ else:
188
+ instructions_response, instructions_status = await get_structured_output_internal(
189
+ prompt=instructions_prompt,
190
+ response_format=EmailGenerationInstructions,
191
+ model=DEFAULT_OPENAI_MODEL,
192
+ tool_config=tool_config,
193
+ use_cache=email_context.message_instructions.use_cache if email_context.message_instructions else True
194
+ )
195
+
196
+ if instructions_status != "SUCCESS":
197
+ raise Exception("Error: Could not generate email instructions in step 1.")
198
+
199
+ return instructions_response
200
+
201
+
202
+ # -----------------------------------------------------------------------------
203
+ # Step 2: Generate email using gpt-5.1-chat (better content generation)
204
+ # -----------------------------------------------------------------------------
205
+ async def generate_email_from_instructions(
206
+ instructions: EmailGenerationInstructions,
207
+ message_instructions: MessageGenerationInstructions,
208
+ tool_config: Optional[List[Dict]] = None,
209
+ use_cache: bool = True,
210
+ ) -> EmailCopy:
211
+ """
212
+ Step 2 of 2-step email generation process.
213
+ Uses gpt-5.1-chat (better content generation) to generate the email
214
+ from the condensed instructions.
215
+ """
216
+ allow_html = getattr(message_instructions, "allow_html", False)
217
+
218
+ if allow_html:
219
+ output_requirements = """
220
+ OUTPUT REQUIREMENTS:
119
221
  - Output must be JSON with "subject", "body", and "body_html" fields.
120
222
  - "body_html" should be clean HTML suitable for email (no external assets), inline styles welcome.
121
223
  - "body" must be the plain-text equivalent of "body_html".
122
- - Keep it concise and relevant. No placeholders or extra instructions.
123
- - Do not include PII or internal references, guids or content identifiers in the email.
124
- - Use conversational names for company/person placeholders when provided.
125
- - Email has salutation Hi <First Name>, unless otherwise specified.
126
- - Make sure the signature in body has the sender_first_name correct and in the format the user specified.
127
- - Do Not Make up information. use the information provided in the context and instructions only.
128
- - Do Not use em dash in the generated output.
129
- """
130
- if not getattr(message_instructions, "allow_html", False):
131
- important_requirements = """
132
- IMPORTANT REQUIREMENTS:
224
+ """
225
+ else:
226
+ output_requirements = """
227
+ OUTPUT REQUIREMENTS:
133
228
  - Output must be JSON with "subject" and "body" fields only.
134
229
  - In the subject or body DO NOT include any HTML tags like <a>, <b>, <i>, etc.
135
230
  - The body and subject should be in plain text.
136
- - If there is a link provided in email use it as is. dont wrap it in any HTML tags.
137
- - Keep it concise and relevant. No placeholders or extra instructions.
138
- - Do not include PII or internal references, guids or content identifiers in the email.
139
- - User conversational name for company name if used.
140
- - Email has saluation Hi <First Name>, unless otherwise specified.
141
- - <First Name> is the first name of the lead. Its conversational name. It does not have any special characters or spaces.
142
- - Make sure the signature in body has the sender_first_name is correct and in the format user has specified.
143
- - Do Not Make up information. use the information provided in the context and instructions only.
144
- - Make sure the body text is well-formatted and that newline and carriage-return characters are correctly present and preserved in the message body.
145
- - Do Not use em dash in the generated output.
146
- """
231
+ - If there is a link provided, use it as is without wrapping in any HTML tags.
232
+ """
233
+
234
+ email_prompt = f"""
235
+ Generate a personalized business email based on these specifications:
147
236
 
148
- # Construct the consolidated prompt
149
- initial_prompt = f"""
150
- Hi AI Assistant,
237
+ RECIPIENT CONTEXT:
238
+ {instructions.recipient_context}
151
239
 
152
- Below is the context in 6 main sections. Use it to craft a concise, professional email:
240
+ SENDER CONTEXT:
241
+ {instructions.sender_context}
153
242
 
154
- 1) Lead Information:
155
- {lead_data.dict()}
243
+ CAMPAIGN CONTEXT:
244
+ {instructions.campaign_context}
156
245
 
157
- 2) Sender Information:
158
- Full Name: {sender_data.sender_full_name or ''}
159
- First Name: {sender_data.sender_first_name or ''}
160
- Last Name: {sender_data.sender_last_name or ''}
161
- Bio: {sender_data.sender_bio or ''}
162
- Appointment Booking URL: {sender_data.sender_appointment_booking_url or ''}
246
+ CONVERSATION CONTEXT:
247
+ {instructions.conversation_context}
163
248
 
164
- 3) Campaign Information:
165
- Product Name: {campaign_data.product_name or ''}
166
- Value Proposition: {campaign_data.value_prop or ''}
167
- Call To Action: {campaign_data.call_to_action or ''}
168
- Pain Points: {campaign_data.pain_points or []}
169
- Proof Points: {campaign_data.proof_points or []}
170
- Triage Guidelines (Email): {campaign_data.email_triage_guidelines or ''}
171
- Triage Guidelines (LinkedIn): {campaign_data.linkedin_triage_guidelines or ''}
249
+ TONE AND STYLE:
250
+ {instructions.tone_and_style}
172
251
 
173
- 4) Messaging Instructions (template/framework):
174
- {selected_instructions}{html_note}
252
+ KEY POINTS TO INCLUDE:
253
+ {instructions.key_points_to_include}
175
254
 
176
- 5) External Data / Vector Store:
177
- (I will be provided with file_search tool if present.)
255
+ CALL TO ACTION:
256
+ {instructions.call_to_action}
178
257
 
179
- 6) Current Conversation Context:
180
- Email Thread: {conversation_data.current_email_thread or ''}
181
- LinkedIn Thread: {conversation_data.current_linkedin_thread or ''}
258
+ FORMATTING REQUIREMENTS:
259
+ {instructions.formatting_requirements}
182
260
 
183
- {important_requirements}
261
+ {output_requirements}
262
+
263
+ ADDITIONAL REQUIREMENTS:
264
+ - Keep it concise and relevant. No placeholders.
265
+ - Do not include internal references or content identifiers in the email.
266
+ - Use only the information provided.
267
+ - Ensure the body text is well-formatted with proper newlines.
268
+ - Do not use em dash in the output.
184
269
  """
185
270
 
186
- # Check if a vector store is available
187
- vector_store_id = (email_context.external_known_data.external_openai_vector_store_id
188
- if email_context.external_known_data else None)
271
+ # Generate email using gpt-5.1-chat (better content generation)
272
+ email_response, email_status = await get_structured_output_internal(
273
+ prompt=email_prompt,
274
+ response_format=EmailCopy,
275
+ model=DEFAULT_EMAIL_GEN_MODEL,
276
+ tool_config=tool_config,
277
+ use_cache=use_cache
278
+ )
189
279
 
190
- initial_response = None
191
- initial_status = ""
280
+ if email_status != "SUCCESS":
281
+ raise Exception("Error: Could not generate email in step 2.")
282
+
283
+ return email_response
192
284
 
193
- # Generate initial draft
194
- if vector_store_id:
195
- initial_response, initial_status = await get_structured_output_with_assistant_and_vector_store(
196
- prompt=initial_prompt,
197
- response_format=EmailCopy,
198
- vector_store_id=vector_store_id,
199
- model=DEFAULT_OPENAI_MODEL,
200
- tool_config=tool_config,
201
- use_cache=email_context.message_instructions.use_cache if email_context.message_instructions else True
202
- )
203
- else:
204
- # Otherwise, generate the initial draft internally
205
- initial_response, initial_status = await get_structured_output_internal(
206
- prompt=initial_prompt,
207
- response_format=EmailCopy,
208
- model=DEFAULT_OPENAI_MODEL,
209
- tool_config=tool_config,
210
- use_cache=email_context.message_instructions.use_cache if email_context.message_instructions else True
211
- )
212
285
 
213
- if initial_status != "SUCCESS":
214
- raise Exception("Error: Could not generate initial draft for the personalized email.")
215
- plain_body = initial_response.body
216
- html_body = getattr(initial_response, "body_html", None)
286
+ # -----------------------------------------------------------------------------
287
+ # Core function to generate an email copy (2-step process)
288
+ # -----------------------------------------------------------------------------
289
+ async def generate_personalized_email_copy(
290
+ email_context: ContentGenerationContext,
291
+ message_instructions: MessageGenerationInstructions,
292
+ variation_text: str,
293
+ tool_config: Optional[List[Dict]] = None,
294
+ ) -> dict:
295
+ """
296
+ Generate a personalized email using a 2-step process:
297
+
298
+ Step 1: Use gpt-4.1 (larger context length) to process all context and generate
299
+ concise instructions for email generation.
300
+
301
+ Step 2: Use gpt-5.1-chat (better content generation) to generate the actual
302
+ email from the condensed instructions.
303
+
304
+ This approach leverages gpt-4.1's larger context window to process extensive
305
+ lead/campaign data, and gpt-5.1-chat's superior content generation for the
306
+ final email output.
307
+ """
308
+ # Step 1: Generate instructions using gpt-4.1
309
+ instructions = await generate_email_instructions(
310
+ email_context=email_context,
311
+ message_instructions=message_instructions,
312
+ variation_text=variation_text,
313
+ tool_config=tool_config,
314
+ )
315
+
316
+ # Step 2: Generate email using gpt-5.1-chat
317
+ use_cache = email_context.message_instructions.use_cache if email_context.message_instructions else True
318
+ email_response = await generate_email_from_instructions(
319
+ instructions=instructions,
320
+ message_instructions=message_instructions,
321
+ tool_config=tool_config,
322
+ use_cache=use_cache,
323
+ )
324
+
325
+ plain_body = email_response.body
326
+ html_body = getattr(email_response, "body_html", None)
217
327
  if not plain_body and html_body:
218
328
  plain_body = _html_to_plain_text(html_body)
219
329
 
@@ -225,7 +335,7 @@ async def generate_personalized_email_copy(
225
335
  receiver_name=email_context.lead_info.full_name or "",
226
336
  receiver_email=email_context.lead_info.email or "",
227
337
  iso_datetime=datetime.utcnow().isoformat(),
228
- subject=initial_response.subject,
338
+ subject=email_response.subject,
229
339
  body=plain_body,
230
340
  html_body=html_body if getattr(message_instructions, "allow_html", False) else None,
231
341
  )
@@ -24,7 +24,22 @@ from dhisana.utils.assistant_tool_tag import assistant_tool
24
24
  # ---------------------------------------------------------------------------------------
25
25
  # CONSTANTS
26
26
  # ---------------------------------------------------------------------------------------
27
- DEFAULT_OPENAI_MODEL = "gpt-4.1"
27
+ DEFAULT_OPENAI_MODEL = "gpt-4.1" # Larger context length - used for generating instructions
28
+ DEFAULT_EMAIL_GEN_MODEL = "gpt-5.1-chat" # Better content generation - used for generating message
29
+
30
+
31
+ # ----------------------------------------------------------------------
32
+ # LinkedIn Generation Instructions Schema (intermediate step)
33
+ # ----------------------------------------------------------------------
34
+ class LinkedInGenerationInstructions(BaseModel):
35
+ """Instructions generated by the first step to guide LinkedIn message generation."""
36
+ recipient_context: str # Key info about the lead/recipient
37
+ sender_context: str # Key info about the sender
38
+ campaign_context: str # Product, value prop, pain points, etc.
39
+ conversation_context: str # Summary of any existing conversation
40
+ tone_and_style: str # How the message should sound
41
+ key_points_to_include: str # Main points to cover
42
+ call_to_action: str # What action to request
28
43
 
29
44
 
30
45
  # ----------------------------------------------------------------------
@@ -68,26 +83,17 @@ LINKEDIN_FRAMEWORK_VARIATIONS = [
68
83
  ]
69
84
 
70
85
  # ----------------------------------------------------------------------
71
- # Core function to generate a LinkedIn copy (similar to generate_personalized_email_copy)
86
+ # Step 1: Generate LinkedIn instructions using gpt-4.1 (larger context)
72
87
  # ----------------------------------------------------------------------
73
- async def generate_personalized_linkedin_copy(
88
+ async def generate_linkedin_instructions(
74
89
  linkedin_context: ContentGenerationContext,
75
90
  variation_text: str,
76
91
  tool_config: Optional[List[Dict]] = None,
77
- ) -> dict:
92
+ ) -> LinkedInGenerationInstructions:
78
93
  """
79
- Generate a personalized LinkedIn connection message using the provided context and instructions.
80
-
81
- Steps:
82
- 1. Build a prompt referencing 6 main sections:
83
- (a) Lead Info
84
- (b) Sender Info
85
- (c) Campaign Info
86
- (d) Variation / Instructions
87
- (e) External Data (if any)
88
- (f) Current Conversation
89
- 2. Generate the LinkedIn message with or without vector store usage.
90
- 3. Return the final subject & body, ensuring < 40 words.
94
+ Step 1 of 2-step LinkedIn message generation process.
95
+ Uses gpt-4.1 (larger context length) to process all context and generate
96
+ concise instructions for message generation.
91
97
  """
92
98
  cleaned_context = cleanup_linkedin_context(linkedin_context)
93
99
 
@@ -96,54 +102,43 @@ async def generate_personalized_linkedin_copy(
96
102
  campaign_data = cleaned_context.campaign_context or CampaignContext()
97
103
  conversation_data = cleaned_context.current_conversation_context or ConversationContext()
98
104
 
99
- # Construct the consolidated prompt
100
- prompt = f"""
101
- Hi AI Assistant,
102
-
103
- Below is the context in 6 main sections. Use it to craft a concise, professional LinkedIn connection request message:
104
- Linked in connect message HAS to be maximum of 40 words. DO NOT go more than 40 words.
105
-
106
- 1) Lead Information:
107
- {lead_data.dict()}
108
-
109
- 2) Sender Information:
110
- Full Name: {sender_data.sender_full_name or ''}
111
- First Name: {sender_data.sender_first_name or ''}
112
- Last Name: {sender_data.sender_last_name or ''}
113
- Bio: {sender_data.sender_bio or ''}
114
- Appointment Booking URL: {sender_data.sender_appointment_booking_url or ''}
115
-
116
- 3) Campaign Information:
117
- Product Name: {campaign_data.product_name or ''}
118
- Value Proposition: {campaign_data.value_prop or ''}
119
- Call To Action: {campaign_data.call_to_action or ''}
120
- Pain Points: {campaign_data.pain_points or []}
121
- Proof Points: {campaign_data.proof_points or []}
122
-
123
- 4) Follow these Instructions to generate the likedin connection request message:
124
- {variation_text}
125
-
126
- 5) External Data / Vector Store:
127
- (File search or additional context if available.)
128
-
129
- 6) Current Conversation Context:
130
- Email Thread: {conversation_data.current_email_thread or ''}
131
- LinkedIn Thread: {conversation_data.current_linkedin_thread or ''}
132
-
133
- IMPORTANT REQUIREMENTS:
134
- - Output must be JSON "body" of the message.
135
- - The entire message body must be under 40 words total.
136
- - In the body DO NOT include any HTML tags like <a>, <b>, <i>, etc.
137
- - The body should be in plain text.
138
- - If there is a link provided in body use it as is. dont wrap it in any HTML tags.
139
- - No placeholders or extra instructions in the final output.
140
- - Do not include personal addresses, IDs, or irrelevant internal data.
141
- - Linked in message always has saluatation Hi <First Name>, unless specified otherwise.
142
- - <First Name> is the first name of the lead. Its conversational name. It does not have any special characters or spaces.
143
- - Make sure the signature in body has the sender_first_name is correct and in the format user has specified.
144
- - User conversational name for company name if used.
145
- - Make sure the body text is well-formatted and that newline and carriage-return characters are correctly present and preserved in the message body.
146
- - Do Not use em dash in the generated output.
105
+ # Construct the prompt for generating instructions
106
+ instructions_prompt = f"""
107
+ Generate LinkedIn connection message instructions based on the following business context.
108
+ The final message must be under 40 words.
109
+
110
+ LEAD INFORMATION:
111
+ {lead_data.dict()}
112
+
113
+ SENDER INFORMATION:
114
+ Full Name: {sender_data.sender_full_name or ''}
115
+ First Name: {sender_data.sender_first_name or ''}
116
+ Last Name: {sender_data.sender_last_name or ''}
117
+ Bio: {sender_data.sender_bio or ''}
118
+ Appointment Booking URL: {sender_data.sender_appointment_booking_url or ''}
119
+
120
+ CAMPAIGN INFORMATION:
121
+ Product Name: {campaign_data.product_name or ''}
122
+ Value Proposition: {campaign_data.value_prop or ''}
123
+ Call To Action: {campaign_data.call_to_action or ''}
124
+ Pain Points: {campaign_data.pain_points or []}
125
+ Proof Points: {campaign_data.proof_points or []}
126
+
127
+ MESSAGING FRAMEWORK:
128
+ {variation_text}
129
+
130
+ CONVERSATION HISTORY:
131
+ Email Thread: {conversation_data.current_email_thread or ''}
132
+ LinkedIn Thread: {conversation_data.current_linkedin_thread or ''}
133
+
134
+ OUTPUT FIELDS:
135
+ - recipient_context: Key details about the lead (name, company, role)
136
+ - sender_context: Sender name and relevant info for signature
137
+ - campaign_context: Product or service, value proposition, key pain points
138
+ - conversation_context: Summary of any prior conversation if exists
139
+ - tone_and_style: Professional, friendly, etc.
140
+ - key_points_to_include: Main points for the message (keep brief for 40 word limit)
141
+ - call_to_action: What action the recipient should take
147
142
  """
148
143
 
149
144
  vector_store_id = (
@@ -151,28 +146,131 @@ async def generate_personalized_linkedin_copy(
151
146
  if linkedin_context.external_known_data else None
152
147
  )
153
148
 
149
+ instructions_response = None
150
+ instructions_status = ""
151
+
152
+ # Generate instructions using gpt-4.1 (larger context)
154
153
  if vector_store_id:
155
- # Use the vector store if available
156
- response_data, status = await get_structured_output_with_assistant_and_vector_store(
157
- prompt=prompt,
158
- response_format=LinkedInConnectMessage,
154
+ instructions_response, instructions_status = await get_structured_output_with_assistant_and_vector_store(
155
+ prompt=instructions_prompt,
156
+ response_format=LinkedInGenerationInstructions,
159
157
  vector_store_id=vector_store_id,
160
158
  model=DEFAULT_OPENAI_MODEL,
161
159
  tool_config=tool_config,
162
160
  use_cache=linkedin_context.message_instructions.use_cache if linkedin_context.message_instructions else True
163
161
  )
164
162
  else:
165
- # Otherwise, generate internally
166
- response_data, status = await get_structured_output_internal(
167
- prompt=prompt,
168
- response_format=LinkedInConnectMessage,
163
+ instructions_response, instructions_status = await get_structured_output_internal(
164
+ prompt=instructions_prompt,
165
+ response_format=LinkedInGenerationInstructions,
169
166
  model=DEFAULT_OPENAI_MODEL,
170
167
  tool_config=tool_config,
171
168
  use_cache=linkedin_context.message_instructions.use_cache if linkedin_context.message_instructions else True
172
169
  )
173
170
 
174
- if status != "SUCCESS":
175
- raise Exception("Error: Could not generate the LinkedIn message.")
171
+ if instructions_status != "SUCCESS":
172
+ raise Exception("Error: Could not generate LinkedIn instructions in step 1.")
173
+
174
+ return instructions_response
175
+
176
+
177
+ # ----------------------------------------------------------------------
178
+ # Step 2: Generate LinkedIn message using gpt-5.1-chat (better content)
179
+ # ----------------------------------------------------------------------
180
+ async def generate_linkedin_from_instructions(
181
+ instructions: LinkedInGenerationInstructions,
182
+ tool_config: Optional[List[Dict]] = None,
183
+ use_cache: bool = True,
184
+ ) -> LinkedInConnectMessage:
185
+ """
186
+ Step 2 of 2-step LinkedIn message generation process.
187
+ Uses gpt-5.1-chat (better content generation) to generate the message
188
+ from the condensed instructions.
189
+ """
190
+ message_prompt = f"""
191
+ Generate a personalized LinkedIn connection request message based on these specifications.
192
+ The message must be under 40 words total.
193
+
194
+ RECIPIENT CONTEXT:
195
+ {instructions.recipient_context}
196
+
197
+ SENDER CONTEXT:
198
+ {instructions.sender_context}
199
+
200
+ CAMPAIGN CONTEXT:
201
+ {instructions.campaign_context}
202
+
203
+ CONVERSATION CONTEXT:
204
+ {instructions.conversation_context}
205
+
206
+ TONE AND STYLE:
207
+ {instructions.tone_and_style}
208
+
209
+ KEY POINTS TO INCLUDE:
210
+ {instructions.key_points_to_include}
211
+
212
+ CALL TO ACTION:
213
+ {instructions.call_to_action}
214
+
215
+ OUTPUT REQUIREMENTS:
216
+ - Output must be JSON with "body" field only.
217
+ - The entire message body must be under 40 words total.
218
+ - Plain text only, no HTML tags.
219
+ - Use salutation Hi followed by first name, unless specified otherwise.
220
+ - Use only the information provided.
221
+ - Do not use em dash in the output.
222
+ """
223
+
224
+ # Generate message using gpt-5.1-chat (better content generation)
225
+ message_response, message_status = await get_structured_output_internal(
226
+ prompt=message_prompt,
227
+ response_format=LinkedInConnectMessage,
228
+ model=DEFAULT_EMAIL_GEN_MODEL,
229
+ tool_config=tool_config,
230
+ use_cache=use_cache
231
+ )
232
+
233
+ if message_status != "SUCCESS":
234
+ raise Exception("Error: Could not generate LinkedIn message in step 2.")
235
+
236
+ return message_response
237
+
238
+
239
+ # ----------------------------------------------------------------------
240
+ # Core function to generate a LinkedIn copy (2-step process)
241
+ # ----------------------------------------------------------------------
242
+ async def generate_personalized_linkedin_copy(
243
+ linkedin_context: ContentGenerationContext,
244
+ variation_text: str,
245
+ tool_config: Optional[List[Dict]] = None,
246
+ ) -> dict:
247
+ """
248
+ Generate a personalized LinkedIn connection message using a 2-step process:
249
+
250
+ Step 1: Use gpt-4.1 (larger context length) to process all context and generate
251
+ concise instructions for message generation.
252
+
253
+ Step 2: Use gpt-5.1-chat (better content generation) to generate the actual
254
+ LinkedIn message from the condensed instructions.
255
+ """
256
+ cleaned_context = cleanup_linkedin_context(linkedin_context)
257
+ lead_data = cleaned_context.lead_info or Lead()
258
+ sender_data = cleaned_context.sender_info or SenderInfo()
259
+
260
+ # Step 1: Generate instructions using gpt-4.1
261
+ instructions = await generate_linkedin_instructions(
262
+ linkedin_context=linkedin_context,
263
+ variation_text=variation_text,
264
+ tool_config=tool_config,
265
+ )
266
+
267
+ # Step 2: Generate message using gpt-5.1-chat
268
+ use_cache = linkedin_context.message_instructions.use_cache if linkedin_context.message_instructions else True
269
+ message_response = await generate_linkedin_from_instructions(
270
+ instructions=instructions,
271
+ tool_config=tool_config,
272
+ use_cache=use_cache,
273
+ )
176
274
 
177
275
  # Wrap in MessageItem for consistency
178
276
  response_item = MessageItem(
@@ -184,7 +282,7 @@ async def generate_personalized_linkedin_copy(
184
282
  receiver_email=lead_data.email or "",
185
283
  iso_datetime=datetime.utcnow().isoformat(),
186
284
  subject="Hi",
187
- body=response_data.body
285
+ body=message_response.body
188
286
  )
189
287
 
190
288
  return response_item.model_dump()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev272
3
+ Version: 0.0.1.dev273
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
@@ -39,11 +39,11 @@ dhisana/utils/field_validators.py,sha256=BZgNCpBG264aRqNUu_J67c6zfr15zlAaIw2XRy8
39
39
  dhisana/utils/g2_tools.py,sha256=a4vmBYCBvLae5CdpOhMN1oNlvO8v9J1B5Sd8T5PzuU8,3346
40
40
  dhisana/utils/generate_content.py,sha256=kkf-aPuA32BNgwk_j5N6unYHOZpO7zIfO6zP95XM9fA,2298
41
41
  dhisana/utils/generate_custom_message.py,sha256=tQsryytoYKP5uF3bRENeZks1LvOMFCP6L1487P_r_hk,12072
42
- dhisana/utils/generate_email.py,sha256=Qufol-qgOk8loNulZ6hMzIRr45Ml7JwJpLO5OMabbH8,13217
42
+ dhisana/utils/generate_email.py,sha256=cEIr9HbGduk-l_0dNY2lCBABFBmmcOHaMyI7oy8ChWQ,16470
43
43
  dhisana/utils/generate_email_response.py,sha256=Xk6t2hW_QbumvXf5uUdsD-Lkq8hfzRD9QWioYcdWM1k,21194
44
44
  dhisana/utils/generate_flow.py,sha256=QMn6bWo0nH0fBvy2Ebub1XfH5udnVAqsPsbIqCtQPXU,4728
45
45
  dhisana/utils/generate_leads_salesnav.py,sha256=FG7q6GSm9IywZ9TgQnn5_N3QNfiI-Qk2gaO_3GS99nY,12236
46
- dhisana/utils/generate_linkedin_connect_message.py,sha256=QxsxDiT-3eQOqAAbW13d0HGJXV36WYPvC-7Zsw_2VTI,10208
46
+ dhisana/utils/generate_linkedin_connect_message.py,sha256=niKnL1YwtROVsPctaIQFHeSIvr4ewqEougUBAvMmoC0,13385
47
47
  dhisana/utils/generate_linkedin_response_message.py,sha256=mWoSs5p2JSTIoFZFGm86x1kgs67J7dHPvGKZPzcdGdU,14569
48
48
  dhisana/utils/generate_structured_output_internal.py,sha256=gJ6Bdwz2VexL505crMOgYYHOhmZo0MCghRoDkLP62WM,33654
49
49
  dhisana/utils/google_custom_search.py,sha256=5rQ4uAF-hjFpd9ooJkd6CjRvSmhZHhqM0jfHItsbpzk,10071
@@ -95,8 +95,8 @@ dhisana/workflow/agent.py,sha256=esv7_i_XuMkV2j1nz_UlsHov_m6X5WZZiZm_tG4OBHU,565
95
95
  dhisana/workflow/flow.py,sha256=xWE3qQbM7j2B3FH8XnY3zOL_QXX4LbTW4ArndnEYJE0,1638
96
96
  dhisana/workflow/task.py,sha256=HlWz9mtrwLYByoSnePOemBUBrMEcj7KbgNjEE1oF5wo,1830
97
97
  dhisana/workflow/test.py,sha256=E7lRnXK0PguTNzyasHytLzTJdkqIPxG5_4qk4hMEeKc,3399
98
- dhisana-0.0.1.dev272.dist-info/METADATA,sha256=gcVzqziXrrxkS44GPUwFkkf1igjYwMWE3PmWwxo_mT0,1190
99
- dhisana-0.0.1.dev272.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
100
- dhisana-0.0.1.dev272.dist-info/entry_points.txt,sha256=jujxteZmNI9EkEaK-pOCoWuBujU8TCevdkfl9ZcKHek,49
101
- dhisana-0.0.1.dev272.dist-info/top_level.txt,sha256=NETTHt6YifG_P7XtRHbQiXZlgSFk9Qh9aR-ng1XTf4s,8
102
- dhisana-0.0.1.dev272.dist-info/RECORD,,
98
+ dhisana-0.0.1.dev273.dist-info/METADATA,sha256=NXgT5Z07SUxtokSLs_puNFVFvN0dRX7BNqR_QxB_em4,1190
99
+ dhisana-0.0.1.dev273.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
100
+ dhisana-0.0.1.dev273.dist-info/entry_points.txt,sha256=jujxteZmNI9EkEaK-pOCoWuBujU8TCevdkfl9ZcKHek,49
101
+ dhisana-0.0.1.dev273.dist-info/top_level.txt,sha256=NETTHt6YifG_P7XtRHbQiXZlgSFk9Qh9aR-ng1XTf4s,8
102
+ dhisana-0.0.1.dev273.dist-info/RECORD,,