diff --git a/app/api/call.py b/app/api/call.py index 70b7f2c..dcd58c5 100644 --- a/app/api/call.py +++ b/app/api/call.py @@ -1,10 +1,10 @@ from fastapi import APIRouter, HTTPException from typing import Any, Dict from pydantic import BaseModel, Field, ValidationError -from models.call import Message, CallSkeleton -from services.redis_service import get_call_skeleton -from services.call_handler import CustomerServiceLangGraph -from custom_types import CustomerServiceState +from app.models.call import Message, CallSkeleton +from app.services.redis_service import get_call_skeleton +from app.services.call_handler import CustomerServiceLangGraph +from app.custom_types import CustomerServiceState from datetime import datetime, timezone router = APIRouter( @@ -93,12 +93,18 @@ async def ai_conversation(data: ConversationInput): "name": user_info.name if user_info else None, "phone": user_info.phone if user_info else None, "address": user_info.address if user_info else None, + "street_number": user_info.street_number if user_info else None, + "street_name": user_info.street_name if user_info else None, + "suburb": user_info.suburb if user_info else None, + "postcode": user_info.postcode if user_info else None, + "state": user_info.state if user_info else None, "service": current_service.name if current_service else None, "service_id": current_service.id if current_service else None, "service_price": current_service.price if current_service else None, "service_description": current_service.description if current_service else None, "available_services": available_services, "service_time": callskeleton.user.serviceBookedTime, + "service_time_mongodb": callskeleton.user.serviceBookedTime, "current_step": "collect_name", "name_attempts": 0, "phone_attempts": 0, @@ -112,12 +118,16 @@ async def ai_conversation(data: ConversationInput): "name_complete": bool(user_info.name if user_info else None), "phone_complete": bool(user_info.phone if user_info else None), "address_complete": bool(user_info.address if user_info else None), + "street_number_complete": bool(user_info.street_number if user_info else None), + "street_name_complete": bool(user_info.street_name if user_info else None), + "suburb_complete": bool(user_info.suburb if user_info else None), + "postcode_complete": bool(user_info.postcode if user_info else None), + "state_complete": bool(user_info.state if user_info else None), "service_complete": bool(callskeleton.user.service), "time_complete": bool(callskeleton.user.serviceBookedTime), "conversation_complete": callskeleton.servicebooked, "service_available": True, "time_available": True, - "message_history": message_history, # Add message history to state } # 3. Set current user input diff --git a/app/api/chat.py b/app/api/chat.py index fdeb383..fdd1597 100644 --- a/app/api/chat.py +++ b/app/api/chat.py @@ -1,8 +1,8 @@ from fastapi import APIRouter, HTTPException from datetime import datetime import time -from models.chat import ChatRequest, ChatResponse -from services.llm_service import llm_service +from app.models.chat import ChatRequest, ChatResponse +from app.services.llm_service import llm_service router = APIRouter(prefix="/ai", tags=["chat"]) diff --git a/app/api/dispatch.py b/app/api/dispatch.py index bed68d8..430e479 100644 --- a/app/api/dispatch.py +++ b/app/api/dispatch.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, Field from typing import Literal, Optional, List, Dict, Any from uuid import uuid4 -from client.mcp_client import call_tool +from app.client.mcp_client import call_tool router = APIRouter(prefix="/dispatch", tags=["dispatch"]) diff --git a/app/api/email.py b/app/api/email.py index c7ee385..1d3df51 100644 --- a/app/api/email.py +++ b/app/api/email.py @@ -6,8 +6,8 @@ from uuid import uuid4 import pendulum -from services.ses_email import send_plain_email, send_email_with_ics -from services.ics_lib import build_ics_request, build_ics_cancel +from app.services.ses_email import send_plain_email, send_email_with_ics +from app.services.ics_lib import build_ics_request, build_ics_cancel router = APIRouter( prefix="/email", diff --git a/app/api/health.py b/app/api/health.py index 44c0594..b5f87fe 100644 --- a/app/api/health.py +++ b/app/api/health.py @@ -1,10 +1,10 @@ from fastapi import APIRouter -from infrastructure.redis_client import get_redis +from app.infrastructure.redis_client import get_redis from fastapi import Query from fastapi.responses import PlainTextResponse from fastapi.exceptions import HTTPException -from client.mcp_client import call_tool, list_tools -from utils.mcp_parse import parse_tool_result, to_dict +from app.client.mcp_client import call_tool, list_tools +from app.utils.mcp_parse import parse_tool_result, to_dict router = APIRouter(prefix="/health", tags=["health"]) diff --git a/app/api/summary.py b/app/api/summary.py index 0effd4d..1303c20 100644 --- a/app/api/summary.py +++ b/app/api/summary.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List, Dict, Any -from services.call_summary import summary_service +from app.services.call_summary import summary_service router = APIRouter(prefix="/ai", tags=["ai-summary"]) diff --git a/app/infrastructure/redis_client.py b/app/infrastructure/redis_client.py index 3e2e22f..46a3994 100644 --- a/app/infrastructure/redis_client.py +++ b/app/infrastructure/redis_client.py @@ -1,11 +1,11 @@ # app/infrastructure/redis_client.py from functools import lru_cache from redis import Redis -from config import settings +from app.config import settings @lru_cache -def get_redis() -> Redis[str]: +def get_redis() -> Redis: if settings.redis_url: return Redis.from_url( settings.redis_url, diff --git a/app/main.py b/app/main.py index 51705de..8d58163 100644 --- a/app/main.py +++ b/app/main.py @@ -1,17 +1,11 @@ -import sys -from pathlib import Path -from config import get_settings -from api import health, chat, call, summary, email, dispatch +from app.config import get_settings +from app.api import health, chat, call, summary, email, dispatch +# from app.intent_classification.api import router as intent_router from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi_mcp.server import FastApiMCP -# Add the app directory to Python path for absolute imports -app_dir = Path(__file__).parent -sys.path.insert(0, str(app_dir)) - - -settings = get_settings() +settings = get_settings() app = FastAPI( title=settings.api_title, @@ -35,6 +29,7 @@ app.include_router(summary.router, prefix=settings.api_prefix) app.include_router(email.router, prefix=settings.api_prefix) app.include_router(dispatch.router, prefix=settings.api_prefix) +# app.include_router(intent_router, prefix=settings.api_prefix) @app.get("/") diff --git a/app/services/call_handler.py b/app/services/call_handler.py index 98a460b..6326c79 100644 --- a/app/services/call_handler.py +++ b/app/services/call_handler.py @@ -7,7 +7,7 @@ from typing import Optional from openai import OpenAI -from custom_types import CustomerServiceState +from app.custom_types import CustomerServiceState from .redis_service import ( update_user_info_field, update_address_components, @@ -23,7 +23,7 @@ extract_time_from_conversation, ) from .llm_speech_corrector import SimplifiedSpeechCorrector -from config import settings +from app.config import settings # Helper function to create default CustomerServiceState @@ -33,6 +33,11 @@ def create_default_customer_service_state() -> CustomerServiceState: "name": None, "phone": None, "address": None, + "street_number": None, + "street_name": None, + "suburb": None, + "postcode": None, + "state": None, "service": None, "service_id": None, "service_price": None, @@ -53,6 +58,11 @@ def create_default_customer_service_state() -> CustomerServiceState: "name_complete": False, "phone_complete": False, "address_complete": False, + "street_number_complete": False, + "street_name_complete": False, + "suburb_complete": False, + "postcode_complete": False, + "state_complete": False, "service_complete": False, "time_complete": False, "conversation_complete": False, @@ -86,7 +96,7 @@ def _replace_service_placeholders( return response_text # Get available services - available_services = state.get("available_services", []) + available_services = state.get("available_services", []) or [] print(f"🔍 [PLACEHOLDER_REPLACEMENT] Processing response: '{response_text}'") print( @@ -431,7 +441,7 @@ async def process_address_collection( state["max_attempts"] = settings.max_attempts # Apply speech correction for Australian address input (NSW/NSEW fix) - original_input = state.get("last_user_input", "") + original_input = state.get("last_user_input") or "" print( f"🔧 [SPEECH_DEBUG] Starting speech correction for address input: '{original_input}'" ) @@ -519,7 +529,7 @@ async def process_address_collection( state["current_step"] = "collect_service" # Create natural transition message thanking user and introducing services - available_services = state.get("available_services", []) + available_services = state.get("available_services", []) or [] services_list = "" for i, service in enumerate(available_services, 1): price_text = ( @@ -691,7 +701,7 @@ async def process_service_collection( cleaned_service = extracted_service.strip() # Match extracted service with available services to get full details - available_services = state.get("available_services", []) + available_services = state.get("available_services", []) or [] matched_service = None for service in available_services: @@ -1062,7 +1072,12 @@ async def start_conversation( "postcode": None, "state": None, "service": None, + "service_id": None, + "service_price": None, + "service_description": None, + "available_services": [], "service_time": None, + "service_time_mongodb": None, "current_step": "collect_name", "name_attempts": 0, "phone_attempts": 0, @@ -1150,8 +1165,9 @@ async def main() -> None: state["last_user_input"] = "" # Trigger initial greeting state = await cs_agent.process_customer_workflow(state, call_sid=None) - if state.get("last_llm_response"): - print(f"🤖 AI: {state['last_llm_response']['response']}") + last_response = state.get("last_llm_response") + if last_response: + print(f"🤖 AI: {last_response['response']}") # Main conversation loop while not state.get("conversation_complete"): @@ -1172,8 +1188,9 @@ async def main() -> None: state = await cs_agent.process_customer_workflow(state, call_sid=None) # Display AI response - if state.get("last_llm_response"): - ai_response = state["last_llm_response"]["response"] + last_response = state.get("last_llm_response") + if last_response: + ai_response = last_response["response"] print(f"🤖 AI: {ai_response}") # Check if completed diff --git a/app/services/call_summary.py b/app/services/call_summary.py index b993aa0..3e23143 100644 --- a/app/services/call_summary.py +++ b/app/services/call_summary.py @@ -1,5 +1,5 @@ from typing import Dict, Any, List -from services.llm_service import llm_service +from app.services.llm_service import llm_service def create_summary_prompt(conversation_text: str, service_info: dict) -> str: diff --git a/app/services/dialog_manager.py b/app/services/dialog_manager.py index 59aa7df..1e65ff1 100644 --- a/app/services/dialog_manager.py +++ b/app/services/dialog_manager.py @@ -1,5 +1,5 @@ import re -from models.call import Message, CallSkeleton, UserInfo, Service +from app.models.call import Message, CallSkeleton, UserInfo, Service from datetime import datetime, timezone from typing import Tuple, Optional from .llm_service import llm_service diff --git a/app/services/llm_service.py b/app/services/llm_service.py index 5ccc786..51951c7 100644 --- a/app/services/llm_service.py +++ b/app/services/llm_service.py @@ -1,6 +1,6 @@ from openai import AsyncOpenAI from typing import Optional -from config import get_settings +from app.config import get_settings settings = get_settings() diff --git a/app/services/llm_speech_corrector.py b/app/services/llm_speech_corrector.py index 12ccaaf..e64d53d 100644 --- a/app/services/llm_speech_corrector.py +++ b/app/services/llm_speech_corrector.py @@ -10,7 +10,7 @@ import re import logging from typing import Dict -from config import get_settings +from app.config import get_settings settings = get_settings() diff --git a/app/services/redis_service.py b/app/services/redis_service.py index c3d62b2..41d3cdf 100644 --- a/app/services/redis_service.py +++ b/app/services/redis_service.py @@ -1,8 +1,8 @@ import json -from models.call import CallSkeleton +from app.models.call import CallSkeleton from typing import Optional, Dict, Any, List, cast -from infrastructure.redis_client import get_redis +from app.infrastructure.redis_client import get_redis from openai.types.chat import ChatCompletionMessageParam r = get_redis() diff --git a/app/services/retrieve/customer_info_extractors.py b/app/services/retrieve/customer_info_extractors.py index 81894b9..1268c11 100644 --- a/app/services/retrieve/customer_info_extractors.py +++ b/app/services/retrieve/customer_info_extractors.py @@ -17,9 +17,9 @@ from datetime import datetime, timezone, timedelta from openai import AsyncOpenAI from openai.types.chat import ChatCompletionMessageParam -from custom_types import CustomerServiceState +from app.custom_types import CustomerServiceState -from utils.prompts.customer_info_prompts import ( +from app.utils.prompts.customer_info_prompts import ( get_name_extraction_prompt, get_phone_extraction_prompt, get_address_extraction_prompt,