Lots of tweaks; in progress is_bot => bot_instance_id
This commit is contained in:
parent
71555c5230
commit
4b33b40637
@ -179,7 +179,7 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
|
||||
switch (param.type) {
|
||||
case "boolean":
|
||||
return (
|
||||
<div key={param.name} className="config-parameter">
|
||||
<div className="config-parameter">
|
||||
<label className="config-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -194,7 +194,7 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
|
||||
|
||||
case "select":
|
||||
return (
|
||||
<div key={param.name} className="config-parameter">
|
||||
<div className="config-parameter">
|
||||
<label className="config-label">{param.label}</label>
|
||||
<select
|
||||
value={value || ""}
|
||||
@ -213,7 +213,7 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
|
||||
|
||||
case "range":
|
||||
return (
|
||||
<div key={param.name} className="config-parameter">
|
||||
<div className="config-parameter">
|
||||
<label className="config-label">{param.label}</label>
|
||||
<div className="range-container">
|
||||
<input
|
||||
@ -233,7 +233,7 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
|
||||
|
||||
case "number":
|
||||
return (
|
||||
<div key={param.name} className="config-parameter">
|
||||
<div className="config-parameter">
|
||||
<label className="config-label">{param.label}</label>
|
||||
<input
|
||||
type="number"
|
||||
@ -251,7 +251,7 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
|
||||
case "string":
|
||||
default:
|
||||
return (
|
||||
<div key={param.name} className="config-parameter">
|
||||
<div className="config-parameter">
|
||||
<label className="config-label">{param.label}</label>
|
||||
<input
|
||||
type="text"
|
||||
@ -275,15 +275,15 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
|
||||
<div key={category.name} className="config-category">
|
||||
<h4 className="category-title">{category.name}</h4>
|
||||
<div className="category-parameters">
|
||||
{category.parameters.map((paramName) => {
|
||||
const param = schema.parameters.find((p) => p.name === paramName);
|
||||
return param ? renderParameter(param) : null;
|
||||
})}
|
||||
{category.parameters?.map((paramName) => {
|
||||
const param = schema.parameters?.find((p) => p.name === paramName);
|
||||
return param ? <div key={paramName}>{renderParameter(param)}</div> : null;
|
||||
}) || null}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
} else {
|
||||
return schema.parameters.map(renderParameter);
|
||||
return schema.parameters?.map((param) => <div key={param.name}>{renderParameter(param)}</div>) || null;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -78,7 +78,6 @@ const BotManager: React.FC<BotManagerProps> = ({ lobbyId, onBotAdded, sx }) => {
|
||||
setAddingBot(true);
|
||||
try {
|
||||
const request: BotJoinLobbyRequest = {
|
||||
bot_name: selectedBot,
|
||||
lobby_id: lobbyId,
|
||||
nick: botNick || `${selectedBot}-bot`,
|
||||
provider_id: providers[selectedBot],
|
||||
|
@ -26,6 +26,7 @@ type User = {
|
||||
has_media?: boolean; // Whether this user provides audio/video streams
|
||||
bot_run_id?: string;
|
||||
bot_provider_id?: string;
|
||||
bot_instance_id?: string; // For bot instances
|
||||
};
|
||||
|
||||
type UserListProps = {
|
||||
@ -48,16 +49,12 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
||||
const apiClient = new ApiClient();
|
||||
|
||||
const handleBotLeave = async (user: User) => {
|
||||
if (!user.is_bot) return;
|
||||
if (!user.is_bot || !user.bot_instance_id) return;
|
||||
|
||||
setLeavingBots((prev) => new Set(prev).add(user.session_id));
|
||||
|
||||
try {
|
||||
const request: BotLeaveLobbyRequest = {
|
||||
session_id: user.session_id,
|
||||
};
|
||||
|
||||
await apiClient.requestBotLeaveLobby(request);
|
||||
await apiClient.requestBotLeaveLobby(user.bot_instance_id);
|
||||
console.log(`Bot ${user.name} leave requested successfully`);
|
||||
} catch (error) {
|
||||
console.error("Failed to request bot leave:", error);
|
||||
@ -156,12 +153,12 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
||||
{users?.map((user) => (
|
||||
<Box
|
||||
key={user.session_id}
|
||||
sx={{ display: "flex", flexDirection: "column", alignItems: "center", border: "3px solid magenta" }}
|
||||
sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}
|
||||
className={`UserEntry ${user.local ? "UserSelf" : ""}`}
|
||||
>
|
||||
<div>
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<Box style={{ display: "flex-wrap", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Box style={{ display: "flex-wrap", alignItems: "center" }}>
|
||||
<div className="Name">{user.name ? user.name : user.session_id}</div>
|
||||
{user.protected && (
|
||||
<div
|
||||
@ -176,9 +173,9 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
||||
🤖
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{user.is_bot && !user.local && (
|
||||
<div style={{ display: "flex", gap: "4px" }}>
|
||||
</Box>
|
||||
{user.is_bot && (
|
||||
<Box style={{ display: "flex-wrap", gap: "4px", border: "3px solid magenta" }}>
|
||||
{user.bot_run_id && (
|
||||
<IconButton
|
||||
size="small"
|
||||
@ -194,14 +191,15 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => handleBotLeave(user)}
|
||||
disabled={leavingBots.has(user.session_id)}
|
||||
disabled={leavingBots.has(user.session_id) || !user.bot_instance_id}
|
||||
style={{ fontSize: "0.7em", minWidth: "50px", height: "24px" }}
|
||||
title={!user.bot_instance_id ? "Bot instance ID not available" : "Remove bot from lobby"}
|
||||
>
|
||||
{leavingBots.has(user.session_id) ? "..." : "Leave"}
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
{user.name && !user.live && <div className="NoNetwork"></div>}
|
||||
</div>
|
||||
{user.name && user.live && peers[user.session_id] && (user.local || user.has_media !== false) ? (
|
||||
@ -240,7 +238,7 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
||||
<DialogContent>
|
||||
{selectedBotForConfig && (
|
||||
<BotConfig
|
||||
botName={selectedBotForConfig.name || "unknown"}
|
||||
botName={selectedBotForConfig.name?.replace(/-bot$/, "") || "unknown"}
|
||||
lobbyId={lobbyId}
|
||||
onConfigUpdate={(config) => {
|
||||
console.log("Bot configuration updated:", config);
|
||||
|
@ -46,7 +46,6 @@ export interface BotListResponse {
|
||||
}
|
||||
|
||||
export interface BotJoinLobbyRequest {
|
||||
bot_name: string;
|
||||
lobby_id: string;
|
||||
nick?: string;
|
||||
provider_id?: string;
|
||||
@ -54,9 +53,34 @@ export interface BotJoinLobbyRequest {
|
||||
|
||||
export interface BotJoinLobbyResponse {
|
||||
status: string;
|
||||
bot_instance_id: string;
|
||||
bot_name: string;
|
||||
run_id: string;
|
||||
provider_id: string;
|
||||
session_id: string;
|
||||
}
|
||||
|
||||
export interface BotInstanceModel {
|
||||
bot_instance_id: string;
|
||||
bot_name: string;
|
||||
nick: string;
|
||||
lobby_id: string;
|
||||
session_id: string;
|
||||
provider_id: string;
|
||||
run_id: string;
|
||||
has_media: boolean;
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
export interface BotLeaveLobbyRequest {
|
||||
bot_instance_id: string;
|
||||
}
|
||||
|
||||
export interface BotLeaveLobbyResponse {
|
||||
status: string;
|
||||
bot_instance_id: string;
|
||||
session_id: string;
|
||||
run_id?: string;
|
||||
}
|
||||
|
||||
export interface BotLeaveLobbyRequest {
|
||||
@ -212,11 +236,13 @@ export class ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
async requestBotLeaveLobby(request: BotLeaveLobbyRequest): Promise<BotLeaveLobbyResponse> {
|
||||
return this.request<BotLeaveLobbyResponse>(this.getApiPath("/ai-voicebot/api/bots/leave"), {
|
||||
method: "POST",
|
||||
body: request,
|
||||
});
|
||||
async requestBotLeaveLobby(botInstanceId: string): Promise<BotLeaveLobbyResponse> {
|
||||
return this.request<BotLeaveLobbyResponse>(
|
||||
this.getApiPath(`/ai-voicebot/api/bots/instances/${encodeURIComponent(botInstanceId)}/leave`),
|
||||
{
|
||||
method: "POST",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Auto-generated endpoints will be added here by update-api-client.js
|
||||
|
@ -68,7 +68,6 @@ export class AdvancedApiEvolutionChecker {
|
||||
'POST:/ai-voicebot/api/lobby/{sessionId}',
|
||||
'GET:/ai-voicebot/api/bots/providers',
|
||||
'GET:/ai-voicebot/api/bots',
|
||||
'POST:/ai-voicebot/api/bots/leave',
|
||||
'POST:/ai-voicebot/api/lobby/{session_id}'
|
||||
]);
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
"""Bot API endpoints"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from logger import logger
|
||||
|
||||
# Import shared models with fallback handling
|
||||
# Import shared models - NO FALLBACKS!
|
||||
try:
|
||||
from ...shared.models import (
|
||||
from shared.models import (
|
||||
BotProviderRegisterRequest,
|
||||
BotProviderRegisterResponse,
|
||||
BotProviderListResponse,
|
||||
@ -13,58 +12,12 @@ try:
|
||||
BotJoinLobbyResponse,
|
||||
BotLeaveLobbyRequest,
|
||||
BotLeaveLobbyResponse,
|
||||
BotInstanceModel,
|
||||
)
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
f"Failed to import shared models: {e}. Ensure shared/models.py is accessible and PYTHONPATH is correctly set."
|
||||
)
|
||||
except ImportError:
|
||||
try:
|
||||
from shared.models import (
|
||||
BotProviderRegisterRequest,
|
||||
BotProviderRegisterResponse,
|
||||
BotProviderListResponse,
|
||||
BotListResponse,
|
||||
BotJoinLobbyRequest,
|
||||
BotJoinLobbyResponse,
|
||||
BotLeaveLobbyRequest,
|
||||
BotLeaveLobbyResponse,
|
||||
)
|
||||
except ImportError:
|
||||
# Create dummy models for standalone testing
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
class BotProviderRegisterRequest(BaseModel):
|
||||
base_url: str
|
||||
name: str
|
||||
description: str
|
||||
provider_key: str
|
||||
|
||||
class BotProviderRegisterResponse(BaseModel):
|
||||
provider_id: str
|
||||
|
||||
class BotProviderListResponse(BaseModel):
|
||||
providers: List[dict]
|
||||
|
||||
class BotListResponse(BaseModel):
|
||||
bots: List[dict]
|
||||
providers: Dict[str, str]
|
||||
|
||||
class BotJoinLobbyRequest(BaseModel):
|
||||
lobby_id: str
|
||||
provider_id: Optional[str] = None
|
||||
nick: Optional[str] = None
|
||||
|
||||
class BotJoinLobbyResponse(BaseModel):
|
||||
status: str
|
||||
bot_name: str
|
||||
run_id: str
|
||||
provider_id: str
|
||||
|
||||
class BotLeaveLobbyRequest(BaseModel):
|
||||
session_id: str
|
||||
|
||||
class BotLeaveLobbyResponse(BaseModel):
|
||||
status: str
|
||||
session_id: str
|
||||
run_id: Optional[str] = None
|
||||
|
||||
|
||||
def create_bot_router(bot_manager, session_manager, lobby_manager):
|
||||
@ -107,11 +60,14 @@ def create_bot_router(bot_manager, session_manager, lobby_manager):
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/leave", response_model=BotLeaveLobbyResponse)
|
||||
async def request_bot_leave_lobby(request: BotLeaveLobbyRequest) -> BotLeaveLobbyResponse:
|
||||
"""Request a bot to leave from all lobbies and disconnect"""
|
||||
|
||||
@router.post(
|
||||
"/instances/{bot_instance_id}/leave", response_model=BotLeaveLobbyResponse
|
||||
)
|
||||
async def request_bot_leave_lobby(bot_instance_id: str) -> BotLeaveLobbyResponse:
|
||||
"""Request a bot instance to leave from all lobbies and disconnect"""
|
||||
try:
|
||||
request = BotLeaveLobbyRequest(bot_instance_id=bot_instance_id)
|
||||
return await bot_manager.request_bot_leave(request, session_manager)
|
||||
except ValueError as e:
|
||||
if "not found" in str(e).lower():
|
||||
@ -120,5 +76,16 @@ def create_bot_router(bot_manager, session_manager, lobby_manager):
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/instances/{bot_instance_id}", response_model=dict)
|
||||
async def get_bot_instance(bot_instance_id: str) -> dict:
|
||||
"""Get information about a specific bot instance"""
|
||||
try:
|
||||
return await bot_manager.get_bot_instance(bot_instance_id)
|
||||
except ValueError as e:
|
||||
if "not found" in str(e).lower():
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
return router
|
||||
|
@ -16,46 +16,14 @@ from pathlib import Path
|
||||
|
||||
from logger import logger
|
||||
|
||||
# Import shared models with fallback handling
|
||||
try:
|
||||
from ...shared.models import (
|
||||
BotConfigSchema,
|
||||
BotConfigParameter,
|
||||
BotLobbyConfig,
|
||||
BotConfigUpdateRequest,
|
||||
BotConfigUpdateResponse,
|
||||
BotConfigListResponse,
|
||||
BotInfoModel
|
||||
)
|
||||
except ImportError:
|
||||
try:
|
||||
from shared.models import (
|
||||
BotConfigSchema,
|
||||
BotConfigParameter,
|
||||
BotLobbyConfig,
|
||||
BotConfigUpdateRequest,
|
||||
BotConfigUpdateResponse,
|
||||
BotConfigListResponse,
|
||||
BotInfoModel
|
||||
)
|
||||
except ImportError:
|
||||
# Create dummy models for standalone testing
|
||||
from pydantic import BaseModel
|
||||
|
||||
class BotConfigSchema(BaseModel):
|
||||
bot_name: str
|
||||
version: str = "1.0"
|
||||
parameters: List[Dict[str, Any]]
|
||||
|
||||
class BotLobbyConfig(BaseModel):
|
||||
bot_name: str
|
||||
lobby_id: str
|
||||
provider_id: str
|
||||
config_values: Dict[str, Any]
|
||||
created_at: float
|
||||
updated_at: float
|
||||
created_by: str
|
||||
# Import shared models
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
)
|
||||
from shared.models import BotConfigSchema, BotLobbyConfig
|
||||
|
||||
class BotConfigManager:
|
||||
"""Manages bot configurations for lobbies"""
|
||||
|
@ -9,52 +9,28 @@ from typing import Dict, List, Optional
|
||||
from pydantic import ValidationError
|
||||
from logger import logger
|
||||
|
||||
# Import shared models with fallback handling
|
||||
try:
|
||||
from ...shared.models import (
|
||||
BotProviderModel,
|
||||
BotProviderRegisterRequest,
|
||||
BotProviderRegisterResponse,
|
||||
BotProviderListResponse,
|
||||
BotListResponse,
|
||||
BotInfoModel,
|
||||
BotJoinLobbyRequest,
|
||||
BotJoinLobbyResponse,
|
||||
BotLeaveLobbyRequest,
|
||||
BotLeaveLobbyResponse,
|
||||
BotProviderBotsResponse,
|
||||
BotProviderJoinResponse,
|
||||
BotJoinPayload,
|
||||
)
|
||||
except ImportError:
|
||||
try:
|
||||
# Try direct import (when PYTHONPATH is set)
|
||||
from shared.models import (
|
||||
BotProviderModel,
|
||||
BotProviderRegisterRequest,
|
||||
BotProviderRegisterResponse,
|
||||
BotProviderListResponse,
|
||||
BotListResponse,
|
||||
BotInfoModel,
|
||||
BotJoinLobbyRequest,
|
||||
BotJoinLobbyResponse,
|
||||
BotLeaveLobbyRequest,
|
||||
BotLeaveLobbyResponse,
|
||||
BotProviderBotsResponse,
|
||||
BotProviderJoinResponse,
|
||||
BotJoinPayload,
|
||||
)
|
||||
except ImportError:
|
||||
# Log a warning for debugging (optional)
|
||||
import warnings
|
||||
# Import shared models
|
||||
import sys
|
||||
|
||||
warnings.warn(
|
||||
"Relative import failed, ensure PYTHONPATH includes project root or run as package"
|
||||
)
|
||||
# Rely on environment setup or raise a clear error
|
||||
raise ImportError(
|
||||
"Cannot import shared.models. Ensure the project is run as a package or PYTHONPATH is set."
|
||||
)
|
||||
sys.path.append(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
)
|
||||
from shared.models import (
|
||||
BotProviderModel,
|
||||
BotProviderRegisterRequest,
|
||||
BotProviderRegisterResponse,
|
||||
BotProviderListResponse,
|
||||
BotListResponse,
|
||||
BotInfoModel,
|
||||
BotJoinLobbyRequest,
|
||||
BotJoinLobbyResponse,
|
||||
BotLeaveLobbyRequest,
|
||||
BotLeaveLobbyResponse,
|
||||
BotProviderBotsResponse,
|
||||
BotProviderJoinResponse,
|
||||
BotJoinPayload,
|
||||
BotInstanceModel,
|
||||
)
|
||||
|
||||
|
||||
class BotProviderConfig:
|
||||
@ -94,6 +70,9 @@ class BotManager:
|
||||
|
||||
def __init__(self):
|
||||
self.bot_providers: Dict[str, BotProviderModel] = {}
|
||||
self.bot_instances: Dict[
|
||||
str, BotInstanceModel
|
||||
] = {} # bot_instance_id -> BotInstanceModel
|
||||
self.lock = threading.RLock()
|
||||
|
||||
# Check if provider authentication is enabled
|
||||
@ -311,13 +290,37 @@ class BotManager:
|
||||
bot_session.bot_run_id = run_id
|
||||
bot_session.bot_provider_id = target_provider_id
|
||||
|
||||
logger.info(f"Bot {bot_name} requested to join lobby {request.lobby_id}")
|
||||
# Create a unique bot instance ID and track the bot instance
|
||||
bot_instance_id = str(uuid.uuid4())
|
||||
bot_instance = BotInstanceModel(
|
||||
bot_instance_id=bot_instance_id,
|
||||
bot_name=bot_name,
|
||||
nick=bot_nick,
|
||||
lobby_id=request.lobby_id,
|
||||
session_id=bot_session_id,
|
||||
provider_id=target_provider_id,
|
||||
run_id=run_id,
|
||||
has_media=bot_has_media,
|
||||
created_at=time.time(),
|
||||
)
|
||||
|
||||
# Set the bot_instance_id on the session as well
|
||||
bot_session.bot_instance_id = bot_instance_id
|
||||
|
||||
with self.lock:
|
||||
self.bot_instances[bot_instance_id] = bot_instance
|
||||
|
||||
logger.info(
|
||||
f"Bot {bot_name} requested to join lobby {request.lobby_id} with instance ID {bot_instance_id}"
|
||||
)
|
||||
|
||||
return BotJoinLobbyResponse(
|
||||
status="requested",
|
||||
bot_instance_id=bot_instance_id,
|
||||
bot_name=bot_name,
|
||||
run_id=run_id,
|
||||
provider_id=target_provider_id,
|
||||
session_id=bot_session_id,
|
||||
)
|
||||
except ValidationError as e:
|
||||
logger.error(f"Invalid response from bot provider: {e}")
|
||||
@ -334,61 +337,98 @@ class BotManager:
|
||||
|
||||
async def request_bot_leave(self, request: BotLeaveLobbyRequest, session_manager) -> BotLeaveLobbyResponse:
|
||||
"""Request a bot to leave from all lobbies and disconnect"""
|
||||
|
||||
|
||||
# Find the bot instance
|
||||
with self.lock:
|
||||
if request.bot_instance_id not in self.bot_instances:
|
||||
raise ValueError("Bot instance not found")
|
||||
bot_instance = self.bot_instances[request.bot_instance_id]
|
||||
|
||||
# Find the bot session
|
||||
bot_session = session_manager.get_session(request.session_id)
|
||||
bot_session = session_manager.get_session(bot_instance.session_id)
|
||||
if not bot_session:
|
||||
raise ValueError("Bot session not found")
|
||||
|
||||
if not bot_session.is_bot:
|
||||
raise ValueError("Session is not a bot")
|
||||
|
||||
run_id = bot_session.bot_run_id
|
||||
provider_id = bot_session.bot_provider_id
|
||||
logger.info(
|
||||
f"Requesting bot instance {bot_instance.bot_instance_id} to leave all lobbies"
|
||||
)
|
||||
|
||||
logger.info(f"Requesting bot {bot_session.getName()} to leave all lobbies")
|
||||
|
||||
# Try to stop the bot at the provider level if we have the information
|
||||
if provider_id and run_id:
|
||||
# Try to stop the bot at the provider level
|
||||
try:
|
||||
with self.lock:
|
||||
if provider_id in self.bot_providers:
|
||||
provider = self.bot_providers[provider_id]
|
||||
if bot_instance.provider_id in self.bot_providers:
|
||||
provider = self.bot_providers[bot_instance.provider_id]
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
f"{provider.base_url}/bots/runs/{run_id}/stop",
|
||||
f"{provider.base_url}/bots/runs/{bot_instance.run_id}/stop",
|
||||
timeout=5.0,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
logger.info(f"Successfully requested bot provider to stop run {run_id}")
|
||||
logger.info(
|
||||
f"Successfully requested bot provider to stop run {bot_instance.run_id}"
|
||||
)
|
||||
else:
|
||||
logger.warning(f"Bot provider returned error when stopping: HTTP {response.status_code}")
|
||||
logger.warning(
|
||||
f"Bot provider returned error when stopping: HTTP {response.status_code}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to request bot stop from provider: {e}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error communicating with bot provider: {e}")
|
||||
|
||||
# Force disconnect the bot session from all lobbies
|
||||
lobbies_to_leave = bot_session.lobbies[:]
|
||||
try:
|
||||
lobbies_to_leave = bot_session.lobbies[:]
|
||||
|
||||
for lobby in lobbies_to_leave:
|
||||
try:
|
||||
await bot_session.leave_lobby(lobby)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error removing bot from lobby {lobby.name}: {e}")
|
||||
for lobby in lobbies_to_leave:
|
||||
try:
|
||||
await bot_session.leave_lobby(lobby)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error removing bot from lobby {lobby.name}: {e}")
|
||||
|
||||
# Close WebSocket connection if it exists
|
||||
if bot_session.ws:
|
||||
try:
|
||||
await bot_session.ws.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error closing bot WebSocket: {e}")
|
||||
bot_session.ws = None
|
||||
# Close WebSocket connection if it exists
|
||||
if bot_session.ws:
|
||||
try:
|
||||
await bot_session.ws.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error closing bot WebSocket: {e}")
|
||||
bot_session.ws = None
|
||||
except Exception as e:
|
||||
logger.warning(f"Error disconnecting bot session: {e}")
|
||||
|
||||
# Remove bot instance from tracking
|
||||
with self.lock:
|
||||
if request.bot_instance_id in self.bot_instances:
|
||||
del self.bot_instances[request.bot_instance_id]
|
||||
|
||||
return BotLeaveLobbyResponse(
|
||||
status="disconnected",
|
||||
session_id=request.session_id,
|
||||
run_id=run_id,
|
||||
bot_instance_id=request.bot_instance_id,
|
||||
session_id=bot_instance.session_id,
|
||||
run_id=bot_instance.run_id,
|
||||
)
|
||||
|
||||
|
||||
async def get_bot_instance(self, bot_instance_id: str) -> dict:
|
||||
"""Get information about a specific bot instance"""
|
||||
with self.lock:
|
||||
if bot_instance_id not in self.bot_instances:
|
||||
raise ValueError("Bot instance not found")
|
||||
bot_instance = self.bot_instances[bot_instance_id]
|
||||
|
||||
return bot_instance.model_dump()
|
||||
|
||||
def get_bot_instance_id_by_session_id(self, session_id: str) -> Optional[str]:
|
||||
"""Get bot_instance_id by session_id"""
|
||||
with self.lock:
|
||||
for bot_instance_id, bot_instance in self.bot_instances.items():
|
||||
if bot_instance.session_id == session_id:
|
||||
return bot_instance_id
|
||||
return None
|
||||
|
||||
def get_provider(self, provider_id: str) -> Optional[BotProviderModel]:
|
||||
"""Get a specific bot provider by ID"""
|
||||
with self.lock:
|
||||
|
@ -24,17 +24,9 @@ except ImportError:
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
from shared.models import ChatMessageModel, ParticipantModel
|
||||
except ImportError:
|
||||
# Fallback: create minimal models for testing
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
class ChatMessageModel(BaseModel):
|
||||
id: str
|
||||
author: str
|
||||
message: str
|
||||
timestamp: float
|
||||
class ParticipantModel(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
raise ImportError(
|
||||
f"Failed to import shared models: {e}. Ensure shared/models.py is accessible and PYTHONPATH is correctly set."
|
||||
)
|
||||
|
||||
from logger import logger
|
||||
|
||||
|
@ -27,24 +27,9 @@ except ImportError:
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
from shared.models import SessionSaved, LobbySaved, SessionsPayload, NamePasswordRecord
|
||||
except ImportError:
|
||||
# Fallback: create minimal models for testing
|
||||
from pydantic import BaseModel
|
||||
class SessionSaved(BaseModel):
|
||||
id: str
|
||||
name: str = ""
|
||||
protected: bool = False
|
||||
is_bot: bool = False
|
||||
has_media: bool = True
|
||||
bot_run_id: Optional[str] = None
|
||||
lobbies: List[str] = []
|
||||
class LobbySaved(BaseModel):
|
||||
name: str
|
||||
private: bool = False
|
||||
class SessionsPayload(BaseModel):
|
||||
sessions: List[SessionSaved]
|
||||
class NamePasswordRecord(BaseModel):
|
||||
name: str
|
||||
password: str
|
||||
raise ImportError(
|
||||
f"Failed to import shared models: {e}. Ensure shared/models.py is accessible and PYTHONPATH is correctly set."
|
||||
)
|
||||
|
||||
from logger import logger
|
||||
|
||||
@ -106,6 +91,7 @@ class Session:
|
||||
self.has_media = has_media # Whether this session provides audio/video streams
|
||||
self.bot_run_id: Optional[str] = None # Bot run ID for tracking
|
||||
self.bot_provider_id: Optional[str] = None # Bot provider ID
|
||||
self.bot_instance_id: Optional[str] = None # Bot instance ID for tracking
|
||||
self.session_lock = threading.RLock() # Instance-level lock
|
||||
|
||||
def getName(self) -> str:
|
||||
@ -208,6 +194,24 @@ class Session:
|
||||
session_name=self.name or self.short
|
||||
))
|
||||
|
||||
def model_dump(self) -> Dict[str, Any]:
|
||||
"""Convert session to dictionary format for API responses"""
|
||||
with self.session_lock:
|
||||
data: Dict[str, Any] = {
|
||||
"id": self.id,
|
||||
"name": self.name or "",
|
||||
"is_bot": self.is_bot,
|
||||
"has_media": self.has_media,
|
||||
"created_at": self.created_at,
|
||||
"last_used": self.last_used,
|
||||
}
|
||||
|
||||
# Include bot_instance_id if this is a bot session and it has one
|
||||
if self.is_bot and self.bot_instance_id:
|
||||
data["bot_instance_id"] = self.bot_instance_id
|
||||
|
||||
return data
|
||||
|
||||
def to_saved(self) -> SessionSaved:
|
||||
"""Convert session to saved format for persistence"""
|
||||
with self.session_lock:
|
||||
@ -228,6 +232,7 @@ class Session:
|
||||
has_media=self.has_media,
|
||||
bot_run_id=self.bot_run_id,
|
||||
bot_provider_id=self.bot_provider_id,
|
||||
bot_instance_id=self.bot_instance_id,
|
||||
)
|
||||
|
||||
|
||||
|
@ -321,6 +321,7 @@ class SessionSaved(BaseModel):
|
||||
has_media: bool = True # Whether this session provides audio/video streams
|
||||
bot_run_id: Optional[str] = None # Bot run ID for tracking
|
||||
bot_provider_id: Optional[str] = None # Bot provider ID
|
||||
bot_instance_id: Optional[str] = None # Bot instance ID for tracking
|
||||
|
||||
|
||||
class SessionsPayload(BaseModel):
|
||||
@ -467,7 +468,6 @@ class BotListResponse(BaseModel):
|
||||
class BotJoinLobbyRequest(BaseModel):
|
||||
"""Request to make a bot join a lobby"""
|
||||
|
||||
bot_name: str
|
||||
lobby_id: str
|
||||
nick: str = ""
|
||||
provider_id: Optional[str] = None # Optional: specify which provider to use
|
||||
@ -487,9 +487,11 @@ class BotJoinLobbyResponse(BaseModel):
|
||||
"""Response after requesting a bot to join a lobby"""
|
||||
|
||||
status: str
|
||||
bot_name: str
|
||||
bot_instance_id: str # Unique ID for this bot instance
|
||||
bot_name: str # Bot type name
|
||||
run_id: str
|
||||
provider_id: str
|
||||
session_id: str # Session ID in the lobby
|
||||
|
||||
|
||||
class BotProviderJoinResponse(BaseModel):
|
||||
@ -503,12 +505,27 @@ class BotProviderJoinResponse(BaseModel):
|
||||
class BotLeaveLobbyRequest(BaseModel):
|
||||
"""Request to make a bot leave a lobby"""
|
||||
|
||||
session_id: str # The session ID of the bot to remove
|
||||
bot_instance_id: str # The unique bot instance ID (not session_id)
|
||||
|
||||
|
||||
class BotLeaveLobbyResponse(BaseModel):
|
||||
"""Response after requesting a bot to leave a lobby"""
|
||||
|
||||
status: str
|
||||
bot_instance_id: str
|
||||
session_id: str
|
||||
run_id: Optional[str] = None
|
||||
|
||||
|
||||
class BotInstanceModel(BaseModel):
|
||||
"""Model representing a bot instance in a lobby"""
|
||||
|
||||
bot_instance_id: str
|
||||
bot_name: str # Bot type name
|
||||
nick: str
|
||||
lobby_id: str
|
||||
session_id: str
|
||||
provider_id: str
|
||||
run_id: str
|
||||
has_media: bool
|
||||
created_at: float # timestamp
|
||||
|
@ -191,7 +191,13 @@ async def check_provider_registration(server_url: str, provider_id: str, insecur
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
providers = data.get("providers", {})
|
||||
return provider_id in [p.get("provider_id") for p in providers.values()]
|
||||
# providers is Dict[bot_name, provider_id], so check if our provider_id is in the values
|
||||
is_registered = provider_id in providers.values()
|
||||
logger.debug(f"Registration check: provider_id={provider_id}, providers={providers}, is_registered={is_registered}")
|
||||
return is_registered
|
||||
else:
|
||||
logger.warning(f"Registration check failed: HTTP {response.status_code}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.debug(f"Provider registration check failed: {e}")
|
||||
return False
|
||||
|
@ -265,8 +265,8 @@ async def handle_chat_message(
|
||||
try:
|
||||
# Initialize bot instance if needed
|
||||
if _bot_instance is None:
|
||||
_bot_instance = EnhancedAIChatbot(chat_message.nick)
|
||||
logger.info(f"Initialized enhanced AI chatbot for session: {chat_message.nick}")
|
||||
_bot_instance = EnhancedAIChatbot(chat_message.sender_name)
|
||||
logger.info(f"Initialized enhanced AI chatbot for session: {chat_message.sender_name}")
|
||||
|
||||
# Generate response
|
||||
response = await _bot_instance.generate_response(chat_message.message)
|
||||
@ -274,7 +274,7 @@ async def handle_chat_message(
|
||||
# Send response
|
||||
if response:
|
||||
await send_message_func(response)
|
||||
logger.info(f"AI Chatbot responded to {chat_message.nick}: {response[:100]}...")
|
||||
logger.info(f"AI Chatbot responded to {chat_message.sender_name}: {response[:100]}...")
|
||||
|
||||
return response
|
||||
|
||||
|
@ -1,477 +0,0 @@
|
||||
"""Step 5B Integration: Enhanced Bot Orchestrator with Advanced Bot Management.
|
||||
|
||||
This module demonstrates how the new advanced bot management features integrate
|
||||
with the existing bot orchestrator to provide:
|
||||
|
||||
1. AI Provider-powered bots with multiple backend support
|
||||
2. Personality-driven bot behavior and responses
|
||||
3. Conversation context and memory management
|
||||
4. Dynamic bot configuration and health monitoring
|
||||
|
||||
This integration enhances the existing bot discovery and management system
|
||||
without breaking compatibility with existing bot implementations.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import asyncio
|
||||
from typing import Dict, Optional, Any
|
||||
from pathlib import Path
|
||||
|
||||
# Import existing bot orchestrator functionality
|
||||
from bot_orchestrator import discover_bots
|
||||
|
||||
# Import advanced bot management modules
|
||||
try:
|
||||
from voicebot.ai_providers import ai_provider_manager, AIProviderType
|
||||
from voicebot.personality_system import personality_manager
|
||||
from voicebot.conversation_context import context_manager
|
||||
AI_FEATURES_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
print(f"Warning: Advanced AI features not available: {e}")
|
||||
AI_FEATURES_AVAILABLE = False
|
||||
|
||||
from logger import logger
|
||||
|
||||
|
||||
class EnhancedBotOrchestrator:
|
||||
"""Enhanced bot orchestrator with Step 5B advanced management features."""
|
||||
|
||||
def __init__(self):
|
||||
self.enhanced_bots = {} # Enhanced bots with AI features
|
||||
self.bot_configurations = {} # Bot-specific configurations
|
||||
self.health_stats = {} # Health monitoring data
|
||||
|
||||
# Load configurations
|
||||
self._load_bot_configurations()
|
||||
|
||||
# Initialize AI systems if available
|
||||
if AI_FEATURES_AVAILABLE:
|
||||
self._initialize_ai_systems()
|
||||
|
||||
def _load_bot_configurations(self):
|
||||
"""Load bot configurations from JSON file."""
|
||||
config_path = Path(__file__).parent / "enhanced_bot_configs.json"
|
||||
|
||||
default_configs = {
|
||||
"ai_chatbot": {
|
||||
"personality": "helpful_assistant",
|
||||
"ai_provider": "openai",
|
||||
"streaming": True,
|
||||
"memory_enabled": True,
|
||||
"advanced_features": True
|
||||
},
|
||||
"technical_expert": {
|
||||
"personality": "technical_expert",
|
||||
"ai_provider": "anthropic",
|
||||
"streaming": False,
|
||||
"memory_enabled": True,
|
||||
"advanced_features": True
|
||||
},
|
||||
"creative_companion": {
|
||||
"personality": "creative_companion",
|
||||
"ai_provider": "local",
|
||||
"streaming": True,
|
||||
"memory_enabled": True,
|
||||
"advanced_features": True
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
if config_path.exists():
|
||||
with open(config_path, 'r') as f:
|
||||
self.bot_configurations = json.load(f)
|
||||
else:
|
||||
self.bot_configurations = default_configs
|
||||
self._save_bot_configurations()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load bot configurations: {e}")
|
||||
self.bot_configurations = default_configs
|
||||
|
||||
def _save_bot_configurations(self):
|
||||
"""Save bot configurations to JSON file."""
|
||||
config_path = Path(__file__).parent / "enhanced_bot_configs.json"
|
||||
try:
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(self.bot_configurations, f, indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save bot configurations: {e}")
|
||||
|
||||
def _initialize_ai_systems(self):
|
||||
"""Initialize AI provider and personality systems."""
|
||||
try:
|
||||
# Ensure default personality templates are loaded
|
||||
personality_manager.ensure_default_templates()
|
||||
|
||||
# Register available AI providers based on environment
|
||||
providers_to_init = []
|
||||
|
||||
if os.getenv("OPENAI_API_KEY"):
|
||||
providers_to_init.append(AIProviderType.OPENAI)
|
||||
|
||||
if os.getenv("ANTHROPIC_API_KEY"):
|
||||
providers_to_init.append(AIProviderType.ANTHROPIC)
|
||||
|
||||
# Local provider is always available
|
||||
providers_to_init.append(AIProviderType.LOCAL)
|
||||
|
||||
for provider_type in providers_to_init:
|
||||
try:
|
||||
provider = ai_provider_manager.create_provider(provider_type)
|
||||
ai_provider_manager.register_provider(f"system_{provider_type.value}", provider)
|
||||
logger.info(f"Initialized AI provider: {provider_type.value}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize provider {provider_type.value}: {e}")
|
||||
|
||||
logger.info("AI systems initialized successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize AI systems: {e}")
|
||||
|
||||
async def discover_enhanced_bots(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""Discover bots with enhanced information about AI capabilities."""
|
||||
# Start with standard bot discovery
|
||||
standard_bots = discover_bots() # Returns List[BotInfoModel]
|
||||
enhanced_bot_info = {}
|
||||
|
||||
# Convert BotInfoModel list to dict and enhance with AI capabilities
|
||||
for bot_info in standard_bots:
|
||||
bot_name = bot_info.name
|
||||
bot_info_dict = {
|
||||
"name": bot_name,
|
||||
"description": bot_info.description,
|
||||
"has_media": bot_info.has_media,
|
||||
"standard_info": {
|
||||
"name": bot_name,
|
||||
"description": bot_info.description,
|
||||
"has_media": bot_info.has_media
|
||||
},
|
||||
"enhanced_features": False,
|
||||
"ai_capabilities": {},
|
||||
"health_status": "unknown"
|
||||
}
|
||||
|
||||
# Check if bot supports enhanced features
|
||||
if bot_name in self.bot_configurations:
|
||||
config = self.bot_configurations[bot_name]
|
||||
if config.get("advanced_features", False):
|
||||
bot_info_dict["enhanced_features"] = True
|
||||
bot_info_dict["ai_capabilities"] = {
|
||||
"personality": config.get("personality", "default"),
|
||||
"ai_provider": config.get("ai_provider", "local"),
|
||||
"streaming": config.get("streaming", False),
|
||||
"memory_enabled": config.get("memory_enabled", False)
|
||||
}
|
||||
|
||||
# Check bot health if it supports it (would need to import the bot module)
|
||||
try:
|
||||
bot_module_path = f"voicebot.bots.{bot_name}"
|
||||
bot_module = __import__(bot_module_path, fromlist=[bot_name])
|
||||
if hasattr(bot_module, 'get_bot_status'):
|
||||
status = await bot_module.get_bot_status()
|
||||
bot_info_dict["health_status"] = "healthy"
|
||||
bot_info_dict["detailed_status"] = status
|
||||
except Exception as e:
|
||||
bot_info_dict["health_status"] = f"import_error: {e}"
|
||||
|
||||
enhanced_bot_info[bot_name] = bot_info_dict
|
||||
|
||||
return enhanced_bot_info
|
||||
|
||||
async def create_enhanced_bot_instance(self, bot_name: str, session_name: str) -> Optional[Any]:
|
||||
"""Create an enhanced bot instance with AI features configured."""
|
||||
if not AI_FEATURES_AVAILABLE:
|
||||
logger.warning(f"Cannot create enhanced bot {bot_name} - AI features not available")
|
||||
return None
|
||||
|
||||
if bot_name not in self.bot_configurations:
|
||||
logger.warning(f"No configuration found for enhanced bot: {bot_name}")
|
||||
return None
|
||||
|
||||
config = self.bot_configurations[bot_name]
|
||||
|
||||
try:
|
||||
# Set environment variables for the bot based on configuration
|
||||
os.environ[f"{bot_name.upper()}_PERSONALITY"] = config.get("personality", "helpful_assistant")
|
||||
os.environ[f"{bot_name.upper()}_PROVIDER"] = config.get("ai_provider", "local")
|
||||
os.environ[f"{bot_name.upper()}_STREAMING"] = str(config.get("streaming", False)).lower()
|
||||
os.environ[f"{bot_name.upper()}_MEMORY"] = str(config.get("memory_enabled", False)).lower()
|
||||
|
||||
# Import and create the bot
|
||||
bot_module_path = f"voicebot.bots.{bot_name}"
|
||||
bot_module = __import__(bot_module_path, fromlist=[bot_name])
|
||||
|
||||
# If the bot has a specific initialization function, use it
|
||||
if hasattr(bot_module, 'create_enhanced_instance'):
|
||||
bot_instance = await bot_module.create_enhanced_instance(session_name, config)
|
||||
else:
|
||||
# Create standard bot instance
|
||||
bot_instance = bot_module
|
||||
|
||||
self.enhanced_bots[f"{bot_name}_{session_name}"] = {
|
||||
"instance": bot_instance,
|
||||
"config": config,
|
||||
"session": session_name,
|
||||
"created_at": time.time()
|
||||
}
|
||||
|
||||
logger.info(f"Created enhanced bot instance: {bot_name} for session {session_name}")
|
||||
return bot_instance
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create enhanced bot instance {bot_name}: {e}")
|
||||
return None
|
||||
|
||||
async def monitor_bot_health(self) -> Dict[str, Any]:
|
||||
"""Monitor health of all enhanced bots and AI systems."""
|
||||
health_report = {
|
||||
"timestamp": time.time(),
|
||||
"ai_systems_available": AI_FEATURES_AVAILABLE,
|
||||
"enhanced_bots": {},
|
||||
"ai_providers": {},
|
||||
"personality_system": {},
|
||||
"conversation_contexts": {}
|
||||
}
|
||||
|
||||
if not AI_FEATURES_AVAILABLE:
|
||||
health_report["status"] = "limited - AI features disabled"
|
||||
return health_report
|
||||
|
||||
try:
|
||||
# Check AI providers
|
||||
for provider_id, provider in ai_provider_manager.list_providers().items():
|
||||
try:
|
||||
provider_instance = ai_provider_manager.get_provider(provider_id)
|
||||
if provider_instance:
|
||||
is_healthy = await provider_instance.health_check()
|
||||
health_report["ai_providers"][provider_id] = {
|
||||
"status": "healthy" if is_healthy else "unhealthy",
|
||||
"type": provider.value if hasattr(provider, 'value') else str(provider)
|
||||
}
|
||||
except Exception as e:
|
||||
health_report["ai_providers"][provider_id] = {
|
||||
"status": f"error: {e}",
|
||||
"type": "unknown"
|
||||
}
|
||||
|
||||
# Check personality system
|
||||
try:
|
||||
templates = personality_manager.list_templates()
|
||||
health_report["personality_system"] = {
|
||||
"status": "healthy",
|
||||
"available_templates": len(templates),
|
||||
"template_ids": [t.id for t in templates]
|
||||
}
|
||||
except Exception as e:
|
||||
health_report["personality_system"] = {
|
||||
"status": f"error: {e}"
|
||||
}
|
||||
|
||||
# Check conversation context system
|
||||
try:
|
||||
context_stats = context_manager.get_statistics()
|
||||
health_report["conversation_contexts"] = {
|
||||
"status": "healthy",
|
||||
"statistics": context_stats
|
||||
}
|
||||
except Exception as e:
|
||||
health_report["conversation_contexts"] = {
|
||||
"status": f"error: {e}"
|
||||
}
|
||||
|
||||
# Check enhanced bot instances
|
||||
for bot_key, bot_data in self.enhanced_bots.items():
|
||||
try:
|
||||
bot_instance = bot_data["instance"]
|
||||
if hasattr(bot_instance, 'health_check'):
|
||||
bot_health = await bot_instance.health_check()
|
||||
health_report["enhanced_bots"][bot_key] = {
|
||||
"status": "healthy",
|
||||
"details": bot_health,
|
||||
"uptime": time.time() - bot_data["created_at"]
|
||||
}
|
||||
else:
|
||||
health_report["enhanced_bots"][bot_key] = {
|
||||
"status": "unknown - no health check available",
|
||||
"uptime": time.time() - bot_data["created_at"]
|
||||
}
|
||||
except Exception as e:
|
||||
health_report["enhanced_bots"][bot_key] = {
|
||||
"status": f"error: {e}",
|
||||
"uptime": time.time() - bot_data.get("created_at", time.time())
|
||||
}
|
||||
|
||||
health_report["status"] = "operational"
|
||||
|
||||
except Exception as e:
|
||||
health_report["status"] = f"system_error: {e}"
|
||||
|
||||
# Store health stats for trending
|
||||
self.health_stats[int(time.time())] = health_report
|
||||
|
||||
# Keep only last 24 hours of health stats
|
||||
cutoff_time = time.time() - (24 * 60 * 60)
|
||||
self.health_stats = {
|
||||
timestamp: stats for timestamp, stats in self.health_stats.items()
|
||||
if timestamp > cutoff_time
|
||||
}
|
||||
|
||||
return health_report
|
||||
|
||||
async def configure_bot_runtime(self, bot_name: str, new_config: Dict[str, Any]) -> bool:
|
||||
"""Dynamically reconfigure a bot at runtime."""
|
||||
if bot_name not in self.bot_configurations:
|
||||
logger.error(f"Bot {bot_name} not found in configurations")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Update configuration
|
||||
old_config = self.bot_configurations[bot_name].copy()
|
||||
self.bot_configurations[bot_name].update(new_config)
|
||||
|
||||
# Save updated configuration
|
||||
self._save_bot_configurations()
|
||||
|
||||
# If there are active instances, try to update them
|
||||
updated_instances = []
|
||||
for bot_key, bot_data in self.enhanced_bots.items():
|
||||
if bot_key.startswith(f"{bot_name}_"):
|
||||
bot_instance = bot_data["instance"]
|
||||
|
||||
# Try to update personality if changed
|
||||
if "personality" in new_config and hasattr(bot_instance, 'switch_personality'):
|
||||
success = await bot_instance.switch_personality(new_config["personality"])
|
||||
if success:
|
||||
updated_instances.append(f"{bot_key} personality")
|
||||
|
||||
# Try to update AI provider if changed
|
||||
if "ai_provider" in new_config and hasattr(bot_instance, 'switch_ai_provider'):
|
||||
success = await bot_instance.switch_ai_provider(new_config["ai_provider"])
|
||||
if success:
|
||||
updated_instances.append(f"{bot_key} provider")
|
||||
|
||||
# Update bot data configuration
|
||||
bot_data["config"] = self.bot_configurations[bot_name]
|
||||
|
||||
logger.info(f"Bot {bot_name} configuration updated. Active instances updated: {updated_instances}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# Rollback configuration on error
|
||||
self.bot_configurations[bot_name] = old_config
|
||||
logger.error(f"Failed to configure bot {bot_name}: {e}")
|
||||
return False
|
||||
|
||||
def get_bot_analytics(self) -> Dict[str, Any]:
|
||||
"""Get analytics and usage statistics for enhanced bots."""
|
||||
analytics = {
|
||||
"enhanced_bots_count": len(self.enhanced_bots),
|
||||
"configurations_count": len(self.bot_configurations),
|
||||
"health_history_points": len(self.health_stats),
|
||||
"bot_breakdown": {},
|
||||
"feature_usage": {
|
||||
"ai_providers": {},
|
||||
"personalities": {},
|
||||
"memory_enabled": 0,
|
||||
"streaming_enabled": 0
|
||||
}
|
||||
}
|
||||
|
||||
# Analyze bot configurations
|
||||
for bot_name, config in self.bot_configurations.items():
|
||||
analytics["bot_breakdown"][bot_name] = {
|
||||
"enhanced_features": config.get("advanced_features", False),
|
||||
"ai_provider": config.get("ai_provider", "none"),
|
||||
"personality": config.get("personality", "none"),
|
||||
"active_instances": sum(1 for key in self.enhanced_bots.keys() if key.startswith(f"{bot_name}_"))
|
||||
}
|
||||
|
||||
# Count feature usage
|
||||
provider = config.get("ai_provider", "none")
|
||||
analytics["feature_usage"]["ai_providers"][provider] = analytics["feature_usage"]["ai_providers"].get(provider, 0) + 1
|
||||
|
||||
personality = config.get("personality", "none")
|
||||
analytics["feature_usage"]["personalities"][personality] = analytics["feature_usage"]["personalities"].get(personality, 0) + 1
|
||||
|
||||
if config.get("memory_enabled", False):
|
||||
analytics["feature_usage"]["memory_enabled"] += 1
|
||||
|
||||
if config.get("streaming", False):
|
||||
analytics["feature_usage"]["streaming_enabled"] += 1
|
||||
|
||||
# Add conversation context statistics if available
|
||||
if AI_FEATURES_AVAILABLE:
|
||||
try:
|
||||
context_stats = context_manager.get_statistics()
|
||||
analytics["conversation_statistics"] = context_stats
|
||||
except Exception as e:
|
||||
analytics["conversation_statistics"] = {"error": str(e)}
|
||||
|
||||
return analytics
|
||||
|
||||
|
||||
# Global enhanced orchestrator instance
|
||||
enhanced_orchestrator = EnhancedBotOrchestrator()
|
||||
|
||||
|
||||
async def demo_step_5b_integration():
|
||||
"""Demonstrate Step 5B integration capabilities."""
|
||||
print("=== Step 5B Advanced Bot Management Demo ===\n")
|
||||
|
||||
# 1. Discover enhanced bots
|
||||
print("1. Discovering bots with enhanced capabilities...")
|
||||
enhanced_bots = await enhanced_orchestrator.discover_enhanced_bots()
|
||||
for bot_name, info in enhanced_bots.items():
|
||||
print(f" Bot: {bot_name}")
|
||||
print(f" Enhanced: {info['enhanced_features']}")
|
||||
if info['enhanced_features']:
|
||||
print(f" AI Capabilities: {info['ai_capabilities']}")
|
||||
print(f" Health: {info['health_status']}")
|
||||
print()
|
||||
|
||||
# 2. Create enhanced bot instance
|
||||
print("2. Creating enhanced AI chatbot instance...")
|
||||
bot_instance = await enhanced_orchestrator.create_enhanced_bot_instance("ai_chatbot", "demo_session")
|
||||
if bot_instance:
|
||||
print(" ✓ Enhanced AI chatbot created successfully")
|
||||
else:
|
||||
print(" ✗ Failed to create enhanced bot")
|
||||
print()
|
||||
|
||||
# 3. Monitor system health
|
||||
print("3. Monitoring system health...")
|
||||
health_report = await enhanced_orchestrator.monitor_bot_health()
|
||||
print(f" System Status: {health_report['status']}")
|
||||
print(f" AI Features Available: {health_report['ai_systems_available']}")
|
||||
if health_report['ai_systems_available']:
|
||||
print(f" AI Providers: {len(health_report['ai_providers'])} registered")
|
||||
print(f" Personality Templates: {health_report['personality_system'].get('available_templates', 0)}")
|
||||
print(f" Enhanced Bot Instances: {len(health_report['enhanced_bots'])}")
|
||||
print()
|
||||
|
||||
# 4. Runtime configuration
|
||||
print("4. Demonstrating runtime configuration...")
|
||||
config_success = await enhanced_orchestrator.configure_bot_runtime("ai_chatbot", {
|
||||
"personality": "technical_expert",
|
||||
"streaming": False
|
||||
})
|
||||
print(f" Configuration Update: {'✓ Success' if config_success else '✗ Failed'}")
|
||||
print()
|
||||
|
||||
# 5. Analytics
|
||||
print("5. Bot analytics and usage statistics...")
|
||||
analytics = enhanced_orchestrator.get_bot_analytics()
|
||||
print(f" Enhanced Bots: {analytics['enhanced_bots_count']}")
|
||||
print(f" Configurations: {analytics['configurations_count']}")
|
||||
print(" Feature Usage:")
|
||||
for feature, usage in analytics['feature_usage'].items():
|
||||
print(f" {feature}: {usage}")
|
||||
print()
|
||||
|
||||
print("=== Step 5B Integration Demo Complete ===")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the demo
|
||||
asyncio.run(demo_step_5b_integration())
|
Loading…
x
Reference in New Issue
Block a user