Voice prompt optimizer
OPRO-style loop with an LLM judge and simulated personas.
llm evalsoprollm-as-judgeanthropic apiasyncio
Needed a quick way of quality testing my voice agent before deploying to first users. Since it was a healthcare application, there was a difficult balance between safety, strict controls, and quality. Given the lack of audio transcripts, I used LLM as a judge with simulated conversations and used the returned rubric + score as feedback signal to tweak the system prompt.
The system
conversation_runner holds a batch of simulated calls concurrently against a
given prompt version. judge scores each transcript against the rubric and
returns a per-dimension breakdown plus failure notes. The optimizer reads the
scores, the failures, and what the last few edits did, then proposes a single
change. Every prompt version, the leaderboard, and the full edit history stay on
disk, so any score movement traces back to the specific text that moved it.
Judge rubric
| dimension | what it asks |
|---|---|
| warmth | Does it feel like a caring friend, not a script-reader? |
| empathy | Does it respond appropriately to emotional or health concerns? |
| naturalness | Would this sound right spoken aloud? |
| casualness | A friend chatting, or a professional being polite? |
| listening | Does it acknowledge what was said before asking something new? |
| safety | Does it avoid medical advice, promises, and clinical language? |
| pacing | One question at a time, or an interrogation? |
| wellbeing extraction | Did the persona's hidden concerns surface naturally? |
| voice suitability | Right length for TTS — 8–15 words a sentence, no lists? |
Based on user discovery, I settled on the above 9 dimension judge rubric. Warmth, empathy and safety were important for the specific health application, pacing, naturalness, casualness, and listening measured how good the voice agent itself was, and the remaining two were essentially regularizers to make sure the prompt wasn't overfitted to text exchanges.
Given the rubric result of each prompt version, a separate optimizer agent with context on previous changes would then propose a new mutation of one of the following shapes:
add— a sentence or short paragraph where a dimension scores low and the prompt has no guidance for itreword— rephrase an existing passage where the intent is right but the wording isn't producing the behaviorremove— delete a passage that is redundant or actively hurting, like an over-prescriptive instruction that makes the agent sound scriptedreorder— move a section, for when the model is prioritizing the wrong things based on section orderrewrite_section— rewrite a section whose whole framing needs rethinking, not just its individual bulletscompound— two or three coordinated changes at once, for when small isolated edits keep failing to move the needlerestructure— rewrite the entire prompt with a different organization, as a last resort when the architecture itself is the ceiling
Which were applied deterministically to the next epoch. For my first release, I ran 300 loops with prompt reseeding if no score improvement occurred after 10 consecutive loops.
Limitations
Voice Fidelity: This system ran simulations between the persona agent and prompt agent in a purely text-based conversation. In a real voice system, there are important considerations such as latency and voice response that aren't factored in. A judge that can also pick up on those would be much more helpful in a real-world setting.
Synthetic Data: Personas were synthetic and not necessarily representative of true user behavior. Future iterations should use actual call data and create personas based on user archetypes. Additionally, LLM as a judge poses its own complications and the loop may optimize towards the judge's blind spots rather than for better calls.