2424import asyncio
2525import contextlib
2626import json
27+ import os
2728import time
2829from typing import Any
2930
@@ -247,8 +248,6 @@ def _aws_credentials_from_env(self) -> dict[str, Any] | None:
247248 Mirrors how the cascade pipeline reaches Bedrock, so the agent's think step can
248249 use the *same* model and credentials for an apples-to-apples comparison.
249250 """
250- import os
251-
252251 access_key = os .environ .get ("AWS_ACCESS_KEY_ID" )
253252 secret_key = os .environ .get ("AWS_SECRET_ACCESS_KEY" )
254253 if not (access_key and secret_key ):
@@ -280,9 +279,18 @@ def _build_settings_dict(self) -> dict[str, Any]:
280279 }
281280 if self ._functions :
282281 think ["functions" ] = self ._functions
283- # BYO endpoint (anthropic/open_ai/google) and long-prompt context window.
282+ # BYO endpoint. For aws_bedrock, the Bedrock runtime URL is required by Deepgram
283+ # even when credentials are supplied — auto-build it from the region if not explicit.
284284 if self ._think_endpoint :
285285 think ["endpoint" ] = self ._think_endpoint
286+ elif self ._think_provider == "aws_bedrock" :
287+ region = (
288+ self ._think_region
289+ or os .environ .get ("AWS_REGION" )
290+ or os .environ .get ("AWS_DEFAULT_REGION" )
291+ or "us-east-1"
292+ )
293+ think ["endpoint" ] = {"url" : f"https://bedrock-runtime.{ region } .amazonaws.com/" }
286294 if self ._context_length is not None :
287295 think ["context_length" ] = self ._context_length
288296
@@ -331,6 +339,7 @@ async def _handle_session(self, websocket: WebSocket) -> None:
331339
332340 _in_model_turn = False
333341 _user_speaking = False
342+ _think_step_active = False # True between ConversationText(user) and first agent audio
334343 _user_speech_start_ts : str | None = None # From the simulator's VAD
335344 _user_speech_stop_ts : str | None = None # From the simulator's VAD
336345 _assistant_turn_start_ts : str | None = None # Wall-clock ms of first audio chunk
@@ -350,13 +359,10 @@ async def _handle_session(self, websocket: WebSocket) -> None:
350359 try :
351360 async with client .agent .v1 .connect () as connection :
352361 logger .info (f"Deepgram agent session connected (think_model={ self ._think_model } )" )
353- if self ._omit_think_model ():
354- # Custom Google endpoint: the typed AgentV1Settings requires
355- # provider.model, but Deepgram wants it only in the URL. Send the
356- # raw (model-less) settings dict via the SDK's low-level _send.
357- await connection ._send (self ._build_settings_dict ())
358- else :
359- await connection .send_settings (self ._build_settings ())
362+ # Always send settings as raw dict — avoids the Pydantic round-trip
363+ # in send_settings() → _send_model() → .dict(), which can silently
364+ # drop fields (e.g. think.functions) not declared in AgentV1Settings.
365+ await connection ._send (self ._build_settings_dict ())
360366 fw_log .turn_start ()
361367
362368 # ----- Concurrent tasks -----
@@ -470,7 +476,7 @@ async def _process_deepgram_events() -> None:
470476 and tool-call requests. Parsing the JSON ourselves is deterministic.
471477 Binary frames (TTS audio) are delivered as ``bytes`` unchanged.
472478 """
473- nonlocal _assistant_turn_text , _in_model_turn , _user_speaking
479+ nonlocal _assistant_turn_text , _in_model_turn , _user_speaking , _think_step_active
474480 nonlocal _user_speech_start_ts , _user_speech_stop_ts , _assistant_turn_start_ts
475481 nonlocal _user_turn_count , _fatal_error
476482 try :
@@ -484,6 +490,7 @@ async def _process_deepgram_events() -> None:
484490 continue
485491 if not _in_model_turn :
486492 _in_model_turn = True
493+ _think_step_active = False
487494 _user_speaking = False
488495 _assistant_turn_start_ts = str (int (round (time .time () * 1000 )))
489496 fw_log .turn_start ()
@@ -526,6 +533,7 @@ async def _process_deepgram_events() -> None:
526533 continue
527534 if event .get ("role" ) == "user" :
528535 _user_speaking = False
536+ _think_step_active = True
529537 _user_turn_count += 1
530538 logger .info (f"User transcription: { text } " )
531539 self .audit_log .append_user_input (text , timestamp_ms = _user_speech_start_ts )
@@ -582,6 +590,7 @@ async def _process_deepgram_events() -> None:
582590 "PromptUpdated" ,
583591 "SpeakUpdated" ,
584592 "ThinkUpdated" ,
593+ "FunctionCallResponse" ,
585594 ):
586595 logger .debug (f"Deepgram agent event: { event_type } " )
587596
@@ -614,6 +623,8 @@ async def _send_keepalives() -> None:
614623 try :
615624 while self ._running and twilio_connected :
616625 await asyncio .sleep (KEEPALIVE_INTERVAL_S )
626+ if _think_step_active :
627+ continue
617628 try :
618629 await connection .send_keep_alive ()
619630 except Exception :
0 commit comments