From d6f7d64d4e5f6ba171d57385c9d305a99cffb44d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=86=A0=E8=BE=B0?= Date: Sat, 23 May 2026 20:24:52 +0800 Subject: [PATCH 1/3] fix(memory): clarify missing ollama embedding model --- .../memory/tree/score/embed/ollama.rs | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/src/openhuman/memory/tree/score/embed/ollama.rs b/src/openhuman/memory/tree/score/embed/ollama.rs index cd8db0f3b9..cfc47f8e77 100644 --- a/src/openhuman/memory/tree/score/embed/ollama.rs +++ b/src/openhuman/memory/tree/score/embed/ollama.rs @@ -105,6 +105,34 @@ impl OllamaEmbedder { } } +fn is_missing_model_response(status: reqwest::StatusCode, body: &str) -> bool { + if status != reqwest::StatusCode::NOT_FOUND { + return false; + } + + let normalized = body.to_ascii_lowercase(); + normalized.contains("model") && normalized.contains("not found") +} + +fn format_embedding_status_error( + status: reqwest::StatusCode, + body: &str, + endpoint: &str, + model: &str, +) -> String { + let trimmed_body = body.trim(); + + if is_missing_model_response(status, trimmed_body) { + return format!( + "Ollama embedding model `{model}` is not installed at {endpoint}. \ + Run `ollama pull {model}` or choose an installed embedding model in \ + Settings -> AI & Skills -> Local AI. status={status} body={trimmed_body}" + ); + } + + format!("ollama embeddings failed status={status} body={trimmed_body}") +} + /// Override Ollama's per-model `num_ctx` default. Ollama loads /// embedding models with `num_ctx = 4096` (or whatever default the /// model's modelfile carries) unless the request explicitly asks for @@ -181,10 +209,12 @@ impl Embedder for OllamaEmbedder { if !resp.status().is_success() { let status = resp.status(); let body = resp.text().await.unwrap_or_default(); - anyhow::bail!( - "ollama embeddings failed status={status} body={}", - body.trim() - ); + anyhow::bail!(format_embedding_status_error( + status, + &body, + &self.endpoint, + &self.model + )); } let payload: EmbedResponse = resp @@ -286,6 +316,23 @@ mod tests { assert!(err.contains("model crashed"), "msg: {err}"); } + #[tokio::test] + async fn missing_model_404_mentions_pull_command() { + let app = Router::new().route( + "/api/embeddings", + post(|| async { (StatusCode::NOT_FOUND, "{\"error\":\"model not found\"}") }), + ); + let url = start_mock(app).await; + let e = OllamaEmbedder::new(url, "custom-embed".into(), 0); + let err = e.embed("hello").await.unwrap_err().to_string(); + + assert!( + err.contains("embedding model `custom-embed` is not installed"), + "msg: {err}" + ); + assert!(err.contains("ollama pull custom-embed"), "msg: {err}"); + } + #[tokio::test] async fn dim_mismatch_rejected() { let app = Router::new().route( @@ -315,9 +362,8 @@ mod tests { } #[tokio::test] - async fn connection_refused_is_descriptive() { - // Port 1 is effectively guaranteed refused on any reasonable host. - let e = OllamaEmbedder::new("http://127.0.0.1:1".into(), String::new(), 500); + async fn request_failure_is_descriptive() { + let e = OllamaEmbedder::new("http://%".into(), String::new(), 500); let err = e.embed("hi").await.unwrap_err().to_string(); assert!( err.contains("is Ollama running"), From 93701b5c138d211fa4890aecebffa32d4b2deec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=86=A0=E8=BE=B0?= Date: Sat, 23 May 2026 20:53:39 +0800 Subject: [PATCH 2/3] chore: retrigger ollama guidance ci From f62aedea079840bfda9b41721ceff7ec999446e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=86=A0=E8=BE=B0?= Date: Sun, 24 May 2026 20:48:29 +0800 Subject: [PATCH 3/3] chore: retry ollama guidance ci