Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
clemlesne committed Feb 10, 2025
2 parents 1ef8235 + 5fe33e5 commit 1074113
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 487 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Development",
"image": "mcr.microsoft.com/devcontainers/python:1-3.12-bookworm",
"image": "mcr.microsoft.com/devcontainers/python:1-3.13-bookworm",
"forwardPorts": [8080],
"init": true,
"remoteEnv": {
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ jobs:
uses: actions/[email protected]

- name: Set up uv
uses: astral-sh/setup-uv@v4.2.0
uses: astral-sh/setup-uv@v5.2.2
with:
enable-cache: true
python-version: "3.12"
python-version: "3.13"
version: "0.5.x"

- name: Set up Azure CLI
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ brew:

install:
@echo "➡️ Installing venv..."
uv venv --python 3.12 --allow-existing
uv venv --python 3.13 --allow-existing

$(MAKE) install-deps

Expand Down
2 changes: 1 addition & 1 deletion app/helpers/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


@asynccontextmanager
async def get_scheduler() -> AsyncGenerator[Scheduler, None]:
async def get_scheduler() -> AsyncGenerator[Scheduler]:
"""
Get the scheduler for async background tasks.
Expand Down
6 changes: 3 additions & 3 deletions app/helpers/call_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class ContextEnum(str, Enum):

def tts_sentence_split(
text: str, include_last: bool
) -> Generator[tuple[str, int], None, None]:
) -> Generator[tuple[str, int]]:
"""
Split a text into sentences.
Expand Down Expand Up @@ -509,7 +509,7 @@ def _context_serializer(contexts: set[ContextEnum | None] | None) -> str | None:


@contextmanager
def _detect_hangup() -> Generator[None, None, None]:
def _detect_hangup() -> Generator[None]:
"""
Catch a call hangup and raise a `CallHangupException` instead of the Call Automation SDK exceptions.
"""
Expand Down Expand Up @@ -542,7 +542,7 @@ async def _use_call_client(
async def use_tts_client(
call: CallStateModel,
out: asyncio.Queue[bytes],
) -> AsyncGenerator[SpeechSynthesizer, None]:
) -> AsyncGenerator[SpeechSynthesizer]:
"""
Use a text-to-speech client for a call.
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/config_models/monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


class LoggingLevelEnum(str, Enum):
# Copied from https://docs.python.org/3.12/library/logging.html#logging-levels
# Copied from https://docs.python.org/3.13/library/logging.html#logging-levels
CRITICAL = "CRITICAL"
DEBUG = "DEBUG"
ERROR = "ERROR"
Expand Down
4 changes: 2 additions & 2 deletions app/helpers/llm_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ async def completion_stream(
messages: list[MessageModel],
system: list[SystemMessage],
tools: list[ChatCompletionsToolDefinition] = [],
) -> AsyncGenerator[StreamingChatResponseMessageUpdate, None]:
) -> AsyncGenerator[StreamingChatResponseMessageUpdate]:
"""
Returns a stream of completions.
Expand Down Expand Up @@ -124,7 +124,7 @@ async def _completion_stream_worker(
messages: list[MessageModel],
system: list[SystemMessage],
tools: list[ChatCompletionsToolDefinition] = [],
) -> AsyncGenerator[StreamingChatResponseMessageUpdate, None]:
) -> AsyncGenerator[StreamingChatResponseMessageUpdate]:
"""
Returns a stream of completions.
"""
Expand Down
4 changes: 2 additions & 2 deletions app/persistence/azure_queue_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async def receive_messages(
self,
max_messages: int,
visibility_timeout: int,
) -> AsyncGenerator[Message, None]:
) -> AsyncGenerator[Message]:
async with self._use_client() as client:
messages = client.receive_messages(
max_messages=max_messages,
Expand Down Expand Up @@ -175,7 +175,7 @@ async def _use_service_client(self) -> QueueServiceClient:
)

@asynccontextmanager
async def _use_client(self) -> AsyncGenerator[QueueClient, None]:
async def _use_client(self) -> AsyncGenerator[QueueClient]:
"""
Generate a queue client.
"""
Expand Down
4 changes: 2 additions & 2 deletions app/persistence/cosmos_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ async def call_transac(
self,
call: CallStateModel,
scheduler: Scheduler,
) -> AsyncGenerator[None, None]:
) -> AsyncGenerator[None]:
# Copy and yield the updated object
init_data = call.model_dump(mode="json", exclude_none=True)
yield
Expand Down Expand Up @@ -397,7 +397,7 @@ async def _use_service_client(self) -> CosmosClient:
)

@asynccontextmanager
async def _use_client(self) -> AsyncGenerator[ContainerProxy, None]:
async def _use_client(self) -> AsyncGenerator[ContainerProxy]:
"""
Generate the container client.
"""
Expand Down
2 changes: 1 addition & 1 deletion app/persistence/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ async def _use_connection_pool(self) -> ConnectionPool:
)

@asynccontextmanager
async def _use_client(self) -> AsyncGenerator[Redis, None]:
async def _use_client(self) -> AsyncGenerator[Redis]:
"""
Return a Redis connection.
"""
Expand Down
33 changes: 18 additions & 15 deletions cicd/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
# Builder container (with UV as package manager)
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim@sha256:aacf61c53ed988e4a32b8b4da19043fdce7a8efcc67fb21ebc4cc0ba85f335b3 AS builder
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim@sha256:525eb0972b68435ca35014cf975e368b1a6cdd48fb0b5836866518b89863ca4c AS build

ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy

RUN rm -f /etc/apt/apt.conf.d/docker-clean \
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
# UV build dependencies
RUN --mount=target=/var/lib/apt/lists,type=cache,id=apt-lists-${TARGETPLATFORM},sharing=locked \
apt-get update -q \
&& apt-get install -y -q --no-install-recommends \
apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential

# Copy project definitions
WORKDIR /app
COPY pyproject.toml uv.lock ./

COPY . .

# Install Python dependencies
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
RUN --mount=target=/root/.cache/uv,type=cache,id=uv-${TARGETPLATFORM},sharing=locked \
uv sync --frozen --no-dev

# Output container (with only venv and app source)
FROM python:3.12-slim-bookworm@sha256:10f3aaab98db50cba827d3b33a91f39dc9ec2d02ca9b85cbc5008220d07b17f3
FROM python:3.13-slim-bookworm@sha256:ae9f9ac89467077ed1efefb6d9042132d28134ba201b2820227d46c9effd3174

# Set default dir
WORKDIR /app

ENV PATH=/app/.venv/bin:$PATH

COPY --from=builder --chown=app:app /app .
# Copy sources and venv
COPY . .
COPY --from=build /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:${PATH}"

# Allow app to know its version
ARG VERSION
ENV VERSION=${VERSION}

CMD ["bash", "-c", "gunicorn app.main:api --bind 0.0.0.0:8080 --graceful-timeout 60 --proxy-protocol --timeout 60 --worker-class uvicorn.workers.UvicornWorker --workers 4"]
# Starting the backend
CMD gunicorn app.main:api --bind 0.0.0.0:8080 --graceful-timeout 60 --proxy-protocol --timeout 60 --worker-class uvicorn.workers.UvicornWorker --workers 4
114 changes: 56 additions & 58 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,83 +1,81 @@
[project]
description = "AI-powered call center solution with Azure and OpenAI GPT."
dynamic = ["version"]
license = {file = "LICENSE"}
license = { file = "LICENSE" }
name = "call-center-ai"
readme = "README.md"
requires-python = ">=3.12"
requires-python = ">=3.13"
dependencies = [
"aiohttp-retry~=2.9", # Retry middleware for aiohttp, used with Twilio SDK
"aiohttp[speedups]~=3.10", # Async HTTP client for Azure and Twilio SDKs, plus async DNS resolver and async Brotli compression
"aiojobs~=1.3", # Async job scheduler
"azure-ai-inference[opentelemetry]~=1.0.0a0", # Azure AI Foundry LLM inference
"azure-ai-translation-text~=1.0", # Azure Cognitive Services Text Translation
"azure-appconfiguration~=1.7", # Outsourced configuration for live updates
"azure-cognitiveservices-speech~=1.41", # Azure AI Speech
"azure-communication-callautomation~=1.4.0a0", # Azure Communication Services Call Automation
"azure-communication-sms~=1.1", # Azure Communication Services SMS
"azure-cosmos~=4.7", # Azure Cosmos DB
"azure-eventgrid~=4.20", # Azure Event Grid
"azure-identity~=1.19", # Azure identity library
"azure-monitor-opentelemetry~=1.6", # Azure Monitor OpenTelemetry
"azure-search-documents~=11.6.0a0", # Azure AI Search
"azure-storage-queue~=12.12", # Azure Storage Queue
"django-htmlmin~=0.11", # Minify HTML
"fastapi~=0.115", # Web framework
"gunicorn~=23.0", # Application server
"jinja2~=3.1", # Template engine, used for prompts and web views
"json-repair~=0.30", # Repair JSON files from LLM
"mistune~=3.0", # Markdown parser for web views
"noisereduce~=3.0", # Noise reduction
"opentelemetry-instrumentation-aiohttp-client~=0.0a0", # OpenTelemetry instrumentation for aiohttp client
"opentelemetry-instrumentation-redis~=0.0a0", # OpenTelemetry instrumentation for Redis
"opentelemetry-semantic-conventions~=0.0a0", # OpenTelemetry conventions, to standardize telemetry data
"phonenumbers~=8.13", # Phone number parsing and formatting, used with Pydantic
"pydantic-extra-types~=2.9", # Extra types for Pydantic
"pydantic-settings~=2.6", # Application configuration management with Pydantic
"pydantic[email]~=2.9", # Data serialization and validation, plus email validation
"pyjwt~=2.9", # Secure inbound calls from Communication Services
"python-dotenv~=1.0", # Load environment variables from .env file
"python-multipart==0.*", # Form parsing
"pytz~=2024.0", # Time zone handling
"pyyaml~=6.0", # YAML parser
"redis~=5.2", # Redis client
"structlog~=24.4", # Pretty logging
"tenacity~=8.2", # Async retrying library
"tiktoken~=0.8", # Tokenization library for OpenAI models
"twilio~=9.3", # Twilio SDK, used for SMS
"typing-extensions~=4.12", # Typing extensions for Python 3.6+
"uvicorn[standard]~=0.32", # Application middleware
"aiohttp-retry~=2.9", # Retry middleware for aiohttp, used with Twilio SDK
"aiohttp[speedups]~=3.10", # Async HTTP client for Azure and Twilio SDKs, plus async DNS resolver and async Brotli compression
"aiojobs~=1.3", # Async job scheduler
"azure-ai-inference[opentelemetry]~=1.0.0a0", # Azure AI Foundry LLM inference
"azure-ai-translation-text~=1.0", # Azure Cognitive Services Text Translation
"azure-appconfiguration~=1.7", # Outsourced configuration for live updates
"azure-cognitiveservices-speech~=1.41", # Azure AI Speech
"azure-communication-callautomation~=1.4.0a0", # Azure Communication Services Call Automation
"azure-communication-sms~=1.1", # Azure Communication Services SMS
"azure-cosmos~=4.7", # Azure Cosmos DB
"azure-eventgrid~=4.20", # Azure Event Grid
"azure-identity~=1.19", # Azure identity library
"azure-monitor-opentelemetry~=1.6", # Azure Monitor OpenTelemetry
"azure-search-documents~=11.6.0a0", # Azure AI Search
"azure-storage-queue~=12.12", # Azure Storage Queue
"django-htmlmin~=0.11", # Minify HTML
"fastapi~=0.115", # Web framework
"gunicorn~=23.0", # Application server
"jinja2~=3.1", # Template engine, used for prompts and web views
"json-repair~=0.30", # Repair JSON files from LLM
"mistune~=3.0", # Markdown parser for web views
"noisereduce~=3.0", # Noise reduction
"opentelemetry-instrumentation-aiohttp-client~=0.0a0", # OpenTelemetry instrumentation for aiohttp client
"opentelemetry-instrumentation-redis~=0.0a0", # OpenTelemetry instrumentation for Redis
"opentelemetry-semantic-conventions~=0.0a0", # OpenTelemetry conventions, to standardize telemetry data
"phonenumbers~=8.13", # Phone number parsing and formatting, used with Pydantic
"pydantic-extra-types~=2.9", # Extra types for Pydantic
"pydantic-settings~=2.6", # Application configuration management with Pydantic
"pydantic[email]~=2.9", # Data serialization and validation, plus email validation
"pyjwt~=2.9", # Secure inbound calls from Communication Services
"python-dotenv~=1.0", # Load environment variables from .env file
"python-multipart==0.*", # Form parsing
"pytz~=2024.0", # Time zone handling
"pyyaml~=6.0", # YAML parser
"redis~=5.2", # Redis client
"structlog~=24.4", # Pretty logging
"tenacity~=8.2", # Async retrying library
"tiktoken~=0.8", # Tokenization library for OpenAI models
"twilio~=9.3", # Twilio SDK, used for SMS
"typing-extensions~=4.12", # Typing extensions for Python 3.6+
"uvicorn[standard]~=0.32", # Application middleware
]

[project.optional-dependencies]
dev = [
"deepeval~=0.21", # LLM model evaluation
"deptry~=0.20", # Dependency tree testing
"pyright~=1.1", # Static type checker
"pytest-assume~=2.4", # Pytest plugin for conditional tests
"pytest-asyncio~=0.24", # Pytest plugin for async tests
"pytest-repeat~=0.9", # Pytest plugin for repeating tests
"pytest-xdist[psutil]~=3.6", # Pytest plugin for parallel testing
"pytest~=8.3", # Testing framework
"ruff~=0.7", # Linter
"deepeval~=0.21", # LLM model evaluation
"deptry~=0.20", # Dependency tree testing
"pyright~=1.1", # Static type checker
"pytest-assume~=2.4", # Pytest plugin for conditional tests
"pytest-asyncio~=0.24", # Pytest plugin for async tests
"pytest-repeat~=0.9", # Pytest plugin for repeating tests
"pytest-xdist[psutil]~=3.6", # Pytest plugin for parallel testing
"pytest~=8.3", # Testing framework
"ruff~=0.7", # Linter
]

[tool.setuptools]
py-modules = [
"app",
]
py-modules = ["app"]

[tool.deptry]
ignore_notebooks = true
pep621_dev_dependency_groups = ["dev"]

[tool.deptry.per_rule_ignores]
DEP002 = [
"aiodns", # Resolver is required for the AIOHTTP AsyncResolver TCP resolver
"aiodns", # Resolver is required for the AIOHTTP AsyncResolver TCP resolver
]

[tool.ruff]
target-version = "py312"
target-version = "py313"

[tool.ruff.lint.isort]
combine-as-imports = true
Expand All @@ -86,4 +84,4 @@ combine-as-imports = true
docstring-code-format = true

[tool.pyright]
pythonVersion = "3.12"
pythonVersion = "3.13"
Loading

0 comments on commit 1074113

Please sign in to comment.