Skip to content

Commit 42286af

Browse files
committed
perf: Remove defer feature update
This is totally an anti pattern. It overloads in few seconds the scheduler queue and slow down the whole request by 5-10 seconds.
1 parent d9604bc commit 42286af

10 files changed

+112
-211
lines changed

app/helpers/call_events.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ async def on_call_connected(
125125
_handle_recording(
126126
call=call,
127127
client=client,
128-
scheduler=scheduler,
129128
server_call_id=server_call_id,
130129
), # Second, start recording the call
131130
)
@@ -235,7 +234,7 @@ async def on_automation_recognize_error(
235234
logger.info(
236235
"Timeout, retrying language selection (%s/%s)",
237236
call.recognition_retry,
238-
await recognition_retry_max(scheduler),
237+
await recognition_retry_max(),
239238
)
240239
await _handle_ivr_language(
241240
call=call,
@@ -321,7 +320,7 @@ async def _pre_recognize_error(
321320
Returns True if the call should continue, False if it should end.
322321
"""
323322
# Voice retries are exhausted, end call
324-
if call.recognition_retry >= await recognition_retry_max(scheduler):
323+
if call.recognition_retry >= await recognition_retry_max():
325324
logger.info("Timeout, ending call")
326325
return False
327326

@@ -793,15 +792,14 @@ async def _handle_ivr_language(
793792
async def _handle_recording(
794793
call: CallStateModel,
795794
client: CallAutomationClient,
796-
scheduler: Scheduler,
797795
server_call_id: str,
798796
) -> None:
799797
"""
800798
Start recording the call.
801799
802800
Feature activation is checked before starting the recording.
803801
"""
804-
if not await recording_enabled(scheduler):
802+
if not await recording_enabled():
805803
return
806804

807805
assert CONFIG.communication_services.recording_container_url

app/helpers/call_llm.py

+7-10
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,6 @@ async def _response_callback(_retry: bool = False) -> None:
227227
in_callback=aec.pull_audio,
228228
out_callback=stt_client.push_audio,
229229
response_callback=_response_callback,
230-
scheduler=scheduler,
231230
stop_callback=_stop_callback,
232231
timeout_callback=_timeout_callback,
233232
)
@@ -307,10 +306,10 @@ def _loading_task() -> asyncio.Task:
307306
# Timeouts
308307
soft_timeout_triggered = False
309308
soft_timeout_task = asyncio.create_task(
310-
asyncio.sleep(await answer_soft_timeout_sec(scheduler))
309+
asyncio.sleep(await answer_soft_timeout_sec())
311310
)
312311
hard_timeout_task = asyncio.create_task(
313-
asyncio.sleep(await answer_hard_timeout_sec(scheduler))
312+
asyncio.sleep(await answer_hard_timeout_sec())
314313
)
315314

316315
def _clear_tasks() -> None:
@@ -340,7 +339,7 @@ def _clear_tasks() -> None:
340339
if hard_timeout_task.done():
341340
logger.warning(
342341
"Hard timeout of %ss reached",
343-
await answer_hard_timeout_sec(scheduler),
342+
await answer_hard_timeout_sec(),
344343
)
345344
# Clean up
346345
_clear_tasks()
@@ -352,7 +351,7 @@ def _clear_tasks() -> None:
352351
if soft_timeout_task.done() and not soft_timeout_triggered:
353352
logger.warning(
354353
"Soft timeout of %ss reached",
355-
await answer_soft_timeout_sec(scheduler),
354+
await answer_soft_timeout_sec(),
356355
)
357356
soft_timeout_triggered = True
358357
# Never store the error message in the call history, it has caused hallucinations in the LLM
@@ -501,7 +500,6 @@ async def _content_callback(buffer: str) -> None:
501500
async for delta in completion_stream(
502501
max_tokens=160, # Lowest possible value for 90% of the cases, if not sufficient, retry will be triggered, 100 tokens ~= 75 words, 20 words ~= 1 sentence, 6 sentences ~= 160 tokens
503502
messages=call.messages,
504-
scheduler=scheduler,
505503
system=system,
506504
tools=tools,
507505
):
@@ -619,7 +617,6 @@ async def _process_audio_for_vad( # noqa: PLR0913
619617
in_callback: Callable[[], Awaitable[tuple[bytes, bool]]],
620618
out_callback: Callable[[bytes], None],
621619
response_callback: Callable[[], Awaitable[None]],
622-
scheduler: Scheduler,
623620
stop_callback: Callable[[], Awaitable[None]],
624621
timeout_callback: Callable[[], Awaitable[None]],
625622
) -> None:
@@ -643,7 +640,7 @@ async def _wait_for_silence() -> None:
643640
"""
644641
# Wait before flushing
645642
nonlocal stop_task
646-
timeout_ms = await vad_silence_timeout_ms(scheduler)
643+
timeout_ms = await vad_silence_timeout_ms()
647644
await asyncio.sleep(timeout_ms / 1000)
648645

649646
# Cancel the clear TTS task
@@ -656,7 +653,7 @@ async def _wait_for_silence() -> None:
656653
await response_callback()
657654

658655
# Wait for silence and trigger timeout
659-
timeout_sec = await phone_silence_timeout_sec(scheduler)
656+
timeout_sec = await phone_silence_timeout_sec()
660657
while True:
661658
# Stop this time if the call played a message
662659
timeout_start = datetime.now(UTC)
@@ -685,7 +682,7 @@ async def _wait_for_stop() -> None:
685682
"""
686683
Stop the TTS if user speaks for too long.
687684
"""
688-
timeout_ms = await vad_cutoff_timeout_ms(scheduler)
685+
timeout_ms = await vad_cutoff_timeout_ms()
689686

690687
# Wait before clearing the TTS queue
691688
await asyncio.sleep(timeout_ms / 1000)

app/helpers/call_utils.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -728,8 +728,7 @@ async def pull_recognition(self) -> str:
728728
try:
729729
await asyncio.wait_for(
730730
self._stt_complete_gate.wait(),
731-
timeout=await recognition_stt_complete_timeout_ms(self._scheduler)
732-
/ 1000,
731+
timeout=await recognition_stt_complete_timeout_ms() / 1000,
733732
)
734733
except TimeoutError:
735734
logger.debug("Complete recognition timeout, using partial recognition")
@@ -856,7 +855,7 @@ async def _rms_speech_detection(self, voice: np.ndarray) -> bool:
856855
# Calculate Root Mean Square (RMS)
857856
rms = np.sqrt(np.mean(voice**2))
858857
# Get VAD threshold, divide by 10 to more usability from user side, as RMS is in range 0-1 and a detection of 0.1 is a good maximum threshold
859-
threshold = await vad_threshold(self._scheduler) / 10
858+
threshold = await vad_threshold() / 10
860859
return rms >= threshold
861860

862861
async def _process_one(self, input_pcm: bytes) -> None:

app/helpers/features.py

+19-40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import TypeVar, cast
22

3-
from aiojobs import Scheduler
43
from azure.appconfiguration.aio import AzureAppConfigurationClient
54
from azure.core.exceptions import ResourceNotFoundError
65

@@ -16,55 +15,51 @@
1615
T = TypeVar("T", bool, int, float, str)
1716

1817

19-
async def answer_hard_timeout_sec(scheduler: Scheduler) -> int:
18+
async def answer_hard_timeout_sec() -> int:
2019
"""
2120
The hard timeout for the bot answer in secs.
2221
"""
2322
return await _default(
2423
default=60,
2524
key="answer_hard_timeout_sec",
26-
scheduler=scheduler,
2725
type_res=int,
2826
)
2927

3028

31-
async def answer_soft_timeout_sec(scheduler: Scheduler) -> int:
29+
async def answer_soft_timeout_sec() -> int:
3230
"""
3331
The soft timeout for the bot answer in secs.
3432
"""
3533
return await _default(
3634
default=30,
3735
key="answer_soft_timeout_sec",
38-
scheduler=scheduler,
3936
type_res=int,
4037
)
4138

4239

43-
async def callback_timeout_hour(scheduler: Scheduler) -> int:
40+
async def callback_timeout_hour() -> int:
4441
"""
4542
The timeout for a callback in hours. Set 0 to disable.
4643
"""
4744
return await _default(
4845
default=24,
4946
key="callback_timeout_hour",
50-
scheduler=scheduler,
5147
type_res=int,
5248
)
5349

5450

55-
async def phone_silence_timeout_sec(scheduler: Scheduler) -> int:
51+
async def phone_silence_timeout_sec() -> int:
5652
"""
5753
Amount of silence in secs to trigger a warning message from the assistant.
5854
"""
5955
return await _default(
6056
default=20,
6157
key="phone_silence_timeout_sec",
62-
scheduler=scheduler,
6358
type_res=int,
6459
)
6560

6661

67-
async def vad_threshold(scheduler: Scheduler) -> float:
62+
async def vad_threshold() -> float:
6863
"""
6964
The threshold for voice activity detection. Between 0.1 and 1.
7065
"""
@@ -73,88 +68,80 @@ async def vad_threshold(scheduler: Scheduler) -> float:
7368
key="vad_threshold",
7469
max_incl=1,
7570
min_incl=0.1,
76-
scheduler=scheduler,
7771
type_res=float,
7872
)
7973

8074

81-
async def vad_silence_timeout_ms(scheduler: Scheduler) -> int:
75+
async def vad_silence_timeout_ms() -> int:
8276
"""
8377
Silence to trigger voice activity detection in milliseconds.
8478
"""
8579
return await _default(
8680
default=500,
8781
key="vad_silence_timeout_ms",
88-
scheduler=scheduler,
8982
type_res=int,
9083
)
9184

9285

93-
async def vad_cutoff_timeout_ms(scheduler: Scheduler) -> int:
86+
async def vad_cutoff_timeout_ms() -> int:
9487
"""
9588
The cutoff timeout for voice activity detection in milliseconds.
9689
"""
9790
return await _default(
9891
default=250,
9992
key="vad_cutoff_timeout_ms",
100-
scheduler=scheduler,
10193
type_res=int,
10294
)
10395

10496

105-
async def recording_enabled(scheduler: Scheduler) -> bool:
97+
async def recording_enabled() -> bool:
10698
"""
10799
Whether call recording is enabled.
108100
"""
109101
return await _default(
110102
default=False,
111103
key="recording_enabled",
112-
scheduler=scheduler,
113104
type_res=bool,
114105
)
115106

116107

117-
async def slow_llm_for_chat(scheduler: Scheduler) -> bool:
108+
async def slow_llm_for_chat() -> bool:
118109
"""
119110
Whether to use the slow LLM for chat.
120111
"""
121112
return await _default(
122113
default=True,
123114
key="slow_llm_for_chat",
124-
scheduler=scheduler,
125115
type_res=bool,
126116
)
127117

128118

129-
async def recognition_retry_max(scheduler: Scheduler) -> int:
119+
async def recognition_retry_max() -> int:
130120
"""
131121
The maximum number of retries for voice recognition. Minimum of 1.
132122
"""
133123
return await _default(
134124
default=3,
135125
key="recognition_retry_max",
136126
min_incl=1,
137-
scheduler=scheduler,
138127
type_res=int,
139128
)
140129

141130

142-
async def recognition_stt_complete_timeout_ms(scheduler: Scheduler) -> int:
131+
async def recognition_stt_complete_timeout_ms() -> int:
143132
"""
144133
The timeout for STT completion in milliseconds.
145134
"""
146135
return await _default(
147136
default=100,
148137
key="recognition_stt_complete_timeout_ms",
149-
scheduler=scheduler,
150138
type_res=int,
151139
)
152140

153141

154-
async def _default( # noqa: PLR0913
142+
async def _default(
155143
default: T,
156144
key: str,
157-
scheduler: Scheduler,
158145
type_res: type[T],
159146
max_incl: T | None = None,
160147
min_incl: T | None = None,
@@ -165,7 +152,6 @@ async def _default( # noqa: PLR0913
165152
# Get the setting
166153
res = await _get(
167154
key=key,
168-
scheduler=scheduler,
169155
type_res=type_res,
170156
)
171157
if res:
@@ -207,11 +193,7 @@ def _validate(
207193
return res
208194

209195

210-
async def _get(
211-
key: str,
212-
scheduler: Scheduler,
213-
type_res: type[T],
214-
) -> T | None:
196+
async def _get(key: str, type_res: type[T]) -> T | None:
215197
"""
216198
Get a setting from the App Configuration service.
217199
"""
@@ -224,15 +206,6 @@ async def _get(
224206
value=cached.decode(),
225207
)
226208

227-
# Defer the update
228-
await scheduler.spawn(_refresh(cache_key, key))
229-
return
230-
231-
232-
async def _refresh(
233-
cache_key: str,
234-
key: str,
235-
) -> T | None:
236209
# Try live
237210
try:
238211
async with await _use_client() as client:
@@ -253,6 +226,12 @@ async def _refresh(
253226
value=res,
254227
)
255228

229+
# Return value
230+
return _parse(
231+
type_res=type_res,
232+
value=res,
233+
)
234+
256235

257236
@async_lru_cache()
258237
async def _use_client() -> AzureAppConfigurationClient:

app/helpers/llm_tools.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ async def new_claim(
8888

8989
# Store the last message and use it at first message of the new claim
9090
self.call = await _db.call_create(
91-
call=CallStateModel(
91+
CallStateModel(
9292
initiate=self.call.initiate.model_copy(),
9393
voice_id=self.call.voice_id,
9494
messages=[
@@ -101,8 +101,7 @@ async def new_claim(
101101
# Reinsert the last message, using more will add the user message asking to create the new claim and the assistant can loop on it sometimes
102102
self.call.messages[-1],
103103
],
104-
),
105-
scheduler=self.scheduler,
104+
)
106105
)
107106
return "Claim, reminders and messages reset"
108107

0 commit comments

Comments
 (0)