Compare commits

..

5 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
28 changed files with 200 additions and 71 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.
Binary file not shown.
Binary file not shown.
+3 -37
View File
@@ -36,53 +36,19 @@ async def chat_ai(
# Initialize DroneBot with history and customer metadata # Initialize DroneBot with history and customer metadata
logger.info("Initializing DroneBot...") logger.info("Initializing DroneBot...")
logger.info(f"History: {history}")
bot = DroneBot(history=history, use_openai_as_fallback=True, customer_metadata=customer_metadata) bot = DroneBot(history=history, use_openai_as_fallback=True, customer_metadata=customer_metadata)
logger.info("DroneBot initialized successfully") logger.info("DroneBot initialized successfully")
# Get response from DroneBot # Get response from DroneBot
logger.info("Calling DroneBot.chat()...") logger.info("Calling DroneBot.chat()...")
result = bot.chat(request.query) result = await bot.chat(request.query)
logger.info(f"DroneBot response received: {result}") logger.info(f"DroneBot response received: {result}")
# Validate result and final_message
if not result or "final_message" not in result:
logger.error(f"Invalid result from DroneBot: {result}")
raise HTTPException(
status_code=500,
detail="Invalid response from DroneBot: missing final_message"
)
final_message = result["final_message"]
logger.info(f"Final message extracted: {final_message}")
if not final_message or not isinstance(final_message, str):
logger.error(f"Final message is not a valid string: {type(final_message)} - {final_message}")
raise HTTPException(
status_code=500,
detail="Invalid response from DroneBot: final_message is not a valid string"
)
try:
logger.info("Attempting to parse final_message as JSON...")
message = json.loads(final_message)
logger.info("JSON parsing successful")
except json.JSONDecodeError as json_error:
logger.warning(f"JSON decode error: {json_error}")
logger.warning(f"Raw final_message: {final_message}")
# If JSON parsing fails, create a fallback structure
message = {
"message": final_message,
"options": None,
"requires_selection": False,
"end": "in_progress",
"form": {}
}
logger.info("Created fallback message structure")
logger.info(f"Final message to return: {message}")
return ChatResponse( return ChatResponse(
status="success", status="success",
message=message message=result
) )
except HTTPException: except HTTPException:
logger.error("Re-raising HTTPException") logger.error("Re-raising HTTPException")
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -3,6 +3,7 @@ class LlmConfig:
class models: class models:
gpt_4o = "gpt-4o" gpt_4o = "gpt-4o"
gpt_4_1 = "gpt-4.1" gpt_4_1 = "gpt-4.1"
gpt_5_mini = "gpt-5-mini"
temperatures = { temperatures = {
"default": 0.7, "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())
+74 -26
View File
@@ -16,7 +16,7 @@ from src.prompts.setup_prompt import setup_prompt_manager
from src.config.llm_config import LlmConfig from src.config.llm_config import LlmConfig
from src.llm.tools import AgenTools from src.llm.tools import AgenTools
from config import Config from config import Config
from src.llm.agent.structured_output import structureOuputTool
prompt_manager = setup_prompt_manager() prompt_manager = setup_prompt_manager()
@@ -43,12 +43,12 @@ class ModelRotationManager:
# Add OpenAI models if enabled # Add OpenAI models if enabled
if use_openai_as_fallback: if use_openai_as_fallback:
openai_models = [ openai_models = [
{ # {
"name": LlmConfig.openai.models.gpt_4o, # "name": LlmConfig.openai.models.gpt_5_mini,
"provider": "openai", # "provider": "openai",
"api_key": Config.OPENAI_API_KEY, # "api_key": Config.OPENAI_API_KEY,
"temperature": LlmConfig.openai.temperatures.get("drone_bot") # "temperature": LlmConfig.openai.temperatures.get("drone_bot")
}, # },
{ {
"name": LlmConfig.openai.models.gpt_4_1, "name": LlmConfig.openai.models.gpt_4_1,
"provider": "openai", "provider": "openai",
@@ -152,12 +152,14 @@ class DroneBot:
# Initialize output variables # Initialize output variables
self.final_message = "" self.final_message = ""
self.final_model_used = "" self.final_model_used = ""
self.structured_result = None
# Create tools # Create tools
self.tools = self._create_tools() self.tools = self._create_tools()
# Initialize prompt manager with customer metadata # Initialize prompt manager with customer metadata
self.prompt_manager = setup_prompt_manager(customer_metadata) self.prompt_manager = setup_prompt_manager(customer_metadata)
self.structure_agent = structureOuputTool()
print(f"DroneBot initialized") print(f"DroneBot initialized")
print(f"OpenAI fallback: {'Enabled' if use_openai_as_fallback else 'Disabled'}") print(f"OpenAI fallback: {'Enabled' if use_openai_as_fallback else 'Disabled'}")
@@ -206,7 +208,7 @@ class DroneBot:
# Case 3: Response only has tool calls, no text content # Case 3: Response only has tool calls, no text content
elif hasattr(response, 'tool_calls') and response.tool_calls: elif hasattr(response, 'tool_calls') and response.tool_calls:
return "Generating your visualization..." return "Calling tool..."
# Case 4: Empty or None content # Case 4: Empty or None content
else: else:
@@ -265,6 +267,25 @@ class DroneBot:
print(f"Extracted final message: {final_message_content}") print(f"Extracted final message: {final_message_content}")
self.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 # Update state
updated_state = {"messages": state["messages"] + [response]} updated_state = {"messages": state["messages"] + [response]}
updated_state["current_model"] = current_model_config["name"] updated_state["current_model"] = current_model_config["name"]
@@ -392,7 +413,7 @@ class DroneBot:
langchain_messages.append(AIMessage(content=msg.content)) langchain_messages.append(AIMessage(content=msg.content))
return langchain_messages 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""" """Main method to interact with DroneBot"""
print(f"DroneBot processing query: {user_query}") print(f"DroneBot processing query: {user_query}")
@@ -431,28 +452,54 @@ class DroneBot:
break break
if not self.final_message: if not self.final_message:
self.final_message = "I've processed your visualization request." self.final_message = "...."
final_response = { # Check if we already have a structured result from the workflow
"messages": output.get("messages", []), if self.structured_result is not None:
"final_message": self.final_message, print("Using pre-computed structured result from workflow")
"final_model_used": self.final_model_used or output.get("current_model", "unknown"), structured_message = self.structured_result
"user_question": user_query 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]}...") # final_response = {
return 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: except Exception as e:
print(f"Error in DroneBot workflow execution: {str(e)}") print(f"Error in DroneBot workflow execution: {str(e)}")
return { return {
"messages": [], "message": "Sorry i encountered an error while processing your request. Please try again.",
"final_message": "Sorry, I encountered an error while processing your visualization request. Please try again.", "options": [],
"final_model_used": "error", "requires_selection": False,
"user_question": user_query, "end": "error",
"error": str(e) "form": {}
} }
# Example usage # Example usage
if __name__ == "__main__": if __name__ == "__main__":
@@ -477,7 +524,8 @@ if __name__ == "__main__":
query = "Can we start" query = "Can we start"
# Chat with DroneBot # Chat with DroneBot
response = bot.chat(query) import asyncio
response = asyncio.run(bot.chat(query))
print("Response:", response["final_message"]) print("Response:", response["final_message"])
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -709,4 +709,7 @@ NOTE: THIS IS SOLELY YOUR TASK PLEASE, NO OTHER THING
: DO NOT ADD ```json or ``` or anything else before or after the json format : 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 : 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 !!! 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
""" """
+8 -7
View File
@@ -1,7 +1,6 @@
# terminal_chat.py
from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed
import json
def terminal_chat(): async def terminal_chat():
print("🚁 DroneBot Terminal Chat") print("🚁 DroneBot Terminal Chat")
print("Type 'exit' to quit.\n") print("Type 'exit' to quit.\n")
@@ -31,14 +30,16 @@ def terminal_chat():
bot.history = history bot.history = history
# Get bot response # Get bot response
response = bot.chat(user_input) response = await bot.chat(user_input)
# Add bot response to history # Add bot response to history
history.append(Message(role="ai", content=response["final_message"])) history.append(Message(role="ai", content=json.dumps(response)))
# Print bot response # Print bot response
print(f"🤖 DroneBot: {response['final_message']}\n")
print(f"🤖 DroneBot: {response}\n")
if __name__ == "__main__": if __name__ == "__main__":
terminal_chat() import asyncio
asyncio.run(terminal_chat())
+1 -1
View File
@@ -1,7 +1,7 @@
# terminal_chat.py # terminal_chat.py
from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed
def terminal_chat(): async def terminal_chat():
print("🚁 DroneBot Terminal Chat") print("🚁 DroneBot Terminal Chat")
print("Type 'exit' to quit.\n") print("Type 'exit' to quit.\n")