Back to work

Case study

PitchPerfect

Practice a sales call out loud with a buyer who has hidden objections, then get a scorecard that quotes what you said.

Code on GitHub (opens in a new tab)Live demo (opens in a new tab)7 rubric lines on every scorecard, each quote checked against the call
01

Problem

People new to sales need to practice real calls and get specific feedback, and employers want to see how a candidate pitches, not only read a resume.[1]Source 1What’s inside, in the PitchPerfect READMEOpen source 1 (opens in a new tab)All sources

I wanted a practice partner that feels like a real buyer, stays on topic, costs little to run and cannot make up what the student said.

02

Constraints and approach

  • The model never sets the score, and it cannot put words in the student’s mouth.
  • The model keys live in one small service that the public internet cannot reach.
  • Student text never becomes part of the instructions, and personal details are removed first.
  • Every call costs money, so practice is capped per student and per day across the site.

A website, an Express and MongoDB api, and a separate practice partner service: a conversational buyer, played by an LLM, with hidden objections. It streams its replies and scores the call on seven rubric lines; code computes the overall score and checks every quote on the scorecard against what the student said.

How one practice call works

The browser talks only to the website, which passes requests to the api; only the api can call the practice partner[2]Source 2The practice session flow (docs/architecture.md)Open source 2 (opens in a new tab)All sources.

Steps of one practice call
StepWhat happens
PlanThe partner makes up a buyer for the chosen skill, or for a real job post: a name, a role, a company, a personality, two or three hidden objections and what would win them over. The hidden part stays on the server.
TalkThe student speaks or types. The browser turns speech into text, which the student can edit before sending. The buyer answers in at most 80 words, streamed and read aloud, and raises its objections only once the student has pitched.
ScoreWhen the call ends, the model scores seven rubric lines from 1 to 5, each with a reason and a quote. Code checks the quotes, works out the overall score and saves the scorecard.
ApplyThe student can attach a scorecard to a job application. It is a live link, not a copy, so deleting the attempt takes it out of the employer’s view.

The scorecard

Seven rubric lines, each scored 1 to 5 with a reason and a quote, then three strengths, three improvements and one weak line rewritten[3]Source 3Rubric and scorecard (docs/practice-partner.md)Open source 3 (opens in a new tab)All sources.

Rubric lines and what a 1 and a 5 mean
Line1 means5 means
ClarityRambling, jargon, no clear pointCrisp, one clear message
Value and relevanceFeatures only, genericBenefits tied to this buyer’s stated needs
StructureNo flowA clear opening, body and close
Discovery questionsNo questions, or only yes or noOpen, layered questions that uncover pain and impact
Objection handlingIgnores or arguesAcknowledges, clarifies, answers with evidence, checks
Confidence and languageHedging, filler, apologeticAssured, concise, positive
Call to actionNo askA specific next step with a time

Design choices

Design choices and their trade-offs
ChoiceWhy
A separate practice partnerThe service that calls the model holds the keys and nothing else: no database, no accounts, no public address. It answers only requests the api has signed, so a break-in to the website cannot reach the keys, and the partner can be tested, scaled or swapped on its own.
The score is computed in codeThe model reads the call and gives seven scores from 1 to 5. The overall 0 to 100 is a weighted mean worked out in code, with the skill’s own line counting double, so the model cannot be talked into a high number.
Quotes checked word for wordA scorecard that quotes the student is only useful if the quote is real. Every quote must appear in what the student said; one that does not is dropped.
Voice in the browserSpeech to text and the buyer’s voice use the browser’s own speech features, so PitchPerfect never uploads or stores audio. Typing is always one click away, and it takes over on its own where a browser has no speech recognition.
The provider is a settingTwo provider adapters sit behind one interface, and the provider and model names come from settings, never from code. A scripted partner fills the same interface for tests and the demo.

The architecture notes explain why the practice partner is its own service[4]Source 4Why three services (docs/architecture.md)Open source 4 (opens in a new tab)All sources.

03

How it is checked

Guardrails sit in layers, so a turn that gets past one still meets the next[5]Source 5Guardrails (docs/practice-partner.md)Open source 5 (opens in a new tab)All sources.

Guardrail layers
LayerWhat it does
Input1 to 1,000 characters a turn, control characters removed. Student text goes inside a marked block that is data only and can never change the rules.
Personal dataEmails, phone numbers and card-like numbers are replaced before the turn is stored or sent to the model.
Pre-checkRules plus a small classification call look for attempts to pull out the instructions, change the partner’s role or get harmful content. A flagged turn gets an in-character redirect; a third flag ends the session.
OutputEvery prompt carries a random canary token. While a reply streams, the last 8 words are held back so a leaked run of the instructions can be stopped. Replies are cut at 80 words and moderated.
Cost5 sessions a day and 30 a month per student, a site-wide daily token budget, a token cap on every call and one turn in flight at a time.
Reliability30 s per model call, one retry, and a circuit breaker that pauses practice after 5 failures in a minute.

The security notes list each threat with its control and the test that checks it, from sign-in limits and stored scripts in job posts to employers reading another company’s applicants[6]Source 6Threats and controls (docs/security.md)Open source 6 (opens in a new tab)All sources.

The eval set is 30 scripted conversations: 8 strong, 8 mediocre and 6 weak calls with an expected score band, and 8 attacks, among them pulling out the prompt, a role swap, a harmful request, a flood of long input and asking the buyer to pose as a human. They run through the real practice service with a real model. A call passes when its score lands in its band; an attack passes only when every attack turn gets the expected answer and no reply leaks. The target is 90% of cases and every attack[7]Source 7Practice partner evals (apps/coach/evals/README.md)Open source 7 (opens in a new tab)All sources.

CI cannot call a model, so a scripted partner that always gives the same answer plays the buyer there. Every push runs lint, type checks, unit and integration tests, a dead-code check, a secret scan and a dependency audit, then end-to-end tests on a throwaway database and on the static demo, and Lighthouse[8]Source 8The CI workflow (.github/workflows/ci.yml)Open source 8 (opens in a new tab)All sources.

04

Results

The live demo runs the same practice engine in your browser with the scripted partner, so anyone can run a call and read the scorecard without an account[9]Source 9The static demo (docs/security.md)Open source 9 (opens in a new tab)All sources.

Practice skills
8
each with a short lesson
Scripted eval conversations
30
8 of them attacks, which must all be handled
Longest buyer reply
80 words
Services
3
the one that holds the API keys is never public
05

Sources