DEV Community

Cover image for Building a Persona Engine: How We Make AI Sound Like Different People
HelperX
HelperX

Posted on

Building a Persona Engine: How We Make AI Sound Like Different People

The hardest part of AI-generated social media content isn't making it grammatically correct. It's making it sound like a specific person — not ChatGPT, not a generic assistant, but a crypto analyst who's skeptical of altcoins, or a SaaS founder who thinks in metrics.

Here's how we built a persona engine that makes one LLM sound like hundreds of different people.

The problem with default LLM output

Ask any LLM to reply to a tweet without persona guidance, and you get this:

"That's a really insightful observation! I think you make a great point about the importance of automation. It's definitely a space worth watching. Thanks for sharing your thoughts!"

Every word is correct. Every sentence is hollow. This is the "helpful assistant" voice that every LLM defaults to — agreeable, complimentary, generic. Post 10 of these and your account looks like a bot.

The persona engine exists to turn this:

"Great point about automation!"

Into this (crypto analyst persona):

"Automation in DeFi is where the real yield compression happens. The protocols that survive will be the ones that automate risk management, not just execution. Most of what's being built right now is sophisticated front-running."

Or this (bootstrapped SaaS founder persona):

"We automated our onboarding emails last quarter. Conversion stayed flat but support tickets dropped 40%. The unsexy automations are the ones that actually matter."

Same tweet being replied to. Completely different voices.

Persona definition

Operators define their persona through structured fields, not free-text:

const personaSchema = {
  role: 'string',           // "crypto analyst", "SaaS founder", "productivity coach"
  expertise: ['string'],     // ["DeFi", "yield farming", "on-chain analytics"]
  perspective: 'string',     // "skeptical of hype, data-driven, focuses on fundamentals"
  tone: 'number',           // 1 (formal) to 5 (casual)
  assertiveness: 'number',  // 1 (agreeable) to 5 (opinionated)
  avoid: ['string'],        // ["emojis", "hashtags", "I agree"]
  vocabulary: ['string'],   // ["yield compression", "TVL", "protocol-level"]
  language: 'string'        // "en", "es", "multi"
};
Enter fullscreen mode Exit fullscreen mode

Structured fields instead of free-text because:

  1. Consistency. Free-text personas drift across prompt iterations. Structured fields are deterministic.
  2. Validation. We can check that required fields exist before the module starts.
  3. Composability. Fields combine in the prompt builder without string concatenation hacks.

Prompt assembly

The persona fields map to specific sections in the system prompt:

function buildSystemPrompt(persona) {
  const sections = [];

  sections.push(
    `You are replying to tweets on X as a ${persona.role}.`
  );

  if (persona.expertise.length > 0) {
    sections.push(
      `Your areas of expertise: ${persona.expertise.join(', ')}.`
    );
  }

  if (persona.perspective) {
    sections.push(
      `Your perspective: ${persona.perspective}.`
    );
  }

  const toneDescriptions = {
    1: 'Your tone is formal and professional. Use complete sentences and proper grammar.',
    2: 'Your tone is professional but approachable. Like an industry conference conversation.',
    3: 'Your tone is conversational and professional. Like messaging a respected colleague.',
    4: 'Your tone is casual but knowledgeable. Like talking to a friend in the industry.',
    5: 'Your tone is casual and direct. Like texting someone you know well.'
  };
  sections.push(toneDescriptions[persona.tone] || toneDescriptions[3]);

  const assertDescriptions = {
    1: 'You tend to agree and build on others\' points.',
    2: 'You share your perspective alongside the author\'s.',
    3: 'You offer your own take, sometimes agreeing, sometimes not.',
    4: 'You\'re opinionated and not afraid to disagree respectfully.',
    5: 'You challenge assumptions and take strong positions.'
  };
  sections.push(assertDescriptions[persona.assertiveness] || assertDescriptions[3]);

  if (persona.vocabulary.length > 0) {
    sections.push(
      `Use terminology like: ${persona.vocabulary.join(', ')}. ` +
      'These words should appear naturally, not forced into every reply.'
    );
  }

  const constraints = [
    'Reply in 2-3 sentences maximum.',
    'Never start with "Great point" or "I agree" or "Interesting".',
    'Never use hashtags.',
    'Never include links or URLs.',
    'Never mention that you are an AI or automated.',
    'Never repeat the original tweet back to the author.',
    'If you cannot generate a genuine, valuable response, output only the word SKIP.',
  ];

  if (persona.avoid.length > 0) {
    persona.avoid.forEach(item => {
      constraints.push(`Never use: ${item}.`);
    });
  }

  sections.push('Rules:\n' + constraints.map(c => `- ${c}`).join('\n'));

  return sections.join('\n\n');
}
Enter fullscreen mode Exit fullscreen mode

The resulting prompt for a crypto analyst at tone 4, assertiveness 4:

You are replying to tweets on X as a crypto analyst.

Your areas of expertise: DeFi, yield farming, on-chain analytics.

Your perspective: skeptical of hype, data-driven, focuses on fundamentals.

Your tone is casual but knowledgeable. Like talking to a friend in the industry.

You're opinionated and not afraid to disagree respectfully.

Use terminology like: yield compression, TVL, protocol-level. These words
should appear naturally, not forced into every reply.

Rules:
- Reply in 2-3 sentences maximum.
- Never start with "Great point" or "I agree" or "Interesting".
...
Enter fullscreen mode Exit fullscreen mode

The consistency problem

A persona that sounds great in one reply can drift by reply #50. The LLM starts reverting to its default voice, especially on topics outside the persona's expertise.

Solution 1: Anchor phrases

We maintain 3-5 anchor phrases per persona — characteristic expressions the persona uses:

const anchorPhrases = {
  crypto_analyst: [
    "the on-chain data tells a different story",
    "this is noise, not signal",
    "fundamentals haven't changed",
    "the real question is who's providing the liquidity"
  ],
  saas_founder: [
    "we tested this",
    "the numbers say otherwise",
    "at our scale",
    "the boring answer is"
  ]
};
Enter fullscreen mode Exit fullscreen mode

These are included in the prompt as examples of the persona's voice — not as templates to copy, but as calibration points for the LLM's output.

Solution 2: Voice consistency check

After generation, we run a lightweight check:

function checkVoiceConsistency(reply, persona) {
  const flags = [];

  // Check for "helpful assistant" patterns
  const assistantPatterns = [
    /^(great|good|excellent|wonderful|fantastic)\s+(point|take|observation)/i,
    /thanks for sharing/i,
    /that's (really |very )?(insightful|interesting|thought-provoking)/i,
    /I (completely |totally )?agree/i,
  ];

  for (const pattern of assistantPatterns) {
    if (pattern.test(reply)) {
      flags.push('assistant_voice_detected');
    }
  }

  // Check tone matches setting
  if (persona.tone <= 2) {
    // Formal personas shouldn't use contractions heavily
    const contractions = (reply.match(/\b(don't|won't|can't|isn't|aren't|I'm|it's)\b/g) || []).length;
    if (contractions > 2) {
      flags.push('tone_mismatch_too_casual');
    }
  }

  return { pass: flags.length === 0, flags };
}
Enter fullscreen mode Exit fullscreen mode

If the check fails, the reply is regenerated with a stronger persona reinforcement in the prompt. Maximum 2 retries before SKIP.

Multi-language personas

Some operators run accounts in non-English markets. The persona engine supports this through the language field:

function addLanguageInstruction(prompt, language) {
  const instructions = {
    en: '', // default, no instruction needed
    es: 'Reply in Spanish. Use casual Latin American Spanish, not formal Castilian.',
    pt: 'Reply in Brazilian Portuguese.',
    de: 'Reply in German. Use informal "du" form.',
    ja: 'Reply in Japanese. Use casual/polite form (desu/masu), not keigo.',
    multi: 'Reply in the same language as the original tweet.'
  };

  if (instructions[language]) {
    return prompt + '\n\n' + instructions[language];
  }
  return prompt;
}
Enter fullscreen mode Exit fullscreen mode

The multi setting is the most interesting — the AI detects the tweet's language and responds in kind. This works surprisingly well for common languages but degrades for less-represented ones in the training data.

Measuring persona quality

How do you know if the persona is working? We track three metrics:

1. Voice drift score
Compare the first 10 replies of a session to the last 10. If the tone or vocabulary shifts significantly, the persona is drifting.

2. Reject rate
Percentage of generated replies that fail the voice consistency check. Healthy: under 10%. Above 20%: the persona definition is too vague or contradicts the LLM's training.

3. Engagement delta
Compare engagement rates across different persona configurations for the same account. If a persona change drops engagement by more than 30%, the new persona doesn't resonate with the target audience.

What we learned

1. Structured personas beat free-text. "Be a crypto analyst who's skeptical" loses to a structured definition with tone=4, assertiveness=4, and a vocabulary list.

2. Anchor phrases are the highest-leverage addition. Three example phrases do more for voice consistency than 500 words of description.

3. The avoid list matters more than the do list. Telling the LLM what NOT to say (emojis, "I agree," hashtags) produces more differentiated output than telling it what to say.

4. Regeneration is cheaper than bad output. A failed voice check + regeneration costs 2x tokens. A bad reply that gets the account flagged costs infinitely more.

5. Persona quality degrades on off-topic tweets. The crypto analyst persona works great for DeFi tweets and poorly for cooking tweets. Keyword targeting must align with persona expertise.


HelperX generates persona-matched AI replies with structured voice definitions, anchor phrases, and consistency checks. Free 30-day trial.

Top comments (0)