Zum Hauptinhalt springen
Ridvan Ereng
Full Stack & AI Engineer
Alle Beiträge

Persona-Engineering: Warum dein Voice Agent eine Identität braucht

Avatar, Rolle, Bio und Email-Account als ein gebündeltes Persona-Objekt. Klingt fancy, löst aber ein konkretes Problem: Empfänger kommunizieren mit einer Person, nicht mit einem Account.

4 min Lesezeit

Wenn ein Voice Agent abnimmt, ist das erste, was der Anrufer hört, nicht die Antwort. Es ist die Stimme. Und gleich danach: ein impliziter Eindruck davon, mit wem hier eigentlich gesprochen wird. Person? Bot? Praktikant? Marketing-Maschine?

In meinem letzten Outreach-Setup habe ich das unterschätzt. Die Calls liefen technisch sauber, aber die Antwortrate auf Voicemail-Drops blieb bei 3,2%. Erst als ich den Agent mit einer richtigen Persona ausgestattet habe (Name, Rolle, Foto, plausibler Hintergrund), kletterte sie auf 11,4%. Hier ist, was ich daraus gelernt habe.

Was eine Persona im Voice-Kontext ist#

Bei einer normalen Email kennt der Empfänger den Absender aus dem From-Header. Im Voice-Kontext gibt es das nicht. Der Anrufer sieht eine Telefonnummer, hört eine Stimme und muss in den ersten zwei Sekunden entscheiden, ob er auflegt.

Was hilft: ein konsistentes Bündel aus

  • Name: Vorname + Nachname, plausibel zum Markt (kein „Max Power")
  • Rolle: konkret und glaubwürdig („Account Executive bei Beispiel GmbH" statt „AI Assistant")
  • Stimme: Voice-Profil, das zur Rolle passt (nicht 22-jährige Cartesia-Stimme für „Senior Sales")
  • Email-Account: gleicher Name, gleicher Domain, gleicher Sprachstil
  • Optional Avatar: für CRM-Anzeige beim Operator

Das ist nicht Cosplay. Das ist normaler B2B-Standard. Echte Sales-Teams haben echte Personen mit echten LinkedIn-Profilen. Wenn dein Agent einen davon ersetzt, muss er die gleiche Konsistenz liefern.

Datenmodell: ein Persona-Bundle, nicht zwei Tabellen#

Was nicht gut funktioniert: Voice-Setup und Email-Account separat verwalten. Klassisch hat man eine email_accounts-Tabelle und eine voice_configs-Tabelle, die unverbunden nebeneinander stehen. Das führt sehr schnell zu Inkonsistenz: Sales schreibt aus julian@example.com, ruft aber an als „Frieda Müller" weil die Voice-Config gerade auf eine andere Voice gesetzt war.

Die saubere Lösung: ein personas-Objekt, das beide bündelt.

CREATE TABLE personas (
  id           uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  workspace_id uuid NOT NULL REFERENCES workspaces(id),
  name         text NOT NULL,
  role         text NOT NULL,
  bio          text,
  avatar_url   text,
  voice_config jsonb NOT NULL,
  created_at   timestamptz NOT NULL DEFAULT now()
);
 
CREATE TABLE persona_email_accounts (
  persona_id        uuid REFERENCES personas(id) ON DELETE CASCADE,
  email_account_id  uuid REFERENCES email_accounts(id) ON DELETE CASCADE,
  PRIMARY KEY (persona_id, email_account_id)
);

Eine Persona kann mehrere Email-Accounts haben (Warmup-Pool), aber alle haben denselben Display-Namen, dieselbe Bio im Footer, dasselbe Voice-Profil. Beim Outbound entscheidet die Persona, nicht der Account.

Avatar-Storage: lokal vs. Upload#

Bei den Avatar-URLs habe ich zwei Wege durchgespielt.

Variante A: 16 vorgenerierte CC0-Avatare im /public-Ordner. Schnell, keine Storage-Kosten, kein Upload-Workflow nötig. Für ein internes Tool absolut ausreichend. Wenn das Setup als SaaS für Kunden gedacht ist, scheitert es daran, dass alle Workspaces dieselben 16 Gesichter teilen.

Variante B: Supabase Storage als Bucket pro Workspace. Upload-Endpoint mit MIME-Validierung und Größenbeschränkung. Storage-URL wird in personas.avatar_url gespeichert. Mehr Flexibilität, aber zusätzliche Failure-Modes (Bucket-Permissions, Cleanup beim Delete-Cascade).

Mein aktueller Default ist B mit Fallback auf A. Wenn der Workspace keinen eigenen Avatar hochlädt, wird einer aus dem Preset-Pool deterministisch zugewiesen (Hash des Persona-Namens auf 0..15). So fühlt es sich nicht zufällig an, aber es kostet keine Tile-Storage.

Sender-Variablen im Email-Composer#

Im Outreach-System bekommen Templates Zugriff auf Persona-Felder. Das ist die kleine Mechanik, die viel ausmacht:

function substituteVariables(template: string, ctx: Context): string {
  return template
    .replace(/\{\{lead_first_name\}\}/g, ctx.lead.firstName)
    .replace(/\{\{lead_company\}\}/g, ctx.lead.company)
    .replace(/\{\{sender_name\}\}/g, ctx.persona.name)
    .replace(/\{\{sender_role\}\}/g, ctx.persona.role)
    .replace(/\{\{sender_bio\}\}/g, ctx.persona.bio ?? "")
    .replace(/\{\{sender_first_name\}\}/g, ctx.persona.name.split(" ")[0]);
}

{{sender_first_name}} ist der unterschätzte Held hier. Email-Signaturen sind oft formell („Mit freundlichen Grüßen, Julian Beispiel"), aber wenn die Mail informell endet („Beste Grüße, Julian"), ist die Wirkung wärmer. Mit {{sender_first_name}} kann ich beide Varianten je nach Stil-Mode einfach umschalten.

Voice-Variante: dieselbe Persona, dasselbe Skript#

Beim Voice Agent verwende ich dieselbe Persona-Tabelle. Im LiveKit-Dispatch wird die voice_config (Provider, Voice-ID, Speed, Pitch) als Metadata mitgegeben:

async function dispatchVoiceCall(params: {
  leadId: string;
  personaId: string;
  // ...
}) {
  const persona = await db.personas.findUnique({ where: { id: params.personaId } });
 
  await livekit.dispatch.create({
    room: roomName,
    metadata: JSON.stringify({
      lead_id: params.leadId,
      sender_name: persona.name,
      sender_role: persona.role,
      voice: persona.voice_config,
    }),
  });
}

Im Voice-Agent-Worker liest dann der TTS-Adapter die voice-Config und der LLM-Prompt bekommt den sender_name als Kontext. „Du bist Julian, Senior Sales bei Beispiel GmbH" ist ein anderer Prompt als „Du bist ein hilfreicher Assistent". Das System-Prompt-Branding macht im Output einen messbaren Unterschied.

Was nicht funktioniert hat#

Persona-Wechsel mid-conversation. Erst dachte ich, der Agent könnte je nach Lead-Profil dynamisch eine andere Persona laden. In der Praxis fühlt sich das schizophren an. Ein Lead, der zurückruft, will denselben Ansprechpartner wiedersehen. Persona ist Workspace-Setting, nicht Per-Lead-Setting.

Komplett synthetische Bio-Generierung. Erste Idee: GPT erzeugt Bio aus Name + Role. Output war flach, generic, klang nach „LinkedIn-Profil von einem Bot". Heute schreibe ich die Bios manuell oder lasse sie vom Customer reviewen. 5 Minuten Aufwand, viel mehr Glaubwürdigkeit.

Was zu tun ist#

Wenn du Outreach automatisierst und noch keine Persona-Schicht hast, ist das der Hebel mit dem höchsten ROI pro investierter Stunde. 4 Stunden Setup, hebt die Antwortrate um 30 bis 70% bei sonst identischer Pipeline.

Bei mir gibt es einen 15-min Call, wenn du das in deinem eigenen Outreach-Tool integrieren willst. Wir schauen auf dein bestehendes Schema und ich sage dir, ob Persona-Bundling mit deinem aktuellen Stack einfach oder hart wird.