Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ const createVoiceAgentSchema = z.object({
tts_config_id: z.string().min(1, 'TTS configuration is required'),
stt_config_id: z.string().min(1, 'STT configuration is required'),
telephony_config_id: z.string().min(1, 'Telephony configuration is required'),
tts_voice_id: z.string().min(1, 'TTS Voice ID is required'),
tts_voice_ids: z.record(z.string(), z.string()).refine((val) => Object.keys(val).length > 0, {
message: 'At least one voice ID is required',
}),
Comment on lines +59 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Zod schema doesn't validate that voice IDs are non-empty strings.

The current schema only checks that at least one key exists in the record, but allows empty string values like { en: '' }. This could lead to agents being created with empty voice IDs, which would fail at runtime.

Proposed fix to validate non-empty voice IDs
-  tts_voice_ids: z.record(z.string(), z.string()).refine((val) => Object.keys(val).length > 0, {
-    message: 'At least one voice ID is required',
-  }),
+  tts_voice_ids: z.record(z.string(), z.string().min(1, 'Voice ID cannot be empty')).refine(
+    (val) => Object.keys(val).length > 0 && Object.values(val).every((v) => v.trim().length > 0),
+    {
+      message: 'At least one non-empty voice ID is required',
+    }
+  ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tts_voice_ids: z.record(z.string(), z.string()).refine((val) => Object.keys(val).length > 0, {
message: 'At least one voice ID is required',
}),
tts_voice_ids: z.record(z.string(), z.string().min(1, 'Voice ID cannot be empty')).refine(
(val) => Object.keys(val).length > 0 && Object.values(val).every((v) => v.trim().length > 0),
{
message: 'At least one non-empty voice ID is required',
}
),
🤖 Prompt for AI Agents
In
`@wavefront/client/src/pages/apps/`[appId]/voice-agents/CreateVoiceAgentDialog.tsx
around lines 59 - 61, The tts_voice_ids Zod record currently only checks for at
least one key but allows empty string values; update the schema for
tts_voice_ids so the record's value schema requires non-empty strings (e.g.,
replace the current z.string() value type with a non-empty string validator such
as z.string().min(1) or z.string().nonempty()) and adjust the validation message
accordingly so entries like { en: '' } are rejected; locate the tts_voice_ids
declaration in the schema and modify the z.record(valueSchema) portion to
enforce non-empty voice IDs.

system_prompt: z.string().min(1, 'System prompt is required'),
welcome_message: z.string().min(1, 'Welcome message is required'),
conversation_config: z.string().optional(),
Expand All @@ -83,6 +85,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen,
const [creating, setCreating] = useState(false);
const [ttsParameters, setTtsParameters] = useState<Record<string, unknown>>({});
const [sttParameters, setSttParameters] = useState<Record<string, unknown>>({});
const [voiceIdState, setVoiceIdState] = useState<Record<string, string>>({ en: '' });

// Fetch configs for dropdowns
const { data: llmConfigs = [] } = useGetLLMConfigs(appId);
Expand All @@ -99,7 +102,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen,
tts_config_id: '',
stt_config_id: '',
telephony_config_id: '',
tts_voice_id: '',
tts_voice_ids: { en: '' },
system_prompt: '',
welcome_message: '',
conversation_config: '{}',
Expand All @@ -114,6 +117,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen,
// Watch config selections to determine providers
const watchedTtsConfigId = form.watch('tts_config_id');
const watchedSttConfigId = form.watch('stt_config_id');
const watchedSupportedLanguages = form.watch('supported_languages');

// Get selected providers
const selectedTtsProvider = ttsConfigs.find((c) => c.id === watchedTtsConfigId)?.provider;
Expand All @@ -132,6 +136,20 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen,
}
}, [selectedSttProvider, isOpen]);

// Sync voice ID state with language changes
useEffect(() => {
if (isOpen && watchedSupportedLanguages) {
setVoiceIdState((prev) => {
const newState: Record<string, string> = {};
// Preserve existing voice IDs for languages still selected
watchedSupportedLanguages.forEach((lang) => {
newState[lang] = prev[lang] || '';
});
return newState;
});
}
}, [watchedSupportedLanguages, isOpen]);
Comment on lines +139 to +151
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Form field not updated when languages are removed.

The useEffect updates voiceIdState when supported languages change, but it doesn't call form.setValue('tts_voice_ids', newState) to sync the form. This means removed languages' voice IDs remain in the form state even though they're not visible in the UI, potentially causing validation issues with the server-side validator that rejects extra languages.

Proposed fix to sync form state with voiceIdState
   useEffect(() => {
     if (isOpen && watchedSupportedLanguages) {
       setVoiceIdState((prev) => {
         const newState: Record<string, string> = {};
         // Preserve existing voice IDs for languages still selected
         watchedSupportedLanguages.forEach((lang) => {
           newState[lang] = prev[lang] || '';
         });
+        // Sync form state to remove voice IDs for deselected languages
+        form.setValue('tts_voice_ids', newState);
         return newState;
       });
     }
-  }, [watchedSupportedLanguages, isOpen]);
+  }, [watchedSupportedLanguages, isOpen, form]);
🤖 Prompt for AI Agents
In
`@wavefront/client/src/pages/apps/`[appId]/voice-agents/CreateVoiceAgentDialog.tsx
around lines 139 - 151, The effect that syncs voiceIdState when
watchedSupportedLanguages or isOpen changes (the useEffect block updating
setVoiceIdState) must also update the form state so removed languages are
cleared; after computing the newState inside that effect, call
form.setValue('tts_voice_ids', newState) to mirror voiceIdState in the form (use
the same key 'tts_voice_ids' as used elsewhere) so the form no longer retains
voice IDs for languages that were removed.


// Reset form when dialog closes
useEffect(() => {
if (!isOpen) {
Expand All @@ -142,7 +160,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen,
tts_config_id: '',
stt_config_id: '',
telephony_config_id: '',
tts_voice_id: '',
tts_voice_ids: { en: '' },
system_prompt: '',
welcome_message: '',
conversation_config: '{}',
Expand All @@ -154,6 +172,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen,
});
setTtsParameters({});
setSttParameters({});
setVoiceIdState({ en: '' });
}
}, [isOpen, form]);

Expand Down Expand Up @@ -232,7 +251,7 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen,
tts_config_id: data.tts_config_id.trim(),
stt_config_id: data.stt_config_id.trim(),
telephony_config_id: data.telephony_config_id.trim(),
tts_voice_id: data.tts_voice_id.trim(),
tts_voice_ids: data.tts_voice_ids,
tts_parameters: Object.keys(builtTtsParameters).length > 0 ? builtTtsParameters : null,
stt_parameters: Object.keys(builtSttParameters).length > 0 ? builtSttParameters : null,
system_prompt: data.system_prompt.trim(),
Expand Down Expand Up @@ -710,17 +729,31 @@ const CreateVoiceAgentDialog: React.FC<CreateVoiceAgentDialogProps> = ({ isOpen,
<h4 className="text-sm font-medium">TTS Voice Settings</h4>
<FormField
control={form.control}
name="tts_voice_id"
name="tts_voice_ids"
render={({ field }) => (
<FormItem>
<FormLabel>
TTS Voice ID<span className="text-red-500">*</span>
TTS Voice IDs<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input placeholder="e.g., alloy, echo, fable (OpenAI) or voice ID (ElevenLabs)" {...field} />
</FormControl>
<div className="space-y-3">
{watchedSupportedLanguages.map((langCode) => (
<div key={langCode} className="flex items-center gap-3">
<Label className="w-24 text-sm font-medium">{getLanguageDisplayName(langCode)}:</Label>
<Input
placeholder={`Voice ID for ${getLanguageDisplayName(langCode)}`}
value={voiceIdState[langCode] || ''}
onChange={(e) => {
const newState = { ...voiceIdState, [langCode]: e.target.value };
setVoiceIdState(newState);
field.onChange(newState);
}}
className="flex-1"
/>
</div>
))}
</div>
<FormDescription>
Provider-specific voice identifier (e.g., for Deepgram: aura-2-helena-en)
Provider-specific voice identifiers per language (e.g., "aura-2-helena-en" for Deepgram)
</FormDescription>
<FormMessage />
</FormItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const updateVoiceAgentSchema = z.object({
tts_config_id: z.string().min(1, 'TTS configuration is required'),
stt_config_id: z.string().min(1, 'STT configuration is required'),
telephony_config_id: z.string().min(1, 'Telephony configuration is required'),
tts_voice_id: z.string().min(1, 'TTS Voice ID is required'),
tts_voice_ids: z.record(z.string(), z.string()).optional(),
system_prompt: z.string().min(1, 'System prompt is required'),
welcome_message: z.string().min(1, 'Welcome message is required'),
conversation_config: z.string().optional(),
Expand Down Expand Up @@ -92,6 +92,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({
// State for TTS/STT parameters (managed separately from form)
const [ttsParameters, setTtsParameters] = useState<Record<string, unknown>>({});
const [sttParameters, setSttParameters] = useState<Record<string, unknown>>({});
const [voiceIdState, setVoiceIdState] = useState<Record<string, string>>(agent.tts_voice_ids || { en: '' });

const form = useForm<UpdateVoiceAgentInput>({
resolver: zodResolver(updateVoiceAgentSchema),
Expand All @@ -102,7 +103,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({
tts_config_id: agent.tts_config_id,
stt_config_id: agent.stt_config_id,
telephony_config_id: agent.telephony_config_id,
tts_voice_id: agent.tts_voice_id,
tts_voice_ids: agent.tts_voice_ids,
system_prompt: agent.system_prompt,
welcome_message: agent.welcome_message,
conversation_config: agent.conversation_config ? JSON.stringify(agent.conversation_config, null, 2) : '{}',
Expand All @@ -117,6 +118,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({
// Watch for config changes to determine providers
const watchedTtsConfigId = form.watch('tts_config_id');
const watchedSttConfigId = form.watch('stt_config_id');
const watchedSupportedLanguages = form.watch('supported_languages');

const selectedTtsProvider = ttsConfigs.find((c) => c.id === watchedTtsConfigId)?.provider;
const selectedSttProvider = sttConfigs.find((c) => c.id === watchedSttConfigId)?.provider;
Expand All @@ -134,7 +136,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({
tts_config_id: agent.tts_config_id,
stt_config_id: agent.stt_config_id,
telephony_config_id: agent.telephony_config_id,
tts_voice_id: agent.tts_voice_id,
tts_voice_ids: agent.tts_voice_ids,
system_prompt: agent.system_prompt,
welcome_message: agent.welcome_message,
conversation_config: agent.conversation_config ? JSON.stringify(agent.conversation_config, null, 2) : '{}',
Expand All @@ -144,6 +146,7 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({
supported_languages: agent.supported_languages || ['en'],
default_language: agent.default_language || 'en',
});
setVoiceIdState(agent.tts_voice_ids || { en: '' });
}
}, [isOpen, agent, form]);

Expand All @@ -161,6 +164,20 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({
}
}, [isOpen, agent.stt_parameters]);

// Sync voice ID state with language changes
useEffect(() => {
if (isOpen && watchedSupportedLanguages) {
setVoiceIdState((prev) => {
const newState: Record<string, string> = {};
// Preserve existing voice IDs for languages still selected
watchedSupportedLanguages.forEach((lang) => {
newState[lang] = prev[lang] || '';
});
return newState;
});
}
}, [watchedSupportedLanguages, isOpen]);
Comment on lines +167 to +179
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "EditVoiceAgentDialog.tsx" -type f

Repository: rootflo/wavefront

Length of output: 140


🏁 Script executed:

cat -n ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsx

Repository: rootflo/wavefront

Length of output: 50373


🏁 Script executed:

sed -n '167,300p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsx

Repository: rootflo/wavefront

Length of output: 5033


🏁 Script executed:

sed -n '775,806p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsx

Repository: rootflo/wavefront

Length of output: 1739


🏁 Script executed:

sed -n '286,288p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsx

Repository: rootflo/wavefront

Length of output: 212


🏁 Script executed:

sed -n '54,54p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsx

Repository: rootflo/wavefront

Length of output: 122


🏁 Script executed:

sed -n '95,180p' ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsx

Repository: rootflo/wavefront

Length of output: 3672


🏁 Script executed:

rg "tts_voice_ids" ./wavefront/client/src/pages/apps/[appId]/voice-agents/EditVoiceAgentDialog.tsx

Repository: rootflo/wavefront

Length of output: 562


Sync form field when supported languages change.

The effect updates voiceIdState but not the form field. When a language is removed without manually editing voice IDs, the form submission includes stale tts_voice_ids with removed languages, causing server validation to fail. Call form.setValue() to keep them in sync.

🛠️ Suggested fix
  useEffect(() => {
    if (isOpen && watchedSupportedLanguages) {
      setVoiceIdState((prev) => {
        const newState: Record<string, string> = {};
        // Preserve existing voice IDs for languages still selected
        watchedSupportedLanguages.forEach((lang) => {
          newState[lang] = prev[lang] || '';
        });
+       form.setValue('tts_voice_ids', newState, { shouldDirty: true });
        return newState;
      });
    }
- }, [watchedSupportedLanguages, isOpen]);
+ }, [watchedSupportedLanguages, isOpen, form]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Sync voice ID state with language changes
useEffect(() => {
if (isOpen && watchedSupportedLanguages) {
setVoiceIdState((prev) => {
const newState: Record<string, string> = {};
// Preserve existing voice IDs for languages still selected
watchedSupportedLanguages.forEach((lang) => {
newState[lang] = prev[lang] || '';
});
return newState;
});
}
}, [watchedSupportedLanguages, isOpen]);
// Sync voice ID state with language changes
useEffect(() => {
if (isOpen && watchedSupportedLanguages) {
setVoiceIdState((prev) => {
const newState: Record<string, string> = {};
// Preserve existing voice IDs for languages still selected
watchedSupportedLanguages.forEach((lang) => {
newState[lang] = prev[lang] || '';
});
form.setValue('tts_voice_ids', newState, { shouldDirty: true });
return newState;
});
}
}, [watchedSupportedLanguages, isOpen, form]);
🤖 Prompt for AI Agents
In
`@wavefront/client/src/pages/apps/`[appId]/voice-agents/EditVoiceAgentDialog.tsx
around lines 167 - 179, The useEffect that syncs voiceIdState (useEffect with
dependencies watchedSupportedLanguages and isOpen) only updates the local state
via setVoiceIdState and does not update the form's tts_voice_ids field, leaving
stale entries for removed languages; update the effect to also call
form.setValue('tts_voice_ids', newState) (or setValue with the same key used in
the form) after computing newState so the form stays in sync with
setVoiceIdState and removed languages are cleared before submit.


// Helper functions to update parameters
const setTtsParameter = (key: string, value: unknown) => {
setTtsParameters((prev) => ({ ...prev, [key]: value }));
Expand Down Expand Up @@ -266,8 +283,8 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({
requestData.telephony_config_id = data.telephony_config_id;
}

if (data.tts_voice_id.trim() !== agent.tts_voice_id) {
requestData.tts_voice_id = data.tts_voice_id.trim();
if (JSON.stringify(data.tts_voice_ids) !== JSON.stringify(agent.tts_voice_ids)) {
requestData.tts_voice_ids = data.tts_voice_ids;
}

// Check if TTS parameters changed
Expand Down Expand Up @@ -757,17 +774,31 @@ const EditVoiceAgentDialog: React.FC<EditVoiceAgentDialogProps> = ({
<h4 className="text-sm font-medium">TTS Voice Settings</h4>
<FormField
control={form.control}
name="tts_voice_id"
name="tts_voice_ids"
render={({ field }) => (
<FormItem>
<FormLabel>
TTS Voice ID<span className="text-red-500">*</span>
TTS Voice IDs<span className="text-red-500">*</span>
</FormLabel>
<FormControl>
<Input placeholder="e.g., alloy, echo, fable (OpenAI) or voice ID (ElevenLabs)" {...field} />
</FormControl>
<div className="space-y-3">
{watchedSupportedLanguages.map((langCode) => (
<div key={langCode} className="flex items-center gap-3">
<Label className="w-24 text-sm font-medium">{getLanguageDisplayName(langCode)}:</Label>
<Input
placeholder={`Voice ID for ${getLanguageDisplayName(langCode)}`}
value={voiceIdState[langCode] || ''}
onChange={(e) => {
const newState = { ...voiceIdState, [langCode]: e.target.value };
setVoiceIdState(newState);
field.onChange(newState);
}}
className="flex-1"
/>
</div>
))}
</div>
<FormDescription>
Provider-specific voice identifier (e.g., for Deepgram: aura-2-helena-en)
Provider-specific voice identifiers per language (e.g., "aura-2-helena-en" for Deepgram)
</FormDescription>
<FormMessage />
</FormItem>
Expand Down
6 changes: 3 additions & 3 deletions wavefront/client/src/types/voice-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface VoiceAgent {
telephony_config_id: string;
system_prompt: string;
welcome_message: string;
tts_voice_id: string;
tts_voice_ids: Record<string, string>;
tts_parameters: Record<string, unknown> | null;
stt_parameters: Record<string, unknown> | null;
conversation_config: Record<string, unknown> | null;
Expand All @@ -39,7 +39,7 @@ export interface CreateVoiceAgentRequest {
telephony_config_id: string;
system_prompt: string;
welcome_message: string;
tts_voice_id: string;
tts_voice_ids: Record<string, string>;
tts_parameters?: Record<string, unknown> | null;
stt_parameters?: Record<string, unknown> | null;
conversation_config?: Record<string, unknown> | null;
Expand All @@ -63,7 +63,7 @@ export interface UpdateVoiceAgentRequest {
telephony_config_id?: string;
system_prompt?: string;
welcome_message?: string;
tts_voice_id?: string;
tts_voice_ids?: Record<string, string>;
tts_parameters?: Record<string, unknown> | null;
stt_parameters?: Record<string, unknown> | null;
conversation_config?: Record<string, unknown> | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
from pipecat.runner.types import WebSocketRunnerArguments
from pipecat.runner.utils import parse_telephony_websocket

# from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3
# from pipecat.audio.turn.smart_turn.base_smart_turn import SmartTurnParams
from pipecat.serializers.twilio import TwilioFrameSerializer
from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.audio.vad.vad_analyzer import VADParams
Expand Down Expand Up @@ -119,6 +117,8 @@ async def inbound_webhook(

# Pass parameters to WebSocket stream
stream.parameter(name='voice_agent_id', value=agent_id)
stream.parameter(name='customer_number', value=From)
stream.parameter(name='agent_number', value=To)

connect.append(stream)
response.append(connect)
Expand All @@ -134,6 +134,8 @@ async def inbound_webhook(

@webhook_router.post('/twiml')
async def twiml_endpoint(
From: str = Form(...),
To: str = Form(...),
voice_agent_id: str = Query(...),
welcome_message_audio_url: str = Query(default=''),
):
Expand Down Expand Up @@ -181,6 +183,8 @@ async def twiml_endpoint(

# Pass parameters to WebSocket stream
stream.parameter(name='voice_agent_id', value=voice_agent_id)
stream.parameter(name='customer_number', value=To)
stream.parameter(name='agent_number', value=From)
Comment on lines +186 to +187
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: From/To mapping is swapped compared to inbound_webhook.

In the twiml_endpoint, the customer/agent number assignment is reversed:

  • Line 188: customer_number=To (should be From)
  • Line 189: agent_number=From (should be To)

In Twilio webhooks, From is the caller (customer) and To is the number being called (agent). Compare with inbound_webhook at lines 122-123 which correctly uses customer_number=From and agent_number=To.

🐛 Fix the swapped parameters
     # Pass parameters to WebSocket stream
     stream.parameter(name='voice_agent_id', value=voice_agent_id)
-    stream.parameter(name='customer_number', value=To)
-    stream.parameter(name='agent_number', value=From)
+    stream.parameter(name='customer_number', value=From)
+    stream.parameter(name='agent_number', value=To)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stream.parameter(name='customer_number', value=To)
stream.parameter(name='agent_number', value=From)
stream.parameter(name='customer_number', value=From)
stream.parameter(name='agent_number', value=To)
🤖 Prompt for AI Agents
In
`@wavefront/server/apps/call_processing/call_processing/controllers/webhook_controller.py`
around lines 188 - 189, The twiml_endpoint is incorrectly swapping Twilio
parameters: change the stream.parameter assignments so customer_number uses From
and agent_number uses To (mirror the correct mapping used in inbound_webhook);
locate the stream.parameter calls inside the twiml_endpoint function and swap
the values for customer_number and agent_number (replace customer_number=To with
customer_number=From and agent_number=From with agent_number=To).


connect.append(stream)
response.append(connect)
Expand Down Expand Up @@ -223,6 +227,8 @@ async def websocket_endpoint(
# Extract parameters from stream
body_data = call_data.get('body', {})
voice_agent_id = body_data.get('voice_agent_id')
customer_number = body_data.get('customer_number')
# agent_number = body_data.get('agent_number')
Comment on lines +230 to +231
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing validation for customer_number.

customer_number is extracted with .get() (line 232) which can return None, but it's passed directly to run_conversation (line 293) which expects str. If missing, this will result in "Customer phone number: None" in the system prompt. Consider adding validation or a fallback.

🛠️ Suggested fix
         voice_agent_id = body_data.get('voice_agent_id')
         customer_number = body_data.get('customer_number')
         # agent_number = body_data.get('agent_number')

         if not voice_agent_id:
             logger.error('voice_agent_id not found in stream parameters')
             await websocket.close(code=1008, reason='Missing voice_agent_id')
             return

+        if not customer_number:
+            logger.warning('customer_number not found in stream parameters, using empty string')
+            customer_number = ''
+
         logger.info(f'Voice agent ID: {voice_agent_id}')

Also applies to: 293-293

🤖 Prompt for AI Agents
In
`@wavefront/server/apps/call_processing/call_processing/controllers/webhook_controller.py`
around lines 232 - 233, customer_number is pulled with
body_data.get('customer_number') and can be None but is passed into
run_conversation (and used in the system prompt), causing "Customer phone
number: None"; update webhook_controller to validate/normalize customer_number
after extraction: if missing or not a string return a 400/422 response (or set a
clear fallback like empty string and log a warning), trim/format the number, and
only call run_conversation when customer_number is a valid string; reference the
extracted variable customer_number and the call to run_conversation in
webhook_controller to locate where to add the check and error response/logging.


if not voice_agent_id:
logger.error('voice_agent_id not found in stream parameters')
Expand Down Expand Up @@ -263,13 +269,12 @@ async def websocket_endpoint(
vad_analyzer=SileroVADAnalyzer(
params=VADParams(
confidence=0.7, # Default is 0.7, can lower to 0.4-0.5 for faster detection
start_secs=0.15, # Default is 0.2, keep it
stop_secs=0.8, # KEY: Lower from default 0.8 for faster cutoff (should be 0.2 for smart turn detection)
start_secs=0.2, # Default is 0.2, keep it
stop_secs=0.2, # KEY: Lower from default 0.8 for faster cutoff (should be 0.2 for smart turn detection)
min_volume=0.6, # Default is 0.6, adjust based on your audio quality
),
), # Voice Activity Detection
serializer=serializer,
# turn_analyzer=LocalSmartTurnAnalyzerV3(params=SmartTurnParams()),
),
)

Expand All @@ -282,6 +287,7 @@ async def websocket_endpoint(
tts_config=configs['tts_config'],
stt_config=configs['stt_config'],
tools=configs['tools'],
customer_number=customer_number,
)

except Exception as e:
Expand Down
Loading
Loading