Skip to content

Commit 1d9fbe5

Browse files
committed
minor fixes probably should've been in other branch fahh
1 parent 3e02fe9 commit 1d9fbe5

4 files changed

Lines changed: 112 additions & 41 deletions

File tree

backends/advanced/src/advanced_omi_backend/chat_service.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
from advanced_omi_backend.database import get_database
2222
from advanced_omi_backend.llm_client import get_llm_client
2323
from advanced_omi_backend.model_registry import get_models_registry
24+
from advanced_omi_backend.observability.otel_setup import (
25+
get_tracer,
26+
is_otel_enabled,
27+
set_otel_session,
28+
set_trace_io,
29+
)
30+
from advanced_omi_backend.prompt_registry import get_prompt_registry
2431
from advanced_omi_backend.services.memory import get_memory_service
2532
from advanced_omi_backend.services.memory.base import MemoryEntry
2633
from advanced_omi_backend.services.obsidian_service import (
@@ -164,8 +171,6 @@ async def _get_system_prompt(self) -> str:
164171

165172
# Fallback to prompt registry
166173
try:
167-
from advanced_omi_backend.prompt_registry import get_prompt_registry
168-
169174
registry = get_prompt_registry()
170175
prompt = await registry.get_prompt("chat.system")
171176
logger.info("Using chat system prompt from prompt registry")
@@ -426,13 +431,6 @@ async def generate_response_stream(
426431
include_obsidian_memory: bool = False,
427432
) -> AsyncGenerator[Dict, None]:
428433
"""Generate streaming response with memory context."""
429-
from advanced_omi_backend.observability.otel_setup import (
430-
get_tracer,
431-
is_otel_enabled,
432-
set_otel_session,
433-
set_trace_io,
434-
)
435-
436434
set_otel_session(session_id)
437435

438436
tracer = get_tracer() if is_otel_enabled() else None

backends/advanced/src/advanced_omi_backend/speaker_recognition_client.py

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,22 @@
1515
import json
1616
import logging
1717
import os
18+
import traceback
19+
import uuid
20+
import wave
1821
from pathlib import Path
1922
from typing import Dict, List, Optional
2023

2124
import aiohttp
2225
from aiohttp import ClientConnectorError
2326

27+
from advanced_omi_backend.config import get_diarization_settings
2428
from advanced_omi_backend.model_registry import get_models_registry
29+
from advanced_omi_backend.models.conversation import Conversation
30+
from advanced_omi_backend.utils.audio_chunk_utils import reconstruct_audio_segment
31+
from advanced_omi_backend.utils.audio_extraction import extract_audio_for_results
32+
from advanced_omi_backend.utils.audio_utils import pcm_to_wav_bytes
33+
from advanced_omi_backend.utils.segment_utils import is_non_speech
2534

2635
logger = logging.getLogger(__name__)
2736

@@ -76,12 +85,23 @@ def __init__(self, service_url: Optional[str] = None):
7685
)
7786
return
7887

79-
# Enabled - determine URL (priority: param > config > env var)
88+
# Enabled - determine URL (priority: param > config > env var > minidisc)
8089
self.service_url = (
8190
service_url
8291
or speaker_config.get("service_url")
8392
or os.getenv("SPEAKER_SERVICE_URL")
8493
)
94+
95+
if not self.service_url:
96+
try:
97+
from discovery import CHRONICLE_SPEAKER, resolve_service_url
98+
99+
self.service_url = resolve_service_url(
100+
None, CHRONICLE_SPEAKER, default=None
101+
)
102+
except ImportError:
103+
pass
104+
85105
self.enabled = bool(self.service_url)
86106

87107
if self.enabled:
@@ -157,8 +177,6 @@ async def diarize_identify_match(
157177
return {"segments": []}
158178

159179
# Fetch conversation to get audio duration for timeout calculation
160-
from advanced_omi_backend.models.conversation import Conversation
161-
162180
conversation = await Conversation.find_one(
163181
Conversation.conversation_id == conversation_id
164182
)
@@ -173,8 +191,6 @@ async def diarize_identify_match(
173191
)
174192

175193
# Read diarization source from config system
176-
from advanced_omi_backend.config import get_diarization_settings
177-
178194
config = get_diarization_settings()
179195
diarization_source = config.get("diarization_source", "pyannote")
180196

@@ -432,18 +448,11 @@ async def identify_provider_segments(
432448
if not self.enabled:
433449
return {"segments": []}
434450

435-
from advanced_omi_backend.config import get_diarization_settings
436-
from advanced_omi_backend.utils.audio_chunk_utils import (
437-
reconstruct_audio_segment,
438-
)
439-
440451
config = get_diarization_settings()
441452
similarity_threshold = config.get("similarity_threshold", 0.45)
442453

443454
MAX_SAMPLES_PER_LABEL = 3
444455

445-
from advanced_omi_backend.utils.segment_utils import is_non_speech
446-
447456
def _is_non_speech(seg: Dict) -> bool:
448457
return is_non_speech(
449458
seg.get("text", ""),
@@ -633,10 +642,6 @@ async def _identify_per_segment(
633642
Returns:
634643
Dict with 'segments' list matching diarize_identify_match() format
635644
"""
636-
from advanced_omi_backend.utils.audio_chunk_utils import (
637-
reconstruct_audio_segment,
638-
)
639-
640645
logger.info(
641646
f"🎤 Per-segment identification: {len(speech_segments)} speech segments "
642647
f"(min_duration={min_segment_duration}s)"
@@ -849,8 +854,6 @@ async def diarize_and_identify(
849854
)
850855

851856
# Get current diarization settings from config
852-
from advanced_omi_backend.config import get_diarization_settings
853-
854857
diarization_settings = get_diarization_settings()
855858

856859
# Add all diarization parameters for the diarize-and-identify endpoint
@@ -947,8 +950,6 @@ async def diarize_and_identify(
947950
logger.error(
948951
f"🎤 [DIARIZE] ❌ Error during speaker diarization and identification: {e}"
949952
)
950-
import traceback
951-
952953
logger.debug(traceback.format_exc())
953954
return {"error": "unknown_error", "message": str(e), "segments": []}
954955

@@ -979,8 +980,6 @@ async def identify_speakers(
979980
logger.info(f"Identifying {len(unique_speakers)} speakers in {audio_path}")
980981

981982
# Get audio duration for timeout calculation
982-
import wave
983-
984983
try:
985984
with wave.open(audio_path, "rb") as wav_file:
986985
frame_count = wav_file.getnframes()
@@ -1007,8 +1006,6 @@ async def identify_speakers(
10071006
content_type="audio/wav",
10081007
)
10091008
# Get current diarization settings
1010-
from advanced_omi_backend.config import get_diarization_settings
1011-
10121009
_diarization_settings = get_diarization_settings()
10131010

10141011
# Add all diarization parameters for the diarize-and-identify endpoint
@@ -1239,8 +1236,6 @@ async def enroll_new_speaker(
12391236
return {"error": "speaker_recognition_disabled"}
12401237

12411238
try:
1242-
import uuid
1243-
12441239
# Generate speaker ID: user_{user_id}_speaker_{random_hex}
12451240
speaker_id = f"user_{user_id}_speaker_{uuid.uuid4().hex[:12]}"
12461241

@@ -1356,10 +1351,6 @@ async def check_if_enrolled_speaker_present(
13561351
- enrolled_present: True if enrolled speaker detected, False otherwise
13571352
- speaker_result: Full speaker recognition result dict with segments
13581353
"""
1359-
from advanced_omi_backend.utils.audio_extraction import (
1360-
extract_audio_for_results,
1361-
)
1362-
13631354
logger.info(
13641355
f"🎤 [SPEAKER CHECK] Starting speaker check for session {session_id}"
13651356
)
@@ -1407,7 +1398,6 @@ async def check_if_enrolled_speaker_present(
14071398
)
14081399

14091400
# Convert PCM to WAV in memory (no disk I/O!)
1410-
from advanced_omi_backend.utils.audio_utils import pcm_to_wav_bytes
14111401

14121402
logger.info(f"🎤 [SPEAKER CHECK] Converting PCM to WAV in memory...")
14131403
wav_data = pcm_to_wav_bytes(

services.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,55 @@ def load_config_yml():
4040
"compose_file": "docker-compose.yml",
4141
"description": "LangFuse Observability & Prompt Management",
4242
"ports": ["3002"],
43+
"health_endpoints": [
44+
("langfuse", None, "3002", "/api/public/health"),
45+
],
4346
},
4447
"backend": {
4548
"path": "backends/advanced",
4649
"compose_file": "docker-compose.yml",
4750
"description": "Advanced Backend + WebUI",
4851
"ports": ["8000", "5173"],
52+
"health_endpoints": [
53+
("backend", "BACKEND_PUBLIC_PORT", "8000", "/readiness"),
54+
],
4955
},
5056
"speaker-recognition": {
5157
"path": "extras/speaker-recognition",
5258
"compose_file": "docker-compose.yml",
5359
"description": "Speaker Recognition Service",
5460
"ports": ["8085", "5174/8444"],
61+
"health_endpoints": [
62+
("speaker", "SPEAKER_SERVICE_PORT", "8085", "/health"),
63+
],
5564
},
5665
"asr-services": {
5766
"path": "extras/asr-services",
5867
"compose_file": "docker-compose.yml",
5968
"description": "Parakeet ASR Service",
6069
"ports": ["8767"],
70+
"health_endpoints": [
71+
("asr", "ASR_PORT", "8767", "/health"),
72+
],
6173
},
6274
"openmemory-mcp": {
6375
"path": "extras/openmemory-mcp",
6476
"compose_file": "docker-compose.yml",
6577
"description": "OpenMemory MCP Server",
6678
"ports": ["8765"],
79+
"health_endpoints": [
80+
("openmemory", None, "8765", "/docs"),
81+
],
6782
},
6883
"llm-services": {
6984
"path": "extras/llm-services",
7085
"compose_file": "docker-compose.yml",
7186
"description": "Local LLM via llama.cpp (chat + embeddings)",
7287
"ports": ["8083", "8082"],
88+
"health_endpoints": [
89+
("chat", "LLM_PORT", "8083", "/health"),
90+
("embeddings", "EMBED_PORT", "8082", "/health"),
91+
],
7392
},
7493
}
7594

@@ -162,6 +181,54 @@ def check_service_configured(service_name):
162181
return (service_path / ".env").exists()
163182

164183

184+
def check_service_health(service_name):
185+
"""Check runtime health of a service by hitting its health endpoints.
186+
187+
Returns (status, detail) where status is one of:
188+
"healthy" — all endpoints responding with < 400
189+
"partial" — some endpoints down (detail says which)
190+
"unhealthy" — responding but returning errors
191+
"stopped" — not reachable at all
192+
"""
193+
import requests
194+
195+
service = SERVICES[service_name]
196+
endpoints = service.get("health_endpoints", [])
197+
if not endpoints:
198+
return ("stopped", "no endpoints defined")
199+
200+
env_path = Path(service["path"]) / ".env"
201+
env_values = dotenv_values(env_path) if env_path.exists() else {}
202+
203+
results = [] # list of (label, ok: bool)
204+
any_unhealthy = False
205+
206+
for label, port_env, default_port, path in endpoints:
207+
port = env_values.get(port_env, default_port) if port_env else default_port
208+
url = f"http://localhost:{port}{path}"
209+
try:
210+
resp = requests.get(url, timeout=2)
211+
if resp.status_code < 400:
212+
results.append((label, True))
213+
else:
214+
results.append((label, False))
215+
any_unhealthy = True
216+
except (requests.ConnectionError, requests.Timeout):
217+
results.append((label, False))
218+
219+
up = [r for r in results if r[1]]
220+
down = [r for r in results if not r[1]]
221+
222+
if len(up) == len(results):
223+
return ("healthy", "")
224+
if len(down) == len(results):
225+
if any_unhealthy:
226+
return ("unhealthy", "")
227+
return ("stopped", "")
228+
down_labels = ", ".join(r[0] for r in down)
229+
return ("partial", f"{down_labels} down")
230+
231+
165232
def run_compose_command(service_name, command, build=False, force_recreate=False):
166233
"""Run docker compose command for a service"""
167234
service = SERVICES[service_name]
@@ -605,13 +672,28 @@ def show_status():
605672
table = Table()
606673
table.add_column("Service", style="cyan")
607674
table.add_column("Configured", justify="center")
675+
table.add_column("Running", justify="center")
608676
table.add_column("Description", style="dim")
609677
table.add_column("Ports", style="green")
610678

611679
for service_name, service_info in SERVICES.items():
612680
configured = "✅" if check_service_configured(service_name) else "❌"
613681
ports = ", ".join(service_info["ports"])
614-
table.add_row(service_name, configured, service_info["description"], ports)
682+
683+
# Check runtime health
684+
status, detail = check_service_health(service_name)
685+
if status == "healthy":
686+
running = "[green]✅ healthy[/green]"
687+
elif status == "partial":
688+
running = f"[yellow]⚠ partial[/yellow] [dim]({detail})[/dim]"
689+
elif status == "unhealthy":
690+
running = "[red]⚠ unhealthy[/red]"
691+
else:
692+
running = "[dim]— stopped[/dim]"
693+
694+
table.add_row(
695+
service_name, configured, running, service_info["description"], ports
696+
)
615697

616698
console.print(table)
617699

setup-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ requests>=2.31.0
55
pyyaml>=6.0.0
66
ruamel-yaml>=0.18.0
77
omegaconf>=2.3.0
8+
minidisc-python>=0.1.0

0 commit comments

Comments
 (0)