fix: rename generation model setting to ollama_llm_model to avoid OLLAMA_MODEL env collision

The deployed container sets OLLAMA_MODEL=nomic-embed-text for embeddings,
which shadowed the generation-model setting and broke Phase 0 keyword
extraction and Phase 4 LLM re-ranking on every request. The setting is now
ollama_llm_model (env: OLLAMA_LLM_MODEL, default gemma4:e2b), startup logs
the resolved generation model, and Phase 0/Phase 4 LLM calls are wrapped in
a 12s asyncio.wait_for with graceful fallback so a hung call cannot gate
retrieval for the full 120s client timeout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 09:57:57 +02:00
co-authored by Claude Fable 5
parent d0e712760d
commit 94482bcb59
8 changed files with 51 additions and 16 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ SEARXNG_URL=http://192.168.86.149:8080
REDIS_HOST=192.168.86.149
PAPERLESS_URL=http://192.168.86.149:8091
OLLAMA_MODEL=mistral-nemo-large:latest
OLLAMA_LLM_MODEL=gemma4:e2b
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
# Wiki.js auth
+7
View File
@@ -5,6 +5,13 @@ All notable changes to Library Desk will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Fixed
- **Ollama generation model env collision** - Renamed the generation-model setting `ollama_model` to `ollama_llm_model` (env: `OLLAMA_LLM_MODEL`, default `gemma4:e2b`). The container env `OLLAMA_MODEL=nomic-embed-text` (meant for embeddings) was shadowing the generation model, breaking Phase 0 keyword extraction and Phase 4 LLM re-ranking on every request. Startup now logs the resolved generation model.
- **LLM call timeouts** - Phase 0 keyword extraction and Phase 4 re-ranking are wrapped in a 12s `asyncio.wait_for` with graceful fallback, so a hung Ollama call can no longer gate retrieval for the full 120s client timeout.
## [1.7.3] - 2026-01-07
### Fixed
+3 -1
View File
@@ -66,7 +66,9 @@ class Settings(BaseSettings):
# Ollama Configuration
ollama_url: str = Field(default="http://ollama:11434", description="Ollama URL")
ollama_model: str = Field(default="mistral-nemo-large:latest", description="Ollama LLM model")
# Named ollama_llm_model (env: OLLAMA_LLM_MODEL) to avoid collision with the
# OLLAMA_MODEL container env var, which is used for the embedding model.
ollama_llm_model: str = Field(default="gemma4:e2b", description="Ollama LLM model for generation (keyword extraction, re-ranking, consolidation)")
ollama_embedding_model: str = Field(default="nomic-embed-text", description="Ollama embedding model")
# HybridRAG Configuration
+2 -1
View File
@@ -142,7 +142,7 @@ async def health(settings: Settings = Depends(get_settings)) -> HealthResponse:
},
"ollama": {
"url": settings.ollama_url,
"model": settings.ollama_model,
"model": settings.ollama_llm_model,
"healthy": service_health.get("ollama", False)
}
}
@@ -412,6 +412,7 @@ async def startup_event():
logger.info(f"Wiki.js: {settings.wikijs_url}")
logger.info(f"SearXNG: {settings.searxng_url}")
logger.info(f"Ollama: {settings.ollama_url}")
logger.info(f"Ollama generation model: {settings.ollama_llm_model} (embedding model: {settings.ollama_embedding_model})")
# Initialize all service clients
await startup_clients()
+2 -2
View File
@@ -505,7 +505,7 @@ JSON:"""
# Call Ollama for analysis (temperature=0.0 for consistent classification)
response = await self.ollama.generate_text(
prompt=prompt,
model=self.settings.ollama_model,
model=self.settings.ollama_llm_model,
stream=False,
temperature=0.0
)
@@ -1079,7 +1079,7 @@ JSON:"""
try:
response = await self.ollama.generate_text(
prompt=prompt,
model=self.settings.ollama_model,
model=self.settings.ollama_llm_model,
stream=False,
temperature=0.0
)
+34 -9
View File
@@ -34,6 +34,10 @@ from src.core.multi_tenancy import get_neo4j_user_base_label, get_neo4j_user_lab
logger = logging.getLogger(__name__)
# Timeout for auxiliary LLM calls (keyword extraction, re-ranking).
# A hung Ollama call must not gate retrieval for the full client timeout.
LLM_CALL_TIMEOUT_SECONDS = 12.0
class HybridRAGService:
"""
@@ -69,7 +73,7 @@ class HybridRAGService:
self.content_extractor = content_extractor
self.settings = settings
self.volatile = volatile_service
self.reranker_model = settings.ollama_model
self.reranker_model = settings.ollama_llm_model
async def search(
self,
@@ -224,10 +228,13 @@ Return format:
JSON:"""
try:
response = await self.ollama.generate_text(
prompt=prompt,
model=self.reranker_model,
temperature=0.0 # Deterministic for consistent extraction
response = await asyncio.wait_for(
self.ollama.generate_text(
prompt=prompt,
model=self.reranker_model,
temperature=0.0 # Deterministic for consistent extraction
),
timeout=LLM_CALL_TIMEOUT_SECONDS
)
# Parse JSON response (handle potential extra text)
@@ -261,6 +268,16 @@ JSON:"""
"synonyms": {},
"expansions": {}
}
except asyncio.TimeoutError:
logger.warning(
f"Keyword extraction timed out after {LLM_CALL_TIMEOUT_SECONDS}s, using fallback"
)
return {
"core_keywords": query.split(),
"entities": [],
"synonyms": {},
"expansions": {}
}
except Exception as e:
logger.error(f"Keyword extraction failed: {e}", exc_info=True)
return {
@@ -772,10 +789,13 @@ Example output: 3,1,5,2,4
Ranking:"""
response = await self.ollama.generate_text(
prompt=prompt,
model=self.reranker_model,
temperature=0.0 # Deterministic for consistent rankings
response = await asyncio.wait_for(
self.ollama.generate_text(
prompt=prompt,
model=self.reranker_model,
temperature=0.0 # Deterministic for consistent rankings
),
timeout=LLM_CALL_TIMEOUT_SECONDS
)
# Parse response: "3,1,5,2,4" → [2, 0, 4, 1, 3] (0-indexed)
@@ -796,6 +816,11 @@ Ranking:"""
logger.info(f"LLM re-ranking: reordered {len(reranked)} results")
return reranked
except asyncio.TimeoutError:
logger.warning(
f"LLM re-ranking timed out after {LLM_CALL_TIMEOUT_SECONDS}s, using RRF order"
)
return results # Fallback to RRF order
except Exception as e:
logger.warning(f"LLM re-ranking failed: {e}, using RRF order")
return results # Fallback to RRF order
+1 -1
View File
@@ -34,7 +34,7 @@ class WikiPageWriter:
settings: Application settings
"""
self.ollama = ollama_client
self.model = settings.ollama_model
self.model = settings.ollama_llm_model
async def create_page(
self,
+1 -1
View File
@@ -38,7 +38,7 @@ def settings():
"""Get mocked application settings for testing."""
mock_settings = MagicMock()
mock_settings.reranker_model = "mistral-nemo"
mock_settings.ollama_model = "mistral-nemo"
mock_settings.ollama_llm_model = "mistral-nemo"
return mock_settings