AI Proposal Writer: A Two-Stage Prompt Engineering Guide

技术宅Ray Novice 1h ago 78 views 4 likes 2 min read

Stop using a single prompt to generate client proposals. If you feed a job post and your resume into an LLM and ask it to "write a professional proposal," you'll get the same generic garbage everyone else is sending: "I am writing to express my interest in your project..." This is the fastest way to get ignored on platforms like Upwork.

The secret to high-conversion AI writing isn't the model you use—it's the architecture. After building a dedicated proposal tool, I found that splitting the process into a two-stage workflow (Extraction then Generation) completely eliminates the "AI smell."

The Architecture: Extraction vs. Generation

The core issue with single-prompting is that the LLM tries to analyze the requirements and write the prose simultaneously, often defaulting to safe, boring templates. By decoupling these, you force the AI to understand the client's pain points before it even thinks about writing a sentence.

Stage 1: Signal Extraction


First, I use a prompt that acts as a data parser. It ignores the "writing" part entirely and just converts the messy job post into a structured JSON object.

const extractionPrompt = ` You are analyzing a freelance job posting. Extract ONLY these fields as JSON:

{ "client_company": "string | null", "specific_problem": "string - the exact pain point described", "desired_outcome": "string - what success looks like", "tone": "formal | casual | urgent", "budget_signal": "string | null - any rate mentioned" }

Job Post: ${jobPost} `;

Stage 2: Proposal Generation


Now that we have the "signals," the second prompt uses that data as a constraint. I implement strict negative constraints (the "NEVER" rules) to kill the generic AI habits.

const proposalPrompt = ` Write an Upwork proposal using this EXACT structure:

OPEN with: "${extracted.specific_problem || 'Your project sounds interesting'}"
Reference their SPECIFIC problem, not your skills
Do NOT start with "Hi" or "I read your post"
PROVE with one quantified result
Example format: "I helped a client [do X] which resulted in [Y]"
Keep it specific, not generic
MATCH their tone: ${extracted.tone}
END with a specific question about their current setup
Example: "Are you currently using [tool]?"
Keep total word count under 200
NEVER start sentences with "I am," "I have," or "I am a"
Client: ${extracted.client_company || 'not mentioned'} `;

Technical Implementation and Lessons

For the build, I went with a standard modern stack: Next.js 14 (App Router) for the frontend, Supabase for the backend and edge functions, and GPT-4o. I found GPT-4o to be the most reliable for the extraction phase because it handles the JSON formatting without hallucinations.

If you're planning a similar deployment, avoid these mistakes I made:

  • Latency: Don't wait for the full response. Implement streaming immediately, or your users will think the app has crashed.
  • Efficiency: Cache your extraction results. There's no reason to re-run the extraction prompt if the job post hasn't changed; hash the input and store the JSON.
  • Iterative UI: Add a "regenerate" or "tweak" button from the start. Even with great prompts, users always want a slightly different version.

This two-stage pattern is a massive lever for any AI workflow where you need tailored output from unstructured data. It moves the LLM from "guessing" what to write to "executing" based on extracted facts.
openainextjsPromptsaas

All Replies (3)

P
PatFounder Advanced 9h ago
I started doing a "tone check" pass and it stopped sounding so robotic. Huge difference.
0 Reply
T
TaylorDreamer Intermediate 9h ago
I usually ask it to critique the first draft for clichés before finalizing. Helps a ton.
0 Reply
Z
Zoe12 Novice 9h ago
Do you find using a specific persona prompt in stage one improves the output quality?
0 Reply

Write a Reply

Markdown supported