From c5c544e49109d673167a1986d98a2bdac20e2ebb Mon Sep 17 00:00:00 2001 From: Depeng Sun Date: Sat, 1 Nov 2025 03:28:04 +1030 Subject: [PATCH 1/7] 111 --- .github/workflows/ci-cd.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 55840b4..755d911 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -13,17 +13,17 @@ concurrency: jobs: ci: runs-on: ubuntu-latest - defaults: - run: - working-directory: apps/ai steps: - - name: Checkout + - name: Checkout (with submodules) uses: actions/checkout@v4 + with: + submodules: true + 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 @@ -35,9 +35,13 @@ jobs: ~/.cache/uv apps/ai/.venv key: uv-${{ runner.os }}-${{ hashFiles('apps/ai/pyproject.toml', 'apps/ai/uv.lock') }} + restore-keys: | + uv-${{ runner.os }}- - name: Sync deps (with dev extras) + working-directory: apps/ai run: uv sync --extra dev --frozen || uv sync --extra dev - name: Run all checks - run: make check-all + working-directory: apps/ai + run: PYTHONPATH=. make check-all From 378ddd5fb3d26b8cbaec84f94ae02c80f337b403 Mon Sep 17 00:00:00 2001 From: Depeng Sun Date: Sat, 1 Nov 2025 03:31:42 +1030 Subject: [PATCH 2/7] 1224 --- .github/workflows/ci-cd.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 755d911..b577107 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -14,10 +14,9 @@ jobs: ci: runs-on: ubuntu-latest steps: - - name: Checkout (with submodules) + - name: Checkout uses: actions/checkout@v4 with: - submodules: true fetch-depth: 0 - name: Setup Python @@ -33,15 +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) - working-directory: apps/ai run: uv sync --extra dev --frozen || uv sync --extra dev - name: Run all checks - working-directory: apps/ai run: PYTHONPATH=. make check-all From 3b7f38477b8cc190e70a83b0777380bfea473df1 Mon Sep 17 00:00:00 2001 From: Depeng Sun Date: Sat, 1 Nov 2025 03:42:57 +1030 Subject: [PATCH 3/7] 123 --- app/services/ses_email.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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, ) From 45b27af4496bc2f0f470063bbfe1e305fd3c8f93 Mon Sep 17 00:00:00 2001 From: Depeng Sun Date: Sat, 1 Nov 2025 03:44:55 +1030 Subject: [PATCH 4/7] 123 --- app/api/email.py | 5 +---- app/services/retrieve/customer_info_extractors.py | 7 +++++-- 2 files changed, 6 insertions(+), 6 deletions(-) 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/retrieve/customer_info_extractors.py b/app/services/retrieve/customer_info_extractors.py index 2061226..b37d94d 100644 --- a/app/services/retrieve/customer_info_extractors.py +++ b/app/services/retrieve/customer_info_extractors.py @@ -16,6 +16,7 @@ from typing import Dict, Any, Optional, List 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] = [ + {"role": "system", "content": prompt} + ] # Add message history if provided (last 4 messages) if message_history: From 604c7b7273844774d11faef43678f6e8992d67eb Mon Sep 17 00:00:00 2001 From: Depeng Sun Date: Sat, 1 Nov 2025 03:46:20 +1030 Subject: [PATCH 5/7] 345 --- app/services/retrieve/customer_info_extractors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/retrieve/customer_info_extractors.py b/app/services/retrieve/customer_info_extractors.py index b37d94d..42b23f9 100644 --- a/app/services/retrieve/customer_info_extractors.py +++ b/app/services/retrieve/customer_info_extractors.py @@ -67,7 +67,9 @@ async def _call_openai_api( 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") + system_content = messages[0].get("content") if isinstance(messages[0], dict) else "" + system_content_str = system_content if isinstance(system_content, str) else "" + print(f" • System prompt length: {len(system_content_str)} chars") try: response = await client.chat.completions.create( From 716d0445c7fa4445230d4ff89f7cd58d9402ad7c Mon Sep 17 00:00:00 2001 From: Depeng Sun Date: Sat, 1 Nov 2025 03:48:04 +1030 Subject: [PATCH 6/7] 1234 --- app/services/redis_service.py | 18 ++++++++++------- .../retrieve/customer_info_extractors.py | 20 +++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) 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 42b23f9..f4c8f6f 100644 --- a/app/services/retrieve/customer_info_extractors.py +++ b/app/services/retrieve/customer_info_extractors.py @@ -13,7 +13,7 @@ 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 @@ -52,7 +52,7 @@ async def _call_openai_api( # Build messages array messages: List[ChatCompletionMessageParam] = [ - {"role": "system", "content": prompt} + cast(ChatCompletionMessageParam, {"role": "system", "content": prompt}) ] # Add message history if provided (last 4 messages) @@ -61,15 +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}'") - system_content = messages[0].get("content") if isinstance(messages[0], dict) else "" - system_content_str = system_content if isinstance(system_content, str) else "" - print(f" • System prompt length: {len(system_content_str)} chars") + print(f" • System prompt length: {len(prompt)} chars")12 try: response = await client.chat.completions.create( @@ -219,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) @@ -244,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) @@ -269,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: @@ -396,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) @@ -468,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) From da5947fd2a6b8f48ca74ccc2d78e1a455b0b3986 Mon Sep 17 00:00:00 2001 From: Depeng Sun Date: Sat, 1 Nov 2025 03:50:47 +1030 Subject: [PATCH 7/7] 123 --- app/services/retrieve/customer_info_extractors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/retrieve/customer_info_extractors.py b/app/services/retrieve/customer_info_extractors.py index f4c8f6f..81894b9 100644 --- a/app/services/retrieve/customer_info_extractors.py +++ b/app/services/retrieve/customer_info_extractors.py @@ -67,7 +67,7 @@ async def _call_openai_api( print(" • Model: gpt-4o-mini") print(f" • Messages count: {len(messages)}") print(f" • User input: '{user_input}'") - print(f" • System prompt length: {len(prompt)} chars")12 + print(f" • System prompt length: {len(prompt)} chars") try: response = await client.chat.completions.create(