AI agents and test cases
An AiAgent is a named LLM configuration owned by a workspace. AiAgentVersion rows make every
prompt change auditable, and AiAgent.latestVersionId points at the published one.
Agent types
type |
Role |
|---|---|
|
Speaks on outbound calls. Mirrored to Retell AI via |
|
Grades completed calls. Its |
|
SMS conversations |
|
Anything else |
AiAgent.subscriptionId is nullable, allowing a shared agent across workspaces.
AiAgent.retellAgentId is globally unique, so no two records can claim the same Retell agent.
Versioning
| Field | Meaning |
|---|---|
|
1-based, unique per |
|
The script (caller) or the grading criteria (analysis) |
|
Full Retell |
|
Vercel AI Gateway slug on the parent |
|
Legacy |
Model resolution is getModelSlug(modelId, llmModelType) in lib/ai/config.ts: an explicit
modelId wins, otherwise gpt → openai/gpt-4o and claude → anthropic/claude-sonnet-4.
Selectable models are enumerated in AI_GATEWAY_MODELS across OpenAI, Anthropic, xAI and Google.
Versions are never mutated in place. Call.agentVersionId records which version spoke, and
CallAiAgentAnalysisResult.aiAgentVersionId records which version graded — this is what makes the
per-agent performance report meaningful.
Caller agent selection at dial time
PhoneNumberCallingService.pickOverrideAgent(subscriptionId):
-
Query active
caller-type agents for the workspace (AiAgentRepository.findCallerAgentsBySubscription) and pick one at random. Random selection spreads load and voice variety across the pool rather than favouring one agent. -
If the workspace has none, fall back to the static
RETELL_AGENT_LISTinscripts/retelai/agents/registry.ts— the single source of the fallback id/name list.
The chosen Retell agent id is sent as override_agent_id on the call. Call.aiAgentId and
Call.aiAgentName are denormalised onto the call row so the Calls table can render
"Brian v3" without a join. Call.agentId is the AiAgent UUID; do not confuse it with
aiAgentId, which is the Retell identifier.
Retell synchronisation
RetellAgentService (lib/services/retell-agent-service.ts) wraps the Retell platform:
| Method | Purpose |
|---|---|
|
Agent CRUD |
|
Branch a new Retell version from an existing one |
|
Make a version live |
|
The response engine behind an agent |
|
Reconcile a local |
Every create, update and delete through /api/ai-agents is mirrored to Retell, so the database
and the platform do not drift.
Voice and behaviour configuration
lib/retellai/agent-config.ts holds the UI’s option lists and the non-negotiable defaults:
-
RETELL_VOICE_MODELS— the available voice models. -
RETELL_LLM_MODELS— response-engine models offered by Retell. -
RETELL_EXPRESSIVE_EMOTION_TAGS— emotion tags for platform voices only, offered as a multi-select. -
HARDCODED_AGENT_CONFIG— applied to every caller agent:
| Setting | Value | Why |
|---|---|---|
|
|
Latency matters more than transcript perfection on a cold call |
|
|
Hang up rather than burning minutes on dead air |
|
|
Hard ceiling on cost per call |
|
|
Stop ringing before most voicemail systems answer |
|
|
Wait for the callee to finish saying "hello" |
|
|
IVR navigation, with a 5 s keypress timeout |
|
|
Agents are never publicly listed |
GET /api/retell/voices proxies Retell’s voice catalogue for the agent editor.
Post-call analysis schema
DEFAULT_POST_CALL_ANALYSIS in lib/retellai/post-call-analysis.ts defines the structured fields
Retell extracts from each call — the shared vocabulary between the caller agent, the Retell
platform and the analysis prompt. Per-agent overrides are merged by mergePostCallAnalysis, so an
agent can add or reshape fields without forking the base schema.
Regression test cases
AgentTestCase captures a real conversation plus the correct answer, so a prompt change can be
regression-tested before it goes live.
| Field group | Fields |
|---|---|
Identity |
|
Inputs |
|
Ground truth |
|
Running a suite
POST /api/test-cases/run invokes TestCaseRunnerService:
-
Skip any case with no transcript — there is nothing to evaluate (
skipped: true). -
Ask the model to predict the outcome from the transcript, given the caller agent version’s prompt and the formatted post-call analysis schema.
-
Parse the response with
PredictionSchema, tolerating a fenced ```json block. -
Compare field by field. A ground truth of
nullmeans "not asserted", producingpass: null— that field is excluded from the verdict rather than counted as a pass. -
passedis true only when every evaluated field passes.
The evaluated set is the intersection of DEFAULT_POST_CALL_ANALYSIS fields and test-case
ground-truth columns: disposition, next_action, call_screening_detected, is_amazon_seller.
String comparison is case-insensitive; booleans are compared strictly.
POST /api/test-cases/suggest-prompt proposes a prompt revision from failing cases.
Prompt suggestions from live issues
/agents/issues surfaces the issues arrays produced by call analysis, filterable by name,
priority, agentName, agentVersion and date range. Selecting an issue and calling
POST /api/ai-agents/issues/suggest-prompt runs PromptSuggestionService, which proposes a
revision addressing it. The loop is: analyse calls → aggregate issues → suggest a prompt →
regression-test it against saved cases → publish a new version.