chore(memory_tree): drop body-read timeouts on Ollama HTTP calls (#1171)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
sanil-23
2026-05-04 00:10:04 -07:00
committed by GitHub
co-authored by Claude Opus 4.7
parent d812bf9da7
commit 677e941ac1
3 changed files with 36 additions and 10 deletions
@@ -66,16 +66,19 @@ impl OllamaEmbedder {
timeout_ms
};
let timeout = Duration::from_millis(timeout_ms);
// No body-read timeout. Ollama is local — slow responses mean
// the model is genuinely processing, not that the network
// broke. A body-read timeout here would cancel mid-stream and
// force retries against the same slow model. `timeout` is
// repurposed as the TCP connect timeout (fast-fail when
// Ollama is actually down).
let client = reqwest::Client::builder()
.timeout(timeout)
.connect_timeout(timeout)
.build()
// Falling back to the default client keeps `new` infallible;
// timeouts will apply per-request via explicit `.timeout()`
// calls below.
.unwrap_or_else(|e| {
log::warn!(
"[memory_tree::embed::ollama] failed to build client \
with timeout — using default: {e}"
with connect_timeout — using default: {e}"
);
reqwest::Client::new()
});
@@ -154,9 +157,11 @@ impl Embedder for OllamaEmbedder {
};
let resp = self
.client
// No per-request body-read timeout — see `OllamaEmbedder::new`
// for rationale. The Client's `connect_timeout` still applies
// and fails fast if Ollama isn't reachable.
.post(self.embed_url())
.json(&req)
.timeout(self.timeout)
.send()
.await
.with_context(|| {
+18 -3
View File
@@ -49,8 +49,13 @@ pub struct LlmExtractorConfig {
pub endpoint: String,
/// Model identifier as known to the endpoint (e.g. `qwen2.5:0.5b`).
pub model: String,
/// Per-request timeout. The default is generous because the first
/// request after a model swap may need to load weights.
/// TCP connect timeout. Used only to fail fast when Ollama is
/// down; no body-read timeout is applied (see `Self::new` for
/// rationale — local Ollama on slow CPU inference can take
/// minutes per call, and cancelling mid-stream triggered
/// cancellation-path retry storms). The default is generous
/// because the first request after a model swap may need to load
/// weights.
pub timeout: Duration,
/// Which entity kinds the LLM is allowed to emit. Anything outside this
/// set is mapped to [`EntityKind::Misc`] or dropped depending on
@@ -105,8 +110,18 @@ impl LlmEntityExtractor {
/// Build the extractor and its inner HTTP client. Fails only when
/// `reqwest` rejects the timeout configuration.
pub fn new(cfg: LlmExtractorConfig) -> anyhow::Result<Self> {
// No body-read timeout. Ollama is a local process — slow
// responses mean the model is genuinely processing under CPU
// load (e.g. gemma3:1b on CPU-only inference can take minutes
// per call), not that the network broke. A body-read timeout
// here would cancel mid-flight generation and force the
// existing 3× retry-with-backoff to re-run the same prompt
// against the same slow model, which empirically caused retry
// storms during high-load ingest. `cfg.timeout` is now the
// TCP connect timeout — short enough to fail fast when
// Ollama is actually unreachable.
let http = Client::builder()
.timeout(cfg.timeout)
.connect_timeout(cfg.timeout)
.build()
.map_err(anyhow::Error::from)?;
Ok(Self { cfg, http })
@@ -115,8 +115,14 @@ impl LlmSummariser {
/// Build a summariser around `cfg`. Returns an error only if the HTTP
/// client fails to construct (timeout / TLS init).
pub fn new(cfg: LlmSummariserConfig) -> Result<Self> {
// No body-read timeout. Ollama is local — slow responses mean
// the model is genuinely processing, not that the network
// broke. A body-read timeout here would cancel mid-stream and
// force retries against the same slow model. `cfg.timeout` is
// repurposed as the TCP connect timeout (fast-fail when
// Ollama is actually down).
let http = Client::builder()
.timeout(cfg.timeout)
.connect_timeout(cfg.timeout)
.build()
.map_err(anyhow::Error::from)?;
Ok(Self {