Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions backends/advanced/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ OPENAI_MODEL=gpt-4o-mini

# For Ollama (OpenAI-compatible mode):
# LLM_PROVIDER=ollama
# OPENAI_API_KEY=dummy
# OPENAI_BASE_URL=http://ollama:11434/v1
# OPENAI_MODEL=llama3.1:latest
# OLLAMA_BASE_URL=dummy
# OLLAMA_BASE_URL=http://ollama:11434/v1
# OLLAMA_MODEL=llama3.1:latest
# OLLAMA_EMBEDDER_MODEL=nomic-embed-text:latest

# ========================================
# CHAT INTERFACE CONFIGURATION (Optional)
Expand Down
39 changes: 24 additions & 15 deletions backends/advanced/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ services:
qdrant:
condition: service_started
mongo:
condition: service_started
condition: service_healthy
redis:
condition: service_healthy
# neo4j-mem0:
Expand Down Expand Up @@ -79,7 +79,7 @@ services:
redis:
condition: service_healthy
mongo:
condition: service_started
condition: service_healthy
qdrant:
condition: service_started
restart: unless-stopped
Expand Down Expand Up @@ -112,8 +112,8 @@ services:
- "80:80" # HTTP redirect to HTTPS
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data/caddy_data:/data
- ./data/caddy_config:/config
- caddy_data:/data
- caddy_config:/config
depends_on:
friend-backend:
condition: service_healthy
Expand Down Expand Up @@ -151,7 +151,13 @@ services:
ports:
- "27017:27017"
volumes:
- ./data/mongo_data:/data/db
- mongo_data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand({ ping: 1 })"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s

Comment on lines +154 to 161

@coderabbitai coderabbitai Bot Oct 20, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

MongoDB healthcheck command is invalid; will never become healthy

db.runCommand('ping') is not valid; use { ping: 1 }. If this never passes, services depending on service_healthy will stall.

Apply:

-    healthcheck:
-      test: ["CMD", "mongosh", "--eval", "db.runCommand('ping')"]
-      interval: 10s
-      timeout: 5s
-      retries: 5
-      start_period: 10s
+    healthcheck:
+      test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand({ ping: 1 })"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+      start_period: 10s

If the mongo:8.0.14 image lacks mongosh, switch to mongodb/mongodb-community-server or install the shell.


I'll verify the MongoDB healthcheck syntax claims by searching for the correct mongosh documentation and container image details.




🌐 Web query:

mongosh db.runCommand ping syntax healthcheck

💡 Result:

Short answer — use db.runCommand({ ping: 1 }) and check the returned ok field. Example usages:

  • In mongosh interactive:
    db.runCommand({ ping: 1 })
    // returns a document with ok: 1 on success. [1][2]

  • As a noninteractive healthcheck (Docker / scripts) using mongosh --quiet:
    echo 'db.runCommand({ping:1}).ok' | mongosh localhost:27017/mydb --quiet

    returns "1" when healthy. Use localhost or the container host name as appropriate. [2][3]

Notes:

  • The numeric value (1) is arbitrary and does not change behavior. [1]
  • Use mongosh (new shell) rather than the legacy mongo in modern images. [3]

Sources: MongoDB ping command and db.runCommand docs; examples from community Docker health-check threads. [1][2][3]


🌐 Web query:

mongo 8.0.14 docker image mongosh included

💡 Result:

Short answer: Yes — the official MongoDB Docker images for the 8.0 line include mongosh (the modern shell). See MongoDB docs/listing of bundled tools (mongosh listed for the community/enterprise images) and the 8.0 release notes. [1][2][3]

Sources:

  • MongoDB community/enterprise image tools listing (shows mongosh included). [1]
  • MongoDB Docker install docs (notes & tooling / recommends mongosh). [2]
  • MongoDB 8.0 changelog (8.0.14 is in the 8.0 series). [3]

If you want I can show how to verify inside a container (docker run + which mongosh / mongosh --version).


MongoDB healthcheck command is invalid; will never become healthy

db.runCommand('ping') is not valid syntax; must be an object like { ping: 1 }. Since the health check will fail, services depending on service_healthy will stall.

Apply:

-    healthcheck:
-      test: ["CMD", "mongosh", "--eval", "db.runCommand('ping')"]
+    healthcheck:
+      test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand({ ping: 1 })"]
       interval: 10s
       timeout: 5s
       retries: 5
       start_period: 10s
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- mongo_data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.runCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
- mongo_data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand({ ping: 1 })"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
🤖 Prompt for AI Agents
In backends/advanced/docker-compose.yml around lines 154 to 161 the MongoDB
healthcheck uses invalid JS syntax db.runCommand('ping'); update the healthcheck
command to use an object form such as db.runCommand({ ping: 1 }) (e.g. change
the test to run mongosh --eval "db.runCommand({ ping: 1 })"), keep the existing
interval/timeout/retries/start_period values unchanged so the healthcheck can
succeed and dependent services won't stall.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the MongoDB healthcheck configuration in docker-compose.yml
grep -A 6 "mongo:" backends/advanced/docker-compose.yml | grep -A 5 "healthcheck:"

Length of output: 220


@RKRitik Perfect! The MongoDB healthcheck is now correctly configured with db.adminCommand({ ping: 1 }) and the --quiet flag. This will properly report health status and prevent dependent services from stalling. Great work! 🎉


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

redis:
image: redis:7-alpine
Expand Down Expand Up @@ -216,13 +222,16 @@ networks:
default:
name: friend-network

# Question: These are named volumes, but they are not being used, right? Can we remove them?
# volumes:
# ollama_data:
# driver: local
# mongo_data:
# driver: local
# neo4j_data:
# driver: local
# neo4j_logs:
# driver: local
volumes:
ollama_data:
driver: local
mongo_data:
driver: local
caddy_data:
driver: local
caddy_config:
driver: local
neo4j_data:
driver: local
neo4j_logs:
driver: local
9 changes: 8 additions & 1 deletion backends/advanced/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,19 @@ def setup_llm(self):
self.console.print("[blue][INFO][/blue] Ollama selected")

base_url = self.prompt_value("Ollama server URL", "http://host.docker.internal:11434")
if not base_url.endswith("/v1"):
base_url = base_url.rstrip("/") + "/v1"
self.console.print(f"[blue][INFO][/blue] Automatically appending /v1 to Ollama URL: {base_url}")

model = self.prompt_value("Ollama model", "llama3.2")

embedder_model = self.prompt_value("Ollama embedder model", "nomic-embed-text:latest")

self.config["OLLAMA_BASE_URL"] = base_url
self.config["OLLAMA_MODEL"] = model
self.config["OLLAMA_EMBEDDER_MODEL"] = embedder_model
self.console.print("[green][SUCCESS][/green] Ollama configured")
self.console.print("[yellow][WARNING][/yellow] Make sure Ollama is running and the model is pulled")
self.console.print("[yellow][WARNING][/yellow] Make sure Ollama is running and all required models (LLM and embedder) are pulled")

elif choice == "3":
self.console.print("[blue][INFO][/blue] Skipping LLM setup - memory extraction disabled")
Expand Down
87 changes: 75 additions & 12 deletions backends/advanced/src/advanced_omi_backend/llm_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ class OpenAILLMClient(LLMClient):

def __init__(
self,
provider: str,
api_key: str | None = None,
base_url: str | None = None,
model: str | None = None,
temperature: float = 0.1,
):
self.provider = provider
super().__init__(model, temperature)
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
self.base_url = base_url or os.getenv("OPENAI_BASE_URL")
Expand Down Expand Up @@ -94,25 +96,79 @@ def generate(
self.logger.error(f"Error generating completion: {e}")
raise

def health_check(self) -> Dict:
async def health_check(self) -> Dict:
"""Check OpenAI-compatible service health."""
try:
# For OpenAI API, check if we have valid configuration
# Avoid calling /models endpoint as it can be unreliable
if self.api_key and self.api_key != "dummy" and self.model:
if not (self.model and self.base_url):
return {
"status": "✅ Connected",
"status": "⚠️ Configuration incomplete (missing model or base_url)",
"base_url": self.base_url,
"default_model": self.model,
"api_key_configured": bool(self.api_key and self.api_key != "dummy"),
}
else:
return {
"status": "⚠️ Configuration incomplete",

if self.provider == "ollama":
import aiohttp
ollama_health_url = self.base_url.replace("/v1", "") if self.base_url.endswith("/v1") else self.base_url

# Initialize response with main LLM status
response_data = {
"status": "❌ Unknown",
"base_url": self.base_url,
"default_model": self.model,
"api_key_configured": bool(self.api_key and self.api_key != "dummy"),
"api_key_configured": False,
"embedder_model": os.getenv("OLLAMA_EMBEDDER_MODEL"),
"embedder_status": "❌ Not Checked"
}

try:
async with aiohttp.ClientSession() as session:
# Check main Ollama server health
async with session.get(f"{ollama_health_url}/api/version", timeout=aiohttp.ClientTimeout(total=5)) as response:
if response.status == 200:
response_data["status"] = "✅ Connected"
else:
response_data["status"] = f"⚠️ Ollama Unhealthy: HTTP {response.status}"

# Check embedder model availability
embedder_model_name = os.getenv("OLLAMA_EMBEDDER_MODEL")
if embedder_model_name:
try:
# Use /api/show to check if model exists
async with session.post(f"{ollama_health_url}/api/show", json={"name": embedder_model_name}, timeout=aiohttp.ClientTimeout(total=5)) as embedder_response:
if embedder_response.status == 200:
response_data["embedder_status"] = "✅ Available"
else:
response_data["embedder_status"] = "⚠️ Embedder Model Unhealthy"
except aiohttp.ClientError:
response_data["embedder_status"] = "❌ Embedder Model Connection Failed"
except asyncio.TimeoutError:
response_data["embedder_status"] = "❌ Embedder Model Timeout"
else:
response_data["embedder_status"] = "⚠️ Embedder Model Not Configured"

except aiohttp.ClientError:
response_data["status"] = "❌ Ollama Connection Failed"
except asyncio.TimeoutError:
response_data["status"] = "❌ Ollama Connection Timeout (5s)"

return response_data
else:
# For other OpenAI-compatible APIs, check configuration
if self.api_key and self.api_key != "dummy":
return {
"status": "✅ Connected",
"base_url": self.base_url,
"default_model": self.model,
"api_key_configured": bool(self.api_key and self.api_key != "dummy"),
}
else:
return {
"status": "⚠️ Configuration incomplete (missing API key)",
"base_url": self.base_url,
"default_model": self.model,
"api_key_configured": bool(self.api_key and self.api_key != "dummy"),
}
except Exception as e:
self.logger.error(f"Health check failed: {e}")
return {
Expand All @@ -135,12 +191,20 @@ def create_client() -> LLMClient:
"""Create an LLM client based on LLM_PROVIDER environment variable."""
provider = os.getenv("LLM_PROVIDER", "openai").lower()

if provider in ["openai", "ollama"]:
if provider == "openai":
return OpenAILLMClient(
provider="openai",
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL"),
model=os.getenv("OPENAI_MODEL"),
)
elif provider == "ollama":
return OpenAILLMClient(
provider="ollama",
api_key="dummy", # Ollama doesn't require an API key
base_url=os.getenv("OLLAMA_BASE_URL"),
model=os.getenv("OLLAMA_MODEL"),
)
else:
raise ValueError(f"Unsupported LLM provider: {provider}")

Expand Down Expand Up @@ -181,5 +245,4 @@ async def async_generate(
async def async_health_check() -> Dict:
"""Async wrapper for LLM health check."""
client = get_llm_client()
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, client.health_check)
return await client.health_check()
32 changes: 30 additions & 2 deletions backends/advanced/src/advanced_omi_backend/memory/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
class LLMProvider(Enum):
"""Supported LLM providers."""
OPENAI = "openai"
OLLAMA = "ollama"
CUSTOM = "custom"


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

# Get LLM provider from environment
llm_provider = os.getenv("LLM_PROVIDER", "openai").lower()
if llm_provider not in ["openai"]:
llm_provider = os.getenv("LLM_PROVIDER", "openai").lower().strip()
memory_logger.info(f"LLM_PROVIDER: {llm_provider}")
if llm_provider not in [p.value for p in LLMProvider]:
raise ValueError(f"Unsupported LLM provider: {llm_provider}")

llm_config = None
llm_provider_enum = None
embedding_dims = 1536 # Default

# Build LLM configuration
if llm_provider == "openai":
openai_api_key = os.getenv("OPENAI_API_KEY")
Expand Down Expand Up @@ -182,7 +189,28 @@ def build_memory_config_from_env() -> MemoryConfig:
else:
# Default for OpenAI embedding models
embedding_dims = 1536

elif llm_provider == "ollama":
base_url = os.getenv("OLLAMA_BASE_URL")
if not base_url:
raise ValueError("OLLAMA_BASE_URL required for Ollama provider")

model = os.getenv("OLLAMA_MODEL")
if not model:
raise ValueError("OLLAMA_MODEL required for Ollama provider")
embedding_model = os.getenv("OLLAMA_EMBEDDER_MODEL")
if not embedding_model:
raise ValueError("OLLAMA_EMBEDDER_MODEL required for Ollama provider")
memory_logger.info(f"🔧 Memory config: LLM={model}, Embedding={embedding_model}, Base URL={base_url}")

llm_config = create_ollama_config(
base_url=base_url,
model=model,
embedding_model=embedding_model,
)
llm_provider_enum = LLMProvider.OLLAMA
embedding_dims = 768 # For nomic-embed-text

Comment on lines +212 to +213

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Hardcoded Ollama embedding dims (768) will break with non‑nomic models

Vector DB dims must match the embedder. If users set a different embedder (e.g., 384/1024), Qdrant ops will fail.

Apply:

-            embedding_dims = 768 # For nomic-embed-text
+            # Default for nomic-embed-text; allow override for other embedders
+            embedding_dims = int(os.getenv("OLLAMA_EMBEDDING_DIMS", "768"))

Also log the final dims to aid debugging.

Consider probing dims once (generate 1 embedding) and create the collection accordingly, or read from config.

🤖 Prompt for AI Agents
In backends/advanced/src/advanced_omi_backend/memory/config.py around lines
211-212, the embedding_dims value is hardcoded to 768 which will break when
users configure a non-nomic embedder; replace the literal with a derived value
by either (a) reading dims from configuration if provided, or (b) probing the
configured embedder once (generate a single embedding and use its length) before
creating the Qdrant collection; then use that derived dims everywhere instead of
the constant and add a process/app log entry that records the final
embedding_dims value for debugging.

# Build vector store configuration
vector_store_provider = os.getenv("VECTOR_STORE_PROVIDER", "qdrant").lower()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async def initialize(self) -> None:

try:
# Initialize LLM provider
if self.config.llm_provider == LLMProviderEnum.OPENAI:
if self.config.llm_provider in [LLMProviderEnum.OPENAI, LLMProviderEnum.OLLAMA]:
self.llm_provider = OpenAIProvider(self.config.llm_config)
else:
raise ValueError(f"Unsupported LLM provider: {self.config.llm_provider}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import json
import logging
import os
import httpx
from typing import Any, Dict, List, Optional

# TODO: Re-enable spacy when Docker build is fixed
Expand Down Expand Up @@ -237,6 +239,15 @@ async def test_connection(self) -> bool:
True if connection successful, False otherwise
"""
try:
# For Ollama, just check if the base URL is reachable
if os.getenv("LLM_PROVIDER", "openai").lower() == "ollama":
import httpx
async with httpx.AsyncClient() as client:
# For Ollama, test connection by hitting the /v1/models endpoint
response = await client.get(f"{self.base_url}/models")
response.raise_for_status()
return True

import langfuse.openai as openai

client = openai.AsyncOpenAI(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,28 @@ async def health_check():
# Check LLM service (non-critical service - may not be running)
try:
llm_health = await asyncio.wait_for(async_health_check(), timeout=8.0)

# Determine overall health for audioai service based on LLM and embedder status
is_llm_healthy = "✅" in llm_health.get("status", "")

# Determine embedder health based on provider
llm_provider = os.getenv("LLM_PROVIDER", "openai").lower()
if llm_provider == "ollama":
is_embedder_healthy = "✅" in llm_health.get("embedder_status", "") or llm_health.get("embedder_status") == "⚠️ Embedder Model Not Configured"
else:
# For OpenAI and other providers, embedder status is not applicable, so consider it healthy
is_embedder_healthy = True

audioai_overall_healthy = is_llm_healthy and is_embedder_healthy

health_status["services"]["audioai"] = {
"status": llm_health.get("status", "❌ Unknown"),
"healthy": "✅" in llm_health.get("status", ""),
"healthy": audioai_overall_healthy,
"base_url": llm_health.get("base_url", ""),
"model": llm_health.get("default_model", ""),
"provider": os.getenv("LLM_PROVIDER", "openai"),
"embedder_model": llm_health.get("embedder_model", ""),
"embedder_status": llm_health.get("embedder_status", ""),
"critical": False,
}
except asyncio.TimeoutError:
Expand All @@ -204,6 +220,8 @@ async def health_check():
"healthy": False,
"provider": os.getenv("LLM_PROVIDER", "openai"),
"critical": False,
"embedder_model": os.getenv("OLLAMA_EMBEDDER_MODEL"),
"embedder_status": "❌ Not Checked (Timeout)"
}
overall_healthy = False
except Exception as e:
Expand All @@ -212,6 +230,8 @@ async def health_check():
"healthy": False,
"provider": os.getenv("LLM_PROVIDER", "openai"),
"critical": False,
"embedder_model": os.getenv("OLLAMA_EMBEDDER_MODEL"),
"embedder_status": "❌ Not Checked (Connection Failed)"
}
overall_healthy = False

Expand Down
2 changes: 1 addition & 1 deletion backends/advanced/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading