Skip to content

Commit 096cde3

Browse files
committed
Add plugins configuration path retrieval and refactor usage
- Introduced a new function `get_plugins_yml_path` to centralize the retrieval of the plugins.yml file path. - Updated `system_controller.py` and `plugin_service.py` to use the new function for improved maintainability and consistency in accessing the plugins configuration. - Enhanced code clarity by removing hardcoded paths and utilizing the centralized configuration method.
1 parent 0eafc22 commit 096cde3

File tree

7 files changed

+289
-18
lines changed

7 files changed

+289
-18
lines changed

backends/advanced/src/advanced_omi_backend/config_loader.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ def get_config_dir() -> Path:
2323
return Path(config_dir)
2424

2525

26+
def get_plugins_yml_path() -> Path:
27+
"""
28+
Get path to plugins.yml file (single source of truth).
29+
30+
Returns:
31+
Path to plugins.yml
32+
"""
33+
return get_config_dir() / "plugins.yml"
34+
35+
2636
def load_config(force_reload: bool = False) -> DictConfig:
2737
"""
2838
Load and merge configuration using OmegaConf.

backends/advanced/src/advanced_omi_backend/controllers/system_controller.py

Lines changed: 201 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import shutil
88
import time
9+
import warnings
910
from datetime import UTC, datetime
1011
from pathlib import Path
1112

@@ -14,15 +15,213 @@
1415

1516
from advanced_omi_backend.config import (
1617
get_diarization_settings as load_diarization_settings,
18+
)
19+
from advanced_omi_backend.config import (
1720
save_diarization_settings,
1821
)
22+
from advanced_omi_backend.config_loader import get_plugins_yml_path
1923
from advanced_omi_backend.model_registry import _find_config_path, load_models_config
2024
from advanced_omi_backend.models.user import User
2125

2226
logger = logging.getLogger(__name__)
2327
audio_logger = logging.getLogger("audio_processing")
2428

2529

30+
async def get_config_diagnostics():
31+
"""
32+
Get comprehensive configuration diagnostics.
33+
34+
Returns warnings, errors, and status for all configuration components.
35+
"""
36+
diagnostics = {
37+
"timestamp": datetime.now(UTC).isoformat(),
38+
"overall_status": "healthy",
39+
"issues": [],
40+
"warnings": [],
41+
"info": [],
42+
"components": {}
43+
}
44+
45+
# Test OmegaConf configuration loading
46+
try:
47+
from advanced_omi_backend.config_loader import load_config
48+
49+
# Capture warnings during config load
50+
with warnings.catch_warnings(record=True) as w:
51+
warnings.simplefilter("always")
52+
config = load_config(force_reload=True)
53+
54+
# Check for OmegaConf warnings
55+
for warning in w:
56+
warning_msg = str(warning.message)
57+
if "some elements are missing" in warning_msg.lower():
58+
# Extract the variable name from warning
59+
if "variable '" in warning_msg.lower():
60+
var_name = warning_msg.split("'")[1]
61+
diagnostics["warnings"].append({
62+
"component": "OmegaConf",
63+
"severity": "warning",
64+
"message": f"Environment variable '{var_name}' not set (using empty default)",
65+
"resolution": f"Set {var_name} in .env file if needed"
66+
})
67+
68+
diagnostics["components"]["omegaconf"] = {
69+
"status": "healthy",
70+
"message": "Configuration loaded successfully"
71+
}
72+
except Exception as e:
73+
diagnostics["overall_status"] = "unhealthy"
74+
diagnostics["issues"].append({
75+
"component": "OmegaConf",
76+
"severity": "error",
77+
"message": f"Failed to load configuration: {str(e)}",
78+
"resolution": "Check config/defaults.yml and config/config.yml syntax"
79+
})
80+
diagnostics["components"]["omegaconf"] = {
81+
"status": "unhealthy",
82+
"message": str(e)
83+
}
84+
85+
# Test model registry
86+
try:
87+
from advanced_omi_backend.model_registry import get_models_registry
88+
89+
with warnings.catch_warnings(record=True) as w:
90+
warnings.simplefilter("always")
91+
registry = get_models_registry()
92+
93+
# Capture model loading warnings
94+
for warning in w:
95+
warning_msg = str(warning.message)
96+
diagnostics["warnings"].append({
97+
"component": "Model Registry",
98+
"severity": "warning",
99+
"message": warning_msg,
100+
"resolution": "Check model definitions in config/defaults.yml"
101+
})
102+
103+
if registry:
104+
diagnostics["components"]["model_registry"] = {
105+
"status": "healthy",
106+
"message": f"Loaded {len(registry.models)} models",
107+
"details": {
108+
"total_models": len(registry.models),
109+
"defaults": dict(registry.defaults) if registry.defaults else {}
110+
}
111+
}
112+
113+
# Check critical models
114+
stt = registry.get_default("stt")
115+
stt_stream = registry.get_default("stt_stream")
116+
llm = registry.get_default("llm")
117+
118+
# STT check
119+
if stt:
120+
if stt.api_key:
121+
diagnostics["info"].append({
122+
"component": "STT (Batch)",
123+
"message": f"Configured: {stt.name} ({stt.model_provider}) - API key present"
124+
})
125+
else:
126+
diagnostics["warnings"].append({
127+
"component": "STT (Batch)",
128+
"severity": "warning",
129+
"message": f"{stt.name} ({stt.model_provider}) - No API key configured",
130+
"resolution": "Transcription will fail without API key"
131+
})
132+
else:
133+
diagnostics["issues"].append({
134+
"component": "STT (Batch)",
135+
"severity": "error",
136+
"message": "No batch STT model configured",
137+
"resolution": "Set defaults.stt in config.yml"
138+
})
139+
diagnostics["overall_status"] = "partial"
140+
141+
# Streaming STT check
142+
if stt_stream:
143+
if stt_stream.api_key:
144+
diagnostics["info"].append({
145+
"component": "STT (Streaming)",
146+
"message": f"Configured: {stt_stream.name} ({stt_stream.model_provider}) - API key present"
147+
})
148+
else:
149+
diagnostics["warnings"].append({
150+
"component": "STT (Streaming)",
151+
"severity": "warning",
152+
"message": f"{stt_stream.name} ({stt_stream.model_provider}) - No API key configured",
153+
"resolution": "Real-time transcription will fail without API key"
154+
})
155+
else:
156+
diagnostics["warnings"].append({
157+
"component": "STT (Streaming)",
158+
"severity": "warning",
159+
"message": "No streaming STT model configured - streaming worker disabled",
160+
"resolution": "Set defaults.stt_stream in config.yml for WebSocket transcription"
161+
})
162+
163+
# LLM check
164+
if llm:
165+
if llm.api_key:
166+
diagnostics["info"].append({
167+
"component": "LLM",
168+
"message": f"Configured: {llm.name} ({llm.model_provider}) - API key present"
169+
})
170+
else:
171+
diagnostics["warnings"].append({
172+
"component": "LLM",
173+
"severity": "warning",
174+
"message": f"{llm.name} ({llm.model_provider}) - No API key configured",
175+
"resolution": "Memory extraction will fail without API key"
176+
})
177+
178+
else:
179+
diagnostics["overall_status"] = "unhealthy"
180+
diagnostics["issues"].append({
181+
"component": "Model Registry",
182+
"severity": "error",
183+
"message": "Failed to load model registry",
184+
"resolution": "Check config/defaults.yml for syntax errors"
185+
})
186+
diagnostics["components"]["model_registry"] = {
187+
"status": "unhealthy",
188+
"message": "Registry failed to load"
189+
}
190+
except Exception as e:
191+
diagnostics["overall_status"] = "partial"
192+
diagnostics["issues"].append({
193+
"component": "Model Registry",
194+
"severity": "error",
195+
"message": f"Error loading registry: {str(e)}",
196+
"resolution": "Check logs for detailed error information"
197+
})
198+
diagnostics["components"]["model_registry"] = {
199+
"status": "unhealthy",
200+
"message": str(e)
201+
}
202+
203+
# Check environment variables
204+
env_checks = [
205+
("DEEPGRAM_API_KEY", "Required for Deepgram transcription"),
206+
("OPENAI_API_KEY", "Required for OpenAI LLM and embeddings"),
207+
("AUTH_SECRET_KEY", "Required for authentication"),
208+
("ADMIN_EMAIL", "Required for admin user login"),
209+
("ADMIN_PASSWORD", "Required for admin user login"),
210+
]
211+
212+
for env_var, description in env_checks:
213+
value = os.getenv(env_var)
214+
if not value or value == "":
215+
diagnostics["warnings"].append({
216+
"component": "Environment Variables",
217+
"severity": "warning",
218+
"message": f"{env_var} not set - {description}",
219+
"resolution": f"Set {env_var} in .env file"
220+
})
221+
222+
return diagnostics
223+
224+
26225
async def get_current_metrics():
27226
"""Get current system metrics."""
28227
try:
@@ -626,7 +825,7 @@ async def validate_chat_config_yaml(prompt_text: str) -> dict:
626825
async def get_plugins_config_yaml() -> str:
627826
"""Get plugins configuration as YAML text."""
628827
try:
629-
plugins_yml_path = Path("/app/plugins.yml")
828+
plugins_yml_path = get_plugins_yml_path()
630829

631830
# Default empty plugins config
632831
default_config = """plugins:
@@ -658,7 +857,7 @@ async def get_plugins_config_yaml() -> str:
658857
async def save_plugins_config_yaml(yaml_content: str) -> dict:
659858
"""Save plugins configuration from YAML text."""
660859
try:
661-
plugins_yml_path = Path("/app/plugins.yml")
860+
plugins_yml_path = get_plugins_yml_path()
662861

663862
# Validate YAML can be parsed
664863
try:

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ class MemoryConfigRequest(BaseModel):
3030
config_yaml: str
3131

3232

33+
@router.get("/config/diagnostics")
34+
async def get_config_diagnostics(current_user: User = Depends(current_superuser)):
35+
"""Get configuration diagnostics including errors, warnings, and status. Admin only."""
36+
return await system_controller.get_config_diagnostics()
37+
38+
3339
@router.get("/metrics")
3440
async def get_current_metrics(current_user: User = Depends(current_superuser)):
3541
"""Get current system metrics. Admin only."""

backends/advanced/src/advanced_omi_backend/services/plugin_service.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import logging
88
import os
99
import re
10-
from typing import Optional, Any
1110
from pathlib import Path
11+
from typing import Any, Optional
12+
1213
import yaml
1314

15+
from advanced_omi_backend.config_loader import get_plugins_yml_path
1416
from advanced_omi_backend.plugins import PluginRouter
1517

1618
logger = logging.getLogger(__name__)
@@ -111,7 +113,7 @@ def init_plugin_router() -> Optional[PluginRouter]:
111113
_plugin_router = PluginRouter()
112114

113115
# Load plugin configuration
114-
plugins_yml = Path("/app/plugins.yml")
116+
plugins_yml = get_plugins_yml_path()
115117
logger.info(f"🔍 Looking for plugins config at: {plugins_yml}")
116118
logger.info(f"🔍 File exists: {plugins_yml.exists()}")
117119

@@ -132,13 +134,17 @@ def init_plugin_router() -> Optional[PluginRouter]:
132134

133135
try:
134136
if plugin_id == 'homeassistant':
135-
from advanced_omi_backend.plugins.homeassistant import HomeAssistantPlugin
137+
from advanced_omi_backend.plugins.homeassistant import (
138+
HomeAssistantPlugin,
139+
)
136140
plugin = HomeAssistantPlugin(plugin_config)
137141
# Note: async initialization happens in app_factory lifespan
138142
_plugin_router.register_plugin(plugin_id, plugin)
139143
logger.info(f"✅ Plugin '{plugin_id}' registered")
140144
elif plugin_id == 'test_event':
141-
from advanced_omi_backend.plugins.test_event import TestEventPlugin
145+
from advanced_omi_backend.plugins.test_event import (
146+
TestEventPlugin,
147+
)
142148
plugin = TestEventPlugin(plugin_config)
143149
# Note: async initialization happens in app_factory lifespan
144150
_plugin_router.register_plugin(plugin_id, plugin)

backends/advanced/webui/src/pages/System.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,31 @@ interface DiarizationSettings {
4646
max_speakers: number
4747
}
4848

49+
interface DiagnosticIssue {
50+
component: string
51+
severity: 'error' | 'warning' | 'info'
52+
message: string
53+
resolution?: string
54+
}
55+
56+
interface ConfigDiagnostics {
57+
timestamp: string
58+
overall_status: 'healthy' | 'partial' | 'unhealthy'
59+
issues: DiagnosticIssue[]
60+
warnings: DiagnosticIssue[]
61+
info: DiagnosticIssue[]
62+
components: Record<string, {
63+
status: string
64+
message: string
65+
details?: any
66+
}>
67+
}
68+
4969
export default function System() {
5070
const [healthData, setHealthData] = useState<HealthData | null>(null)
5171
const [readinessData, setReadinessData] = useState<any>(null)
5272
const [metricsData, setMetricsData] = useState<MetricsData | null>(null)
73+
const [configDiagnostics, setConfigDiagnostics] = useState<ConfigDiagnostics | null>(null)
5374
const [processorStatus, setProcessorStatus] = useState<ProcessorStatus | null>(null)
5475
const [activeClients, setActiveClients] = useState<ActiveClient[]>([])
5576
const [loading, setLoading] = useState(false)

backends/advanced/webui/src/services/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export const systemApi = {
187187
getHealth: () => api.get('/health'),
188188
getReadiness: () => api.get('/readiness'),
189189
getMetrics: () => api.get('/api/metrics'),
190+
getConfigDiagnostics: () => api.get('/api/config/diagnostics'),
190191
getProcessorStatus: () => api.get('/api/processor/status'),
191192
getProcessorTasks: () => api.get('/api/processor/tasks'),
192193
getActiveClients: () => api.get('/api/clients/active'),

0 commit comments

Comments
 (0)