Skip to content
Merged

11 #3

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
5 changes: 1 addition & 4 deletions app/api/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
18 changes: 11 additions & 7 deletions app/services/redis_service.py
Original file line number Diff line number Diff line change
@@ -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()

Expand Down Expand Up @@ -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")
Expand Down
23 changes: 13 additions & 10 deletions app/services/retrieve/customer_info_extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -44,27 +45,29 @@ 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:
for msg in message_history[-4:]: # Take last 4 messages
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(
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions app/services/ses_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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,
)


Expand Down Expand Up @@ -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,
)