Skip to content
Merged
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
20 changes: 15 additions & 5 deletions app/api/call.py
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions app/api/chat.py
Original file line number Diff line number Diff line change
@@ -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"])

Expand Down
2 changes: 1 addition & 1 deletion app/api/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])

Expand Down
4 changes: 2 additions & 2 deletions app/api/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions app/api/health.py
Original file line number Diff line number Diff line change
@@ -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"])
Expand Down
2 changes: 1 addition & 1 deletion app/api/summary.py
Original file line number Diff line number Diff line change
@@ -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"])

Expand Down
4 changes: 2 additions & 2 deletions app/infrastructure/redis_client.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
15 changes: 5 additions & 10 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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("/")
Expand Down
37 changes: 27 additions & 10 deletions app/services/call_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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}'"
)
Expand Down Expand Up @@ -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 = (
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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"):
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/services/call_summary.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
2 changes: 1 addition & 1 deletion app/services/dialog_manager.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/services/llm_service.py
Original file line number Diff line number Diff line change
@@ -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()

Expand Down
2 changes: 1 addition & 1 deletion app/services/llm_speech_corrector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
4 changes: 2 additions & 2 deletions app/services/redis_service.py
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
4 changes: 2 additions & 2 deletions app/services/retrieve/customer_info_extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down