diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 821b9aa5..254f19f8 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -102,7 +102,7 @@ jobs: - name: Container meta id: meta - uses: docker/metadata-action@v5.0.0 + uses: docker/metadata-action@v5.5.0 with: images: ${{ env.CONTAINER_REGISTRY_GHCR }}/${{ env.CONTAINER_NAME }} tags: | @@ -125,7 +125,7 @@ jobs: echo "tag=$tag" >> $GITHUB_OUTPUT - name: Build/push container - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@v5.1.0 with: build-args: | VERSION=${{ needs.init.outputs.VERSION_FULL }} @@ -159,7 +159,7 @@ jobs: run: semgrep ci --sarif --output=semgrep.sarif - name: Upload results to GitHub Security - uses: github/codeql-action/upload-sarif@v2.22.8 + uses: github/codeql-action/upload-sarif@v3.23.0 with: sarif_file: semgrep.sarif diff --git a/.python-version b/.python-version index ace200aa..ad14e4bc 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -claimaiphonebot311 +claimaiphonebot312 diff --git a/Dockerfile b/Dockerfile index 7db2a947..8e3be4e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,14 @@ # Base container -FROM docker.io/library/python:3.11-slim-bullseye@sha256:9f35f3a6420693c209c11bba63dcf103d88e47ebe0b205336b5168c122967edf AS base - -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 -RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked \ - apt-get update -q +FROM docker.io/library/python:3.12-slim-bookworm@sha256:448eb6cade8c9edfa66b0840829744a10a0131f6e9ef95052913170c8291a348 AS base # Build container FROM base AS build +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 RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked --mount=target=/root/.cache/pip,type=cache,sharing=locked \ - apt-get install -y -q --no-install-recommends \ + apt-get update -q \ + && apt-get install -y -q --no-install-recommends \ gcc \ python3-dev \ && python3 -m pip install --upgrade \ diff --git a/helpers/config_models/monitoring.py b/helpers/config_models/monitoring.py index a9fa6f6a..a7b9e1bb 100644 --- a/helpers/config_models/monitoring.py +++ b/helpers/config_models/monitoring.py @@ -3,7 +3,7 @@ class LoggingLevel(str, Enum): - # Copied from https://docs.python.org/3.11/library/logging.html#logging-levels + # Copied from https://docs.python.org/3.12/library/logging.html#logging-levels CRITICAL = "CRITICAL" DEBUG = "DEBUG" ERROR = "ERROR" diff --git a/helpers/config_models/openai.py b/helpers/config_models/openai.py index ad47aa71..4da9d621 100644 --- a/helpers/config_models/openai.py +++ b/helpers/config_models/openai.py @@ -1,7 +1,7 @@ -from pydantic import BaseModel, HttpUrl +from pydantic import BaseModel class OpenAiModel(BaseModel): - endpoint: HttpUrl + endpoint: str gpt_deployment: str gpt_model: str diff --git a/main.py b/main.py index f5560463..73d8e5a9 100644 --- a/main.py +++ b/main.py @@ -18,9 +18,9 @@ from contextlib import asynccontextmanager from datetime import datetime from enum import Enum -from fastapi import FastAPI, status, Request +from fastapi import FastAPI, status, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse, Response +from fastapi.responses import JSONResponse from helpers.config import CONFIG from helpers.logging import build_logger from helpers.prompts import LLM as LLMPrompt, TTS as TTSPrompt @@ -71,7 +71,9 @@ CONFIG.communication_service.access_key.get_secret_value() ), ) -sms_client = SmsClient(credential=AZ_CREDENTIAL, endpoint=CONFIG.communication_service.endpoint) +sms_client = SmsClient( + credential=AZ_CREDENTIAL, endpoint=CONFIG.communication_service.endpoint +) db = sqlite3.connect(".local.sqlite", check_same_thread=False) EVENTS_DOMAIN = environ.get("EVENTS_DOMAIN").strip("/") @@ -170,10 +172,14 @@ def eventgrid_unregister() -> None: description="Liveness healthckeck, always returns 204, used to check if the API is up.", ) async def health_liveness_get() -> None: - return None + pass -@api.get("/call/initiate", description="Initiate an outbound call to a phone number.") +@api.get( + "/call/initiate", + status_code=status.HTTP_204_NO_CONTENT, + description="Initiate an outbound call to a phone number.", +) def call_initiate_get(phone_number: str) -> None: _logger.info(f"Initiating outbound call to {phone_number}") call_connection_properties = call_automation_client.create_call( @@ -185,7 +191,6 @@ def call_initiate_get(phone_number: str) -> None: _logger.info( f"Created call with connection id: {call_connection_properties.call_connection_id}" ) - return Response(status_code=status.HTTP_204_NO_CONTENT) @api.post( @@ -204,7 +209,7 @@ async def call_inbound_post(request: Request): _logger.info(f"Validating Event Grid subscription ({validation_code})") return JSONResponse( content={"validationResponse": event.data["validationCode"]}, - status_code=200, + status_code=status.HTTP_200_OK, ) elif event_type == SystemEventNames.AcsIncomingCallEventName: @@ -234,14 +239,19 @@ async def call_inbound_post(request: Request): # See: https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/communication-services/how-tos/call-automation/secure-webhook-endpoint.md async def call_event_post(request: Request, call_id: UUID) -> None: for event_dict in await request.json(): - event = CloudEvent.from_dict(event_dict) + call = get_call_by_id(call_id) + if not call: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Call {call_id} not found", + ) + event = CloudEvent.from_dict(event_dict) connection_id = event.data["callConnectionId"] operation_context = event.data.get("operationContext", None) client = call_automation_client.get_call_connection( call_connection_id=connection_id ) - call = get_call_by_id(call_id) event_type = event.type _logger.debug(f"Call event received {event_type} for call {call}") @@ -376,7 +386,7 @@ async def call_event_post(request: Request, call_id: UUID) -> None: elif ( event_type == "Microsoft.Communication.CallTransferFailed" ): # Call transfer failed - _logger.debig(f"Call transfer failed event ({call.id})") + _logger.debug(f"Call transfer failed event ({call.id})") result_information = event.data["resultInformation"] sub_code = result_information["subCode"] _logger.info(f"Error during call transfer, subCode {sub_code} ({call.id})") @@ -390,9 +400,7 @@ async def call_event_post(request: Request, call_id: UUID) -> None: save_call(call) -async def intelligence( - call: CallModel, client: CallConnectionClient -) -> None: +async def intelligence(call: CallModel, client: CallConnectionClient) -> None: chat_res = await gpt_chat(call) _logger.info(f"Chat ({call.id}): {chat_res}") @@ -412,7 +420,11 @@ async def intelligence( text=TTSPrompt.GOODBYE, ) - elif chat_res.intent in (IndentAction.NEW_CLAIM, IndentAction.UPDATED_CLAIM, IndentAction.NEW_OR_UPDATED_REMINDER): + elif chat_res.intent in ( + IndentAction.NEW_CLAIM, + IndentAction.UPDATED_CLAIM, + IndentAction.NEW_OR_UPDATED_REMINDER, + ): await handle_play( call=call, client=client, @@ -877,10 +889,14 @@ async def handle_hangup(client: CallConnectionClient, call: CallModel) -> None: ) response = responses[0] - if (response.successful): - _logger.info(f"SMS report sent {response.message_id} to {response.to} ({call.id})") + if response.successful: + _logger.info( + f"SMS report sent {response.message_id} to {response.to} ({call.id})" + ) else: - _logger.warn(f"Failed SMS to {response.to}, status {response.http_status_code}, error {response.error_message} ({call.id})") + _logger.warn( + f"Failed SMS to {response.to}, status {response.http_status_code}, error {response.error_message} ({call.id})" + ) except Exception: _logger.warn(f"SMS error ({call.id})", exc_info=True) @@ -932,7 +948,7 @@ def save_call(call: CallModel): db.commit() -def get_call_by_id(call_id: UUID) -> CallModel: +def get_call_by_id(call_id: UUID) -> Optional[CallModel]: cursor = db.execute( "SELECT data FROM calls WHERE id = ?", (call_id.hex,), diff --git a/requirements.txt b/requirements.txt index 44b1942b..73365bbf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ azure-eventgrid==4.16.0 azure-identity==1.15.0 azure-mgmt-eventgrid==10.2.0 fastapi==0.108.0 -openai==1.7.0 +openai==1.7.1 phonenumbers==8.13.27 pydantic-extra-types==2.4.0 python-dotenv==1.0.0