Compare commits
5 commits
307809bbdd
...
0a1bd25869
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a1bd25869 | |||
| a9cbbbffa0 | |||
| 1cee912b72 | |||
| e9a428ab1c | |||
| 0c9665dd8b |
6 changed files with 117 additions and 28 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
// Bootstrap manager implementation
|
// Bootstrap manager implementation
|
||||||
use crate::core::bootstrap::bootstrap_types::{BootstrapManager, BootstrapProgress};
|
use crate::core::bootstrap::bootstrap_types::{BootstrapManager, BootstrapProgress};
|
||||||
use crate::core::bootstrap::bootstrap_utils::{cache_health_check, safe_pkill, vault_health_check};
|
use crate::core::bootstrap::bootstrap_utils::{cache_health_check, safe_pkill, vault_health_check, vector_db_health_check};
|
||||||
use crate::core::config::AppConfig;
|
use crate::core::config::AppConfig;
|
||||||
use crate::core::package_manager::{InstallMode, PackageManager};
|
use crate::core::package_manager::{InstallMode, PackageManager};
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
|
@ -79,16 +79,32 @@ impl BootstrapManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if pm.is_installed("vector_db") {
|
if pm.is_installed("vector_db") {
|
||||||
info!("Starting Vector database...");
|
let vector_db_already_running = vector_db_health_check();
|
||||||
|
if vector_db_already_running {
|
||||||
|
info!("Vector database (Qdrant) is already running");
|
||||||
|
} else {
|
||||||
|
info!("Starting Vector database (Qdrant)...");
|
||||||
match pm.start("vector_db") {
|
match pm.start("vector_db") {
|
||||||
Ok(_child) => {
|
Ok(_child) => {
|
||||||
info!("Vector database started");
|
info!("Vector database process started, waiting for readiness...");
|
||||||
|
// Wait for vector_db to be ready
|
||||||
|
for i in 0..15 {
|
||||||
|
sleep(Duration::from_secs(1)).await;
|
||||||
|
if vector_db_health_check() {
|
||||||
|
info!("Vector database (Qdrant) is responding");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if i == 14 {
|
||||||
|
warn!("Vector database did not respond after 15 seconds");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to start Vector database: {}", e);
|
warn!("Failed to start Vector database: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if pm.is_installed("postgres") {
|
if pm.is_installed("postgres") {
|
||||||
info!("Starting PostgreSQL...");
|
info!("Starting PostgreSQL...");
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,40 @@ pub fn cache_health_check() -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if Qdrant vector database is healthy
|
||||||
|
pub fn vector_db_health_check() -> bool {
|
||||||
|
// Qdrant has a /healthz endpoint, use curl to check
|
||||||
|
// Try both HTTP and HTTPS
|
||||||
|
let urls = [
|
||||||
|
"http://localhost:6333/healthz",
|
||||||
|
"https://localhost:6333/healthz",
|
||||||
|
];
|
||||||
|
|
||||||
|
for url in &urls {
|
||||||
|
if let Ok(output) = Command::new("curl")
|
||||||
|
.args(["-f", "-s", "--connect-timeout", "2", "-k", url])
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
if output.status.success() {
|
||||||
|
// Qdrant healthz returns "OK" or JSON with status
|
||||||
|
let response = String::from_utf8_lossy(&output.stdout);
|
||||||
|
if response.contains("OK") || response.contains("\"status\":\"ok\"") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: just check if port 6333 is listening
|
||||||
|
match Command::new("nc")
|
||||||
|
.args(["-z", "-w", "1", "127.0.0.1", "6333"])
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) => output.status.success(),
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get current user safely
|
/// Get current user safely
|
||||||
pub fn safe_fuser() -> String {
|
pub fn safe_fuser() -> String {
|
||||||
// Return shell command that uses $USER environment variable
|
// Return shell command that uses $USER environment variable
|
||||||
|
|
|
||||||
|
|
@ -427,9 +427,11 @@ impl BotOrchestrator {
|
||||||
// DEBUG: Log which bot we're getting config for
|
// DEBUG: Log which bot we're getting config for
|
||||||
info!("[CONFIG_TRACE] Getting LLM config for bot_id: {}", session.bot_id);
|
info!("[CONFIG_TRACE] Getting LLM config for bot_id: {}", session.bot_id);
|
||||||
|
|
||||||
|
// For local LLM server, use the actual model name
|
||||||
|
// Default to DeepSeek model if not configured
|
||||||
let model = config_manager
|
let model = config_manager
|
||||||
.get_config(&session.bot_id, "llm-model", Some("gpt-3.5-turbo"))
|
.get_config(&session.bot_id, "llm-model", Some("DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf"))
|
||||||
.unwrap_or_else(|_| "gpt-3.5-turbo".to_string());
|
.unwrap_or_else(|_| "DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf".to_string());
|
||||||
|
|
||||||
let key = config_manager
|
let key = config_manager
|
||||||
.get_config(&session.bot_id, "llm-key", Some(""))
|
.get_config(&session.bot_id, "llm-key", Some(""))
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,25 @@ impl BotDatabaseManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get database name for this bot
|
// Get bot info (including name) from database
|
||||||
let db_name = self.get_bot_database_name(bot_id)?
|
let mut conn = self.main_pool.get()?;
|
||||||
.ok_or_else(|| format!("No database configured for bot {}", bot_id))?;
|
let bot_info: Option<BotDatabaseInfo> = sql_query(
|
||||||
|
"SELECT id, name, database_name FROM bots WHERE id = $1 AND is_active = true",
|
||||||
|
)
|
||||||
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
let bot_info = bot_info.ok_or_else(|| format!("Bot {} not found or not active", bot_id))?;
|
||||||
|
|
||||||
|
// Ensure bot has a database, create if needed
|
||||||
|
let db_name = if let Some(name) = bot_info.database_name {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
// Bot doesn't have a database configured, create it now
|
||||||
|
info!("Bot {} ({}) has no database, creating now", bot_info.name, bot_id);
|
||||||
|
self.ensure_bot_has_database(bot_id, &bot_info.name)?
|
||||||
|
};
|
||||||
|
|
||||||
// Create new pool
|
// Create new pool
|
||||||
let pool = self.create_pool_for_database(&db_name)?;
|
let pool = self.create_pool_for_database(&db_name)?;
|
||||||
|
|
|
||||||
|
|
@ -81,18 +81,23 @@ pub async fn ensure_llama_servers_running(
|
||||||
};
|
};
|
||||||
|
|
||||||
let llm_model = if llm_model.is_empty() {
|
let llm_model = if llm_model.is_empty() {
|
||||||
info!("No LLM model configured, using default: ../../../../data/llm/DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf");
|
info!("No LLM model configured, using default: DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf");
|
||||||
"../../../../data/llm/DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf".to_string()
|
"DeepSeek-R1-Distill-Qwen-1.5B-Q3_K_M.gguf".to_string()
|
||||||
} else {
|
} else {
|
||||||
llm_model
|
llm_model
|
||||||
};
|
};
|
||||||
|
|
||||||
let embedding_model = if embedding_model.is_empty() {
|
let embedding_model = if embedding_model.is_empty() {
|
||||||
info!("No embedding model configured, using default: ../../../../data/llm/bge-small-en-v1.5-f32.gguf");
|
info!("No embedding model configured, using default: bge-small-en-v1.5-f32.gguf");
|
||||||
"../../../../data/llm/bge-small-en-v1.5-f32.gguf".to_string()
|
"bge-small-en-v1.5-f32.gguf".to_string()
|
||||||
} else {
|
} else {
|
||||||
embedding_model
|
embedding_model
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// For llama-server startup, use path relative to botserver root
|
||||||
|
// The models are in ./data/llm/ and the llama-server runs from botserver root
|
||||||
|
let llm_model_path = format!("./data/llm/{}", llm_model);
|
||||||
|
let embedding_model_path = format!("./data/llm/{}", embedding_model);
|
||||||
if !llm_server_enabled {
|
if !llm_server_enabled {
|
||||||
info!("Local LLM server management disabled (llm-server=false). Using external endpoints.");
|
info!("Local LLM server management disabled (llm-server=false). Using external endpoints.");
|
||||||
info!(" LLM URL: {llm_url}");
|
info!(" LLM URL: {llm_url}");
|
||||||
|
|
@ -160,13 +165,13 @@ pub async fn ensure_llama_servers_running(
|
||||||
info!("Starting LLM server...");
|
info!("Starting LLM server...");
|
||||||
let app_state_clone = Arc::clone(&app_state);
|
let app_state_clone = Arc::clone(&app_state);
|
||||||
let llm_server_path_clone = llm_server_path.clone();
|
let llm_server_path_clone = llm_server_path.clone();
|
||||||
let llm_model_clone = llm_model.clone();
|
let llm_model_path_clone = llm_model_path.clone();
|
||||||
let llm_url_clone = llm_url.clone();
|
let llm_url_clone = llm_url.clone();
|
||||||
tasks.push(tokio::spawn(async move {
|
tasks.push(tokio::spawn(async move {
|
||||||
start_llm_server(
|
start_llm_server(
|
||||||
app_state_clone,
|
app_state_clone,
|
||||||
llm_server_path_clone,
|
llm_server_path_clone,
|
||||||
llm_model_clone,
|
llm_model_path_clone,
|
||||||
llm_url_clone,
|
llm_url_clone,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
|
@ -177,7 +182,7 @@ pub async fn ensure_llama_servers_running(
|
||||||
info!("Starting Embedding server...");
|
info!("Starting Embedding server...");
|
||||||
tasks.push(tokio::spawn(start_embedding_server(
|
tasks.push(tokio::spawn(start_embedding_server(
|
||||||
llm_server_path.clone(),
|
llm_server_path.clone(),
|
||||||
embedding_model.clone(),
|
embedding_model_path.clone(),
|
||||||
embedding_url.clone(),
|
embedding_url.clone(),
|
||||||
)));
|
)));
|
||||||
} else if embedding_model.is_empty() {
|
} else if embedding_model.is_empty() {
|
||||||
|
|
@ -381,8 +386,8 @@ pub fn start_llm_server(
|
||||||
|
|
||||||
let n_predict = config_manager
|
let n_predict = config_manager
|
||||||
.get_config(&default_bot_id, "llm-server-n-predict", None)
|
.get_config(&default_bot_id, "llm-server-n-predict", None)
|
||||||
.unwrap_or_else(|_| "50".to_string());
|
.unwrap_or_else(|_| "512".to_string()); // Increased default for DeepSeek R1 reasoning
|
||||||
let n_predict = if n_predict.is_empty() { "50".to_string() } else { n_predict };
|
let n_predict = if n_predict.is_empty() { "512".to_string() } else { n_predict };
|
||||||
|
|
||||||
let n_ctx_size = config_manager
|
let n_ctx_size = config_manager
|
||||||
.get_config(&default_bot_id, "llm-server-ctx-size", None)
|
.get_config(&default_bot_id, "llm-server-ctx-size", None)
|
||||||
|
|
@ -436,10 +441,10 @@ pub fn start_llm_server(
|
||||||
})?;
|
})?;
|
||||||
} else {
|
} else {
|
||||||
let cmd_arg = format!(
|
let cmd_arg = format!(
|
||||||
"cd {llama_cpp_path} && ./llama-server {args} --verbose >llm-stdout.log 2>&1 &"
|
"{llama_cpp_path}/llama-server {args} --verbose >{llama_cpp_path}/llm-stdout.log 2>&1 &"
|
||||||
);
|
);
|
||||||
info!(
|
info!(
|
||||||
"Executing LLM server command: cd {llama_cpp_path} && ./llama-server {args} --verbose"
|
"Executing LLM server command: {llama_cpp_path}/llama-server {args} --verbose"
|
||||||
);
|
);
|
||||||
let cmd = SafeCommand::new("sh")
|
let cmd = SafeCommand::new("sh")
|
||||||
.and_then(|c| c.arg("-c"))
|
.and_then(|c| c.arg("-c"))
|
||||||
|
|
@ -464,9 +469,13 @@ pub async fn start_embedding_server(
|
||||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let port = extract_port(&url);
|
let port = extract_port(&url);
|
||||||
|
|
||||||
let full_model_path = if model_path.starts_with('/') {
|
// model_path is already the full path (constructed with ../../../../data/llm/ prefix)
|
||||||
|
// Only prepend llama_cpp_path if model_path is a simple filename (not a path)
|
||||||
|
let full_model_path = if model_path.contains('/') || model_path.contains('.') {
|
||||||
|
// model_path is already a full or relative path, use as-is
|
||||||
model_path.clone()
|
model_path.clone()
|
||||||
} else {
|
} else {
|
||||||
|
// model_path is just a filename, prepend llama_cpp_path
|
||||||
format!("{llama_cpp_path}/{model_path}")
|
format!("{llama_cpp_path}/{model_path}")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -496,10 +505,10 @@ pub async fn start_embedding_server(
|
||||||
})?;
|
})?;
|
||||||
} else {
|
} else {
|
||||||
let cmd_arg = format!(
|
let cmd_arg = format!(
|
||||||
"cd {llama_cpp_path} && ./llama-server -m {model_path} --verbose --host 0.0.0.0 --port {port} --embedding --n-gpu-layers 99 --ubatch-size 2048 >llmembd-stdout.log 2>&1 &"
|
"{llama_cpp_path}/llama-server -m {model_path} --verbose --host 0.0.0.0 --port {port} --embedding --n-gpu-layers 99 --ubatch-size 2048 >{llama_cpp_path}/llmembd-stdout.log 2>&1 &"
|
||||||
);
|
);
|
||||||
info!(
|
info!(
|
||||||
"Executing embedding server command: cd {llama_cpp_path} && ./llama-server -m {model_path} --host 0.0.0.0 --port {port} --embedding"
|
"Executing embedding server command: {llama_cpp_path}/llama-server -m {model_path} --host 0.0.0.0 --port {port} --embedding"
|
||||||
);
|
);
|
||||||
let cmd = SafeCommand::new("sh")
|
let cmd = SafeCommand::new("sh")
|
||||||
.and_then(|c| c.arg("-c"))
|
.and_then(|c| c.arg("-c"))
|
||||||
|
|
|
||||||
|
|
@ -432,7 +432,7 @@ pub async fn create_app_state(
|
||||||
info!("LLM Model: {}", llm_model);
|
info!("LLM Model: {}", llm_model);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _llm_key = std::env::var("LLM_KEY")
|
let llm_key = std::env::var("LLM_KEY")
|
||||||
.or_else(|_| std::env::var("OPENAI_API_KEY"))
|
.or_else(|_| std::env::var("OPENAI_API_KEY"))
|
||||||
.or_else(|_| {
|
.or_else(|_| {
|
||||||
config_manager
|
config_manager
|
||||||
|
|
@ -441,6 +441,18 @@ pub async fn create_app_state(
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// If llm-url points to external API but no key is configured, fall back to local LLM
|
||||||
|
let llm_url = if llm_key.is_empty()
|
||||||
|
&& !llm_url.contains("localhost")
|
||||||
|
&& !llm_url.contains("127.0.0.1")
|
||||||
|
&& (llm_url.contains("api.z.ai") || llm_url.contains("openai.com") || llm_url.contains("anthropic.com"))
|
||||||
|
{
|
||||||
|
warn!("External LLM URL configured ({}), but no API key provided. Falling back to local LLM at http://localhost:8081", llm_url);
|
||||||
|
"http://localhost:8081".to_string()
|
||||||
|
} else {
|
||||||
|
llm_url
|
||||||
|
};
|
||||||
|
|
||||||
// LLM endpoint path configuration
|
// LLM endpoint path configuration
|
||||||
let llm_endpoint_path = config_manager
|
let llm_endpoint_path = config_manager
|
||||||
.get_config(
|
.get_config(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue