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:
+1
-1
@@ -9,7 +9,7 @@ SEARXNG_URL=http://192.168.86.149:8080
|
|||||||
REDIS_HOST=192.168.86.149
|
REDIS_HOST=192.168.86.149
|
||||||
PAPERLESS_URL=http://192.168.86.149:8091
|
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
|
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
||||||
|
|
||||||
# Wiki.js auth
|
# Wiki.js auth
|
||||||
|
|||||||
@@ -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/),
|
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).
|
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
|
## [1.7.3] - 2026-01-07
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+3
-1
@@ -66,7 +66,9 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
# Ollama Configuration
|
# Ollama Configuration
|
||||||
ollama_url: str = Field(default="http://ollama:11434", description="Ollama URL")
|
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")
|
ollama_embedding_model: str = Field(default="nomic-embed-text", description="Ollama embedding model")
|
||||||
|
|
||||||
# HybridRAG Configuration
|
# HybridRAG Configuration
|
||||||
|
|||||||
+2
-1
@@ -142,7 +142,7 @@ async def health(settings: Settings = Depends(get_settings)) -> HealthResponse:
|
|||||||
},
|
},
|
||||||
"ollama": {
|
"ollama": {
|
||||||
"url": settings.ollama_url,
|
"url": settings.ollama_url,
|
||||||
"model": settings.ollama_model,
|
"model": settings.ollama_llm_model,
|
||||||
"healthy": service_health.get("ollama", False)
|
"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"Wiki.js: {settings.wikijs_url}")
|
||||||
logger.info(f"SearXNG: {settings.searxng_url}")
|
logger.info(f"SearXNG: {settings.searxng_url}")
|
||||||
logger.info(f"Ollama: {settings.ollama_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
|
# Initialize all service clients
|
||||||
await startup_clients()
|
await startup_clients()
|
||||||
|
|||||||
@@ -505,7 +505,7 @@ JSON:"""
|
|||||||
# Call Ollama for analysis (temperature=0.0 for consistent classification)
|
# Call Ollama for analysis (temperature=0.0 for consistent classification)
|
||||||
response = await self.ollama.generate_text(
|
response = await self.ollama.generate_text(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
model=self.settings.ollama_model,
|
model=self.settings.ollama_llm_model,
|
||||||
stream=False,
|
stream=False,
|
||||||
temperature=0.0
|
temperature=0.0
|
||||||
)
|
)
|
||||||
@@ -1079,7 +1079,7 @@ JSON:"""
|
|||||||
try:
|
try:
|
||||||
response = await self.ollama.generate_text(
|
response = await self.ollama.generate_text(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
model=self.settings.ollama_model,
|
model=self.settings.ollama_llm_model,
|
||||||
stream=False,
|
stream=False,
|
||||||
temperature=0.0
|
temperature=0.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ from src.core.multi_tenancy import get_neo4j_user_base_label, get_neo4j_user_lab
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
class HybridRAGService:
|
||||||
"""
|
"""
|
||||||
@@ -69,7 +73,7 @@ class HybridRAGService:
|
|||||||
self.content_extractor = content_extractor
|
self.content_extractor = content_extractor
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.volatile = volatile_service
|
self.volatile = volatile_service
|
||||||
self.reranker_model = settings.ollama_model
|
self.reranker_model = settings.ollama_llm_model
|
||||||
|
|
||||||
async def search(
|
async def search(
|
||||||
self,
|
self,
|
||||||
@@ -224,10 +228,13 @@ Return format:
|
|||||||
JSON:"""
|
JSON:"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await self.ollama.generate_text(
|
response = await asyncio.wait_for(
|
||||||
prompt=prompt,
|
self.ollama.generate_text(
|
||||||
model=self.reranker_model,
|
prompt=prompt,
|
||||||
temperature=0.0 # Deterministic for consistent extraction
|
model=self.reranker_model,
|
||||||
|
temperature=0.0 # Deterministic for consistent extraction
|
||||||
|
),
|
||||||
|
timeout=LLM_CALL_TIMEOUT_SECONDS
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parse JSON response (handle potential extra text)
|
# Parse JSON response (handle potential extra text)
|
||||||
@@ -261,6 +268,16 @@ JSON:"""
|
|||||||
"synonyms": {},
|
"synonyms": {},
|
||||||
"expansions": {}
|
"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:
|
except Exception as e:
|
||||||
logger.error(f"Keyword extraction failed: {e}", exc_info=True)
|
logger.error(f"Keyword extraction failed: {e}", exc_info=True)
|
||||||
return {
|
return {
|
||||||
@@ -772,10 +789,13 @@ Example output: 3,1,5,2,4
|
|||||||
|
|
||||||
Ranking:"""
|
Ranking:"""
|
||||||
|
|
||||||
response = await self.ollama.generate_text(
|
response = await asyncio.wait_for(
|
||||||
prompt=prompt,
|
self.ollama.generate_text(
|
||||||
model=self.reranker_model,
|
prompt=prompt,
|
||||||
temperature=0.0 # Deterministic for consistent rankings
|
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)
|
# 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")
|
logger.info(f"LLM re-ranking: reordered {len(reranked)} results")
|
||||||
return reranked
|
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:
|
except Exception as e:
|
||||||
logger.warning(f"LLM re-ranking failed: {e}, using RRF order")
|
logger.warning(f"LLM re-ranking failed: {e}, using RRF order")
|
||||||
return results # Fallback to RRF order
|
return results # Fallback to RRF order
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class WikiPageWriter:
|
|||||||
settings: Application settings
|
settings: Application settings
|
||||||
"""
|
"""
|
||||||
self.ollama = ollama_client
|
self.ollama = ollama_client
|
||||||
self.model = settings.ollama_model
|
self.model = settings.ollama_llm_model
|
||||||
|
|
||||||
async def create_page(
|
async def create_page(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def settings():
|
|||||||
"""Get mocked application settings for testing."""
|
"""Get mocked application settings for testing."""
|
||||||
mock_settings = MagicMock()
|
mock_settings = MagicMock()
|
||||||
mock_settings.reranker_model = "mistral-nemo"
|
mock_settings.reranker_model = "mistral-nemo"
|
||||||
mock_settings.ollama_model = "mistral-nemo"
|
mock_settings.ollama_llm_model = "mistral-nemo"
|
||||||
return mock_settings
|
return mock_settings
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user