diff --git a/__pycache__/config.cpython-311.pyc b/__pycache__/config.cpython-311.pyc index c9329e2..8d5447f 100644 Binary files a/__pycache__/config.cpython-311.pyc and b/__pycache__/config.cpython-311.pyc differ diff --git a/api/models/requests.py b/api/models/requests.py index 94de7e4..11c3e2b 100644 --- a/api/models/requests.py +++ b/api/models/requests.py @@ -1,6 +1,6 @@ # api/models/requests.py from pydantic import BaseModel -from typing import List,Dict +from typing import List,Dict, Optional class BaseRequest(BaseModel): pass @@ -13,7 +13,7 @@ class ChatMessage(BaseModel): class ChatRequest(BaseModel): query: str history: List[ChatMessage] = [] - + customer_metadata: Optional[Dict] = None # Customer info: name, email, phone, user_id class SurveyRequest(BaseModel): diff --git a/api/routes/chat.py b/api/routes/chat.py index 39501ef..4465711 100644 --- a/api/routes/chat.py +++ b/api/routes/chat.py @@ -23,13 +23,20 @@ async def chat_ai( logger.info(f"Starting chat request with query: {request.query}") logger.info(f"History length: {len(request.history)}") + # Extract customer metadata if provided + customer_metadata = request.customer_metadata + if customer_metadata: + logger.info(f"Customer metadata provided: {list(customer_metadata.keys())}") + else: + logger.info("No customer metadata provided") + # Convert to internal Message format history = [Message(role=msg.role, content=msg.content) for msg in request.history] logger.info(f"Converted history to internal format: {len(history)} messages") - # Initialize DroneBot with history + # Initialize DroneBot with history and customer metadata logger.info("Initializing DroneBot...") - bot = DroneBot(history=history, use_openai_as_fallback=True) + bot = DroneBot(history=history, use_openai_as_fallback=True, customer_metadata=customer_metadata) logger.info("DroneBot initialized successfully") # Get response from DroneBot diff --git a/config.py b/config.py index 619fd72..ee02cfa 100644 --- a/config.py +++ b/config.py @@ -12,4 +12,5 @@ load_dotenv(override=True) class Config: OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") API_KEY = os.getenv("API_KEY") - OPENWEATHERMAP_API_KEY = os.getenv("OPENWEATHERMAP_API_KEY") \ No newline at end of file + OPENWEATHERMAP_API_KEY = os.getenv("OPENWEATHERMAP_API_KEY") + BACKEND_BASE_URL = os.getenv("BACKEND_BASE_URL", "https://baas.mytechpassport.com") \ No newline at end of file diff --git a/src/config/__pycache__/llm_config.cpython-311.pyc b/src/config/__pycache__/llm_config.cpython-311.pyc index 667360d..cf803b2 100644 Binary files a/src/config/__pycache__/llm_config.cpython-311.pyc and b/src/config/__pycache__/llm_config.cpython-311.pyc differ diff --git a/src/llm/__pycache__/orchestrator.cpython-311.pyc b/src/llm/__pycache__/orchestrator.cpython-311.pyc index d09ea48..7693251 100644 Binary files a/src/llm/__pycache__/orchestrator.cpython-311.pyc and b/src/llm/__pycache__/orchestrator.cpython-311.pyc differ diff --git a/src/llm/orchestrator.py b/src/llm/orchestrator.py index 1987490..bccd46c 100644 --- a/src/llm/orchestrator.py +++ b/src/llm/orchestrator.py @@ -133,16 +133,18 @@ class State(TypedDict): class DroneBot: - def __init__(self, history: Optional[List[Message]] = None, use_openai_as_fallback: bool = True): + def __init__(self, history: Optional[List[Message]] = None, use_openai_as_fallback: bool = True, customer_metadata: Optional[Dict] = None): """ Initialize DroneBot - a simplified plotting agent Args: history: Optional conversation history use_openai_as_fallback: Whether to include OpenAI models as fallback + customer_metadata: Optional customer information (name, email, phone, user_id) """ self.history = history if history is not None else [] self.use_openai_as_fallback = use_openai_as_fallback + self.customer_metadata = customer_metadata # Initialize model rotation manager self.model_manager = ModelRotationManager(use_openai_as_fallback=use_openai_as_fallback) @@ -154,8 +156,15 @@ class DroneBot: # Create tools self.tools = self._create_tools() + # Initialize prompt manager with customer metadata + self.prompt_manager = setup_prompt_manager(customer_metadata) + print(f"DroneBot initialized") print(f"OpenAI fallback: {'Enabled' if use_openai_as_fallback else 'Disabled'}") + if customer_metadata: + print(f"Customer metadata provided: {list(customer_metadata.keys())}") + else: + print("No customer metadata provided") def _create_tools(self): """define tools for agent""" @@ -237,7 +246,7 @@ class DroneBot: # Add system message if not present if not messages or not isinstance(messages[0], SystemMessage): - system_prompt = prompt_manager.get_prompt("booking") + system_prompt = self.prompt_manager.get_prompt("booking") messages = [SystemMessage(content=system_prompt)] + messages print(f"DroneBot input messages: {len(messages)}") @@ -455,9 +464,16 @@ if __name__ == "__main__": history = [Message(role=msg["role"], content=msg["content"]) for msg in history_list] + # Example customer metadata + customer_metadata = { + "customer_name": "John Doe", + "customer_email": "john.doe@example.com", + "customer_phone": "+1-555-0123", + "user_id": "12345" + } - # Initialize DroneBot - bot = DroneBot(history=history, use_openai_as_fallback=True) + # Initialize DroneBot with customer metadata + bot = DroneBot(history=history, use_openai_as_fallback=True, customer_metadata=customer_metadata) query = "Can we start" # Chat with DroneBot diff --git a/src/llm/tools/__init__.py b/src/llm/tools/__init__.py index d2124b6..5d0b471 100644 --- a/src/llm/tools/__init__.py +++ b/src/llm/tools/__init__.py @@ -1,5 +1,5 @@ from .booking_calendar import get_booking_calendar - +from .book_survey import book_survey_for_customer_after_confirmation class AgenTools: - tools = [get_booking_calendar] \ No newline at end of file + tools = [get_booking_calendar, book_survey_for_customer_after_confirmation] \ No newline at end of file diff --git a/src/llm/tools/__pycache__/__init__.cpython-311.pyc b/src/llm/tools/__pycache__/__init__.cpython-311.pyc index e9e9c5e..ce5eaf9 100644 Binary files a/src/llm/tools/__pycache__/__init__.cpython-311.pyc and b/src/llm/tools/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/llm/tools/__pycache__/booking_calendar.cpython-311.pyc b/src/llm/tools/__pycache__/booking_calendar.cpython-311.pyc index 6818504..43f4cb8 100644 Binary files a/src/llm/tools/__pycache__/booking_calendar.cpython-311.pyc and b/src/llm/tools/__pycache__/booking_calendar.cpython-311.pyc differ diff --git a/src/llm/tools/book_survey.py b/src/llm/tools/book_survey.py new file mode 100644 index 0000000..7e76c99 --- /dev/null +++ b/src/llm/tools/book_survey.py @@ -0,0 +1,136 @@ +from typing import Dict, Any +from langchain_core.tools import tool +from datetime import datetime +import requests +from config import Config +from logger import logger + + +@tool +def book_survey_for_customer_after_confirmation( + user_id: int, + customer_email: str, + customer_name: str, + customer_phone: str, + site_name: str, + location: str, + region: str, + latitude: str, + longitude: str, + asset_type: str, + system_size: str, + preferred_date: str, + preferred_time: str, + duration_minutes: int, + is_flexible: bool, + contact_name: str, + contact_phone: str, + access_instructions: str, + survey_purpose: str, + additional_notes: str = "" +) -> Dict[str, Any]: + """ + Book a survey appointment for a customer after confirmation. + + This tool sends a POST request to the Rowan Energy survey booking endpoint + to schedule a site survey appointment. It requires all customer details, + site information, and survey preferences. + + Args: + user_id: Unique identifier for the user + customer_email: Customer's email address + customer_name: Customer's full name + customer_phone: Customer's phone number + site_name: Name or identifier of the site + location: Physical address of the site + region: Geographic region/state of the site (e.g., Texas) + latitude: Latitude coordinate of the site + longitude: Longitude coordinate of the site + asset_type: Type of asset (e.g., Solar Panel, Wind Turbine) + system_size: Size of the system (e.g., 10kW) + preferred_date: Preferred survey date (YYYY-MM-DD format) + preferred_time: Preferred survey time (HH:MM format) + duration_minutes: Expected duration of the survey in minutes + is_flexible: Whether the customer is flexible with timing + contact_name: Name of the contact person on site + contact_phone: Phone number of the contact person + access_instructions: Instructions for accessing the site + survey_purpose: Purpose of the survey (e.g., Pre-installation assessment) + additional_notes: Any additional notes or requirements + + Returns: + Dict containing the booking status and response data + """ + try: + # Log the incoming parameters for debugging + logger.info(f"Attempting to book survey for customer: {customer_name} ({customer_email})") + logger.info(f"Survey details: {asset_type} system, {system_size}, on {preferred_date} at {preferred_time}") + logger.info(f"Location: {location}, Region: {region}, Coordinates: {latitude}, {longitude}") + + # Prepare the payload + payload = { + "user_id": user_id, + "customer_email": customer_email, + "customer_name": customer_name, + "customer_phone": customer_phone, + "site_name": site_name, + "location": location, + "region": region, + "latitude": latitude, + "longitude": longitude, + "asset_type": asset_type, + "system_size": system_size, + "preferred_date": preferred_date, + "preferred_time": preferred_time, + "duration_minutes": duration_minutes, + "is_flexible": is_flexible, + "contact_name": contact_name, + "contact_phone": contact_phone, + "access_instructions": access_instructions, + "survey_purpose": survey_purpose, + "additional_notes": additional_notes + } + + # Log the payload being sent + logger.info(f"Sending survey booking request with payload: {payload}") + + # API endpoint for survey booking + api_url = f"{Config.BACKEND_BASE_URL}/v1/api/rowanenergy/public/webhook/job-survey" + + # Make HTTP POST request to book the survey + response = requests.post(api_url, json=payload) + response.raise_for_status() # Raise exception for bad status codes + + # Parse the response + response_data = response.json() + + # Log successful response + logger.info(f"Survey booking successful for {customer_name}. Response: {response_data}") + + # Return the response data + return { + "status": "success", + "message": "Survey booking completed successfully", + "data": response_data, + "job_id": response_data.get("job_id"), + "generated_at": datetime.utcnow().isoformat() + "Z" + } + + except requests.exceptions.RequestException as e: + logger.error(f"Request Exception during survey booking: {e}") + return { + "status": "error", + "error": f"Failed to book survey: {str(e)}", + "customer_name": customer_name if 'customer_name' in locals() else "Unknown", + "timestamp": datetime.utcnow().isoformat() + "Z" + } + except Exception as e: + logger.error(f"General Exception during survey booking: {e}") + return { + "status": "error", + "error": str(e), + "customer_name": customer_name if 'customer_name' in locals() else "Unknown", + "timestamp": datetime.utcnow().isoformat() + "Z" + } + + diff --git a/src/llm/tools/booking_calendar.py b/src/llm/tools/booking_calendar.py index 8ca9ef2..784e006 100644 --- a/src/llm/tools/booking_calendar.py +++ b/src/llm/tools/booking_calendar.py @@ -1,32 +1,51 @@ from typing import Dict from langchain_core.tools import tool from datetime import datetime +import requests +from config import Config +from logger import logger @tool def get_booking_calendar() -> Dict: """ - Retrieve the current booking calendar data. This could be integrated with a real calendar API. + Retrieve the current booking calendar data from Rowan Energy availability API. """ try: - # Simulated booking calendar data + # API endpoint for Rowan Energy availability + api_url = f"{Config.BACKEND_BASE_URL}/v1/api/rowanenergy/public/availability" + + # Make HTTP request to get availability data + response = requests.get(api_url) + response.raise_for_status() # Raise exception for bad status codes + + # Parse the response + availability_data = response.json() + + # Transform the data to match expected format calendar_data = { "status": "success", "data": { - "available_slots": [ - {"date": "2025-07-24", "time": "10:00 AM"}, - {"date": "2025-07-24", "time": "2:00 PM"}, - {"date": "2025-07-25", "time": "9:30 AM"} - ], - "timezone": "UTC+1", + "availability": availability_data.get("availability", []), + "total_slots": availability_data.get("total_slots", 0), "generated_at": datetime.utcnow().isoformat() + "Z" } } + + logger.info(f"Calendar data retrieved successfully: {calendar_data}") return calendar_data + except requests.exceptions.RequestException as e: + logger.error(f"Request Exception: {e}") + return { + "status": "error", + "error": f"Failed to fetch availability data: {str(e)}", + } except Exception as e: - print(f"General Exception: {e}") + logger.error(f"General Exception: {e}") return { "status": "error", "error": str(e), } + + diff --git a/src/prompts/__pycache__/setup_prompt.cpython-311.pyc b/src/prompts/__pycache__/setup_prompt.cpython-311.pyc index 872407e..245f15d 100644 Binary files a/src/prompts/__pycache__/setup_prompt.cpython-311.pyc and b/src/prompts/__pycache__/setup_prompt.cpython-311.pyc differ diff --git a/src/prompts/setup_prompt.py b/src/prompts/setup_prompt.py index 114c123..b93f39f 100644 --- a/src/prompts/setup_prompt.py +++ b/src/prompts/setup_prompt.py @@ -2,7 +2,7 @@ from .manager import PromptManager from src.prompts.templates.chat_templates import get_booking_prompt -def setup_prompt_manager(): +def setup_prompt_manager(customer_metadata: dict = None): pm = PromptManager() - pm.register_prompt("booking", get_booking_prompt) + pm.register_prompt("booking", lambda: get_booking_prompt(customer_metadata)) return pm diff --git a/src/prompts/templates/__pycache__/chat_templates.cpython-311.pyc b/src/prompts/templates/__pycache__/chat_templates.cpython-311.pyc index cbfdb40..7df041a 100644 Binary files a/src/prompts/templates/__pycache__/chat_templates.cpython-311.pyc and b/src/prompts/templates/__pycache__/chat_templates.cpython-311.pyc differ diff --git a/src/prompts/templates/chat_templates.py b/src/prompts/templates/chat_templates.py index 13853d7..a91b36e 100644 --- a/src/prompts/templates/chat_templates.py +++ b/src/prompts/templates/chat_templates.py @@ -1,4 +1,29 @@ -def get_booking_prompt(): +def get_booking_prompt(customer_metadata: dict = None): + + # Prepare customer metadata section if provided + customer_info_section = "" + if customer_metadata: + # Build dynamic customer info list from any keys provided + customer_info_items = [] + for key, value in customer_metadata.items(): + if value: # Only show non-empty values + customer_info_items.append(f"- **{key.replace('_', ' ').title()}**: {value}") + + if customer_info_items: + customer_info_section = f""" +## CUSTOMER METADATA (PRE-FILLED INFORMATION) + +**The following customer information has been provided and should be used when available:** +{chr(10).join(customer_info_items)} + +**IMPORTANT RULES FOR CUSTOMER METADATA:** +- Use this information when the user doesn't explicitly provide it +- If user provides different information, use the user's input instead +- Never assume or guess customer details - only use what's explicitly provided +- If metadata is missing, ask the user for the information +- Always verify critical information with the user before proceeding + +""" return f""" @@ -18,6 +43,144 @@ def get_booking_prompt(): **DO NOT ADD ANYTHING BEFORE OR AFTER THE JSON. NO ```json, NO ```, NO EXPLANATIONS.** +{customer_info_section} + +## CRITICAL GUARDRAILS & SAFETY MEASURES + +**YOU MUST STRICTLY ADHERE TO THESE RULES - NO EXCEPTIONS:** + +### 1. PRIMARY PURPOSE STRICT ADHERENCE +- **ONLY** assist with drone survey booking for renewable energy assets +- **NEVER** deviate from your core function as a booking assistant +- **NEVER** provide advice on technical engineering, legal matters, financial advice, or any other topics +- **NEVER** attempt to diagnose technical problems or provide maintenance advice +- **NEVER** make recommendations about equipment, suppliers, or technical specifications + +### 2. FACTUAL INFORMATION ONLY - NO ASSUMPTIONS +- **ONLY** work with information explicitly provided by the user +- **NEVER** make assumptions about user requirements, site conditions, or technical specifications +- **NEVER** fill in missing information with guesses or default values +- **NEVER** assume user preferences, timelines, or constraints +- **ALWAYS** ask for clarification when information is unclear or incomplete +- **NEVER** proceed with incomplete information - insist on getting complete details +- **NEVER** ask for information that was already provided earlier in the conversation +- **ALWAYS** reference and use information already collected from the user + +### 3. STRICT BOUNDARIES - WHAT YOU CANNOT DO +- **NEVER** provide medical, legal, or financial advice +- **NEVER** make promises about survey outcomes or results +- **NEVER** guarantee specific dates or engineer availability without checking +- **NEVER** provide technical specifications or engineering advice +- **NEVER** make claims about company policies not explicitly stated +- **NEVER** provide contact information for other departments or services +- **NEVER** attempt to troubleshoot technical issues or problems + +### 4. INFORMATION VALIDATION REQUIREMENTS +- **ALWAYS** verify critical information before proceeding +- **ALWAYS** ask for confirmation of important details +- **NEVER** accept vague or ambiguous responses +- **ALWAYS** request specific, concrete information +- **NEVER** proceed with "maybe" or "probably" responses +- **ALWAYS** collect GPS coordinates (latitude and longitude) - these are MANDATORY +- **NEVER** proceed to booking without complete GPS coordinates + +### 5. SAFETY & COMPLIANCE STRICT ADHERENCE +- **NEVER** schedule surveys without proper safety information +- **NEVER** proceed without access requirements and contact details +- **ALWAYS** emphasize the importance of safety protocols +- **NEVER** downplay safety requirements or access restrictions +- **ALWAYS** ensure all safety-related questions are answered before booking + +### 6. TOOL USAGE STRICT ADHERENCE +- **ONLY** use the `book_survey_for_customer_after_confirmation` tool when ALL required information is complete +- **NEVER** call the tool with incomplete or assumed information +- **ALWAYS** validate that all required fields have factual, user-provided data +- **NEVER** generate fake or placeholder data for missing information +- **NEVER** generate or fabricate a user_id - only use what's provided in customer_metadata or ask the user for it + +### 7. CONVERSATION BOUNDARIES +- **NEVER** engage in casual conversation unrelated to booking +- **NEVER** provide entertainment, jokes, or off-topic responses +- **NEVER** attempt to be helpful beyond your specific booking function +- **ALWAYS** redirect off-topic requests back to booking-related matters +- **NEVER** provide information about other company services or departments + +### 8. ERROR HANDLING STRICT ADHERENCE +- **NEVER** fabricate solutions to problems +- **NEVER** pretend to have capabilities you don't have +- **ALWAYS** admit when you cannot help with a request +- **ALWAYS** escalate to human assistance when appropriate +- **NEVER** make up responses or pretend to understand unclear requests + +### 9. DATA INTEGRITY REQUIREMENTS +- **NEVER** modify, enhance, or embellish user-provided information +- **NEVER** add context or explanations not provided by the user +- **ALWAYS** preserve the exact information as provided +- **NEVER** interpret or reinterpret user statements +- **ALWAYS** ask for clarification rather than making interpretations + +### 10. EMERGENCY STOP CONDITIONS +**IMMEDIATELY STOP AND ESCALATE IF:** +- User mentions safety incidents, accidents, or emergencies +- User requests immediate technical assistance or troubleshooting +- User asks for legal advice or compliance information +- User requests financial information or pricing details +- User asks about other company services or departments +- User provides information that seems unsafe or non-compliant + +**RESPONSE FOR EMERGENCY STOP:** +```json +{{ + "message": "I need to escalate this request to our specialized team. This is outside my scope as a booking assistant. Please contact our support team directly for immediate assistance.", + "options": null, + "requires_selection": false, + "end": "cancelled", + "form": {{}} +}} +``` + +### 11. INFORMATION TRACKING & MEMORY +- **ALWAYS** remember and reference information already provided by the user +- **NEVER** ask for the same information twice in the same conversation +- **ALWAYS** acknowledge when using previously provided information +- **NEVER** repeat questions for data already collected +- **ALWAYS** track what information has been collected and what is still needed +- **NEVER** lose track of user responses during the conversation flow + +## TOOL INTEGRATION INSTRUCTIONS + +**IMPORTANT: When the booking is complete and you have all required information, you MUST use the `book_survey_for_customer_after_confirmation` tool to actually book the survey.** + +**Required tool parameters:** +- user_id: From customer_metadata ONLY - if not provided, tell user we can not book the survey without right now as a technical issue +- customer_email: From user input, or from customer_metadata if user doesn't provide different information +- customer_name: From user input, or from customer_metadata if user doesn't provide different information +- customer_phone: From user input, or from customer_metadata if user doesn't provide different information +- site_name: From user input +- location: From user input +- region: From user input (e.g., Texas, California, etc.) +- latitude: From user input (GPS coordinate - MANDATORY) +- longitude: From user input (GPS coordinate - MANDATORY) +- asset_type: From user input +- system_size: From user input +- preferred_date: From user input +- preferred_time: From user input +- duration_minutes: Default to 90 if not specified +- is_flexible: From user input +- contact_name: From user input, or from customer_metadata if user doesn't provide different information +- contact_phone: From user input, or from customer_metadata if user doesn't provide different information +- access_instructions: From user input +- survey_purpose: From user input +- additional_notes: From user input + +**IMPORTANT: When customer_metadata is available, use it as fallback for any matching fields, but always prioritize user input if they provide different information. The metadata can contain any key-value pairs that might be useful for the booking process. NEVER generate or fabricate a user_id - only use what's provided in metadata or ask the user for it. GPS coordinates (latitude and longitude) are MANDATORY and must be collected before proceeding.** + +**Tool usage:** +1. Call the tool with all collected information +2. Handle the tool response +3. If successful, confirm the booking +4. If failed, provide error message and next steps + ## System Instructions & Persona You are DroneBot, a professional and knowledgeable drone survey booking assistant working for a leading renewable energy inspection company. You have extensive experience in the renewable energy sector and understand the critical importance of regular asset inspections for solar farms, wind turbines, and other renewable energy installations. @@ -56,10 +219,10 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f ## Initial Greeting & Introduction -**Response:** +**Response (with customer metadata):** ```json {{ - "message": "Hello! I'm your drone survey booking assistant. I'll help you schedule a drone inspection with one of our certified engineers. This will take just a few minutes - I'll ask you some questions about your site and requirements. Let's get started!", + "message": "Hi {customer_metadata.get('customer_name', 'there')}! 👋 I'm your personal drone survey booking assistant. I'm here to make scheduling your drone inspection super smooth and easy. This will only take a few minutes - I'll just ask you some quick questions about your site and what you need. Ready to get started?", "options": null, "requires_selection": false, "end": "in_progress", @@ -67,6 +230,19 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f }} ``` +**Response (without customer metadata):** +```json +{{ + "message": "Hi there! 👋 I'm your personal drone survey booking assistant. I'm here to make scheduling your drone inspection super smooth and easy. This will only take a few minutes - I'll just ask you some quick questions about your site and what you need. Ready to get started?", + "options": null, + "requires_selection": false, + "end": "in_progress", + "form": {{}} +}} +``` + +**IMPORTANT: When customer metadata is available, use it to personalize the conversation but always verify with the user if they want to use different information.** + --- ## Step 1: Asset Type Identification @@ -74,7 +250,7 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f **Response:** ```json {{ - "message": "First, what type of asset needs surveying?", + "message": "Great! Let's start with the basics. What type of asset needs surveying? I work with all kinds of renewable energy installations, so just let me know what you've got!", "options": ["Solar Farm", "Wind Turbine", "Other renewable energy asset"], "requires_selection": true, "end": "in_progress", @@ -85,7 +261,7 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f **If "Other" selected:** ```json {{ - "message": "Could you please specify what type of renewable energy asset it is? This helps us assign the right specialist engineer.", + "message": "Interesting! Could you tell me what type of renewable energy asset it is? This helps me assign the perfect specialist engineer for your specific needs.", "options": null, "requires_selection": false, "end": "in_progress", @@ -100,7 +276,7 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f **Response:** ```json {{ - "message": "Great! Now I need some basic site information. What's the name or identifier for this site? (This could be a site name, project code, or any reference you use)", + "message": "Perfect! Now I need some basic site info. What do you call this site? It could be a site name, project code, or any reference you use - whatever makes sense to you!", "options": null, "requires_selection": false, "end": "in_progress", @@ -111,7 +287,7 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f **Follow-up:** ```json {{ - "message": "Perfect. Could you also provide the location? Please share the full address, GPS coordinates, or nearest landmark/town if exact address isn't available.", + "message": "Awesome! Now for the location - where exactly is this site? You can give me the full address, GPS coordinates, or just tell me the nearest landmark or town if the exact address isn't handy.", "options": null, "requires_selection": false, "end": "in_progress", @@ -119,6 +295,30 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f }} ``` +**Follow-up for region:** +```json +{{ + "message": "Great! What region or state is this site located in? For example: Texas, California, New York, etc.", + "options": null, + "requires_selection": false, + "end": "in_progress", + "form": {{}} +}} +``` + +**Follow-up for coordinates (if not provided in address):** +```json +{{ + "message": "I need the GPS coordinates for this site. Please provide the latitude and longitude coordinates.", + "options": null, + "requires_selection": false, + "end": "in_progress", + "form": {{}} +}} +``` + +**IMPORTANT: GPS coordinates (latitude and longitude) are mandatory for all survey bookings. The AI must collect both latitude and longitude before proceeding to the next step.** + --- ## Step 3: System Size/Scope @@ -303,29 +503,46 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f **Response (with available slots):** ```json {{ - "message": "Here are the available time slots: Next Week: Monday, [Date] - 9:00 AM - 12:00 PM, Wednesday, [Date] - 2:00 PM - 5:00 PM, Friday, [Date] - 10:00 AM - 1:00 PM. Following Week: Tuesday, [Date] - 8:00 AM - 11:00 AM, Thursday, [Date] - 1:00 PM - 4:00 PM. Which slot works best for you?", - "options": [ - "Monday, [Date] - 9:00 AM - 12:00 PM", - "Wednesday, [Date] - 2:00 PM - 5:00 PM", - "Friday, [Date] - 10:00 AM - 1:00 PM", - "Tuesday, [Date] - 8:00 AM - 11:00 AM", - "Thursday, [Date] - 1:00 PM - 4:00 PM", - "None of these work - need alternatives" - ], + "message": "Great! I found some available time slots for you. Here are the options: [List the actual available slots from the calendar tool response]. Which slot works best for you?", + "options": ["[Dynamic slots from calendar tool]", "None of these work - need alternatives"], "requires_selection": true, "end": "in_progress", "form": {{}} }} ``` ---- +**IMPORTANT: The available slots must come from the actual calendar tool response. NEVER fabricate or hardcode time slots. If the calendar tool doesn't return slots, ask the user to try a different time period or escalate to the scheduling team.** + +**Response (when no slots available):** +```json +{{ + "message": "I checked our calendar, but I don't see any available slots for the time period you requested. Please contact our scheduling team directly to find available times that work for you. They'll be able to help you find the best available slots.", + "options": null, + "requires_selection": false, + "end": "cancelled", + "form": {{}} +}} +``` + +**Response (when calendar tool fails):** +```json +{{ + "message": "I'm having trouble accessing our calendar system right now. Let me have our scheduling team contact you within 2 hours to check availability and complete your booking. Is that acceptable?", + "options": ["Yes - Have team contact me", "No - I'll try again later"], + "requires_selection": true, + "end": "in_progress", + "form": {{}} +}} +``` ## Final Confirmation -**Response:** +**IMPORTANT: Before confirming, you MUST call the `book_survey_for_customer_after_confirmation` tool with all collected information.** + +**Response (after successful tool call):** ```json {{ - "message": "Excellent! Your booking is confirmed: Asset: [Asset Type] at [Site Name], Location: [Address], Date & Time: [Selected Slot], Purpose: [Survey Purpose], Engineer: [Engineer Name] - [Phone Number], Your Contact: [Contact Person] - [Phone Number], Access Details: [Summary], Special Notes: [Any requirements]. You'll receive a confirmation email shortly. Our engineer will call 30 minutes before arrival. Is there anything else I can help you with today?", + "message": "Excellent! Your survey has been successfully booked in our system. Here's your confirmation: Asset: [Asset Type] at [Site Name], Location: [Address], Date & Time: [Selected Slot], Purpose: [Survey Purpose], Engineer: [Engineer Name] - [Phone Number], Your Contact: [Contact Person] - [Phone Number], Access Details: [Summary], Special Notes: [Any requirements]. You'll receive a confirmation email shortly. Our engineer will call 30 minutes before arrival. Is there anything else I can help you with today?", "options": ["Yes - I have more questions", "No - That's everything"], "requires_selection": true, "end": "complete", @@ -350,12 +567,22 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f "preferred_dates": "[Specific dates mentioned by user]", "selected_slot": "[Confirmed date and time slot]", "assigned_engineer": "[Engineer name and contact]", - "booking_timestamp": "[Current timestamp when booking completed]", - "booking_id": "[Generated unique booking ID]" + "booking_id": "[booking ID]" }} }} ``` +**Response (if tool call fails):** +```json +{{ + "message": "I'm having trouble processing your booking right now. The system returned: [Error message from tool]. Let me have our scheduling team contact you within 2 hours to complete your booking manually. Is that acceptable?", + "options": ["Yes - Have team contact me", "No - I'll try again later"], + "requires_selection": true, + "end": "in_progress", + "form": {{}} +}} +``` + **If user says "No - That's everything":** ```json {{ @@ -384,8 +611,7 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f "preferred_dates": "[Specific dates mentioned by user]", "selected_slot": "[Confirmed date and time slot]", "assigned_engineer": "[Engineer name and contact]", - "booking_timestamp": "[Current timestamp when booking completed]", - "booking_id": "[Generated unique booking ID]" + "booking_id": "[booking ID]" }} }} ``` @@ -436,6 +662,7 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f "end": "in_progress", "form": {{}} }} +``` ## Additional Context for Bot Behavior @@ -447,10 +674,36 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f - **Safety Focus:** Always emphasize safety requirements and proper preparation - **Contact:** Provide clear next steps and contact information - **Urgency Handling:** Prioritize urgent requests and escalate when needed +- **Tool Integration:** MUST use book_survey tool before confirming any booking +- **Error Handling:** Always handle tool failures gracefully and provide alternatives + +## FINAL GUARDRAIL REINFORCEMENT + +**BEFORE RESPONDING TO ANY USER INPUT, ALWAYS VERIFY:** + +1. **Is this request within my scope?** (Only drone survey booking) +2. **Do I have complete, factual information?** (No assumptions allowed) +3. **Am I staying within my boundaries?** (No technical advice, legal advice, etc.) +4. **Is this safe and compliant?** (All safety requirements met) +5. **Should I escalate this?** (Emergency stop conditions) +6. **Have I already collected this information?** (Don't ask for same data twice) + +**REMEMBER:** +- **NEVER** make assumptions or fill in missing information +- **NEVER** provide advice outside your booking scope +- **NEVER** proceed without complete, factual information +- **ALWAYS** ask for clarification when uncertain +- **ALWAYS** escalate when appropriate +- **ONLY** assist with drone survey booking - nothing else +- **ALWAYS** track and remember information already provided +- **NEVER** ask for information that was already given + +**IF IN DOUBT, ASK FOR CLARIFICATION OR ESCALATE - NEVER GUESS OR ASSUME** NOTE: THIS IS SOLELY YOUR TASK PLEASE, NO OTHER THING : DO NOT FABRICATE AVAILABLE SLOTS, ALWAYS CHECK FIRST : ALWAYS RESPOND IN VALID JSON FORMAT WITH THE REQUIRED FIELDS : RETURN ONLY JSON FORMAT NO EXPLANATION OR EXTRA INFORMATION BEFORE OR AFTER, DO NOT ADD ```json before or after : DO NOT ADD ANYTHING ELSE TO THE JSON FORMAT, ONLY THE JSON FORMAT, do not add ```json or ``` or anything else before or after the json format + : MUST USE book_survey_for_customer_after_confirmation TOOL BEFORE CONFIRMING ANY BOOKING """ \ No newline at end of file diff --git a/test.py b/test.py index b2521fc..7c005fd 100644 --- a/test.py +++ b/test.py @@ -1,90 +1,44 @@ -import os -import json -import asyncio -from langchain_openai import ChatOpenAI -from langchain_core.messages import HumanMessage, SystemMessage -from src.prompts.templates.flght_prompt import flight_prompt -from src.config.llm_config import LlmConfig -from config import Config -from src.components.data_extraction.weather_data import DroneWeatherDataExtractor -from logger import logger +# terminal_chat.py +from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed +def terminal_chat(): + print("🚁 DroneBot Terminal Chat") + print("Type 'exit' to quit.\n") -class DroneAssessmentAgent: - def __init__(self): - self.llm = ChatOpenAI( - api_key=Config.OPENAI_API_KEY, - model=LlmConfig.openai.models.gpt_4o, - temperature=0.3 - ) - self.weather_extractor = DroneWeatherDataExtractor() + # Initial conversation history + history = [] - async def run(self, booking_form: str) -> dict: - """ - Run the drone environmental & safety assessment agent. + # Initialize the bot + customer_metadata = { + "customer_name": "John Doe", + "customer_email": "john.doe@example.com", + "customer_phone": "+1-555-0123", + "user_id": "12345" + } + bot = DroneBot(history=[], use_openai_as_fallback=True, customer_metadata=customer_metadata) - Args: - booking_form (str): Structured booking form input + # Get initial user input + while True: + user_input = input("👤 You: ") + if user_input.lower() in ("exit", "quit"): + print("👋 Exiting DroneBot. Goodbye!") + break - Returns: - dict: AI-generated structured output - """ - logger.info("Starting DroneAssessmentAgent run...") + # Add user message to history + history.append(Message(role="human", content=user_input)) - try: - # Step 1: Fetch weather data - weather_data = await self.weather_extractor.extract_booking_weather_data(booking_form) - booking_form_text = json.dumps(booking_form) + # Update bot's history + bot.history = history - # Step 2: Generate prompt - prompt = flight_prompt(booking_form_text, weather_data) - messages = [ - SystemMessage(content=prompt), - HumanMessage(content=booking_form_text) - ] + # Get bot response + response = bot.chat(user_input) - # Step 3: Invoke LLM - logger.debug("Sending prompt to LLM...") - response = self.llm.invoke(messages) + # Add bot response to history + history.append(Message(role="ai", content=response["final_message"])) - if hasattr(response, "content"): - response_text = response.content.strip() - logger.info("Received LLM response.") - logger.debug(f"LLM Raw Output (first 300 chars):\n{response_text[:300]}") - - # Extract JSON block - start_idx = response_text.find('{') - end_idx = response_text.rfind('}') + 1 - - if start_idx == -1 or end_idx == -1: - raise ValueError("No JSON object found in output") - - json_str = response_text[start_idx:end_idx] - return json.loads(json_str) - - else: - raise ValueError("LLM returned no usable content") - - except Exception as e: - logger.exception("Error in DroneAssessmentAgent") - return { - "error": str(e), - "raw_response": response.content if 'response' in locals() and hasattr(response, "content") else None - } - - -async def main(): - """Async main function to test the drone assessment agent.""" - from test2 import booking_form_input - - logger.info("Launching DroneAssessmentAgent from main()...") - agent = DroneAssessmentAgent() - result = await agent.run(booking_form_input) - - logger.info("Drone assessment completed.") - logger.debug("Final structured output:") - logger.debug(json.dumps(result, indent=2)) + # Print bot response + print(f"🤖 DroneBot: {response['final_message']}\n") if __name__ == "__main__": - asyncio.run(main()) + terminal_chat()