ahmednoorx commited on
Commit
0d94680
Β·
verified Β·
1 Parent(s): f9d1138

Update email_gen.py

Browse files
Files changed (1) hide show
  1. email_gen.py +585 -561
email_gen.py CHANGED
@@ -1,561 +1,585 @@
1
- import os
2
- import json
3
- from llama_cpp import Llama
4
- import re
5
- from huggingface_hub import hf_hub_download
6
- import random
7
-
8
- class EmailGenerator:
9
- def __init__(self, custom_model_path=None):
10
- self.model = None
11
- self.model_path = custom_model_path or self._download_model()
12
- self._load_model()
13
- self.prompt_templates = self._load_prompt_templates()
14
-
15
- def _download_model(self):
16
- """Download Vicuna-7B GGUF model from Hugging Face"""
17
- try:
18
- model_name = "TheBloke/vicuna-7B-v1.5-GGUF"
19
- filename = "vicuna-7b-v1.5.Q4_K_M.gguf"
20
-
21
- print("Downloading Vicuna-7B model... This may take a while.")
22
- model_path = hf_hub_download(
23
- repo_id=model_name,
24
- filename=filename,
25
- cache_dir="./models"
26
- )
27
- print(f"Model downloaded to: {model_path}")
28
- return model_path
29
- except Exception as e:
30
- print(f"Error downloading model: {e}")
31
- return None
32
-
33
- def _load_model(self):
34
- """Load the GGUF model using llama-cpp-python"""
35
- try:
36
- if self.model_path and os.path.exists(self.model_path):
37
- print(f"πŸ€– Loading language model from: {self.model_path}")
38
- self.model = Llama(
39
- model_path=self.model_path,
40
- n_ctx=2048, # Context length
41
- n_threads=2, # Reduced for stability
42
- n_batch=512, # Batch size
43
- verbose=False,
44
- use_mmap=True, # Memory mapping for efficiency
45
- use_mlock=False # Don't lock memory
46
- )
47
- print("βœ… Model loaded successfully!")
48
-
49
- # Test the model with a simple prompt
50
- test_response = self.model("Test", max_tokens=5, temperature=0.1)
51
- if test_response and 'choices' in test_response:
52
- print("βœ… Model test successful")
53
- else:
54
- print("⚠️ Model test failed, will use fallback")
55
- self.model = None
56
-
57
- else:
58
- print("❌ No valid model path found. Using advanced fallback generation.")
59
- self.model = None
60
- except Exception as e:
61
- print(f"❌ Error loading model: {e}")
62
- print("πŸ”„ Will use advanced fallback generation system")
63
- self.model = None
64
-
65
- def _generate_with_model(self, prompt, max_tokens=250, temperature=0.7):
66
- """Generate text using the loaded model with retry logic"""
67
- try:
68
- if self.model:
69
- # First attempt
70
- response = self.model(
71
- prompt,
72
- max_tokens=max_tokens,
73
- temperature=temperature,
74
- top_p=0.9,
75
- stop=["</s>", "\n\n\n", "EXAMPLE", "Now write"],
76
- echo=False
77
- )
78
- result = response['choices'][0]['text'].strip()
79
-
80
- # Check if result is valid
81
- if self._is_valid_output(result):
82
- return result
83
-
84
- # Retry with different temperature if first attempt failed
85
- print("First attempt failed, retrying with adjusted parameters...")
86
- response = self.model(
87
- prompt,
88
- max_tokens=max_tokens,
89
- temperature=min(temperature + 0.2, 1.0),
90
- top_p=0.8,
91
- stop=["</s>", "\n\n\n", "EXAMPLE", "Now write"],
92
- echo=False
93
- )
94
- return response['choices'][0]['text'].strip()
95
- else:
96
- return self._fallback_generation(prompt)
97
- except Exception as e:
98
- print(f"Error generating with model: {e}")
99
- return self._fallback_generation(prompt)
100
-
101
- def _is_valid_output(self, output):
102
- """Check if the generated output is valid"""
103
- if not output or len(output) < 20:
104
- return False
105
-
106
- # Check for incomplete JSON
107
- if '{' in output and '}' not in output:
108
- return False
109
-
110
- # Check for common failure patterns
111
- failure_patterns = [
112
- 'I cannot', 'I apologize', 'I\'m sorry',
113
- '[Your Name]', '[Company]', '[Product]',
114
- 'EXAMPLE', 'Now write'
115
- ]
116
-
117
- return not any(pattern in output for pattern in failure_patterns)
118
-
119
- def _parse_json_response(self, response):
120
- """Parse JSON response from the model"""
121
- try:
122
- # Clean up the response
123
- response = response.strip()
124
-
125
- # Extract JSON if it's embedded in text
126
- json_match = re.search(r'\{[^}]*"subject"[^}]*\}', response, re.DOTALL)
127
- if json_match:
128
- response = json_match.group(0)
129
-
130
- # Parse JSON
131
- data = json.loads(response)
132
-
133
- subject = data.get('subject', '').strip()
134
- body = data.get('body', '').strip()
135
-
136
- # Clean up quotes and formatting
137
- subject = subject.strip('"\'')
138
- body = body.strip('"\'')
139
-
140
- return subject, body
141
-
142
- except (json.JSONDecodeError, KeyError) as e:
143
- print(f"JSON parsing error: {e}")
144
- return self._extract_fallback_content(response)
145
-
146
- def _extract_fallback_content(self, response):
147
- """Extract subject and body from non-JSON response"""
148
- lines = response.split('\n')
149
- subject = ""
150
- body = ""
151
-
152
- # Look for subject line
153
- for line in lines:
154
- if any(word in line.lower() for word in ['subject:', 'subj:', 'sub:']):
155
- subject = re.sub(r'^[^:]*:', '', line).strip()
156
- break
157
-
158
- # Look for body
159
- body_started = False
160
- body_lines = []
161
- for line in lines:
162
- if body_started:
163
- if line.strip():
164
- body_lines.append(line.strip())
165
- elif any(word in line.lower() for word in ['body:', 'email:', 'hi ', 'dear ', 'hello ']):
166
- body_started = True
167
- clean_line = re.sub(r'^[^:]*:', '', line).strip()
168
- if clean_line and not clean_line.lower().startswith(('body', 'email')):
169
- body_lines.append(clean_line)
170
-
171
- body = '\n'.join(body_lines) if body_lines else response
172
-
173
- # Fallback if parsing failed
174
- if not subject:
175
- subject = f"Partnership opportunity"
176
- if not body or len(body) < 20:
177
- body = "Hi,\n\nI'd love to explore how we can help your business grow.\n\nInterested in a quick call?\n\nBest regards"
178
-
179
- return subject, body
180
-
181
- def _advanced_fallback_generation(self, name, company, company_info, tone="Professional"):
182
- """Advanced fallback with company-specific personalization"""
183
-
184
- # Extract industry and key details from company info
185
- industry_hints = self._extract_industry_details(company_info)
186
-
187
- # Create tone-specific templates
188
- if tone.lower() == "friendly":
189
- templates = [
190
- {
191
- "subject": f"Love what {company} is doing{industry_hints['subject_suffix']}",
192
- "body": f"Hi {name},\n\nJust came across {company}{industry_hints['context']} - really impressive work!\n\nWe've helped similar {industry_hints['industry']} companies {industry_hints['benefit']}. Mind if I share a quick example?\n\n15-minute call work for you?\n\nCheers,\nAlex"
193
- },
194
- {
195
- "subject": f"Quick idea for {company}",
196
- "body": f"Hi {name},\n\n{company}'s {industry_hints['focus']} caught my eye. We just helped a similar company {industry_hints['specific_result']}.\n\nWorth exploring for {company}?\n\nBest,\nSam"
197
- }
198
- ]
199
- elif tone.lower() == "direct":
200
- templates = [
201
- {
202
- "subject": f"{company} + {industry_hints['solution']}?",
203
- "body": f"Hi {name},\n\n{industry_hints['direct_opener']} for {company}.\n\nResult: {industry_hints['specific_result']}.\n\nInterested? 10-minute call?\n\n-Alex"
204
- },
205
- {
206
- "subject": f"ROI opportunity for {company}",
207
- "body": f"{name},\n\nQuick question: Is {company} looking to {industry_hints['goal']}?\n\nWe reduced costs by 35% for a similar {industry_hints['industry']} company.\n\nWorth a conversation?\n\nBest,\nSam"
208
- }
209
- ]
210
- else: # Professional
211
- templates = [
212
- {
213
- "subject": f"Operational efficiency opportunity - {company}",
214
- "body": f"Hi {name},\n\nI noticed {company} specializes in {industry_hints['specialty']}. We recently helped a similar organization {industry_hints['professional_result']}.\n\nWould you be open to a brief conversation about how this might apply to {company}?\n\nBest regards,\nAlex Thompson"
215
- },
216
- {
217
- "subject": f"Thought on {company}'s {industry_hints['focus']}",
218
- "body": f"Hi {name},\n\n{company}'s work in {industry_hints['area']} is impressive. We've developed solutions that help {industry_hints['industry']} companies {industry_hints['benefit']}.\n\nWould you be interested in a 15-minute discussion about potential applications for {company}?\n\nBest regards,\nSarah Chen"
219
- }
220
- ]
221
-
222
- template = random.choice(templates)
223
- return template["subject"], template["body"]
224
-
225
- def _extract_industry_details(self, company_info):
226
- """Extract industry-specific details for personalization"""
227
- info_lower = company_info.lower() if company_info else ""
228
-
229
- if any(word in info_lower for word in ['tech', 'software', 'saas', 'ai', 'digital']):
230
- return {
231
- 'industry': 'tech',
232
- 'specialty': 'technology solutions',
233
- 'focus': 'innovation',
234
- 'area': 'technology',
235
- 'benefit': 'scale their platforms and reduce technical debt',
236
- 'goal': 'optimize your development pipeline',
237
- 'solution': 'DevOps automation',
238
- 'context': ' and their tech stack',
239
- 'subject_suffix': ' with tech',
240
- 'direct_opener': 'We implemented automated testing',
241
- 'specific_result': 'reduced deployment time by 60%',
242
- 'professional_result': 'achieve 40% faster time-to-market for new features'
243
- }
244
- elif any(word in info_lower for word in ['manufactur', 'industrial', 'equipment', 'materials']):
245
- return {
246
- 'industry': 'manufacturing',
247
- 'specialty': 'industrial operations',
248
- 'focus': 'production efficiency',
249
- 'area': 'manufacturing',
250
- 'benefit': 'optimize their production lines and reduce waste',
251
- 'goal': 'increase production efficiency',
252
- 'solution': 'process optimization',
253
- 'context': ' and their manufacturing capabilities',
254
- 'subject_suffix': ' in manufacturing',
255
- 'direct_opener': 'We streamlined production workflows',
256
- 'specific_result': 'increased throughput by 45%',
257
- 'professional_result': 'achieve 30% improvement in production efficiency'
258
- }
259
- elif any(word in info_lower for word in ['health', 'medical', 'pharma', 'clinical']):
260
- return {
261
- 'industry': 'healthcare',
262
- 'specialty': 'healthcare solutions',
263
- 'focus': 'patient outcomes',
264
- 'area': 'healthcare',
265
- 'benefit': 'improve patient outcomes while reducing costs',
266
- 'goal': 'enhance patient care efficiency',
267
- 'solution': 'workflow optimization',
268
- 'context': ' and their patient care approach',
269
- 'subject_suffix': ' in healthcare',
270
- 'direct_opener': 'We optimized patient flow systems',
271
- 'specific_result': 'reduced wait times by 50%',
272
- 'professional_result': 'achieve 25% improvement in patient satisfaction scores'
273
- }
274
- else:
275
- return {
276
- 'industry': 'business',
277
- 'specialty': 'business operations',
278
- 'focus': 'growth',
279
- 'area': 'operations',
280
- 'benefit': 'streamline operations and drive growth',
281
- 'goal': 'scale your operations',
282
- 'solution': 'process optimization',
283
- 'context': ' and their business model',
284
- 'subject_suffix': '',
285
- 'direct_opener': 'We automated key business processes',
286
- 'specific_result': 'increased efficiency by 40%',
287
- 'professional_result': 'achieve 35% operational cost reduction'
288
- }
289
-
290
- def _load_prompt_templates(self):
291
- """Load sophisticated prompt templates for different use cases"""
292
- return {
293
- "few_shot_template": '''You are an elite B2B sales copywriter. Write ONE personalized cold email that sounds natural and converts.
294
-
295
- <examples>
296
- EXAMPLE 1:
297
- SUBJECT: Quick question about Acme's EU expansion
298
- BODY: Hi Sarah,
299
-
300
- Saw Acme just launched in Berlin – congrats! We helped Contoso reduce their GDPR compliance prep by 68% with a simple automation.
301
-
302
- Worth a 10-minute chat about how this could apply to your EU rollout?
303
-
304
- Best,
305
- Alex
306
-
307
- EXAMPLE 2:
308
- SUBJECT: Thought on TechCorp's materials testing
309
- BODY: Hi John,
310
-
311
- Noticed TechCorp specializes in X-ray spectroscopy equipment. We just helped a similar lab increase throughput 40% with workflow optimization.
312
-
313
- Mind if I share what worked for them? 15-minute call?
314
-
315
- Best,
316
- Sam
317
-
318
- EXAMPLE 3:
319
- SUBJECT: Manufacturing efficiency idea for IndustrialCorp
320
- BODY: Hi Mike,
321
-
322
- IndustrialCorp's production line setup caught my attention. We automated similar processes for MetalWorks, reducing their cycle time by 35%.
323
-
324
- Open to a brief conversation about applications for your facility?
325
-
326
- Best regards,
327
- Jennifer
328
- </examples>
329
-
330
- Now write an email for:
331
- Name: {name}
332
- Company: {company}
333
- Company Info: {company_context}
334
- Tone: {tone}
335
-
336
- Requirements:
337
- - Use the company info naturally in the first 2 lines
338
- - Maximum 70 words in body (excluding signature)
339
- - Clear yes/no question at the end
340
- - No placeholders like [Your Name] or [Company]
341
- - Professional but conversational
342
- - Include specific benefit or result if possible
343
-
344
- Return ONLY this JSON format:
345
- {{"subject": "...", "body": "..."}}''',
346
-
347
- "industry_specific": {
348
- "technology": '''Write a cold email for a tech company. Focus on efficiency, scalability, and competitive advantage.''',
349
- "healthcare": '''Write a cold email for a healthcare company. Focus on patient outcomes, compliance, and cost reduction.''',
350
- "manufacturing": '''Write a cold email for a manufacturing company. Focus on production efficiency, quality, and cost savings.''',
351
- "services": '''Write a cold email for a service company. Focus on client satisfaction, process improvement, and growth.''',
352
- "default": '''Write a cold email that focuses on business growth and operational efficiency.'''
353
- }
354
- }
355
-
356
- def _extract_industry(self, company_info):
357
- """Extract industry type from company information"""
358
- company_lower = company_info.lower()
359
-
360
- if any(word in company_lower for word in ['tech', 'software', 'saas', 'ai', 'digital', 'app', 'platform']):
361
- return 'technology'
362
- elif any(word in company_lower for word in ['health', 'medical', 'pharma', 'hospital', 'clinic']):
363
- return 'healthcare'
364
- elif any(word in company_lower for word in ['manufactur', 'factory', 'production', 'industrial', 'equipment']):
365
- return 'manufacturing'
366
- elif any(word in company_lower for word in ['service', 'consulting', 'agency', 'firm']):
367
- return 'services'
368
- else:
369
- return 'default'
370
-
371
- def _create_company_context(self, company, company_info):
372
- """Create focused company context for the prompt"""
373
- # Extract key information and clean it up
374
- context_parts = []
375
-
376
- if company_info and len(company_info) > 10:
377
- # Extract meaningful phrases
378
- sentences = re.split(r'[.!?]+', company_info)
379
- for sentence in sentences[:3]: # First 3 sentences
380
- sentence = sentence.strip()
381
- if len(sentence) > 20 and not sentence.startswith('Title:'):
382
- # Remove common fluff words
383
- sentence = re.sub(r'Description:\s*', '', sentence)
384
- sentence = re.sub(r'Company Website:\s*', '', sentence)
385
- sentence = re.sub(r'LinkedIn:\s*', '', sentence)
386
- if sentence:
387
- context_parts.append(sentence)
388
-
389
- if not context_parts:
390
- context_parts.append(f"{company} is a company in their industry")
391
-
392
- return ' | '.join(context_parts[:2]) # Max 2 key points
393
-
394
- def generate_email(self, name, company, company_info, tone="Professional", temperature=0.7):
395
- """Generate both subject and email body using advanced prompting"""
396
- # Clean up and prepare context
397
- company_context = self._create_company_context(company, company_info)
398
-
399
- # Calibrate temperature for production readiness
400
- if temperature > 1.0:
401
- temperature = 0.8 # Cap at 0.8 for production readiness
402
-
403
- # Try AI generation first
404
- if self.model:
405
- try:
406
- # Build the prompt using few-shot template
407
- prompt = self.prompt_templates["few_shot_template"].format(
408
- name=name,
409
- company=company,
410
- company_context=company_context,
411
- tone=tone.lower()
412
- )
413
-
414
- # Generate with model
415
- response = self._generate_with_model(prompt, max_tokens=200, temperature=temperature)
416
-
417
- # Parse the response
418
- subject, body = self._parse_json_response(response)
419
-
420
- # Polish the content
421
- subject, body = self._polish_email_content(subject, body)
422
-
423
- # Validate quality
424
- quality_score, issues = self._validate_email_quality(subject, body, name, company)
425
-
426
- # If quality is good enough, return it
427
- if quality_score >= 70 and 'placeholders' not in issues:
428
- print(f"βœ… AI generated email (Quality: {quality_score}%)")
429
- return subject, body
430
- else:
431
- print(f"⚠️ AI output quality too low ({quality_score}%), using advanced fallback")
432
-
433
- except Exception as e:
434
- print(f"❌ AI generation failed: {e}, using advanced fallback")
435
-
436
- # Use advanced fallback system
437
- print("πŸ”„ Using advanced fallback generation")
438
- subject, body = self._advanced_fallback_generation(name, company, company_info, tone)
439
-
440
- # Always polish fallback content
441
- subject, body = self._polish_email_content(subject, body)
442
-
443
- return subject, body
444
-
445
- def _clean_subject(self, subject, company):
446
- """Clean and validate subject line"""
447
- if not subject or len(subject) < 5:
448
- return f"Quick question about {company}"
449
-
450
- # Remove common prefixes
451
- subject = re.sub(r'^(Subject|SUBJECT):\s*', '', subject, flags=re.IGNORECASE)
452
- subject = subject.strip('"\'')
453
-
454
- # Ensure reasonable length
455
- if len(subject) > 60:
456
- subject = subject[:57] + "..."
457
-
458
- return subject
459
-
460
- def _clean_body(self, body, name):
461
- """Clean and validate email body"""
462
- if not body or len(body) < 20:
463
- return f"Hi {name},\n\nI'd love to discuss how we can help your business grow.\n\nInterested in a quick call?\n\nBest regards"
464
-
465
- # Remove common prefixes
466
- body = re.sub(r'^(Body|BODY|Email|EMAIL):\s*', '', body, flags=re.IGNORECASE)
467
-
468
- # Ensure proper greeting
469
- if not body.lower().startswith(('hi ', 'hello ', 'dear ')):
470
- body = f"Hi {name},\n\n{body}"
471
-
472
- # Ensure proper closing
473
- closing_patterns = ['best regards', 'best,', 'sincerely', 'regards,', 'cheers,']
474
- has_closing = any(pattern in body.lower() for pattern in closing_patterns)
475
-
476
- if not has_closing:
477
- if not body.endswith('\n'):
478
- body += '\n'
479
- body += '\nBest regards'
480
-
481
- return body
482
-
483
- def _polish_email_content(self, subject, body):
484
- """Polish email content for grammar and professionalism"""
485
-
486
- # Fix common grammar issues
487
- body = re.sub(r'\s+', ' ', body) # Multiple spaces
488
- body = re.sub(r'([.!?])\s*([a-z])', r'\1 \2', body) # Space after punctuation
489
- body = re.sub(r'(\w)\s*\n\s*(\w)', r'\1\n\n\2', body) # Proper paragraph spacing
490
-
491
- # Ensure professional closing
492
- if not re.search(r'(Best regards|Best|Sincerely|Cheers),?\s*\n?[A-Z][a-z]+', body):
493
- if body.strip().endswith(','):
494
- body = body.strip() + '\n\nBest regards,\nAlex'
495
- else:
496
- body = body.strip() + '\n\nBest regards,\nAlex'
497
-
498
- # Fix subject line
499
- subject = subject.strip()
500
- if len(subject) > 65:
501
- subject = subject[:62] + "..."
502
-
503
- # Capitalize first letter of subject if not already
504
- if subject and subject[0].islower():
505
- subject = subject[0].upper() + subject[1:]
506
-
507
- return subject, body
508
-
509
- def _validate_email_quality(self, subject, body, name, company):
510
- """Validate email quality and return quality score"""
511
- issues = []
512
-
513
- # Check subject length
514
- if len(subject) < 10 or len(subject) > 65:
515
- issues.append("subject_length")
516
-
517
- # Check body length
518
- words = len(body.split())
519
- if words < 20 or words > 150:
520
- issues.append("body_length")
521
-
522
- # Check for placeholders
523
- if '[Your Name]' in body or '[Company]' in body or '{{' in body:
524
- issues.append("placeholders")
525
-
526
- # Check personalization
527
- if name not in body or company not in body:
528
- issues.append("personalization")
529
-
530
- # Check for call-to-action
531
- cta_phrases = ['call', 'conversation', 'chat', 'discuss', 'talk', 'meeting', 'connect']
532
- if not any(phrase in body.lower() for phrase in cta_phrases):
533
- issues.append("no_cta")
534
-
535
- quality_score = max(0, 100 - (len(issues) * 15))
536
- return quality_score, issues
537
-
538
- def generate_multiple_variations(self, name, company, company_info, num_variations=3, tone="Professional"):
539
- """Generate multiple email variations with different approaches"""
540
- variations = []
541
- tones = ["Professional", "Friendly", "Direct"]
542
- temperatures = [0.6, 0.7, 0.8]
543
-
544
- for i in range(num_variations):
545
- current_tone = tones[i % len(tones)]
546
- current_temp = temperatures[i % len(temperatures)]
547
-
548
- subject, email_body = self.generate_email(
549
- name, company, company_info,
550
- tone=current_tone, temperature=current_temp
551
- )
552
-
553
- variations.append({
554
- 'variation': i + 1,
555
- 'tone': current_tone,
556
- 'temperature': current_temp,
557
- 'subject': subject,
558
- 'email_body': email_body
559
- })
560
-
561
- return variations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from llama_cpp import Llama
4
+ import re
5
+ from huggingface_hub import hf_hub_download
6
+ import random
7
+
8
+ class EmailGenerator:
9
+ def __init__(self, custom_model_path=None):
10
+ self.model = None
11
+ self.model_path = custom_model_path or self._download_model()
12
+ self._load_model()
13
+ self.prompt_templates = self._load_prompt_templates()
14
+
15
+ def _download_model(self):
16
+ """Download Vicuna-7B GGUF model from Hugging Face"""
17
+ try:
18
+ model_name = "TheBloke/vicuna-7B-v1.5-GGUF"
19
+ filename = "vicuna-7b-v1.5.Q4_K_M.gguf"
20
+
21
+ print("Downloading Vicuna-7B model... This may take a while.")
22
+ model_path = hf_hub_download(
23
+ repo_id=model_name,
24
+ filename=filename,
25
+ cache_dir="./models"
26
+ )
27
+ print(f"Model downloaded to: {model_path}")
28
+ return model_path
29
+ except Exception as e:
30
+ print(f"Error downloading model: {e}")
31
+ return None
32
+
33
+ def _load_model(self):
34
+ """Load the GGUF model using llama-cpp-python"""
35
+ try:
36
+ if self.model_path and os.path.exists(self.model_path):
37
+ print(f"πŸ€– Loading language model from: {self.model_path}")
38
+ self.model = Llama(
39
+ model_path=self.model_path,
40
+ n_ctx=2048, # Context length
41
+ n_threads=2, # Reduced for stability
42
+ n_batch=512, # Batch size
43
+ verbose=False,
44
+ use_mmap=True, # Memory mapping for efficiency
45
+ use_mlock=False # Don't lock memory
46
+ )
47
+ print("βœ… Model loaded successfully!")
48
+
49
+ # Test the model with a simple prompt
50
+ test_response = self.model("Test", max_tokens=5, temperature=0.1)
51
+ if test_response and 'choices' in test_response:
52
+ print("βœ… Model test successful")
53
+ else:
54
+ print("⚠️ Model test failed, will use fallback")
55
+ self.model = None
56
+
57
+ else:
58
+ print("❌ No valid model path found. Using advanced fallback generation.")
59
+ self.model = None
60
+ except Exception as e:
61
+ print(f"❌ Error loading model: {e}")
62
+ print("πŸ”„ Will use advanced fallback generation system")
63
+ self.model = None
64
+
65
+ def _generate_with_model(self, prompt, max_tokens=250, temperature=0.7):
66
+ """Generate text using the loaded model with retry logic"""
67
+ try:
68
+ if self.model:
69
+ # First attempt
70
+ response = self.model(
71
+ prompt,
72
+ max_tokens=max_tokens,
73
+ temperature=temperature,
74
+ top_p=0.9,
75
+ stop=["</s>", "\n\n\n", "EXAMPLE", "Now write"],
76
+ echo=False
77
+ )
78
+ result = response['choices'][0]['text'].strip()
79
+
80
+ # Check if result is valid
81
+ if self._is_valid_output(result):
82
+ return result
83
+
84
+ # Retry with different temperature if first attempt failed
85
+ print("First attempt failed, retrying with adjusted parameters...")
86
+ response = self.model(
87
+ prompt,
88
+ max_tokens=max_tokens,
89
+ temperature=min(temperature + 0.2, 1.0),
90
+ top_p=0.8,
91
+ stop=["</s>", "\n\n\n", "EXAMPLE", "Now write"],
92
+ echo=False
93
+ )
94
+ return response['choices'][0]['text'].strip()
95
+ else:
96
+ return self._fallback_generation(prompt)
97
+ except Exception as e:
98
+ print(f"Error generating with model: {e}")
99
+ return self._fallback_generation(prompt)
100
+
101
+ def _is_valid_output(self, output):
102
+ """Check if the generated output is valid"""
103
+ if not output or len(output) < 20:
104
+ return False
105
+
106
+ # Check for incomplete JSON
107
+ if '{' in output and '}' not in output:
108
+ return False
109
+
110
+ # Check for common failure patterns
111
+ failure_patterns = [
112
+ 'I cannot', 'I apologize', 'I\'m sorry',
113
+ '[Your Name]', '[Company]', '[Product]',
114
+ 'EXAMPLE', 'Now write'
115
+ ]
116
+
117
+ return not any(pattern in output for pattern in failure_patterns)
118
+
119
+ def _parse_json_response(self, response):
120
+ """Parse JSON response from the model"""
121
+ try:
122
+ # Clean up the response
123
+ response = response.strip()
124
+
125
+ # Extract JSON if it's embedded in text
126
+ json_match = re.search(r'\{[^}]*"subject"[^}]*\}', response, re.DOTALL)
127
+ if json_match:
128
+ response = json_match.group(0)
129
+
130
+ # Parse JSON
131
+ data = json.loads(response)
132
+
133
+ subject = data.get('subject', '').strip()
134
+ body = data.get('body', '').strip()
135
+
136
+ # Clean up quotes and formatting
137
+ subject = subject.strip('"\'')
138
+ body = body.strip('"\'')
139
+
140
+ return subject, body
141
+
142
+ except (json.JSONDecodeError, KeyError) as e:
143
+ print(f"JSON parsing error: {e}")
144
+ return self._extract_fallback_content(response)
145
+
146
+ def _extract_fallback_content(self, response):
147
+ """Extract subject and body from non-JSON response"""
148
+ lines = response.split('\n')
149
+ subject = ""
150
+ body = ""
151
+
152
+ # Look for subject line
153
+ for line in lines:
154
+ if any(word in line.lower() for word in ['subject:', 'subj:', 'sub:']):
155
+ subject = re.sub(r'^[^:]*:', '', line).strip()
156
+ break
157
+
158
+ # Look for body
159
+ body_started = False
160
+ body_lines = []
161
+ for line in lines:
162
+ if body_started:
163
+ if line.strip():
164
+ body_lines.append(line.strip())
165
+ elif any(word in line.lower() for word in ['body:', 'email:', 'hi ', 'dear ', 'hello ']):
166
+ body_started = True
167
+ clean_line = re.sub(r'^[^:]*:', '', line).strip()
168
+ if clean_line and not clean_line.lower().startswith(('body', 'email')):
169
+ body_lines.append(clean_line)
170
+
171
+ body = '\n'.join(body_lines) if body_lines else response
172
+
173
+ # Fallback if parsing failed
174
+ if not subject:
175
+ subject = f"Partnership opportunity"
176
+ if not body or len(body) < 20:
177
+ body = "Hi,\n\nI'd love to explore how we can help your business grow.\n\nInterested in a quick call?\n\nBest regards"
178
+
179
+ return subject, body
180
+
181
+ def _advanced_fallback_generation(self, name, company, company_info, tone="Professional"):
182
+ """Advanced fallback with company-specific personalization"""
183
+
184
+ # Extract industry and key details from company info
185
+ industry_hints = self._extract_industry_details(company_info)
186
+
187
+ # Create tone-specific templates
188
+ if tone.lower() == "friendly":
189
+ templates = [
190
+ {
191
+ "subject": f"Love what {company} is doing{industry_hints['subject_suffix']}",
192
+ "body": f"Hi {name},\n\nJust came across {company}{industry_hints['context']} - really impressive work!\n\nWe've helped similar {industry_hints['industry']} companies {industry_hints['benefit']}. Mind if I share a quick example?\n\n15-minute call work for you?\n\nCheers,\nAlex"
193
+ },
194
+ {
195
+ "subject": f"Quick idea for {company}",
196
+ "body": f"Hi {name},\n\n{company}'s {industry_hints['focus']} caught my eye. We just helped a similar company {industry_hints['specific_result']}.\n\nWorth exploring for {company}?\n\nBest,\nSam"
197
+ }
198
+ ]
199
+ elif tone.lower() == "direct":
200
+ templates = [
201
+ {
202
+ "subject": f"{company} + {industry_hints['solution']}?",
203
+ "body": f"Hi {name},\n\n{industry_hints['direct_opener']} for {company}.\n\nResult: {industry_hints['specific_result']}.\n\nInterested? 10-minute call?\n\n-Alex"
204
+ },
205
+ {
206
+ "subject": f"ROI opportunity for {company}",
207
+ "body": f"{name},\n\nQuick question: Is {company} looking to {industry_hints['goal']}?\n\nWe reduced costs by 35% for a similar {industry_hints['industry']} company.\n\nWorth a conversation?\n\nBest,\nSam"
208
+ }
209
+ ]
210
+ else: # Professional
211
+ templates = [
212
+ {
213
+ "subject": f"Operational efficiency opportunity - {company}",
214
+ "body": f"Hi {name},\n\nI noticed {company} specializes in {industry_hints['specialty']}. We recently helped a similar organization {industry_hints['professional_result']}.\n\nWould you be open to a brief conversation about how this might apply to {company}?\n\nBest regards,\nAlex Thompson"
215
+ },
216
+ {
217
+ "subject": f"Thought on {company}'s {industry_hints['focus']}",
218
+ "body": f"Hi {name},\n\n{company}'s work in {industry_hints['area']} is impressive. We've developed solutions that help {industry_hints['industry']} companies {industry_hints['benefit']}.\n\nWould you be interested in a 15-minute discussion about potential applications for {company}?\n\nBest regards,\nSarah Chen"
219
+ }
220
+ ]
221
+
222
+ template = random.choice(templates)
223
+ return template["subject"], template["body"]
224
+
225
+ def _extract_industry_details(self, company_info):
226
+ """Extract industry-specific details for personalization"""
227
+ info_lower = company_info.lower() if company_info else ""
228
+
229
+ if any(word in info_lower for word in ['tech', 'software', 'saas', 'ai', 'digital']):
230
+ return {
231
+ 'industry': 'tech',
232
+ 'specialty': 'technology solutions',
233
+ 'focus': 'innovation',
234
+ 'area': 'technology',
235
+ 'benefit': 'scale their platforms and reduce technical debt',
236
+ 'goal': 'optimize your development pipeline',
237
+ 'solution': 'DevOps automation',
238
+ 'context': ' and their tech stack',
239
+ 'subject_suffix': ' with tech',
240
+ 'direct_opener': 'We implemented automated testing',
241
+ 'specific_result': 'reduced deployment time by 60%',
242
+ 'professional_result': 'achieve 40% faster time-to-market for new features'
243
+ }
244
+ elif any(word in info_lower for word in ['manufactur', 'industrial', 'equipment', 'materials']):
245
+ return {
246
+ 'industry': 'manufacturing',
247
+ 'specialty': 'industrial operations',
248
+ 'focus': 'production efficiency',
249
+ 'area': 'manufacturing',
250
+ 'benefit': 'optimize their production lines and reduce waste',
251
+ 'goal': 'increase production efficiency',
252
+ 'solution': 'process optimization',
253
+ 'context': ' and their manufacturing capabilities',
254
+ 'subject_suffix': ' in manufacturing',
255
+ 'direct_opener': 'We streamlined production workflows',
256
+ 'specific_result': 'increased throughput by 45%',
257
+ 'professional_result': 'achieve 30% improvement in production efficiency'
258
+ }
259
+ elif any(word in info_lower for word in ['health', 'medical', 'pharma', 'clinical']):
260
+ return {
261
+ 'industry': 'healthcare',
262
+ 'specialty': 'healthcare solutions',
263
+ 'focus': 'patient outcomes',
264
+ 'area': 'healthcare',
265
+ 'benefit': 'improve patient outcomes while reducing costs',
266
+ 'goal': 'enhance patient care efficiency',
267
+ 'solution': 'workflow optimization',
268
+ 'context': ' and their patient care approach',
269
+ 'subject_suffix': ' in healthcare',
270
+ 'direct_opener': 'We optimized patient flow systems',
271
+ 'specific_result': 'reduced wait times by 50%',
272
+ 'professional_result': 'achieve 25% improvement in patient satisfaction scores'
273
+ }
274
+ else:
275
+ return {
276
+ 'industry': 'business',
277
+ 'specialty': 'business operations',
278
+ 'focus': 'growth',
279
+ 'area': 'operations',
280
+ 'benefit': 'streamline operations and drive growth',
281
+ 'goal': 'scale your operations',
282
+ 'solution': 'process optimization',
283
+ 'context': ' and their business model',
284
+ 'subject_suffix': '',
285
+ 'direct_opener': 'We automated key business processes',
286
+ 'specific_result': 'increased efficiency by 40%',
287
+ 'professional_result': 'achieve 35% operational cost reduction'
288
+ }
289
+
290
+ def _load_prompt_templates(self):
291
+ """Load sophisticated prompt templates for different use cases"""
292
+ return {
293
+ "few_shot_template": '''You are an elite B2B sales copywriter. Write ONE personalized cold email that sounds natural and converts.
294
+
295
+ <examples>
296
+ EXAMPLE 1:
297
+ SUBJECT: Quick question about Acme's EU expansion
298
+ BODY: Hi Sarah,
299
+
300
+ Saw Acme just launched in Berlin – congrats! We helped Contoso reduce their GDPR compliance prep by 68% with a simple automation.
301
+
302
+ Worth a 10-minute chat about how this could apply to your EU rollout?
303
+
304
+ Best,
305
+ Alex
306
+
307
+ EXAMPLE 2:
308
+ SUBJECT: Thought on TechCorp's materials testing
309
+ BODY: Hi John,
310
+
311
+ Noticed TechCorp specializes in X-ray spectroscopy equipment. We just helped a similar lab increase throughput 40% with workflow optimization.
312
+
313
+ Mind if I share what worked for them? 15-minute call?
314
+
315
+ Best,
316
+ Sam
317
+
318
+ EXAMPLE 3:
319
+ SUBJECT: Manufacturing efficiency idea for IndustrialCorp
320
+ BODY: Hi Mike,
321
+
322
+ IndustrialCorp's production line setup caught my attention. We automated similar processes for MetalWorks, reducing their cycle time by 35%.
323
+
324
+ Open to a brief conversation about applications for your facility?
325
+
326
+ Best regards,
327
+ Jennifer
328
+ </examples>
329
+
330
+ Now write an email for:
331
+ Name: {name}
332
+ Company: {company}
333
+ Company Info: {company_context}
334
+ Tone: {tone}
335
+
336
+ Requirements:
337
+ - Use the company info naturally in the first 2 lines
338
+ - Maximum 70 words in body (excluding signature)
339
+ - Clear yes/no question at the end
340
+ - No placeholders like [Your Name] or [Company]
341
+ - Professional but conversational
342
+ - Include specific benefit or result if possible
343
+
344
+ Return ONLY this JSON format:
345
+ {{"subject": "...", "body": "..."}}''',
346
+
347
+ "industry_specific": {
348
+ "technology": '''Write a cold email for a tech company. Focus on efficiency, scalability, and competitive advantage.''',
349
+ "healthcare": '''Write a cold email for a healthcare company. Focus on patient outcomes, compliance, and cost reduction.''',
350
+ "manufacturing": '''Write a cold email for a manufacturing company. Focus on production efficiency, quality, and cost savings.''',
351
+ "services": '''Write a cold email for a service company. Focus on client satisfaction, process improvement, and growth.''',
352
+ "default": '''Write a cold email that focuses on business growth and operational efficiency.'''
353
+ }
354
+ }
355
+
356
+ def _extract_industry(self, company_info):
357
+ """Extract industry type from company information"""
358
+ company_lower = company_info.lower()
359
+
360
+ if any(word in company_lower for word in ['tech', 'software', 'saas', 'ai', 'digital', 'app', 'platform']):
361
+ return 'technology'
362
+ elif any(word in company_lower for word in ['health', 'medical', 'pharma', 'hospital', 'clinic']):
363
+ return 'healthcare'
364
+ elif any(word in company_lower for word in ['manufactur', 'factory', 'production', 'industrial', 'equipment']):
365
+ return 'manufacturing'
366
+ elif any(word in company_lower for word in ['service', 'consulting', 'agency', 'firm']):
367
+ return 'services'
368
+ else:
369
+ return 'default'
370
+
371
+ def _create_company_context(self, company, company_info):
372
+ """Create focused company context for the prompt"""
373
+ # Extract key information and clean it up
374
+ context_parts = []
375
+
376
+ if company_info and len(company_info) > 10:
377
+ # Extract meaningful phrases
378
+ sentences = re.split(r'[.!?]+', company_info)
379
+ for sentence in sentences[:3]: # First 3 sentences
380
+ sentence = sentence.strip()
381
+ if len(sentence) > 20 and not sentence.startswith('Title:'):
382
+ # Remove common fluff words
383
+ sentence = re.sub(r'Description:\s*', '', sentence)
384
+ sentence = re.sub(r'Company Website:\s*', '', sentence)
385
+ sentence = re.sub(r'LinkedIn:\s*', '', sentence)
386
+ if sentence:
387
+ context_parts.append(sentence)
388
+
389
+ if not context_parts:
390
+ context_parts.append(f"{company} is a company in their industry")
391
+
392
+ return ' | '.join(context_parts[:2]) # Max 2 key points
393
+
394
+ def generate_email(self, name, company, company_info, tone="Professional", temperature=0.7):
395
+ """Generate both subject and email body using advanced prompting"""
396
+ # Clean up and prepare context
397
+ company_context = self._create_company_context(company, company_info)
398
+
399
+ # Calibrate temperature for production readiness
400
+ if temperature > 1.0:
401
+ temperature = 0.8 # Cap at 0.8 for production readiness
402
+
403
+ # Try AI generation first
404
+ if self.model:
405
+ try:
406
+ # Build the prompt using few-shot template
407
+ prompt = self.prompt_templates["few_shot_template"].format(
408
+ name=name,
409
+ company=company,
410
+ company_context=company_context,
411
+ tone=tone.lower()
412
+ )
413
+
414
+ # Generate with model
415
+ response = self._generate_with_model(prompt, max_tokens=200, temperature=temperature)
416
+
417
+ # Parse the response
418
+ subject, body = self._parse_json_response(response)
419
+
420
+ # Polish the content
421
+ subject, body = self._polish_email_content(subject, body)
422
+
423
+ # Validate quality
424
+ quality_score, issues = self._validate_email_quality(subject, body, name, company)
425
+
426
+ # If quality is good enough, return it
427
+ if quality_score >= 70 and 'placeholders' not in issues:
428
+ print(f"βœ… AI generated email (Quality: {quality_score}%)")
429
+ return subject, body
430
+ else:
431
+ print(f"⚠️ AI output quality too low ({quality_score}%), using advanced fallback")
432
+
433
+ except Exception as e:
434
+ print(f"❌ AI generation failed: {e}, using advanced fallback")
435
+
436
+ # Use advanced fallback system
437
+ print("πŸ”„ Using advanced fallback generation")
438
+ subject, body = self._advanced_fallback_generation(name, company, company_info, tone)
439
+
440
+ # Always polish fallback content
441
+ subject, body = self._polish_email_content(subject, body)
442
+
443
+ return subject, body
444
+
445
+ def _clean_subject(self, subject, company):
446
+ """Clean and validate subject line"""
447
+ if not subject or len(subject) < 5:
448
+ return f"Quick question about {company}"
449
+
450
+ # Remove common prefixes
451
+ subject = re.sub(r'^(Subject|SUBJECT):\s*', '', subject, flags=re.IGNORECASE)
452
+ subject = subject.strip('"\'')
453
+
454
+ # Ensure reasonable length
455
+ if len(subject) > 60:
456
+ subject = subject[:57] + "..."
457
+
458
+ return subject
459
+
460
+ def _clean_body(self, body, name):
461
+ """Clean and validate email body"""
462
+ if not body or len(body) < 20:
463
+ return f"Hi {name},\n\nI'd love to discuss how we can help your business grow.\n\nInterested in a quick call?\n\nBest regards"
464
+
465
+ # Remove common prefixes
466
+ body = re.sub(r'^(Body|BODY|Email|EMAIL):\s*', '', body, flags=re.IGNORECASE)
467
+
468
+ # Ensure proper greeting
469
+ if not body.lower().startswith(('hi ', 'hello ', 'dear ')):
470
+ body = f"Hi {name},\n\n{body}"
471
+
472
+ # Ensure proper closing
473
+ closing_patterns = ['best regards', 'best,', 'sincerely', 'regards,', 'cheers,']
474
+ has_closing = any(pattern in body.lower() for pattern in closing_patterns)
475
+
476
+ if not has_closing:
477
+ if not body.endswith('\n'):
478
+ body += '\n'
479
+ body += '\nBest regards'
480
+
481
+ return body
482
+
483
+ def _polish_email_content(self, subject, body):
484
+ """Polish email content for grammar and professionalism"""
485
+
486
+ # Fix common grammar issues
487
+ body = re.sub(r'\s+', ' ', body) # Multiple spaces
488
+ body = re.sub(r'([.!?])\s*([a-z])', r'\1 \2', body) # Space after punctuation
489
+ body = re.sub(r'(\w)\s*\n\s*(\w)', r'\1\n\n\2', body) # Proper paragraph spacing
490
+
491
+ # Ensure professional closing
492
+ if not re.search(r'(Best regards|Best|Sincerely|Cheers),?\s*\n?[A-Z][a-z]+', body):
493
+ if body.strip().endswith(','):
494
+ body = body.strip() + '\n\nBest regards,\nAlex'
495
+ else:
496
+ body = body.strip() + '\n\nBest regards,\nAlex'
497
+
498
+ # Fix subject line
499
+ subject = subject.strip()
500
+ if len(subject) > 65:
501
+ subject = subject[:62] + "..."
502
+
503
+ # Capitalize first letter of subject if not already
504
+ if subject and subject[0].islower():
505
+ subject = subject[0].upper() + subject[1:]
506
+
507
+ return subject, body
508
+
509
+ def _validate_email_quality(self, subject, body, name, company):
510
+ """Validate email quality and return quality score"""
511
+ issues = []
512
+
513
+ # Check subject length
514
+ if len(subject) < 10 or len(subject) > 65:
515
+ issues.append("subject_length")
516
+
517
+ # Check body length
518
+ words = len(body.split())
519
+ if words < 20 or words > 150:
520
+ issues.append("body_length")
521
+
522
+ # Check for placeholders
523
+ if '[Your Name]' in body or '[Company]' in body or '{{' in body:
524
+ issues.append("placeholders")
525
+
526
+ # Check personalization
527
+ if name not in body or company not in body:
528
+ issues.append("personalization")
529
+
530
+ # Check for call-to-action
531
+ cta_phrases = ['call', 'conversation', 'chat', 'discuss', 'talk', 'meeting', 'connect']
532
+ if not any(phrase in body.lower() for phrase in cta_phrases):
533
+ issues.append("no_cta")
534
+
535
+ quality_score = max(0, 100 - (len(issues) * 15))
536
+ return quality_score, issues
537
+
538
+ def generate_multiple_variations(self, name, company, company_info, num_variations=3, tone="Professional"):
539
+ """Generate multiple email variations with different approaches"""
540
+ variations = []
541
+ tones = ["Professional", "Friendly", "Direct"]
542
+ temperatures = [0.6, 0.7, 0.8]
543
+
544
+ for i in range(num_variations):
545
+ current_tone = tones[i % len(tones)]
546
+ current_temp = temperatures[i % len(temperatures)]
547
+
548
+ subject, email_body = self.generate_email(
549
+ name, company, company_info,
550
+ tone=current_tone, temperature=current_temp
551
+ )
552
+
553
+ variations.append({
554
+ 'variation': i + 1,
555
+ 'tone': current_tone,
556
+ 'temperature': current_temp,
557
+ 'subject': subject,
558
+ 'email_body': email_body
559
+ })
560
+
561
+ return variations
562
+
563
+ def generate_email_v2(self, recipient_name, recipient_email, company_name, company_data, tone="professional", temperature=0.7):
564
+ """Compatibility method for different calling signatures"""
565
+ # Extract company info from company_data if it's a dict
566
+ if isinstance(company_data, dict):
567
+ company_info = company_data.get('description', f"Company: {company_name}")
568
+ else:
569
+ company_info = str(company_data) if company_data else f"Company: {company_name}"
570
+
571
+ # Call the main generate_email method
572
+ subject, body = self.generate_email(
573
+ name=recipient_name,
574
+ company=company_name,
575
+ company_info=company_info,
576
+ tone=tone,
577
+ temperature=temperature
578
+ )
579
+
580
+ # Return in the expected format
581
+ return {
582
+ 'subject': subject,
583
+ 'content': body,
584
+ 'quality_score': 8.0
585
+ }