Skip to content

Commit 47a8387

Browse files
committed
Merge branch 'develop'
2 parents 0acf079 + 3f72173 commit 47a8387

32 files changed

+146
-100
lines changed

Diff for: app/helpers/cache.py

+44-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import asyncio
2-
import functools
32
from collections import OrderedDict
43
from collections.abc import AsyncGenerator, Awaitable
54
from contextlib import asynccontextmanager
5+
from functools import wraps
66

77
from aiojobs import Scheduler
88

@@ -20,17 +20,17 @@ async def get_scheduler() -> AsyncGenerator[Scheduler, None]:
2020
yield scheduler
2121

2222

23-
def async_lru_cache(maxsize: int = 128):
23+
def lru_acache(maxsize: int = 128):
2424
"""
25-
Caches a function's return value each time it is called.
25+
Caches an async function's return value each time it is called.
2626
2727
If the maxsize is reached, the least recently used value is removed.
2828
"""
2929

3030
def decorator(func):
3131
cache: OrderedDict[tuple, Awaitable] = OrderedDict()
3232

33-
@functools.wraps(func)
33+
@wraps(func)
3434
async def wrapper(*args, **kwargs) -> Awaitable:
3535
# Create a cache key from event loop, args and kwargs, using frozenset for kwargs to ensure hashability
3636
key = (
@@ -49,6 +49,46 @@ async def wrapper(*args, **kwargs) -> Awaitable:
4949
cache[key] = value
5050
cache.move_to_end(key)
5151

52+
# Remove the least recently used key if the cache is full
53+
if len(cache) > maxsize:
54+
cache.popitem(last=False)
55+
56+
return value
57+
58+
return wrapper
59+
60+
return decorator
61+
62+
63+
def lru_cache(maxsize: int = 128):
64+
"""
65+
Caches a sync function's return value each time it is called.
66+
67+
If the maxsize is reached, the least recently used value is removed.
68+
"""
69+
70+
def decorator(func):
71+
cache: OrderedDict[tuple, Awaitable] = OrderedDict()
72+
73+
@wraps(func)
74+
def wrapper(*args, **kwargs) -> Awaitable:
75+
# Create a cache key from args and kwargs, using frozenset for kwargs to ensure hashability
76+
key = (
77+
args,
78+
frozenset(kwargs.items()),
79+
)
80+
81+
if key in cache:
82+
# Move the recently accessed key to the end (most recently used)
83+
cache.move_to_end(key)
84+
return cache[key]
85+
86+
# Compute the value since it's not cached
87+
value = func(*args, **kwargs)
88+
cache[key] = value
89+
cache.move_to_end(key)
90+
91+
# Remove the least recently used key if the cache is full
5292
if len(cache) > maxsize:
5393
cache.popitem(last=False)
5494

Diff for: app/helpers/call_events.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
from app.models.next import NextModel
4747
from app.models.synthesis import SynthesisModel
4848

49-
_sms = CONFIG.sms.instance()
50-
_db = CONFIG.database.instance()
49+
_sms = CONFIG.sms.instance
50+
_db = CONFIG.database.instance
5151

5252

5353
@tracer.start_as_current_span("on_new_call")

Diff for: app/helpers/call_llm.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
extract_message_style,
5050
)
5151

52-
_db = CONFIG.database.instance()
52+
_db = CONFIG.database.instance
5353

5454

5555
# TODO: Refacto, this function is too long
@@ -497,7 +497,7 @@ async def _content_callback(buffer: str) -> None:
497497
translated_messages = await asyncio.gather(
498498
*[message.translate(call.lang.short_code) for message in call.messages]
499499
)
500-
logger.debug("Translated messages: %s", translated_messages)
500+
# logger.debug("Translated messages: %s", translated_messages)
501501

502502
# Execute LLM inference
503503
content_buffer_pointer = 0

Diff for: app/helpers/call_utils.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from azure.core.exceptions import HttpResponseError, ResourceNotFoundError
4141
from noisereduce import reduce_noise
4242

43-
from app.helpers.cache import async_lru_cache
43+
from app.helpers.cache import lru_acache
4444
from app.helpers.config import CONFIG
4545
from app.helpers.features import (
4646
recognition_stt_complete_timeout_ms,
@@ -71,7 +71,7 @@
7171
r"[^\w\sÀ-ÿ'«»“”\"\"‘’''(),.!?;:\-\+_@/&€$%=]" # noqa: RUF001
7272
) # Sanitize text for TTS
7373

74-
_db = CONFIG.database.instance()
74+
_db = CONFIG.database.instance
7575

7676

7777
class CallHangupException(Exception):
@@ -526,7 +526,7 @@ def _detect_hangup() -> Generator[None, None, None]:
526526
raise e
527527

528528

529-
@async_lru_cache()
529+
@lru_acache()
530530
async def _use_call_client(
531531
client: CallAutomationClient, voice_id: str
532532
) -> CallConnectionClient:
@@ -616,7 +616,7 @@ async def __aenter__(self):
616616
)()
617617

618618
# Create client
619-
self.client = SpeechRecognizer(
619+
self._client = SpeechRecognizer(
620620
audio_config=AudioConfig(stream=self._stream),
621621
language=self._call.lang.short_code,
622622
speech_config=SpeechConfig(
@@ -626,24 +626,25 @@ async def __aenter__(self):
626626
)
627627

628628
# TSS events
629-
self.client.recognized.connect(self._complete_callback)
630-
self.client.recognizing.connect(self._partial_callback)
629+
self._client.recognized.connect(self._complete_callback)
630+
self._client.recognizing.connect(self._partial_callback)
631631

632632
# Debugging events
633-
self.client.canceled.connect(
633+
self._client.canceled.connect(
634634
lambda event: logger.warning("STT cancelled: %s", event)
635635
)
636-
self.client.session_started.connect(lambda _: logger.debug("STT started"))
637-
self.client.session_stopped.connect(lambda _: logger.debug("STT stopped"))
636+
self._client.session_started.connect(lambda _: logger.debug("STT started"))
637+
self._client.session_stopped.connect(lambda _: logger.debug("STT stopped"))
638638

639639
# Start STT
640-
self.client.start_continuous_recognition_async()
640+
self._client.start_continuous_recognition_async()
641641

642642
return self
643643

644644
async def __aexit__(self, *args, **kwargs):
645645
# Stop STT
646-
self.client.stop_continuous_recognition_async()
646+
if self._client:
647+
self._client.stop_continuous_recognition_async()
647648

648649
def _partial_callback(self, event):
649650
"""

Diff for: app/helpers/config_models/ai_search.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from functools import lru_cache
1+
from functools import cached_property
22

33
from pydantic import BaseModel, Field
44

@@ -17,14 +17,14 @@ class AiSearchModel(BaseModel, frozen=True):
1717
strictness: float = Field(default=2, ge=0, le=5)
1818
top_n_documents: int = Field(default=5, ge=1)
1919

20-
@lru_cache
20+
@cached_property
2121
def instance(self) -> ISearch:
2222
from app.helpers.config import CONFIG
2323
from app.persistence.ai_search import (
2424
AiSearchSearch,
2525
)
2626

2727
return AiSearchSearch(
28-
cache=CONFIG.cache.instance(),
28+
cache=CONFIG.cache.instance,
2929
config=self,
3030
)

Diff for: app/helpers/config_models/cache.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from enum import Enum
2-
from functools import lru_cache
2+
from functools import cached_property
33

44
from pydantic import BaseModel, Field, SecretStr, ValidationInfo, field_validator
55

@@ -16,7 +16,7 @@ class ModeEnum(str, Enum):
1616
class MemoryModel(BaseModel, frozen=True):
1717
max_size: int = Field(default=128, ge=10)
1818

19-
@lru_cache
19+
@cached_property
2020
def instance(self) -> ICache:
2121
from app.persistence.memory import (
2222
MemoryCache,
@@ -32,7 +32,7 @@ class RedisModel(BaseModel, frozen=True):
3232
port: int = 6379
3333
ssl: bool = True
3434

35-
@lru_cache
35+
@cached_property
3636
def instance(self) -> ICache:
3737
from app.persistence.redis import (
3838
RedisCache,
@@ -68,10 +68,11 @@ def _validate_memory(
6868
raise ValueError("Memory config required")
6969
return memory
7070

71+
@cached_property
7172
def instance(self) -> ICache:
7273
if self.mode == ModeEnum.MEMORY:
7374
assert self.memory
74-
return self.memory.instance()
75+
return self.memory.instance
7576

7677
assert self.redis
77-
return self.redis.instance()
78+
return self.redis.instance

Diff for: app/helpers/config_models/database.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from functools import lru_cache
1+
from functools import cached_property
22

33
from pydantic import BaseModel
44

@@ -10,21 +10,22 @@ class CosmosDbModel(BaseModel, frozen=True):
1010
database: str
1111
endpoint: str
1212

13-
@lru_cache
13+
@cached_property
1414
def instance(self) -> IStore:
1515
from app.helpers.config import CONFIG
1616
from app.persistence.cosmos_db import (
1717
CosmosDbStore,
1818
)
1919

2020
return CosmosDbStore(
21-
cache=CONFIG.cache.instance(),
21+
cache=CONFIG.cache.instance,
2222
config=self,
2323
)
2424

2525

2626
class DatabaseModel(BaseModel):
2727
cosmos_db: CosmosDbModel
2828

29+
@cached_property
2930
def instance(self) -> IStore:
30-
return self.cosmos_db.instance()
31+
return self.cosmos_db.instance

Diff for: app/helpers/config_models/llm.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from azure.ai.inference.aio import ChatCompletionsClient
22
from pydantic import BaseModel
33

4-
from app.helpers.cache import async_lru_cache
4+
from app.helpers.cache import lru_acache
55
from app.helpers.http import azure_transport
66
from app.helpers.identity import credential
77

@@ -14,8 +14,8 @@ class DeploymentModel(BaseModel, frozen=True):
1414
seed: int = 42 # Reproducible results
1515
temperature: float = 0.0 # Most focused and deterministic
1616

17-
@async_lru_cache()
18-
async def instance(self) -> tuple[ChatCompletionsClient, "DeploymentModel"]:
17+
@lru_acache()
18+
async def client(self) -> tuple[ChatCompletionsClient, "DeploymentModel"]:
1919
return ChatCompletionsClient(
2020
# Reliability
2121
seed=self.seed,

Diff for: app/helpers/config_models/queue.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from functools import lru_cache
1+
from functools import cached_property
22

33
from pydantic import BaseModel
44

@@ -10,7 +10,7 @@ class QueueModel(BaseModel, frozen=True):
1010
sms_name: str
1111
training_name: str
1212

13-
@lru_cache
13+
@cached_property
1414
def call(self):
1515
from app.persistence.azure_queue_storage import AzureQueueStorage
1616

@@ -19,7 +19,7 @@ def call(self):
1919
name=self.call_name,
2020
)
2121

22-
@lru_cache
22+
@cached_property
2323
def post(self):
2424
from app.persistence.azure_queue_storage import AzureQueueStorage
2525

@@ -28,7 +28,7 @@ def post(self):
2828
name=self.post_name,
2929
)
3030

31-
@lru_cache
31+
@cached_property
3232
def sms(self):
3333
from app.persistence.azure_queue_storage import AzureQueueStorage
3434

@@ -37,7 +37,7 @@ def sms(self):
3737
name=self.sms_name,
3838
)
3939

40-
@lru_cache
40+
@cached_property
4141
def training(self):
4242
from app.persistence.azure_queue_storage import AzureQueueStorage
4343

Diff for: app/helpers/config_models/sms.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from enum import Enum
2-
from functools import lru_cache
2+
from functools import cached_property
33

44
from pydantic import BaseModel, SecretStr, ValidationInfo, field_validator
55

@@ -21,7 +21,7 @@ class CommunicationServiceModel(BaseModel, frozen=True):
2121
Model is purely empty to fit to the `ISms` interface and the "mode" enum code organization. As the Communication Services is also used as the only call interface, it is not necessary to duplicate the models.
2222
"""
2323

24-
@lru_cache
24+
@cached_property
2525
def instance(self) -> ISms:
2626
from app.helpers.config import CONFIG
2727
from app.persistence.communication_services import (
@@ -36,7 +36,7 @@ class TwilioModel(BaseModel, frozen=True):
3636
auth_token: SecretStr
3737
phone_number: PhoneNumber
3838

39-
@lru_cache
39+
@cached_property
4040
def instance(self) -> ISms:
4141
from app.persistence.twilio import (
4242
TwilioSms,
@@ -77,10 +77,11 @@ def _validate_twilio(
7777
raise ValueError("Twilio config required")
7878
return twilio
7979

80+
@cached_property
8081
def instance(self) -> ISms:
8182
if self.mode == ModeEnum.COMMUNICATION_SERVICES:
8283
assert self.communication_services
83-
return self.communication_services.instance()
84+
return self.communication_services.instance
8485

8586
assert self.twilio
86-
return self.twilio.instance()
87+
return self.twilio.instance

Diff for: app/helpers/features.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from azure.appconfiguration.aio import AzureAppConfigurationClient
44
from azure.core.exceptions import ResourceNotFoundError
55

6-
from app.helpers.cache import async_lru_cache
6+
from app.helpers.cache import lru_acache
77
from app.helpers.config import CONFIG
88
from app.helpers.config_models.cache import MemoryModel
99
from app.helpers.http import azure_transport
@@ -233,7 +233,7 @@ async def _get(key: str, type_res: type[T]) -> T | None:
233233
)
234234

235235

236-
@async_lru_cache()
236+
@lru_acache()
237237
async def _use_client() -> AzureAppConfigurationClient:
238238
"""
239239
Generate the App Configuration client and close it after use.

0 commit comments

Comments
 (0)