Seth commited on
Commit
edcff68
Β·
1 Parent(s): 8f69e9d
backend/app/gpt_service.py CHANGED
@@ -423,6 +423,18 @@ def generate_linkedin_sequence(
423
  plan_extra += (
424
  "\nThe first LinkedIn touch in this list is always a connection request before any follow-up DMs.\n"
425
  )
 
 
 
 
 
 
 
 
 
 
 
 
426
  user_prompt = f"""Generate a complete LinkedIn outreach sequence for this contact.
427
 
428
  Contact Information:
@@ -434,20 +446,12 @@ Contact Information:
434
  - Industry: {industry if industry else 'Not specified'}
435
  - Location: {location if location else 'Not specified'}
436
 
437
- Generate all {num_messages} messages following the rules in the system prompt.
438
  {plan_extra}
439
  CRITICAL: Write for LinkedIn DMs or connection notes β€” short, plain, professional. No email-style subjects.
440
 
441
- Output format (strict):
442
- Message 1
443
- [text]
444
-
445
- Message 2
446
- [text]
447
-
448
- Message 3
449
- [text]
450
- (add Message 4, Message 5 if the prompt specifies more than 3)
451
 
452
  Use the contact's real first name where a greeting is appropriate. Do not use placeholder tokens like {{{{first_name}}}} in the final output."""
453
 
@@ -507,8 +511,12 @@ Use the contact's real first name where a greeting is appropriate. Do not use pl
507
  )
508
  messages.append({"email_number": 1, "subject": "", "body": chunk})
509
 
 
 
 
 
510
  out = []
511
- for m in messages:
512
  out.append(
513
  {
514
  "first_name": first_name or "",
@@ -517,7 +525,7 @@ Use the contact's real first name where a greeting is appropriate. Do not use pl
517
  "company": company or "",
518
  "title": title or "",
519
  "product": product_name,
520
- "email_number": m["email_number"],
521
  "subject": "",
522
  "email_content": m["body"] or "",
523
  }
 
423
  plan_extra += (
424
  "\nThe first LinkedIn touch in this list is always a connection request before any follow-up DMs.\n"
425
  )
426
+ output_blocks = "\n\n".join(
427
+ [f"Message {i}\n[text for message {i}]" for i in range(1, num_messages + 1)]
428
+ )
429
+ if li_actions:
430
+ system_prompt = (
431
+ "πŸ”’ CAMPAIGN SEQUENCE (AUTHORITATIVE β€” OVERRIDES ANY FIXED '3 MESSAGE' OR OTHER COUNT IN THE PROMPT BELOW)\n"
432
+ f"You MUST output exactly {num_messages} labeled sections: Message 1 through Message {num_messages}.\n"
433
+ "Do not output Message "
434
+ + str(num_messages + 1)
435
+ + " or higher. Do not invent extra LinkedIn touches.\n\n"
436
+ + system_prompt
437
+ )
438
  user_prompt = f"""Generate a complete LinkedIn outreach sequence for this contact.
439
 
440
  Contact Information:
 
446
  - Industry: {industry if industry else 'Not specified'}
447
  - Location: {location if location else 'Not specified'}
448
 
449
+ Generate exactly {num_messages} messages (no more, no fewer) following the rules in the system prompt.
450
  {plan_extra}
451
  CRITICAL: Write for LinkedIn DMs or connection notes β€” short, plain, professional. No email-style subjects.
452
 
453
+ Output format (strict) β€” use exactly these {num_messages} labeled blocks and nothing beyond:
454
+ {output_blocks}
 
 
 
 
 
 
 
 
455
 
456
  Use the contact's real first name where a greeting is appropriate. Do not use placeholder tokens like {{{{first_name}}}} in the final output."""
457
 
 
511
  )
512
  messages.append({"email_number": 1, "subject": "", "body": chunk})
513
 
514
+ # Keep only the first N parsed messages when the model returns extras (campaign plan is authoritative).
515
+ if len(messages) > num_messages:
516
+ messages = messages[:num_messages]
517
+
518
  out = []
519
+ for idx, m in enumerate(messages, start=1):
520
  out.append(
521
  {
522
  "first_name": first_name or "",
 
525
  "company": company or "",
526
  "title": title or "",
527
  "product": product_name,
528
+ "email_number": idx,
529
  "subject": "",
530
  "email_content": m["body"] or "",
531
  }
frontend/src/components/campaigns/CreateCampaignWizard.jsx CHANGED
@@ -84,6 +84,7 @@ function buildCampaignSequenceAppendix(steps) {
84
  lines.push(
85
  '',
86
  'LinkedIn: the first LinkedIn message in your output must be the connection request whenever the sequence includes a connection step before DMs.',
 
87
  'Keep narrative continuity with Gmail touches in the same campaign week where relevant.'
88
  );
89
  return lines.join('\n');
@@ -567,7 +568,7 @@ export default function CreateCampaignWizard({
567
  if (!cur) {
568
  cur =
569
  LINKEDIN_DEFAULT_TEMPLATES[p.name] ||
570
- `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nGenerate a 3-message LinkedIn sequence for ${p.name}.\nLabel: Message 1, Message 2, Message 3.\nUse {{first_name}}, {{company}}. Sender: Anna.`;
571
  }
572
  if (!cur || cur.includes(WIZARD_SEQUENCE_TAG)) return;
573
  next[p.name] = `${cur}\n\n${appendix}`;
 
84
  lines.push(
85
  '',
86
  'LinkedIn: the first LinkedIn message in your output must be the connection request whenever the sequence includes a connection step before DMs.',
87
+ 'Write exactly one labeled LinkedIn message (Message i) per LinkedIn action listed above β€” same count and order; do not add extra LinkedIn messages beyond those actions.',
88
  'Keep narrative continuity with Gmail touches in the same campaign week where relevant.'
89
  );
90
  return lines.join('\n');
 
568
  if (!cur) {
569
  cur =
570
  LINKEDIN_DEFAULT_TEMPLATES[p.name] ||
571
+ `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${p.name}. Sender: Anna.\nMatch the Message count in the generation request exactly.`;
572
  }
573
  if (!cur || cur.includes(WIZARD_SEQUENCE_TAG)) return;
574
  next[p.name] = `${cur}\n\n${appendix}`;
frontend/src/components/prompts/PromptEditor.jsx CHANGED
@@ -802,33 +802,33 @@ Sender first name is always Anna. Never use the prospect as the sender.
802
  No fake LinkedIn "I saw your post" personalization. Use only provided fields.
803
  Tone: calm, peer-to-peer, non-salesy, no emojis, no em dashes.
804
 
805
- Output format (strict) β€” 3 messages:
806
- Message 1
807
- [connection note or first DM: ≀300 characters if connection note; otherwise short paragraph]
808
 
809
- Message 2
810
- [follow-up DM, new angle]
811
-
812
- Message 3
813
- [light check-in, one question]
814
 
815
  Use variables in your reasoning: {{first_name}}, {{company}}, {{sender_name}} but output final text with real first name filled in.`,
816
 
817
  'Sales Order Processing': `πŸ”’ LINKEDIN SYSTEM PROMPT
818
  You write concise LinkedIn DMs for order-ops / AR professionals. Sender: Anna. Plain language, operational focus, one question per message. No hype.
819
- Output Message 1, 2, 3 as labeled blocks.`,
820
 
821
  'Document Management': `πŸ”’ LINKEDIN SYSTEM PROMPT
822
- Short LinkedIn sequence (3 messages) for document / records leaders. Calm, practical, non-marketing. Sender Anna. Use Message 1/2/3 format.`,
 
823
 
824
  'Invoice Processing': `πŸ”’ LINKEDIN SYSTEM PROMPT
825
- 3-step LinkedIn outreach for invoice / AP operations. Under 120 words per message. Message 1, 2, 3 format. Anna as sender.`,
 
826
 
827
  'Expense Management': `πŸ”’ LINKEDIN SYSTEM PROMPT
828
- LinkedIn DMs for finance teams about expense workflows. 3 messages, professional, no buzzwords. Message 1, 2, 3.`,
 
829
 
830
  'Procurement Automation': `πŸ”’ LINKEDIN SYSTEM PROMPT
831
- Procurement-focused LinkedIn sequence in 3 messages. Practical questions only. Label Message 1, 2, 3.`,
 
832
  };
833
 
834
  const PromptEditor = forwardRef(function PromptEditor(
@@ -866,7 +866,7 @@ const PromptEditor = forwardRef(function PromptEditor(
866
  const emailDefault = DEFAULT_TEMPLATES[product.name];
867
  const liDefault = LINKEDIN_DEFAULT_TEMPLATES[product.name];
868
  const emailFallback = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
869
- const liFallback = `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nGenerate a 3-message LinkedIn sequence for ${product.name}.\nLabel: Message 1, Message 2, Message 3.\nUse {{first_name}}, {{company}}. Sender: Anna.`;
870
  const email = savedEmail || emailDefault || emailFallback;
871
  if (includeLinkedinInCampaign) {
872
  const li = savedLi || liDefault || liFallback;
@@ -889,7 +889,7 @@ const PromptEditor = forwardRef(function PromptEditor(
889
  } else if (defaultTemplate) {
890
  newPrompts[product.name] = defaultTemplate;
891
  } else if (variant === 'linkedin') {
892
- newPrompts[product.name] = `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nGenerate a 3-message LinkedIn sequence for ${product.name}.\nLabel: Message 1, Message 2, Message 3.\nUse {{first_name}}, {{company}}. Sender: Anna.`;
893
  } else {
894
  newPrompts[product.name] = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
895
  }
@@ -960,7 +960,7 @@ const PromptEditor = forwardRef(function PromptEditor(
960
  if (includeLinkedinInCampaign) {
961
  const liDefault =
962
  LINKEDIN_DEFAULT_TEMPLATES[productName] ||
963
- `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nGenerate a 3-message LinkedIn sequence for ${productName}.\nLabel: Message 1, Message 2, Message 3.\nUse {{first_name}}, {{company}}. Sender: Anna.`;
964
  handlePromptChange(
965
  productName,
966
  `${emailDefault}${CAMPAIGN_COMBINED_PROMPT_SPLIT}${liDefault}`
@@ -974,7 +974,7 @@ const PromptEditor = forwardRef(function PromptEditor(
974
  const defaultTemplate =
975
  library[productName] ||
976
  (variant === 'linkedin'
977
- ? `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nGenerate a 3-message LinkedIn sequence for ${productName}.\nLabel: Message 1, Message 2, Message 3.\nUse {{first_name}}, {{company}}. Sender: Anna.`
978
  : `Subject: {{first_name}}, let's talk about ${productName}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${productName} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`);
979
  handlePromptChange(productName, defaultTemplate);
980
  };
 
802
  No fake LinkedIn "I saw your post" personalization. Use only provided fields.
803
  Tone: calm, peer-to-peer, non-salesy, no emojis, no em dashes.
804
 
805
+ When the user message specifies how many labeled blocks (Message 1 … Message N) to write, follow that N exactly β€” it mirrors the campaign sequence (connection note vs DM). If no count is given, default to 3 messages.
 
 
806
 
807
+ Each labeled block:
808
+ Message i
809
+ [body text]
 
 
810
 
811
  Use variables in your reasoning: {{first_name}}, {{company}}, {{sender_name}} but output final text with real first name filled in.`,
812
 
813
  'Sales Order Processing': `πŸ”’ LINKEDIN SYSTEM PROMPT
814
  You write concise LinkedIn DMs for order-ops / AR professionals. Sender: Anna. Plain language, operational focus, one question per message. No hype.
815
+ Use the exact number of labeled Message blocks requested in the user prompt (campaign sequence).`,
816
 
817
  'Document Management': `πŸ”’ LINKEDIN SYSTEM PROMPT
818
+ Short LinkedIn touches for document / records leaders. Calm, practical, non-marketing. Sender Anna.
819
+ Match the count and order of Message 1…N from the user prompt; do not add extra messages.`,
820
 
821
  'Invoice Processing': `πŸ”’ LINKEDIN SYSTEM PROMPT
822
+ LinkedIn outreach for invoice / AP operations. Under ~120 words per touch unless it is a connection note (keep those shorter).
823
+ Follow the user prompt's Message count exactly. Anna as sender.`,
824
 
825
  'Expense Management': `πŸ”’ LINKEDIN SYSTEM PROMPT
826
+ LinkedIn DMs for finance teams about expense workflows. Professional, no buzzwords.
827
+ Write exactly as many labeled Message sections as the user prompt specifies.`,
828
 
829
  'Procurement Automation': `πŸ”’ LINKEDIN SYSTEM PROMPT
830
+ Procurement-focused LinkedIn touches. Practical questions only.
831
+ Use only the Message 1…N blocks requested in the user prompt; same count as the campaign sequence.`,
832
  };
833
 
834
  const PromptEditor = forwardRef(function PromptEditor(
 
866
  const emailDefault = DEFAULT_TEMPLATES[product.name];
867
  const liDefault = LINKEDIN_DEFAULT_TEMPLATES[product.name];
868
  const emailFallback = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
869
+ const liFallback = `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${product.name}. Sender: Anna.\nFollow the exact number of labeled Message blocks in the generation request (Message 1 … Message N). Do not add extra messages.`;
870
  const email = savedEmail || emailDefault || emailFallback;
871
  if (includeLinkedinInCampaign) {
872
  const li = savedLi || liDefault || liFallback;
 
889
  } else if (defaultTemplate) {
890
  newPrompts[product.name] = defaultTemplate;
891
  } else if (variant === 'linkedin') {
892
+ newPrompts[product.name] = `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${product.name}. Sender: Anna.\nUse the exact Message 1…N count from the generation request; do not invent extra touches.\nUse {{first_name}}, {{company}}.`;
893
  } else {
894
  newPrompts[product.name] = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
895
  }
 
960
  if (includeLinkedinInCampaign) {
961
  const liDefault =
962
  LINKEDIN_DEFAULT_TEMPLATES[productName] ||
963
+ `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${productName}. Sender: Anna.\nMatch the Message count in the generation request exactly.`;
964
  handlePromptChange(
965
  productName,
966
  `${emailDefault}${CAMPAIGN_COMBINED_PROMPT_SPLIT}${liDefault}`
 
974
  const defaultTemplate =
975
  library[productName] ||
976
  (variant === 'linkedin'
977
+ ? `πŸ”’ LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${productName}. Sender: Anna.\nMatch the Message count in the generation request exactly.`
978
  : `Subject: {{first_name}}, let's talk about ${productName}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${productName} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`);
979
  handlePromptChange(productName, defaultTemplate);
980
  };