Seth commited on
Commit Β·
edcff68
1
Parent(s): 8f69e9d
update
Browse files
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
|
| 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 |
-
|
| 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":
|
| 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\
|
| 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 |
-
|
| 806 |
-
Message 1
|
| 807 |
-
[connection note or first DM: β€300 characters if connection note; otherwise short paragraph]
|
| 808 |
|
| 809 |
-
|
| 810 |
-
|
| 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 |
-
|
| 820 |
|
| 821 |
'Document Management': `π LINKEDIN SYSTEM PROMPT
|
| 822 |
-
Short LinkedIn
|
|
|
|
| 823 |
|
| 824 |
'Invoice Processing': `π LINKEDIN SYSTEM PROMPT
|
| 825 |
-
|
|
|
|
| 826 |
|
| 827 |
'Expense Management': `π LINKEDIN SYSTEM PROMPT
|
| 828 |
-
LinkedIn DMs for finance teams about expense workflows.
|
|
|
|
| 829 |
|
| 830 |
'Procurement Automation': `π LINKEDIN SYSTEM PROMPT
|
| 831 |
-
Procurement-focused LinkedIn
|
|
|
|
| 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\
|
| 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\
|
| 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\
|
| 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\
|
| 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 |
};
|