Compare commits

12 Commits

Author SHA1 Message Date
OwusuBlessing 02852b2992 fixed llm issues with response 2025-08-18 21:21:08 +01:00
OwusuBlessing 3e7a300eef fixed issues 2025-08-18 14:43:03 +01:00
michael bbc94dfcc0 Merge remote changes and remove Python cache files 2025-08-15 16:51:58 +00:00
michael e21039a7d3 Remove Python cache files from tracking 2025-08-15 16:51:21 +00:00
michael 9605242892 fix merge 2025-08-15 16:48:31 +00:00
OwusuBlessing fa78b98c09 updated prompts 2025-08-15 17:46:05 +01:00
OwusuBlessing f9b35029fe updated with backend apis 2025-08-15 17:36:39 +01:00
OwusuBlessing 093c212928 updated and integrated backedn apis 2025-08-15 17:33:43 +01:00
OwusuBlessing bab442e955 fixed issues history 2025-08-12 19:32:39 +01:00
OwusuBlessing 1905492f93 updated survey route 2025-08-12 15:57:07 +01:00
michael 8ea9510b90 updated on dev server 2025-08-12 13:40:42 +00:00
OwusuBlessing 9952eeda71 updated bot chat api 2025-08-11 23:16:48 +01:00
37 changed files with 812 additions and 87 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -1,6 +1,6 @@
# api/models/requests.py
from pydantic import BaseModel
from typing import List
from typing import List,Dict, Optional
class BaseRequest(BaseModel):
pass
@@ -13,9 +13,9 @@ class ChatMessage(BaseModel):
class ChatRequest(BaseModel):
query: str
history: List[ChatMessage] = []
customer_metadata: Dict = None # Customer info: name, email, phone, user_id
class SurveyRequest(BaseModel):
job_id: str
booking_form_input: Dict
+1 -3
View File
@@ -3,9 +3,7 @@ from pydantic import BaseModel
from typing import Dict
class ChatResponse(BaseModel):
status: str
message: str
message: Dict
class SurveyAgentResponse(BaseModel):
status: str
Binary file not shown.
Binary file not shown.
+29 -7
View File
@@ -7,7 +7,7 @@ from api.dependencies.auth import get_api_key
from src.llm.orchestrator import DroneBot, Message # Adjust import as needed
from src.llm.agent.flight_assesment import DroneAssessmentAgent
import json
from logger import logger
router = APIRouter(
prefix="/chat",
tags=["chat"]
@@ -20,20 +20,41 @@ async def chat_ai(
):
"""Chat with DroneBot using query and history."""
try:
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
bot = DroneBot(history=history, use_openai_as_fallback=True)
# Initialize DroneBot with history and customer metadata
logger.info("Initializing DroneBot...")
logger.info(f"History: {history}")
bot = DroneBot(history=history, use_openai_as_fallback=True, customer_metadata=customer_metadata)
logger.info("DroneBot initialized successfully")
# Get response from DroneBot
logger.info("Calling DroneBot.chat()...")
result = await bot.chat(request.query)
logger.info(f"DroneBot response received: {result}")
# Get response
result = bot.chat(request.query)
return ChatResponse(
status="success",
message=result["final_message"]
message=result
)
except HTTPException:
logger.error("Re-raising HTTPException")
raise
except Exception as e:
logger.error(f"Unexpected error in chat_ai: {str(e)}", exc_info=True)
raise HTTPException(
status_code=500,
detail=str(e)
@@ -47,7 +68,8 @@ async def run_survey_agent(
):
"""Chat with DroneBot using query and history."""
try:
from test2 import booking_form_input
booking_form_input = request.booking_form_input
#from test2 import booking_form_input
agent = DroneAssessmentAgent()
result = await agent.run(booking_form_input)
return SurveyAgentResponse(
+1
View File
@@ -13,3 +13,4 @@ class Config:
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
API_KEY = os.getenv("API_KEY")
OPENWEATHERMAP_API_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
BACKEND_BASE_URL = os.getenv("BACKEND_BASE_URL", "https://baas.mytechpassport.com")
+1 -1
View File
@@ -37,4 +37,4 @@ async def root():
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=5120)
uvicorn.run(app, host="0.0.0.0", port=5340)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -3,6 +3,7 @@ class LlmConfig:
class models:
gpt_4o = "gpt-4o"
gpt_4_1 = "gpt-4.1"
gpt_5_mini = "gpt-5-mini"
temperatures = {
"default": 0.7,
Binary file not shown.
Binary file not shown.
+110
View File
@@ -0,0 +1,110 @@
import os
import json
import asyncio
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from typing import List, Optional, Dict, Any
from typing_extensions import TypedDict, Annotated
from src.config.llm_config import LlmConfig
from config import Config
from logger import logger
class DroneAssessmentResponse(TypedDict):
"""Structured response for drone assessment that matches the chat template format"""
message: Annotated[str, "The main response message about the drone assessment from ai"]
options: Annotated[Optional[List[str]], None, "List of options for user selection from ai"]
requires_selection: Annotated[bool, False, "Whether the user needs to make a selection from ai"]
end: Annotated[str, "in_progress", "The current state: 'in_progress', 'complete', 'cancelled', or other status"]
form: Annotated[Dict[str, str], {}, "Form data with assessment results as string key-value pairs"]
class structureOuputTool:
def __init__(self):
self.llm = ChatOpenAI(
api_key=Config.OPENAI_API_KEY,
model=LlmConfig.openai.models.gpt_4o,
temperature=0.3
)
# Create structured output LLM
self.structured_llm = self.llm.with_structured_output(DroneAssessmentResponse)
def create_assessment_prompt(self, response: dict) -> str:
"""Create a prompt to convert AI response to structured output"""
prompt = f"""
Your task is to analyze the response from AI and return the structured output in the format provided.
RESPONSE FROM AI:
{response}
IMPORTANT: Copy the exact values from the response. Do not fabricate or add anything.
- If message is empty, return empty string
- If options is empty, return empty list []
- If form is empty, return empty dict {{}}
- If requires_selection is empty, return False
- If end is empty, return "in_progress"
Just convert to structured format - no additional content.
"""
return prompt
def run(self, booking_form: dict) -> dict:
"""
Convert booking form data to structured assessment response.
Args:
booking_form (dict): Structured booking form input
Returns:
dict: AI-generated structured output
"""
logger.info("Starting DroneAssessmentAgent run...")
try:
# Generate assessment prompt
prompt = self.create_assessment_prompt(booking_form)
messages = [
SystemMessage(content=prompt),
HumanMessage(content="Please convert this booking form data into a structured assessment response.")
]
# Invoke structured LLM
logger.debug("Sending prompt to structured LLM...")
response = self.structured_llm.invoke(messages)
# TypedDict response is already a dict, no need to convert
result = response
logger.info("Received structured LLM response")
logger.debug(f"Structured output: {json.dumps(result, indent=2)}")
return result
except Exception as e:
logger.exception("Error in DroneAssessmentAgent")
return {
"message": "Error occurred during assessment",
"options": ["Try again", "Contact support"],
"requires_selection": True,
"end": "error",
"form": {
"error": str(e),
"assessment_status": "failed"
}
}
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))
if __name__ == "__main__":
asyncio.run(main())
+117 -32
View File
@@ -16,7 +16,7 @@ from src.prompts.setup_prompt import setup_prompt_manager
from src.config.llm_config import LlmConfig
from src.llm.tools import AgenTools
from config import Config
from src.llm.agent.structured_output import structureOuputTool
prompt_manager = setup_prompt_manager()
@@ -43,12 +43,12 @@ class ModelRotationManager:
# Add OpenAI models if enabled
if use_openai_as_fallback:
openai_models = [
{
"name": LlmConfig.openai.models.gpt_4o,
"provider": "openai",
"api_key": Config.OPENAI_API_KEY,
"temperature": LlmConfig.openai.temperatures.get("drone_bot")
},
# {
# "name": LlmConfig.openai.models.gpt_5_mini,
# "provider": "openai",
# "api_key": Config.OPENAI_API_KEY,
# "temperature": LlmConfig.openai.temperatures.get("drone_bot")
# },
{
"name": LlmConfig.openai.models.gpt_4_1,
"provider": "openai",
@@ -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)
@@ -150,12 +152,21 @@ class DroneBot:
# Initialize output variables
self.final_message = ""
self.final_model_used = ""
self.structured_result = None
# Create tools
self.tools = self._create_tools()
# Initialize prompt manager with customer metadata
self.prompt_manager = setup_prompt_manager(customer_metadata)
self.structure_agent = structureOuputTool()
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"""
@@ -169,17 +180,35 @@ class DroneBot:
try:
# Case 1: Response has direct string content
if hasattr(response, 'content') and isinstance(response.content, str) and response.content.strip():
return response.content.strip()
content = response.content.strip()
# Check if the content is valid JSON
try:
json_content = json.loads(content)
# If it's valid JSON, return the message field or the entire JSON
if isinstance(json_content, dict) and "message" in json_content:
return content # Return the full JSON string
else:
return content # Return the JSON string even if no message field
except json.JSONDecodeError:
# Not JSON, return as plain text
return content
# Case 2: Response has list content (complex format)
elif hasattr(response, 'content') and isinstance(response.content, list):
for item in response.content:
if isinstance(item, dict) and item.get('type') == 'text' and item.get('text'):
return item['text'].strip()
text_content = item['text'].strip()
# Check if it's JSON
try:
json.loads(text_content)
return text_content # Return JSON string
except json.JSONDecodeError:
return text_content # Return plain text
# Case 3: Response only has tool calls, no text content
elif hasattr(response, 'tool_calls') and response.tool_calls:
return "Generating your visualization..."
return "Calling tool..."
# Case 4: Empty or None content
else:
@@ -219,7 +248,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)}")
@@ -228,13 +257,35 @@ class DroneBot:
response = llm_with_tools.invoke(messages)
print(f"DroneBot response: {type(response).__name__}")
print(f"Response content type: {type(response.content)}")
print(f"Response content: {response.content}")
if hasattr(response, 'tool_calls'):
print(f" Tool calls: {len(response.tool_calls) if response.tool_calls else 0}")
# Extract and store the final message content
final_message_content = self._extract_final_message_content(response)
print(f"Extracted final message: {final_message_content}")
self.final_message = final_message_content
# Try to parse as JSON first, if it fails, use structured agent
try:
json.loads(final_message_content)
print("Final message is valid JSON, no need for structured agent")
except json.JSONDecodeError:
print("Final message is not valid JSON, calling structured agent")
# Since we can't await here, we'll store the raw message
# The structured processing can happen later if needed
try:
# Try to run synchronously first
structured_result = self.structure_agent.run(self.final_message)
print(f"Structured agent result: {structured_result}")
# Store the structured result separately, keep final_message as string
self.structured_result = structured_result
except Exception as e:
print(f"Error in structured agent (sync): {str(e)}")
# Keep the original message if structured agent fails
pass
# Update state
updated_state = {"messages": state["messages"] + [response]}
updated_state["current_model"] = current_model_config["name"]
@@ -362,7 +413,7 @@ class DroneBot:
langchain_messages.append(AIMessage(content=msg.content))
return langchain_messages
def chat(self, user_query: str) -> Dict[str, Any]:
async def chat(self, user_query: str) -> Dict[str, Any]:
"""Main method to interact with DroneBot"""
print(f"DroneBot processing query: {user_query}")
@@ -401,28 +452,54 @@ class DroneBot:
break
if not self.final_message:
self.final_message = "I've processed your visualization request."
self.final_message = "...."
final_response = {
"messages": output.get("messages", []),
"final_message": self.final_message,
"final_model_used": self.final_model_used or output.get("current_model", "unknown"),
"user_question": user_query
}
# Check if we already have a structured result from the workflow
if self.structured_result is not None:
print("Using pre-computed structured result from workflow")
structured_message = self.structured_result
else:
# Try to parse final_message as JSON, if it fails, use structured agent
try:
structured_message = json.loads(self.final_message)
print("Final message is valid JSON, no structured agent needed")
except json.JSONDecodeError:
print("Final message is not valid JSON, calling structured agent")
try:
# Now we can properly await the async function
structured_result = await self.structure_agent.run(self.final_message)
print(f"Structured agent result: {structured_result}")
# Assign the structured result to structured_message
structured_message = structured_result
except Exception as e:
print(f"Error in structured agent: {str(e)}")
structured_message = {
"message": "Sorry i encountered an error while processing your request. Please try again.",
"options": [],
"requires_selection": False,
"end": "error",
"form": {}
}
print(f"Final message: {self.final_message[:100]}...")
return final_response
# final_response = {
# "messages": output.get("messages", []),
# "final_message": structured_message,
# "final_model_used": self.final_model_used or output.get("current_model", "unknown"),
# "user_question": user_query
# }
#print(f"Final message: {self.final_message[:100]}...")
return structured_message
except Exception as e:
print(f"Error in DroneBot workflow execution: {str(e)}")
return {
"messages": [],
"final_message": "Sorry, I encountered an error while processing your visualization request. Please try again.",
"final_model_used": "error",
"user_question": user_query,
"error": str(e)
}
"message": "Sorry i encountered an error while processing your request. Please try again.",
"options": [],
"requires_selection": False,
"end": "error",
"form": {}
}
# Example usage
if __name__ == "__main__":
@@ -434,13 +511,21 @@ 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
response = bot.chat(query)
import asyncio
response = asyncio.run(bot.chat(query))
print("Response:", response["final_message"])
+2 -2
View File
@@ -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]
tools = [get_booking_calendar, book_survey_for_customer_after_confirmation]
Binary file not shown.
+136
View File
@@ -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"
}
+28 -9
View File
@@ -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),
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -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
+301 -25
View File
@@ -1,7 +1,186 @@
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"""
## CRITICAL INSTRUCTION - YOU MUST FOLLOW THIS EXACTLY
**YOU MUST ALWAYS RESPOND WITH VALID JSON ONLY. NO OTHER TEXT, NO EXPLANATIONS, NO MARKDOWN FORMATTING.**
**EVERY SINGLE RESPONSE MUST BE A VALID JSON OBJECT WITH THIS EXACT STRUCTURE:**
{{
"message": "Your conversational response to the user",
"options": ["Option 1", "Option 2", "Option 3"] or null,
"requires_selection": true/false,
"end": "complete" | "in_progress" | "cancelled",
"form": {{}}
}}
**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.
@@ -16,7 +195,7 @@ Your primary function is to guide users through a comprehensive booking process,
You MUST ALWAYS respond with a structured JSON object containing exactly these fields:
```json
{{
"message": "Your conversational response to the user",
"options": ["Option 1", "Option 2", "Option 3"] or null,
@@ -24,7 +203,7 @@ You MUST ALWAYS respond with a structured JSON object containing exactly these f
"end": "complete" | "in_progress" | "cancelled",
"form": {{}}
}}
```
**Field Explanations:**
- **message**: Your conversational response (friendly, professional tone)
@@ -40,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",
@@ -51,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
@@ -58,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",
@@ -69,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",
@@ -84,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",
@@ -95,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",
@@ -103,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
@@ -287,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",
@@ -334,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
{{
@@ -368,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]"
}}
}}
```
@@ -420,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
@@ -431,9 +674,42 @@ 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
: DO NOT ADD ```json or ``` or anything else before or after the json format
: 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
CRITICAL !!! ONLY JSON !!!
FINAL CRITICAL INSTRUCTIONS:
RETURN ONLY JSON FORMAT NO EXPLANATION OR EXTRA INFORMATION BEFORE OR AFTER, DO NOT ADD ```json before or after
"""
+45
View File
@@ -0,0 +1,45 @@
from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed
import json
async def terminal_chat():
print("🚁 DroneBot Terminal Chat")
print("Type 'exit' to quit.\n")
# Initial conversation history
history = []
# 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)
# Get initial user input
while True:
user_input = input("👤 You: ")
if user_input.lower() in ("exit", "quit"):
print("👋 Exiting DroneBot. Goodbye!")
break
# Add user message to history
history.append(Message(role="human", content=user_input))
# Update bot's history
bot.history = history
# Get bot response
response = await bot.chat(user_input)
# Add bot response to history
history.append(Message(role="ai", content=json.dumps(response)))
# Print bot response
print(f"🤖 DroneBot: {response}\n")
if __name__ == "__main__":
import asyncio
asyncio.run(terminal_chat())
+32
View File
@@ -0,0 +1,32 @@
booking_form_input = {
"job_id": "1043",
"job_overview": {
"job_number": "Job #1043",
"site_name": "Hightower Solar Farm",
"assigned_date": "January 23, 2025",
"status": "Scheduled"
},
"site_information": {
"site_name": "Hightower Solar Farm",
"region": "North England",
"full_address": "Grange Lane, Manchester M34 7TF",
"gps_coordinates": {
"latitude": "53.4408° N",
"longitude": "2.2426° W"
}
},
"timing": {
"start_time": "09:00 AM",
"end_time": "10:30 AM",
"survey_duration": "3045 mins",
"buffer_time": "45 mins"
},
"form": {
"asset_type": "Solar Farm",
"system_size": "5.2 MW capacity, approximately 16,000 panels across 12 hectares",
"survey_purpose": "Insurance assessment",
"assigned_engineer": "David Wilson - 0161-555-0876",
"contact_person": "Sarah Thompson",
"contact_phone": "0161-555-0234"
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
# terminal_chat.py
from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed
def terminal_chat():
async def terminal_chat():
print("🚁 DroneBot Terminal Chat")
print("Type 'exit' to quit.\n")