From 1977c4c0af943ea2e7f24d6468703340cba130f1 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sun, 12 Apr 2026 19:33:35 -0300 Subject: [PATCH] fix: extract base URL for embedding health checks - Add extract_base_url() helper to parse scheme://host:port from full URLs - Fix health check to use base URL instead of full endpoint path - Allows embedding-url config like http://host:port/v1/embeddings to work correctly - Health check now goes to http://host:port/health instead of http://host:port/v1/embeddings/health --- src/core/kb/embedding_generator.rs | 47 ++++++++++++++++++++---------- src/llm/local.rs | 20 +++++++++++-- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/core/kb/embedding_generator.rs b/src/core/kb/embedding_generator.rs index 0249b2c9..edf23244 100644 --- a/src/core/kb/embedding_generator.rs +++ b/src/core/kb/embedding_generator.rs @@ -326,12 +326,27 @@ impl KbEmbeddingGenerator { } } + fn extract_base_url(url: &str) -> String { + if let Ok(parsed) = url::Url::parse(url) { + format!( + "{}://{}{}", + parsed.scheme(), + parsed.host_str().unwrap_or("localhost"), + parsed.port().map(|p| format!(":{}", p)).unwrap_or_default() + ) + } else { + url.to_string() + } + } + pub async fn check_health(&self) -> bool { - // Strategy: try /health endpoint first. + // Strategy: try /health endpoint on BASE URL first. // - 200 OK → local server with health endpoint, ready // - 404/405 etc → server is reachable but has no /health (remote API or llama.cpp) // - Connection refused/timeout → server truly unavailable - let health_url = format!("{}/health", self.config.embedding_url); + // Extract base URL (scheme://host:port) from embedding URL for health check + let base_url = Self::extract_base_url(&self.config.embedding_url); + let health_url = format!("{}/health", base_url); match tokio::time::timeout( Duration::from_secs(self.config.connect_timeout_seconds), @@ -343,26 +358,26 @@ impl KbEmbeddingGenerator { info!("Embedding server health check passed ({})", self.config.embedding_url); set_embedding_server_ready(true); true - } else if status.as_u16() == 404 || status.as_u16() == 405 { - // Server is reachable but has no /health endpoint (remote API, llama.cpp /embedding-only) - // Try a HEAD request to the embedding URL itself to confirm it's up - info!("No /health endpoint at {} (status {}), probing base URL", self.config.embedding_url, status); - match tokio::time::timeout( - Duration::from_secs(self.config.connect_timeout_seconds), - self.client.head(&self.config.embedding_url).send() - ).await { - Ok(Ok(_)) => { - info!("Embedding server reachable at {}, marking as ready", self.config.embedding_url); + } else if status.as_u16() == 404 || status.as_u16() == 405 { + // Server is reachable but has no /health endpoint (remote API, llama.cpp /embedding-only) + // Try a HEAD request to the base URL to confirm it's up + info!("No /health endpoint at {} (status {}), probing base URL", base_url, status); + match tokio::time::timeout( + Duration::from_secs(self.config.connect_timeout_seconds), + self.client.head(&base_url).send() + ).await { + Ok(Ok(_)) => { + info!("Embedding server reachable at {}, marking as ready", base_url); set_embedding_server_ready(true); true } - Ok(Err(e)) => { - warn!("Embedding server unreachable at {}: {}", self.config.embedding_url, e); + Ok(Err(e)) => { + warn!("Embedding server unreachable at {}: {}", base_url, e); set_embedding_server_ready(false); false } - Err(_) => { - warn!("Embedding server probe timed out for {}", self.config.embedding_url); + Err(_) => { + warn!("Embedding server probe timed out for {}", base_url); set_embedding_server_ready(false); false } diff --git a/src/llm/local.rs b/src/llm/local.rs index d3c0a5d9..4546130b 100644 --- a/src/llm/local.rs +++ b/src/llm/local.rs @@ -337,13 +337,27 @@ pub async fn ensure_llama_servers_running( } */ // END OF OLD BLOCKING CODE } +fn extract_base_url(url: &str) -> String { + if let Ok(parsed) = url::Url::parse(url) { + format!( + "{}://{}{}", + parsed.scheme(), + parsed.host_str().unwrap_or("localhost"), + parsed.port().map(|p| format!(":{}", p)).unwrap_or_default() + ) + } else { + url.to_string() + } +} + pub async fn is_server_running(url: &str) -> bool { + let base_url = extract_base_url(url); let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(5)) .build() .unwrap_or_default(); - match client.get(format!("{url}/health")).send().await { + match client.get(format!("{base_url}/health")).send().await { Ok(response) => { if response.status().is_success() { return true; @@ -352,11 +366,11 @@ pub async fn is_server_running(url: &str) -> bool { info!("Health check returned status: {}", response.status()); false } - Err(e) => match client.get(url).send().await { + Err(e) => match client.get(&base_url).send().await { Ok(response) => response.status().is_success(), Err(_) => { if !e.is_connect() { - warn!("Health check error for {url}: {e}"); + warn!("Health check error for {base_url}: {e}"); } false }