Skip to content

Commit 059dfd0

Browse files
Merge pull request #125 from RKRitik/fix/ollama-setup-and-stability
Fix/ollama setup and stability
2 parents d407036 + 69fa704 commit 059dfd0

10 files changed

Lines changed: 188 additions & 37 deletions

File tree

backends/advanced/.env.template

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ OPENAI_MODEL=gpt-4o-mini
2929

3030
# For Ollama (OpenAI-compatible mode):
3131
# LLM_PROVIDER=ollama
32-
# OPENAI_API_KEY=dummy
33-
# OPENAI_BASE_URL=http://ollama:11434/v1
34-
# OPENAI_MODEL=llama3.1:latest
32+
# OLLAMA_BASE_URL=dummy
33+
# OLLAMA_BASE_URL=http://ollama:11434/v1
34+
# OLLAMA_MODEL=llama3.1:latest
35+
# OLLAMA_EMBEDDER_MODEL=nomic-embed-text:latest
3536

3637
# ========================================
3738
# CHAT INTERFACE CONFIGURATION (Optional)

backends/advanced/docker-compose.yml

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ services:
3737
qdrant:
3838
condition: service_started
3939
mongo:
40-
condition: service_started
40+
condition: service_healthy
4141
redis:
4242
condition: service_healthy
4343
# neo4j-mem0:
@@ -79,7 +79,7 @@ services:
7979
redis:
8080
condition: service_healthy
8181
mongo:
82-
condition: service_started
82+
condition: service_healthy
8383
qdrant:
8484
condition: service_started
8585
restart: unless-stopped
@@ -112,8 +112,8 @@ services:
112112
- "80:80" # HTTP redirect to HTTPS
113113
volumes:
114114
- ./Caddyfile:/etc/caddy/Caddyfile:ro
115-
- ./data/caddy_data:/data
116-
- ./data/caddy_config:/config
115+
- caddy_data:/data
116+
- caddy_config:/config
117117
depends_on:
118118
friend-backend:
119119
condition: service_healthy
@@ -151,7 +151,13 @@ services:
151151
ports:
152152
- "27017:27017"
153153
volumes:
154-
- ./data/mongo_data:/data/db
154+
- mongo_data:/data/db
155+
healthcheck:
156+
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand({ ping: 1 })"]
157+
interval: 10s
158+
timeout: 5s
159+
retries: 5
160+
start_period: 10s
155161

156162
redis:
157163
image: redis:7-alpine
@@ -216,13 +222,16 @@ networks:
216222
default:
217223
name: friend-network
218224

219-
# Question: These are named volumes, but they are not being used, right? Can we remove them?
220-
# volumes:
221-
# ollama_data:
222-
# driver: local
223-
# mongo_data:
224-
# driver: local
225-
# neo4j_data:
226-
# driver: local
227-
# neo4j_logs:
228-
# driver: local
225+
volumes:
226+
ollama_data:
227+
driver: local
228+
mongo_data:
229+
driver: local
230+
caddy_data:
231+
driver: local
232+
caddy_config:
233+
driver: local
234+
neo4j_data:
235+
driver: local
236+
neo4j_logs:
237+
driver: local

backends/advanced/init.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,19 @@ def setup_llm(self):
243243
self.console.print("[blue][INFO][/blue] Ollama selected")
244244

245245
base_url = self.prompt_value("Ollama server URL", "http://host.docker.internal:11434")
246+
if not base_url.endswith("/v1"):
247+
base_url = base_url.rstrip("/") + "/v1"
248+
self.console.print(f"[blue][INFO][/blue] Automatically appending /v1 to Ollama URL: {base_url}")
249+
246250
model = self.prompt_value("Ollama model", "llama3.2")
247251

252+
embedder_model = self.prompt_value("Ollama embedder model", "nomic-embed-text:latest")
253+
248254
self.config["OLLAMA_BASE_URL"] = base_url
249255
self.config["OLLAMA_MODEL"] = model
256+
self.config["OLLAMA_EMBEDDER_MODEL"] = embedder_model
250257
self.console.print("[green][SUCCESS][/green] Ollama configured")
251-
self.console.print("[yellow][WARNING][/yellow] Make sure Ollama is running and the model is pulled")
258+
self.console.print("[yellow][WARNING][/yellow] Make sure Ollama is running and all required models (LLM and embedder) are pulled")
252259

253260
elif choice == "3":
254261
self.console.print("[blue][INFO][/blue] Skipping LLM setup - memory extraction disabled")

backends/advanced/src/advanced_omi_backend/llm_client.py

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ class OpenAILLMClient(LLMClient):
4545

4646
def __init__(
4747
self,
48+
provider: str,
4849
api_key: str | None = None,
4950
base_url: str | None = None,
5051
model: str | None = None,
5152
temperature: float = 0.1,
5253
):
54+
self.provider = provider
5355
super().__init__(model, temperature)
5456
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
5557
self.base_url = base_url or os.getenv("OPENAI_BASE_URL")
@@ -94,25 +96,79 @@ def generate(
9496
self.logger.error(f"Error generating completion: {e}")
9597
raise
9698

97-
def health_check(self) -> Dict:
99+
async def health_check(self) -> Dict:
98100
"""Check OpenAI-compatible service health."""
99101
try:
100-
# For OpenAI API, check if we have valid configuration
101-
# Avoid calling /models endpoint as it can be unreliable
102-
if self.api_key and self.api_key != "dummy" and self.model:
102+
if not (self.model and self.base_url):
103103
return {
104-
"status": "✅ Connected",
104+
"status": "⚠️ Configuration incomplete (missing model or base_url)",
105105
"base_url": self.base_url,
106106
"default_model": self.model,
107107
"api_key_configured": bool(self.api_key and self.api_key != "dummy"),
108108
}
109-
else:
110-
return {
111-
"status": "⚠️ Configuration incomplete",
109+
110+
if self.provider == "ollama":
111+
import aiohttp
112+
ollama_health_url = self.base_url.replace("/v1", "") if self.base_url.endswith("/v1") else self.base_url
113+
114+
# Initialize response with main LLM status
115+
response_data = {
116+
"status": "❌ Unknown",
112117
"base_url": self.base_url,
113118
"default_model": self.model,
114-
"api_key_configured": bool(self.api_key and self.api_key != "dummy"),
119+
"api_key_configured": False,
120+
"embedder_model": os.getenv("OLLAMA_EMBEDDER_MODEL"),
121+
"embedder_status": "❌ Not Checked"
115122
}
123+
124+
try:
125+
async with aiohttp.ClientSession() as session:
126+
# Check main Ollama server health
127+
async with session.get(f"{ollama_health_url}/api/version", timeout=aiohttp.ClientTimeout(total=5)) as response:
128+
if response.status == 200:
129+
response_data["status"] = "✅ Connected"
130+
else:
131+
response_data["status"] = f"⚠️ Ollama Unhealthy: HTTP {response.status}"
132+
133+
# Check embedder model availability
134+
embedder_model_name = os.getenv("OLLAMA_EMBEDDER_MODEL")
135+
if embedder_model_name:
136+
try:
137+
# Use /api/show to check if model exists
138+
async with session.post(f"{ollama_health_url}/api/show", json={"name": embedder_model_name}, timeout=aiohttp.ClientTimeout(total=5)) as embedder_response:
139+
if embedder_response.status == 200:
140+
response_data["embedder_status"] = "✅ Available"
141+
else:
142+
response_data["embedder_status"] = "⚠️ Embedder Model Unhealthy"
143+
except aiohttp.ClientError:
144+
response_data["embedder_status"] = "❌ Embedder Model Connection Failed"
145+
except asyncio.TimeoutError:
146+
response_data["embedder_status"] = "❌ Embedder Model Timeout"
147+
else:
148+
response_data["embedder_status"] = "⚠️ Embedder Model Not Configured"
149+
150+
except aiohttp.ClientError:
151+
response_data["status"] = "❌ Ollama Connection Failed"
152+
except asyncio.TimeoutError:
153+
response_data["status"] = "❌ Ollama Connection Timeout (5s)"
154+
155+
return response_data
156+
else:
157+
# For other OpenAI-compatible APIs, check configuration
158+
if self.api_key and self.api_key != "dummy":
159+
return {
160+
"status": "✅ Connected",
161+
"base_url": self.base_url,
162+
"default_model": self.model,
163+
"api_key_configured": bool(self.api_key and self.api_key != "dummy"),
164+
}
165+
else:
166+
return {
167+
"status": "⚠️ Configuration incomplete (missing API key)",
168+
"base_url": self.base_url,
169+
"default_model": self.model,
170+
"api_key_configured": bool(self.api_key and self.api_key != "dummy"),
171+
}
116172
except Exception as e:
117173
self.logger.error(f"Health check failed: {e}")
118174
return {
@@ -135,12 +191,20 @@ def create_client() -> LLMClient:
135191
"""Create an LLM client based on LLM_PROVIDER environment variable."""
136192
provider = os.getenv("LLM_PROVIDER", "openai").lower()
137193

138-
if provider in ["openai", "ollama"]:
194+
if provider == "openai":
139195
return OpenAILLMClient(
196+
provider="openai",
140197
api_key=os.getenv("OPENAI_API_KEY"),
141198
base_url=os.getenv("OPENAI_BASE_URL"),
142199
model=os.getenv("OPENAI_MODEL"),
143200
)
201+
elif provider == "ollama":
202+
return OpenAILLMClient(
203+
provider="ollama",
204+
api_key="dummy", # Ollama doesn't require an API key
205+
base_url=os.getenv("OLLAMA_BASE_URL"),
206+
model=os.getenv("OLLAMA_MODEL"),
207+
)
144208
else:
145209
raise ValueError(f"Unsupported LLM provider: {provider}")
146210

@@ -181,5 +245,4 @@ async def async_generate(
181245
async def async_health_check() -> Dict:
182246
"""Async wrapper for LLM health check."""
183247
client = get_llm_client()
184-
loop = asyncio.get_running_loop()
185-
return await loop.run_in_executor(None, client.health_check)
248+
return await client.health_check()

backends/advanced/src/advanced_omi_backend/memory/config.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
class LLMProvider(Enum):
1313
"""Supported LLM providers."""
1414
OPENAI = "openai"
15+
OLLAMA = "ollama"
1516
CUSTOM = "custom"
1617

1718

@@ -72,6 +73,7 @@ def create_ollama_config(
7273
) -> Dict[str, Any]:
7374
"""Create Ollama configuration."""
7475
return {
76+
"api_key": "dummy", # Ollama doesn't require an API key
7577
"base_url": base_url,
7678
"model": model,
7779
"embedding_model": embedding_model,
@@ -146,10 +148,15 @@ def build_memory_config_from_env() -> MemoryConfig:
146148
memory_config = config_loader.get_memory_extraction_config()
147149

148150
# Get LLM provider from environment
149-
llm_provider = os.getenv("LLM_PROVIDER", "openai").lower()
150-
if llm_provider not in ["openai"]:
151+
llm_provider = os.getenv("LLM_PROVIDER", "openai").lower().strip()
152+
memory_logger.info(f"LLM_PROVIDER: {llm_provider}")
153+
if llm_provider not in [p.value for p in LLMProvider]:
151154
raise ValueError(f"Unsupported LLM provider: {llm_provider}")
152155

156+
llm_config = None
157+
llm_provider_enum = None
158+
embedding_dims = 1536 # Default
159+
153160
# Build LLM configuration
154161
if llm_provider == "openai":
155162
openai_api_key = os.getenv("OPENAI_API_KEY")
@@ -182,7 +189,28 @@ def build_memory_config_from_env() -> MemoryConfig:
182189
else:
183190
# Default for OpenAI embedding models
184191
embedding_dims = 1536
192+
193+
elif llm_provider == "ollama":
194+
base_url = os.getenv("OLLAMA_BASE_URL")
195+
if not base_url:
196+
raise ValueError("OLLAMA_BASE_URL required for Ollama provider")
185197

198+
model = os.getenv("OLLAMA_MODEL")
199+
if not model:
200+
raise ValueError("OLLAMA_MODEL required for Ollama provider")
201+
embedding_model = os.getenv("OLLAMA_EMBEDDER_MODEL")
202+
if not embedding_model:
203+
raise ValueError("OLLAMA_EMBEDDER_MODEL required for Ollama provider")
204+
memory_logger.info(f"🔧 Memory config: LLM={model}, Embedding={embedding_model}, Base URL={base_url}")
205+
206+
llm_config = create_ollama_config(
207+
base_url=base_url,
208+
model=model,
209+
embedding_model=embedding_model,
210+
)
211+
llm_provider_enum = LLMProvider.OLLAMA
212+
embedding_dims = 768 # For nomic-embed-text
213+
186214
# Build vector store configuration
187215
vector_store_provider = os.getenv("VECTOR_STORE_PROVIDER", "qdrant").lower()
188216

backends/advanced/src/advanced_omi_backend/memory/memory_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async def initialize(self) -> None:
6969

7070
try:
7171
# Initialize LLM provider
72-
if self.config.llm_provider == LLMProviderEnum.OPENAI:
72+
if self.config.llm_provider in [LLMProviderEnum.OPENAI, LLMProviderEnum.OLLAMA]:
7373
self.llm_provider = OpenAIProvider(self.config.llm_config)
7474
else:
7575
raise ValueError(f"Unsupported LLM provider: {self.config.llm_provider}")

backends/advanced/src/advanced_omi_backend/memory/providers/llm_providers.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import json
1212
import logging
13+
import os
14+
import httpx
1315
from typing import Any, Dict, List, Optional
1416

1517
# TODO: Re-enable spacy when Docker build is fixed
@@ -237,6 +239,15 @@ async def test_connection(self) -> bool:
237239
True if connection successful, False otherwise
238240
"""
239241
try:
242+
# For Ollama, just check if the base URL is reachable
243+
if os.getenv("LLM_PROVIDER", "openai").lower() == "ollama":
244+
import httpx
245+
async with httpx.AsyncClient() as client:
246+
# For Ollama, test connection by hitting the /v1/models endpoint
247+
response = await client.get(f"{self.base_url}/models")
248+
response.raise_for_status()
249+
return True
250+
240251
import langfuse.openai as openai
241252

242253
client = openai.AsyncOpenAI(

backends/advanced/src/advanced_omi_backend/routers/modules/health_routes.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,28 @@ async def health_check():
190190
# Check LLM service (non-critical service - may not be running)
191191
try:
192192
llm_health = await asyncio.wait_for(async_health_check(), timeout=8.0)
193+
194+
# Determine overall health for audioai service based on LLM and embedder status
195+
is_llm_healthy = "✅" in llm_health.get("status", "")
196+
197+
# Determine embedder health based on provider
198+
llm_provider = os.getenv("LLM_PROVIDER", "openai").lower()
199+
if llm_provider == "ollama":
200+
is_embedder_healthy = "✅" in llm_health.get("embedder_status", "") or llm_health.get("embedder_status") == "⚠️ Embedder Model Not Configured"
201+
else:
202+
# For OpenAI and other providers, embedder status is not applicable, so consider it healthy
203+
is_embedder_healthy = True
204+
205+
audioai_overall_healthy = is_llm_healthy and is_embedder_healthy
206+
193207
health_status["services"]["audioai"] = {
194208
"status": llm_health.get("status", "❌ Unknown"),
195-
"healthy": "✅" in llm_health.get("status", ""),
209+
"healthy": audioai_overall_healthy,
196210
"base_url": llm_health.get("base_url", ""),
197211
"model": llm_health.get("default_model", ""),
198212
"provider": os.getenv("LLM_PROVIDER", "openai"),
213+
"embedder_model": llm_health.get("embedder_model", ""),
214+
"embedder_status": llm_health.get("embedder_status", ""),
199215
"critical": False,
200216
}
201217
except asyncio.TimeoutError:
@@ -204,6 +220,8 @@ async def health_check():
204220
"healthy": False,
205221
"provider": os.getenv("LLM_PROVIDER", "openai"),
206222
"critical": False,
223+
"embedder_model": os.getenv("OLLAMA_EMBEDDER_MODEL"),
224+
"embedder_status": "❌ Not Checked (Timeout)"
207225
}
208226
overall_healthy = False
209227
except Exception as e:
@@ -212,6 +230,8 @@ async def health_check():
212230
"healthy": False,
213231
"provider": os.getenv("LLM_PROVIDER", "openai"),
214232
"critical": False,
233+
"embedder_model": os.getenv("OLLAMA_EMBEDDER_MODEL"),
234+
"embedder_status": "❌ Not Checked (Connection Failed)"
215235
}
216236
overall_healthy = False
217237

backends/advanced/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)