setup assisant bot
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
OPENAI_API_KEY="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"
|
||||
API_KEY="drbot-nhLybL86VBiUTtyngshsdj9efmc"
|
||||
@@ -0,0 +1,2 @@
|
||||
OPENAI_API_KEY="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"
|
||||
API_KEY="drbot-nhLybL86VBiUTtyngshsdj9efmc"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,19 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from functools import lru_cache
|
||||
from config import Config
|
||||
|
||||
class Settings(BaseSettings):
|
||||
API_V1_STR: str = "/api/v1"
|
||||
PROJECT_NAME: str = "drone bot API"
|
||||
VERSION: str = "1.0.0"
|
||||
DESCRIPTION: str = ""
|
||||
|
||||
# API Key validation
|
||||
API_KEY_ACCESS: str = Config.API_KEY
|
||||
|
||||
class Config:
|
||||
case_sensitive = True
|
||||
|
||||
@lru_cache()
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
from fastapi import HTTPException, Depends, Security
|
||||
from fastapi.security import APIKeyHeader
|
||||
from api.models.requests import BaseRequest
|
||||
from config import Config
|
||||
|
||||
api_key_header = APIKeyHeader(name="Authorization", auto_error=False)
|
||||
|
||||
async def get_api_key(api_key_header: str = Security(api_key_header)) -> str:
|
||||
"""Validate API key from header"""
|
||||
if not api_key_header or not api_key_header.startswith('Bearer '):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={"error": "Unauthorized", "message": "API key is missing or invalid."}
|
||||
)
|
||||
|
||||
token = api_key_header.split(' ')[1]
|
||||
|
||||
if token != Config.API_KEY:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail={"error": "Unauthorized", "message": "API key does not match."}
|
||||
)
|
||||
|
||||
return token
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
# api/models/requests.py
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
class BaseRequest(BaseModel):
|
||||
pass
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
role: str # "human" or "ai"
|
||||
content: str
|
||||
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
query: str
|
||||
history: List[ChatMessage] = []
|
||||
@@ -0,0 +1,8 @@
|
||||
# api/models/responses.py
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
status: str
|
||||
message: str
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
# api/routes/chat_ai.py
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from api.models.requests import ChatRequest, ChatMessage
|
||||
from api.models.responses import ChatResponse
|
||||
from api.dependencies.auth import get_api_key
|
||||
from src.llm.orchestrator import DroneBot, Message # Adjust import as needed
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/chat-ai",
|
||||
tags=["chat"]
|
||||
)
|
||||
|
||||
@router.post("", response_model=ChatResponse)
|
||||
async def chat_ai(
|
||||
request: ChatRequest,
|
||||
_: str = Depends(get_api_key)
|
||||
):
|
||||
"""Chat with DroneBot using query and history."""
|
||||
try:
|
||||
# Convert to internal Message format
|
||||
history = [Message(role=msg.role, content=msg.content) for msg in request.history]
|
||||
|
||||
# Initialize DroneBot with history
|
||||
bot = DroneBot(history=history, use_openai_as_fallback=True)
|
||||
|
||||
# Get response
|
||||
result = bot.chat(request.query)
|
||||
|
||||
return ChatResponse(
|
||||
status="success",
|
||||
message=result["final_message"]
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=str(e)
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import json
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
class Config:
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||
API_KEY = os.getenv("API_KEY")
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from api.routes import chat
|
||||
from api.config import Settings
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.PROJECT_NAME,
|
||||
description=settings.DESCRIPTION,
|
||||
version=settings.VERSION,
|
||||
openapi_url=f"{settings.API_V1_STR}/openapi.json",
|
||||
docs_url=f"{settings.API_V1_STR}/docs",
|
||||
redoc_url=f"{settings.API_V1_STR}/redoc",
|
||||
)
|
||||
|
||||
# Add CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # Modify this in production
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Include routers
|
||||
app.include_router(chat.router, prefix=settings.API_V1_STR)
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {
|
||||
"message": "Welcome to DroneBot AI API",
|
||||
"version": settings.VERSION,
|
||||
"docs_url": f"{settings.API_V1_STR}/docs"
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=5120)
|
||||
@@ -0,0 +1,21 @@
|
||||
numpy==1.26.4
|
||||
openai==1.58.1
|
||||
pandas==2.2.3
|
||||
python-dotenv==1.0.1
|
||||
reportlab==4.2.5
|
||||
requests==2.32.3
|
||||
fastapi
|
||||
Jinja2==3.1.5
|
||||
matplotlib==3.8.2
|
||||
slack_sdk==3.34.0
|
||||
tabulate==0.9.0
|
||||
python-multipart==0.0.20
|
||||
langgraph==0.3.2
|
||||
langchain==0.3.19
|
||||
langchain-openai== 0.3.7
|
||||
langchain-anthropic==0.3.9
|
||||
gunicorn==23.0.0
|
||||
tqdm
|
||||
uvicorn[standard]
|
||||
cryptography
|
||||
pydantic-settings
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,60 @@
|
||||
class LlmConfig:
|
||||
class openai:
|
||||
class models:
|
||||
gpt_4o = "gpt-4o"
|
||||
gpt_4_1 = "gpt-4.1"
|
||||
|
||||
temperatures = {
|
||||
"default": 0.7,
|
||||
"drone_bot": 0.3,
|
||||
"creative": 0.9
|
||||
}
|
||||
|
||||
max_tokens = {
|
||||
"default": 2048,
|
||||
"summary_bot": 512,
|
||||
"explainer": 1024
|
||||
}
|
||||
|
||||
class anthropic:
|
||||
class models:
|
||||
claude_3_opus = "claude-3-opus"
|
||||
claude_3_sonnet = "claude-3-sonnet"
|
||||
|
||||
temperatures = {
|
||||
"default": 0.5,
|
||||
"drone_bot": 0.4,
|
||||
"research": 0.2
|
||||
}
|
||||
|
||||
max_tokens = {
|
||||
"default": 4096,
|
||||
"summary_bot": 1024
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_config(cls, provider: str, model_name: str, temp_name: str = "default", token_preset: str = "default") -> dict:
|
||||
if not hasattr(cls, provider):
|
||||
raise ValueError(f"Provider '{provider}' not found.")
|
||||
|
||||
provider_cls = getattr(cls, provider)
|
||||
|
||||
# Get model value from class (e.g., LlmConfig.openai.models.gpt_4o)
|
||||
model_cls = getattr(provider_cls, "models")
|
||||
if not hasattr(model_cls, model_name):
|
||||
raise ValueError(f"Model '{model_name}' not found under provider '{provider}'.")
|
||||
|
||||
model = getattr(model_cls, model_name)
|
||||
|
||||
if temp_name not in provider_cls.temperatures:
|
||||
raise ValueError(f"Temperature preset '{temp_name}' not found under provider '{provider}'.")
|
||||
|
||||
if token_preset not in provider_cls.max_tokens:
|
||||
raise ValueError(f"Max token preset '{token_preset}' not found under provider '{provider}'.")
|
||||
|
||||
return {
|
||||
"provider": provider,
|
||||
"model": model,
|
||||
"temperature": provider_cls.temperatures[temp_name],
|
||||
"max_tokens": provider_cls.max_tokens[token_preset]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,446 @@
|
||||
import json
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
import os
|
||||
from typing import List, Dict, Any, Optional
|
||||
from typing_extensions import TypedDict
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
|
||||
from dataclasses import dataclass
|
||||
import random
|
||||
import time
|
||||
from functools import wraps
|
||||
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
|
||||
|
||||
prompt_manager = setup_prompt_manager()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
role: str # 'human' or 'ai'
|
||||
content: str
|
||||
|
||||
|
||||
class ModelRotationManager:
|
||||
"""Manages model rotation and failure tracking"""
|
||||
|
||||
def __init__(self, use_openai_as_fallback: bool = True):
|
||||
# Base Anthropic models
|
||||
self.models = [
|
||||
# {
|
||||
# "name": "claude-3-5-sonnet-latest",
|
||||
# "provider": "anthropic",
|
||||
# "api_key": os.getenv("ANTHROPIC_API_KEY"),
|
||||
# "temperature": 0.001
|
||||
# }
|
||||
]
|
||||
|
||||
# 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_4_1,
|
||||
"provider": "openai",
|
||||
"api_key": Config.OPENAI_API_KEY,
|
||||
"temperature": LlmConfig.openai.temperatures.get("drone_bot")
|
||||
}
|
||||
]
|
||||
self.models.extend(openai_models)
|
||||
|
||||
|
||||
# Track failures per model
|
||||
self.failure_counts = {model["name"]: 0 for model in self.models}
|
||||
self.current_model_index = 0
|
||||
self.max_failures_per_model = 2
|
||||
self.use_openai_as_fallback = use_openai_as_fallback
|
||||
|
||||
print(f"ModelRotationManager initialized with {len(self.models)} models")
|
||||
print(f"OpenAI fallback: {'Enabled' if use_openai_as_fallback else 'Disabled'}")
|
||||
for model in self.models:
|
||||
print(f" - {model['name']} ({model['provider']})")
|
||||
|
||||
def get_current_model(self):
|
||||
"""Get the current model configuration"""
|
||||
return self.models[self.current_model_index]
|
||||
|
||||
def create_llm_instance(self, model_config: dict):
|
||||
"""Create an LLM instance based on model configuration"""
|
||||
if model_config["provider"] == "anthropic":
|
||||
return ChatAnthropic(
|
||||
api_key=model_config["api_key"],
|
||||
model=model_config["name"],
|
||||
temperature=model_config["temperature"]
|
||||
)
|
||||
elif model_config["provider"] == "openai":
|
||||
return ChatOpenAI(
|
||||
api_key=model_config["api_key"],
|
||||
model=model_config["name"],
|
||||
temperature=model_config["temperature"]
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported provider: {model_config['provider']}")
|
||||
|
||||
def rotate_on_failure(self, failed_model_name: str):
|
||||
"""Rotate to next model on failure"""
|
||||
self.failure_counts[failed_model_name] += 1
|
||||
|
||||
print(f"Model {failed_model_name} failed. Failure count: {self.failure_counts[failed_model_name]}")
|
||||
|
||||
# Find next available model
|
||||
attempts = 0
|
||||
while attempts < len(self.models):
|
||||
self.current_model_index = (self.current_model_index + 1) % len(self.models)
|
||||
next_model = self.models[self.current_model_index]
|
||||
|
||||
if self.failure_counts[next_model["name"]] < self.max_failures_per_model:
|
||||
print(f"Rotating to model: {next_model['name']}")
|
||||
return next_model
|
||||
|
||||
attempts += 1
|
||||
|
||||
# If all models have failed, reset and try again
|
||||
print("All models have failed. Resetting failure counts and trying again.")
|
||||
self.failure_counts = {model["name"]: 0 for model in self.models}
|
||||
self.current_model_index = 0
|
||||
return self.models[0]
|
||||
|
||||
def get_next_model(self):
|
||||
"""Get next available model"""
|
||||
current_model = self.get_current_model()
|
||||
if self.failure_counts[current_model["name"]] < self.max_failures_per_model:
|
||||
return current_model
|
||||
else:
|
||||
return self.rotate_on_failure(current_model["name"])
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
messages: List[HumanMessage | AIMessage]
|
||||
final_response: Optional[str]
|
||||
user_question: Optional[str]
|
||||
current_model: Optional[str]
|
||||
|
||||
|
||||
|
||||
class DroneBot:
|
||||
def __init__(self, history: Optional[List[Message]] = None, use_openai_as_fallback: bool = True):
|
||||
"""
|
||||
Initialize DroneBot - a simplified plotting agent
|
||||
|
||||
Args:
|
||||
history: Optional conversation history
|
||||
use_openai_as_fallback: Whether to include OpenAI models as fallback
|
||||
"""
|
||||
self.history = history if history is not None else []
|
||||
self.use_openai_as_fallback = use_openai_as_fallback
|
||||
|
||||
# Initialize model rotation manager
|
||||
self.model_manager = ModelRotationManager(use_openai_as_fallback=use_openai_as_fallback)
|
||||
|
||||
# Initialize output variables
|
||||
self.final_message = ""
|
||||
self.final_model_used = ""
|
||||
|
||||
# Create tools
|
||||
self.tools = self._create_tools()
|
||||
|
||||
print(f"DroneBot initialized")
|
||||
print(f"OpenAI fallback: {'Enabled' if use_openai_as_fallback else 'Disabled'}")
|
||||
|
||||
def _create_tools(self):
|
||||
"""define tools for agent"""
|
||||
# Mock tool definition - replace with your actual tool
|
||||
|
||||
tools = AgenTools.tools
|
||||
return tools
|
||||
|
||||
def _extract_final_message_content(self, response: AIMessage) -> str:
|
||||
"""Extract the final message content from an AIMessage response"""
|
||||
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()
|
||||
|
||||
# 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()
|
||||
|
||||
# Case 3: Response only has tool calls, no text content
|
||||
elif hasattr(response, 'tool_calls') and response.tool_calls:
|
||||
return "Generating your visualization..."
|
||||
|
||||
# Case 4: Empty or None content
|
||||
else:
|
||||
return "Processing your request..."
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error extracting message content: {str(e)}")
|
||||
return "I encountered an issue processing your request."
|
||||
|
||||
|
||||
def create_workflow(self) -> StateGraph:
|
||||
"""Create the DroneBot workflow"""
|
||||
print("Creating DroneBot workflow")
|
||||
|
||||
# Create state graph
|
||||
workflow = StateGraph(State)
|
||||
|
||||
def drone_bot(state: State):
|
||||
"""Main chatbot that handles plotting requests"""
|
||||
current_model_config = self.model_manager.get_current_model()
|
||||
|
||||
# Add retry logic with model rotation
|
||||
max_retries = 3
|
||||
last_error = None
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
llm = self.model_manager.create_llm_instance(current_model_config)
|
||||
|
||||
print(f"Using model: {current_model_config['name']} ({current_model_config['provider']}) - Attempt {attempt + 1}")
|
||||
|
||||
# Bind tools to the LLM
|
||||
llm_with_tools = llm.bind_tools(self.tools)
|
||||
|
||||
# Get messages from state
|
||||
messages = state["messages"]
|
||||
|
||||
# Add system message if not present
|
||||
if not messages or not isinstance(messages[0], SystemMessage):
|
||||
system_prompt = prompt_manager.get_prompt("booking")
|
||||
messages = [SystemMessage(content=system_prompt)] + messages
|
||||
|
||||
print(f"DroneBot input messages: {len(messages)}")
|
||||
|
||||
# Get response from LLM
|
||||
response = llm_with_tools.invoke(messages)
|
||||
|
||||
print(f"DroneBot response: {type(response).__name__}")
|
||||
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)
|
||||
self.final_message = final_message_content
|
||||
|
||||
# Update state
|
||||
updated_state = {"messages": state["messages"] + [response]}
|
||||
updated_state["current_model"] = current_model_config["name"]
|
||||
self.final_model_used = current_model_config["name"]
|
||||
|
||||
return updated_state
|
||||
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
print(f"Attempt {attempt + 1} failed with model {current_model_config['name']}: {str(e)}")
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
current_model_config = self.model_manager.rotate_on_failure(current_model_config["name"])
|
||||
time.sleep(1)
|
||||
|
||||
# If all retries failed, raise the last error
|
||||
raise last_error
|
||||
|
||||
def route_tools(state: State):
|
||||
"""Route to tools if the last message has tool calls"""
|
||||
messages = state.get("messages", [])
|
||||
if not messages:
|
||||
return END
|
||||
|
||||
ai_message = messages[-1]
|
||||
|
||||
if hasattr(ai_message, "tool_calls") and ai_message.tool_calls:
|
||||
return "tools"
|
||||
return END
|
||||
|
||||
# Tool execution node
|
||||
class ToolNode:
|
||||
"""A node that runs the plotting tools"""
|
||||
|
||||
def __init__(self, tools: list, dronebot_instance) -> None:
|
||||
self.tools_by_name = {tool.name: tool for tool in tools}
|
||||
self.dronebot = dronebot_instance
|
||||
|
||||
def __call__(self, inputs: dict):
|
||||
messages = inputs.get("messages", [])
|
||||
if not messages:
|
||||
raise ValueError("No message found in input")
|
||||
|
||||
message = messages[-1]
|
||||
outputs = []
|
||||
|
||||
if not hasattr(message, "tool_calls") or not message.tool_calls:
|
||||
print("No tool calls found in message")
|
||||
return {"messages": messages}
|
||||
|
||||
print(f"Processing {len(message.tool_calls)} tool calls")
|
||||
|
||||
for tool_call in message.tool_calls:
|
||||
tool_name = tool_call["name"]
|
||||
tool_args = tool_call["args"]
|
||||
tool_call_id = tool_call["id"]
|
||||
|
||||
print(f"Executing tool: {tool_name}")
|
||||
|
||||
try:
|
||||
if tool_name in self.tools_by_name:
|
||||
tool_result = self.tools_by_name[tool_name].invoke(tool_args)
|
||||
else:
|
||||
raise ValueError(f"Tool {tool_name} not found")
|
||||
|
||||
print(f"Tool {tool_name} executed successfully")
|
||||
|
||||
|
||||
|
||||
# Create tool message
|
||||
tool_message = ToolMessage(
|
||||
content=json.dumps(tool_result),
|
||||
name=tool_name,
|
||||
tool_call_id=tool_call_id,
|
||||
)
|
||||
outputs.append(tool_message)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error executing tool {tool_name}: {str(e)}")
|
||||
error_result = {
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
"tool_name": tool_name
|
||||
}
|
||||
error_message = ToolMessage(
|
||||
content=json.dumps(error_result),
|
||||
name=tool_name,
|
||||
tool_call_id=tool_call_id,
|
||||
)
|
||||
outputs.append(error_message)
|
||||
|
||||
result = {"messages": messages + outputs}
|
||||
print(f"ToolNode returning {len(outputs)} tool messages")
|
||||
return result
|
||||
|
||||
tool_node = ToolNode(tools=self.tools, dronebot_instance=self)
|
||||
|
||||
# Add nodes
|
||||
workflow.add_node("chatbot", drone_bot)
|
||||
workflow.add_node("tools", tool_node)
|
||||
|
||||
# Add edges
|
||||
workflow.add_edge(START, "chatbot")
|
||||
workflow.add_conditional_edges(
|
||||
"chatbot",
|
||||
route_tools,
|
||||
{"tools": "tools", END: END},
|
||||
)
|
||||
workflow.add_edge("tools", "chatbot")
|
||||
|
||||
# Compile with memory
|
||||
memory = MemorySaver()
|
||||
app = workflow.compile(checkpointer=memory)
|
||||
|
||||
print("DroneBot workflow created successfully")
|
||||
return app
|
||||
|
||||
def convert_to_langchain_messages(self, messages: List[Message]) -> List[HumanMessage | AIMessage]:
|
||||
"""Convert Message objects to LangChain message objects"""
|
||||
langchain_messages = []
|
||||
for msg in messages:
|
||||
if msg.role == "human" or msg.role == "user":
|
||||
langchain_messages.append(HumanMessage(content=msg.content))
|
||||
elif msg.role == "ai" or msg.role == "assistant":
|
||||
langchain_messages.append(AIMessage(content=msg.content))
|
||||
return langchain_messages
|
||||
|
||||
def chat(self, user_query: str) -> Dict[str, Any]:
|
||||
"""Main method to interact with DroneBot"""
|
||||
print(f"DroneBot processing query: {user_query}")
|
||||
|
||||
conversation_id = f"dronebot_{int(time.time())}"
|
||||
config = {"configurable": {"thread_id": conversation_id}}
|
||||
app = self.create_workflow()
|
||||
|
||||
# Prepare input messages
|
||||
input_messages = []
|
||||
|
||||
# Add history if available
|
||||
if self.history:
|
||||
input_messages.extend(self.convert_to_langchain_messages(self.history))
|
||||
|
||||
# Add current query
|
||||
input_messages.append(HumanMessage(content=user_query))
|
||||
|
||||
# Initialize state
|
||||
initial_state = {
|
||||
"messages": input_messages,
|
||||
"final_response": None,
|
||||
"user_question": user_query,
|
||||
"current_model": self.model_manager.get_current_model()["name"]
|
||||
}
|
||||
|
||||
try:
|
||||
output = app.invoke(initial_state, config)
|
||||
print("DroneBot workflow completed successfully")
|
||||
|
||||
# Ensure final_message is properly set
|
||||
if not self.final_message:
|
||||
messages = output.get("messages", [])
|
||||
for message in reversed(messages):
|
||||
if isinstance(message, AIMessage):
|
||||
self.final_message = self._extract_final_message_content(message)
|
||||
break
|
||||
|
||||
if not self.final_message:
|
||||
self.final_message = "I've processed your visualization request."
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
print(f"Final message: {self.final_message[:100]}...")
|
||||
return final_response
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
# Example conversation history
|
||||
history_list = [
|
||||
{"role": "human", "content": "Hello DroneBot!"},
|
||||
{"role": "ai", "content": "Hello! How can I help you today?"}
|
||||
]
|
||||
|
||||
history = [Message(role=msg["role"], content=msg["content"]) for msg in history_list]
|
||||
|
||||
|
||||
# Initialize DroneBot
|
||||
bot = DroneBot(history=history, use_openai_as_fallback=True)
|
||||
|
||||
query = "Can we start"
|
||||
# Chat with DroneBot
|
||||
response = bot.chat(query)
|
||||
|
||||
print("Response:", response["final_message"])
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from .booking_calendar import get_booking_calendar
|
||||
|
||||
|
||||
class AgenTools:
|
||||
tools = [get_booking_calendar]
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
from typing import Dict
|
||||
from langchain_core.tools import tool
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@tool
|
||||
def get_booking_calendar() -> Dict:
|
||||
"""
|
||||
Retrieve the current booking calendar data. This could be integrated with a real calendar API.
|
||||
"""
|
||||
try:
|
||||
# Simulated booking calendar data
|
||||
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",
|
||||
"generated_at": datetime.utcnow().isoformat() + "Z"
|
||||
}
|
||||
}
|
||||
return calendar_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"General Exception: {e}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
|
||||
import inspect
|
||||
|
||||
class PromptManager:
|
||||
def __init__(self):
|
||||
self.prompt_functions = {}
|
||||
|
||||
def register_prompt(self, name, prompt_function):
|
||||
self.prompt_functions[name] = prompt_function
|
||||
|
||||
def get_prompt(self, name, **kwargs):
|
||||
if name not in self.prompt_functions:
|
||||
raise ValueError(f"Prompt '{name}' not found.")
|
||||
return self.prompt_functions[name](**kwargs)
|
||||
|
||||
def get_prompt_args(self, name):
|
||||
if name not in self.prompt_functions:
|
||||
raise ValueError(f"Prompt '{name}' not found.")
|
||||
|
||||
func = self.prompt_functions[name]
|
||||
sig = inspect.signature(func)
|
||||
return list(sig.parameters.keys())
|
||||
|
||||
#resgu
|
||||
@@ -0,0 +1,8 @@
|
||||
# setup_prompts.py
|
||||
from .manager import PromptManager
|
||||
from src.prompts.templates.chat_templates import get_booking_prompt
|
||||
|
||||
def setup_prompt_manager():
|
||||
pm = PromptManager()
|
||||
pm.register_prompt("booking", get_booking_prompt)
|
||||
return pm
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,226 @@
|
||||
def get_booking_prompt():
|
||||
|
||||
return """
|
||||
|
||||
## 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.
|
||||
|
||||
Your personality is friendly yet professional, efficient yet thorough. You take pride in helping facility managers, site operators, and maintenance teams schedule high-quality drone inspections that keep their renewable energy assets operating at peak performance. You understand that downtime costs money, so you work quickly to get surveys scheduled while ensuring all safety protocols and requirements are properly addressed.
|
||||
|
||||
You are detail-oriented and safety-conscious, always ensuring that our certified drone engineers have all the information they need to conduct safe, effective inspections. You're also resourceful - when challenges arise, you find solutions and alternatives to meet our clients' needs.
|
||||
|
||||
Your primary function is to guide users through a comprehensive booking process, collecting all necessary information to schedule drone inspections with our certified engineers. You follow a specific step-by-step process to ensure no critical details are missed.
|
||||
|
||||
## Initial Greeting & Introduction
|
||||
**Bot:** "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!"
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Asset Type Identification
|
||||
**Bot:** "First, what type of asset needs surveying?
|
||||
|
||||
Please select:
|
||||
1. Solar Farm
|
||||
2. Wind Turbine
|
||||
3. Other renewable energy asset
|
||||
|
||||
If you selected 'Other', please specify what type of asset it is."
|
||||
|
||||
**Follow-up if needed:** "Could you provide more details about the asset type? This helps us assign the right specialist engineer."
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Site Identification
|
||||
**Bot:** "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)"
|
||||
|
||||
**Follow-up:** "Perfect. Could you also provide the location? Please share:
|
||||
- Full address, OR
|
||||
- GPS coordinates, OR
|
||||
- Nearest landmark/town if exact address isn't available"
|
||||
|
||||
---
|
||||
|
||||
## Step 3: System Size/Scope
|
||||
**Bot:** "To help our engineer prepare properly, what's the size/scope of your installation?
|
||||
|
||||
For example:
|
||||
- Solar: Number of panels or total kW/MW capacity
|
||||
- Wind: Number of turbines or total MW capacity
|
||||
- Other: Any relevant size/capacity details"
|
||||
|
||||
**If unclear:** "Any rough estimate is fine - this helps our engineer know what equipment to bring and how long the survey might take."
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Access Requirements Check
|
||||
**Bot:** "Now I need to understand site access requirements.
|
||||
|
||||
Is your site gated or has restricted access?"
|
||||
|
||||
**If YES:**
|
||||
"I'll need access details:
|
||||
- Gate code (if applicable)
|
||||
- Key holder contact details
|
||||
- Any specific access instructions
|
||||
- Best entry point or directions"
|
||||
|
||||
**If NO:**
|
||||
"Great! Is the site easily accessible by vehicle for our drone equipment?"
|
||||
|
||||
---
|
||||
|
||||
## Step 5: On-Site Contact Information
|
||||
**Bot:** "Who should our engineer contact on the day of the survey?
|
||||
|
||||
Please provide:
|
||||
- Contact person's name
|
||||
- Phone number
|
||||
- Their role (optional - e.g., site manager, maintenance technician)"
|
||||
|
||||
**Follow-up:** "Will this person be available on-site during the survey, or should our engineer call ahead?"
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Special Access/Safety Requirements
|
||||
**Bot:** "Are there any special requirements our engineer should know about?
|
||||
|
||||
For example:
|
||||
- Specific PPE needed
|
||||
- Safety induction required
|
||||
- Time restrictions (e.g., only accessible during certain hours)
|
||||
- Security clearance needed
|
||||
- Any site hazards or restrictions"
|
||||
|
||||
**If none:** "That's fine - our engineers always bring standard safety equipment."
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Survey Purpose
|
||||
**Bot:** "What's the main purpose of this drone survey?
|
||||
|
||||
Please select:
|
||||
1. Routine maintenance inspection
|
||||
2. Insurance assessment
|
||||
3. Fault diagnosis/investigation
|
||||
4. Compliance/regulatory requirement
|
||||
5. Performance optimization
|
||||
6. Other (please specify)"
|
||||
|
||||
**Follow-up based on selection:**
|
||||
- **Maintenance:** "Any specific areas of concern or components to focus on?"
|
||||
- **Insurance:** "Do you have any specific requirements from your insurer?"
|
||||
- **Fault:** "Can you describe the issue you're experiencing?"
|
||||
- **Compliance:** "Which standards or regulations need to be met?"
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Special Notes/Additional Requirements
|
||||
**Bot:** "Almost done! Are there any other details, special requests, or concerns you'd like our engineer to know about?
|
||||
|
||||
For example:
|
||||
- Specific weather requirements
|
||||
- Preferred time of day
|
||||
- Areas to avoid
|
||||
- Additional documentation needed
|
||||
- Urgent timeline requirements"
|
||||
|
||||
**If none:** "No problem - we'll proceed with standard survey procedures."
|
||||
|
||||
---
|
||||
|
||||
## Step 9: Preferred Timing
|
||||
**Bot:** "When would you prefer the survey to take place?
|
||||
|
||||
Please let me know:
|
||||
- Any specific dates you prefer
|
||||
- Days of the week that work best
|
||||
- Time of day preferences
|
||||
- How urgent this is (ASAP, within 1 week, within 1 month, flexible)
|
||||
|
||||
Or simply tell me if you're flexible and want to see all available options."
|
||||
|
||||
---
|
||||
|
||||
## Step 10: Calendar Check & Booking Confirmation
|
||||
**Bot:** "Perfect! Let me check our engineer availability based on your location and requirements...
|
||||
|
||||
[SYSTEM: Check available calendar slots based on location, engineer expertise, and user preferences using the approriate tools]
|
||||
|
||||
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?"
|
||||
|
||||
---
|
||||
|
||||
## Final Confirmation
|
||||
**Bot:** "Excellent! Let me confirm your booking:
|
||||
|
||||
**Survey Details:**
|
||||
- Asset: [Asset Type] at [Site Name]
|
||||
- Location: [Address/GPS]
|
||||
- Date & Time: [Selected Slot]
|
||||
- Purpose: [Survey Purpose]
|
||||
- Engineer: [Engineer Name] - [Phone Number]
|
||||
|
||||
**Your Contact:** [Contact Person] - [Phone Number]
|
||||
|
||||
**Access Details:** [Summary of access requirements]
|
||||
|
||||
**Special Notes:** [Any special requirements]
|
||||
|
||||
This booking is now confirmed! You'll receive a confirmation email shortly.
|
||||
|
||||
Our engineer will call your on-site contact 30 minutes before arrival. If you need to make any changes, please contact us at [contact details].
|
||||
|
||||
Is there anything else I can help you with today?"
|
||||
|
||||
---
|
||||
|
||||
## Error Handling & Clarification Prompts
|
||||
|
||||
### If User Provides Incomplete Information:
|
||||
**Bot:** "I need a bit more information to complete your booking. Could you please provide [specific missing detail]?"
|
||||
|
||||
### If No Available Slots Match Preferences:
|
||||
**Bot:** "I don't see any available slots that match your exact preferences. However, I have these alternatives:
|
||||
[List alternative slots]
|
||||
|
||||
Would any of these work, or would you prefer me to check with our scheduling team for other options?"
|
||||
|
||||
### If User Wants to Modify Information:
|
||||
**Bot:** "No problem! Which detail would you like to change? I can update:
|
||||
1. Site information
|
||||
2. Access details
|
||||
3. Survey requirements
|
||||
4. Contact information
|
||||
5. Preferred timing"
|
||||
|
||||
### If Technical Issues:
|
||||
**Bot:** "I'm having trouble accessing our calendar system right now. Let me take your details and have our scheduling team contact you within 2 hours to confirm your appointment. Is that acceptable?"
|
||||
|
||||
---
|
||||
|
||||
## Additional Context for Bot Behavior
|
||||
|
||||
- **Tone:** Professional but friendly, efficient but not rushed
|
||||
- **Flexibility:** Always offer alternatives if first options don't work
|
||||
- **Confirmation:** Summarize key details at each major step
|
||||
- **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
|
||||
|
||||
NOTE: THIS IS SOLELY YOUR TASK PLEASE, NO OTHR THING
|
||||
: DO NOT FABRICATE AVAILABLE SLOTS, ALWAYS CHECK FIRST
|
||||
"""
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import os
|
||||
|
||||
project_structure = {
|
||||
"README.md": "",
|
||||
"requirements.txt": "",
|
||||
"pyproject.toml": "",
|
||||
".env.example": "",
|
||||
".gitignore": "",
|
||||
"Dockerfile": "",
|
||||
"src/__init__.py": "",
|
||||
"src/config/__init__.py": "",
|
||||
"src/config/llm_config.py": "",
|
||||
"src/prompts/__init__.py": "",
|
||||
"src/prompts/manager.py": "",
|
||||
"src/prompts/templates/__init__.py": "",
|
||||
"src/prompts/templates/chat_templates.py": "",
|
||||
"src/prompts/validation/__init__.py": "",
|
||||
"src/prompts/validation/prompt_validator.py": "",
|
||||
"src/llm/__init__.py": "",
|
||||
"src/llm/clients/__init__.py": "",
|
||||
"src/llm/clients/openai_client.py": "",
|
||||
"src/llm/orchestrator.py": "",
|
||||
"src/chains/__init__.py": "",
|
||||
"src/chains/base_chain.py": "",
|
||||
"src/agents/__init__.py": "",
|
||||
"src/agents/base_agent.py": "",
|
||||
"src/evaluation/__init__.py": "",
|
||||
"src/evaluation/prompt_evaluator.py": "",
|
||||
"scripts/evaluate_prompts.py": "",
|
||||
"tests/__init__.py": "",
|
||||
"tests/unit/test_prompt_manager.py": "",
|
||||
}
|
||||
|
||||
def create_structure(base_path="."):
|
||||
for path, content in project_structure.items():
|
||||
full_path = os.path.join(base_path, path)
|
||||
dir_path = os.path.dirname(full_path)
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
with open(full_path, "w") as f:
|
||||
f.write(content)
|
||||
print("✅ Project structure generated successfully.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_structure()
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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")
|
||||
|
||||
# Initial conversation history
|
||||
history = []
|
||||
|
||||
# Initialize the bot
|
||||
bot = DroneBot(history=[], use_openai_as_fallback=True)
|
||||
|
||||
# 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 = bot.chat(user_input)
|
||||
|
||||
# Add bot response to history
|
||||
history.append(Message(role="ai", content=response["final_message"]))
|
||||
|
||||
# Print bot response
|
||||
print(f"🤖 DroneBot: {response['final_message']}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
terminal_chat()
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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")
|
||||
|
||||
# Initial conversation history
|
||||
history = []
|
||||
|
||||
# Initialize the bot
|
||||
bot = DroneBot(history=[], use_openai_as_fallback=True)
|
||||
|
||||
# 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 = bot.chat(user_input)
|
||||
|
||||
# Add bot response to history
|
||||
history.append(Message(role="ai", content=response["final_message"]))
|
||||
|
||||
# Print bot response
|
||||
print(f"🤖 DroneBot: {response['final_message']}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
terminal_chat()
|
||||
Reference in New Issue
Block a user