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.
- dhisana/utils/generate_email.py +209 -99
- dhisana/utils/generate_linkedin_connect_message.py +173 -75
- {dhisana-0.0.1.dev272.dist-info → dhisana-0.0.1.dev273.dist-info}/METADATA +1 -1
- {dhisana-0.0.1.dev272.dist-info → dhisana-0.0.1.dev273.dist-info}/RECORD +7 -7
- {dhisana-0.0.1.dev272.dist-info → dhisana-0.0.1.dev273.dist-info}/WHEEL +0 -0
- {dhisana-0.0.1.dev272.dist-info → dhisana-0.0.1.dev273.dist-info}/entry_points.txt +0 -0
- {dhisana-0.0.1.dev272.dist-info → dhisana-0.0.1.dev273.dist-info}/top_level.txt +0 -0
dhisana/utils/generate_email.py
CHANGED
|
@@ -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
|
-
#
|
|
89
|
+
# Step 1: Generate email instructions using gpt-4.1 (larger context)
|
|
72
90
|
# -----------------------------------------------------------------------------
|
|
73
|
-
async def
|
|
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
|
-
) ->
|
|
96
|
+
) -> EmailGenerationInstructions:
|
|
79
97
|
"""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
Hi AI Assistant,
|
|
237
|
+
RECIPIENT CONTEXT:
|
|
238
|
+
{instructions.recipient_context}
|
|
151
239
|
|
|
152
|
-
|
|
240
|
+
SENDER CONTEXT:
|
|
241
|
+
{instructions.sender_context}
|
|
153
242
|
|
|
154
|
-
|
|
155
|
-
|
|
243
|
+
CAMPAIGN CONTEXT:
|
|
244
|
+
{instructions.campaign_context}
|
|
156
245
|
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
|
|
174
|
-
|
|
252
|
+
KEY POINTS TO INCLUDE:
|
|
253
|
+
{instructions.key_points_to_include}
|
|
175
254
|
|
|
176
|
-
|
|
177
|
-
|
|
255
|
+
CALL TO ACTION:
|
|
256
|
+
{instructions.call_to_action}
|
|
178
257
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
LinkedIn Thread: {conversation_data.current_linkedin_thread or ''}
|
|
258
|
+
FORMATTING REQUIREMENTS:
|
|
259
|
+
{instructions.formatting_requirements}
|
|
182
260
|
|
|
183
|
-
|
|
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
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
191
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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=
|
|
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
|
-
#
|
|
86
|
+
# Step 1: Generate LinkedIn instructions using gpt-4.1 (larger context)
|
|
72
87
|
# ----------------------------------------------------------------------
|
|
73
|
-
async def
|
|
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
|
-
) ->
|
|
92
|
+
) -> LinkedInGenerationInstructions:
|
|
78
93
|
"""
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
175
|
-
raise Exception("Error: Could not generate
|
|
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=
|
|
285
|
+
body=message_response.body
|
|
188
286
|
)
|
|
189
287
|
|
|
190
288
|
return response_item.model_dump()
|
|
@@ -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=
|
|
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=
|
|
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.
|
|
99
|
-
dhisana-0.0.1.
|
|
100
|
-
dhisana-0.0.1.
|
|
101
|
-
dhisana-0.0.1.
|
|
102
|
-
dhisana-0.0.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|