diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 55840b4..b577107 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -13,17 +13,16 @@ concurrency: jobs: ci: runs-on: ubuntu-latest - defaults: - run: - working-directory: apps/ai steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.11' # 可改成 3.12 + python-version: '3.11' # 或 3.12 - name: Install uv run: pipx install uv @@ -33,11 +32,13 @@ jobs: with: path: | ~/.cache/uv - apps/ai/.venv - key: uv-${{ runner.os }}-${{ hashFiles('apps/ai/pyproject.toml', 'apps/ai/uv.lock') }} + ./.venv + key: uv-${{ runner.os }}-${{ hashFiles('pyproject.toml', 'uv.lock') }} + restore-keys: | + uv-${{ runner.os }}- - name: Sync deps (with dev extras) run: uv sync --extra dev --frozen || uv sync --extra dev - name: Run all checks - run: make check-all + run: PYTHONPATH=. make check-all diff --git a/app/api/email.py b/app/api/email.py index 8e56c71..c7ee385 100644 --- a/app/api/email.py +++ b/app/api/email.py @@ -66,10 +66,7 @@ class SendICSArgs(BaseMailArgs, EventInfo): def _to_pendulum_with_tz(dt: datetime, tz_name: str) -> pendulum.DateTime: - if dt.tzinfo is None: - return pendulum.parse(dt.isoformat(), tz=tz_name) - else: - return pendulum.instance(dt).in_tz(tz_name) + return pendulum.instance(dt, tz=tz_name) @router.post( diff --git a/app/services/redis_service.py b/app/services/redis_service.py index b3fbc18..c3d62b2 100644 --- a/app/services/redis_service.py +++ b/app/services/redis_service.py @@ -1,8 +1,9 @@ import json from models.call import CallSkeleton -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, List, cast from infrastructure.redis_client import get_redis +from openai.types.chat import ChatCompletionMessageParam r = get_redis() @@ -47,20 +48,23 @@ def get_call_skeleton_dict(call_sid: str) -> Dict[str, Any]: return json.loads(data) -def get_message_history(call_sid: str) -> list: +def get_message_history(call_sid: str) -> List[ChatCompletionMessageParam]: """Get message history from Redis for a specific call""" try: skeleton_dict = get_call_skeleton_dict(call_sid) history = skeleton_dict.get("history", []) # Convert to the format expected by extractors - message_history = [] + message_history: List[ChatCompletionMessageParam] = [] for msg in history[-8:]: # Last 8 messages for context message_history.append( - { - "role": "user" if msg.get("speaker") == "customer" else "assistant", - "content": msg.get("message", ""), - } + cast( + ChatCompletionMessageParam, + { + "role": "user" if msg.get("speaker") == "customer" else "assistant", + "content": msg.get("message", ""), + }, + ) ) print(f"🔍 Redis: Retrieved {len(message_history)} messages from history") diff --git a/app/services/retrieve/customer_info_extractors.py b/app/services/retrieve/customer_info_extractors.py index 2061226..81894b9 100644 --- a/app/services/retrieve/customer_info_extractors.py +++ b/app/services/retrieve/customer_info_extractors.py @@ -13,9 +13,10 @@ import json import os -from typing import Dict, Any, Optional, List +from typing import Dict, Any, Optional, List, cast from datetime import datetime, timezone, timedelta from openai import AsyncOpenAI +from openai.types.chat import ChatCompletionMessageParam from custom_types import CustomerServiceState from utils.prompts.customer_info_prompts import ( @@ -44,13 +45,15 @@ async def _call_openai_api( prompt: str, conversation_context: str, user_input: str, - message_history: Optional[List[Dict[str, Any]]] = None, + message_history: Optional[List[ChatCompletionMessageParam]] = None, ) -> Dict[str, Any]: client = _get_openai_client() user_input = user_input or "" # Build messages array - messages = [{"role": "system", "content": prompt}] + messages: List[ChatCompletionMessageParam] = [ + cast(ChatCompletionMessageParam, {"role": "system", "content": prompt}) + ] # Add message history if provided (last 4 messages) if message_history: @@ -58,13 +61,13 @@ async def _call_openai_api( messages.append(msg) # Add current user input - messages.append({"role": "user", "content": f"User input: {user_input}"}) + messages.append(cast(ChatCompletionMessageParam, {"role": "user", "content": f"User input: {user_input}"})) print("🔍 [LLM_DEBUG] Sending request to OpenAI:") print(" • Model: gpt-4o-mini") print(f" • Messages count: {len(messages)}") print(f" • User input: '{user_input}'") - print(f" • System prompt length: {len(messages[0]['content'])} chars") + print(f" • System prompt length: {len(prompt)} chars") try: response = await client.chat.completions.create( @@ -214,7 +217,7 @@ def _validate_extracted_time( async def extract_name_from_conversation( - state: CustomerServiceState, message_history: Optional[List[Dict[str, Any]]] = None + state: CustomerServiceState, message_history: Optional[List[ChatCompletionMessageParam]] = None ) -> Dict[str, Any]: try: context = _build_conversation_context(state) @@ -239,7 +242,7 @@ async def extract_name_from_conversation( async def extract_phone_from_conversation( - state: CustomerServiceState, message_history: Optional[List[Dict[str, Any]]] = None + state: CustomerServiceState, message_history: Optional[List[ChatCompletionMessageParam]] = None ) -> Dict[str, Any]: try: context = _build_conversation_context(state) @@ -264,7 +267,7 @@ async def extract_phone_from_conversation( async def extract_address_from_conversation( - state: CustomerServiceState, message_history: Optional[List[Dict[str, Any]]] = None + state: CustomerServiceState, message_history: Optional[List[ChatCompletionMessageParam]] = None ) -> Dict[str, Any]: """Extract address from conversation with memory of previously collected information and parse into components""" try: @@ -391,7 +394,7 @@ async def extract_address_from_conversation( async def extract_service_from_conversation( - state: CustomerServiceState, message_history: Optional[List[Dict[str, Any]]] = None + state: CustomerServiceState, message_history: Optional[List[ChatCompletionMessageParam]] = None ) -> Dict[str, Any]: try: context = _build_conversation_context(state) @@ -463,7 +466,7 @@ async def extract_service_from_conversation( async def extract_time_from_conversation( - state: CustomerServiceState, message_history: Optional[List[Dict[str, Any]]] = None + state: CustomerServiceState, message_history: Optional[List[ChatCompletionMessageParam]] = None ) -> Dict[str, Any]: try: context = _build_conversation_context(state) diff --git a/app/services/ses_email.py b/app/services/ses_email.py index 0e2c1cb..fe47f67 100644 --- a/app/services/ses_email.py +++ b/app/services/ses_email.py @@ -11,10 +11,8 @@ SMTP_PASS = os.getenv("SMTP_PASS") # 只开启一种 TLS:587 -> STARTTLS;465 -> SSL/TLS -TLS_KW = dict( - start_tls=(SMTP_PORT == 587), - use_tls=(SMTP_PORT == 465), -) +STARTTLS_ENABLED = SMTP_PORT == 587 +USE_TLS_ENABLED = SMTP_PORT == 465 async def send_plain_email(to: str, subject: str, body: str) -> None: @@ -31,7 +29,8 @@ async def send_plain_email(to: str, subject: str, body: str) -> None: username=SMTP_USER, password=SMTP_PASS, timeout=10, - **TLS_KW, + start_tls=STARTTLS_ENABLED, + use_tls=USE_TLS_ENABLED, ) @@ -68,5 +67,6 @@ async def send_email_with_ics( username=SMTP_USER, password=SMTP_PASS, timeout=10, - **TLS_KW, + start_tls=STARTTLS_ENABLED, + use_tls=USE_TLS_ENABLED, )