Skip to content

Commit 1398f78

Browse files
Refactor pre-commit configuration and enhance development scripts (#309)
* Refactor pre-commit configuration and enhance development scripts - Removed local hooks for Robot Framework tests and cleanup from `.pre-commit-config.yaml`, streamlining the pre-commit setup. - Updated `Makefile` to install pre-commit using the `uv` tool, improving dependency management. - Enhanced `restart.sh`, `start.sh`, `status.sh`, and `stop.sh` scripts to source a new `check_uv.sh` script for better environment validation. - Added new environment variables for Galileo observability in `.env.template`, improving observability setup. - Introduced OpenTelemetry initialization in `app_factory.py` for enhanced observability during application runtime. * Update button event handling and plugin architecture - Changed button state terminology from `SINGLE_TAP` and `DOUBLE_TAP` to `SINGLE_PRESS` and `DOUBLE_PRESS` across various files, including documentation and code implementations. - Enhanced the `send_button_event` method to reflect the updated button state values, ensuring consistency in event handling. - Introduced new methods for managing button events in the plugin architecture, improving the overall interaction with device buttons. - Updated tests to align with the new button state definitions, ensuring robust coverage for the updated functionality. * Add unit tests for Qwen3-ASR output parsing and repetition detection - Introduced a new test file `test_qwen3_asr_parsing.py` to validate the functionality of the `_parse_qwen3_output` and `detect_and_fix_repetitions` methods. - Implemented various test cases covering standard and edge cases for ASR output parsing, including language detection, handling of empty inputs, and unexpected text. - Added tests for repetition detection to ensure proper functionality based on specified thresholds. - Enhanced the `Makefile` to include a new target for running specific tests by name, tag, or file, improving test execution flexibility. - Created a shared prerequisite check script `check_uv.sh` to ensure the `uv` package manager is installed before running scripts, enhancing setup reliability. * Add unit tests for Qwen3-ASR output parsing and repetition detection - Introduced a new test file `test_qwen3_asr_parsing.py` to validate the functionality of the `_parse_qwen3_output` and `detect_and_fix_repetitions` methods. - Implemented various test cases covering standard and edge cases for ASR output parsing, including language detection, handling of empty inputs, and unexpected text. - Added tests for repetition detection to ensure proper functionality based on specified thresholds. * Refactor Redis session handling and enhance error management - Updated session retrieval logic in `queue_routes.py` to ensure proper closure of Redis connections using `await redis_client.aclose()`, improving resource management. - Enhanced error handling during session data retrieval, providing clearer logging for issues encountered while fetching session information. - Streamlined the session key scanning process, maintaining existing functionality while improving code readability and maintainability. - Added optional parameters to the `transcribe` method in `mock_provider.py` for better flexibility in handling context information and progress callbacks during transcription tasks.
1 parent 5a8a853 commit 1398f78

46 files changed

Lines changed: 3442 additions & 1691 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,22 @@
11
repos:
2-
# Local hooks (project-specific checks)
3-
- repo: local
4-
hooks:
5-
# Run Robot Framework endpoint tests before push
6-
- id: robot-framework-tests
7-
name: Robot Framework Tests (Endpoints)
8-
entry: bash -c 'cd tests && make endpoints OUTPUTDIR=.pre-commit-results'
9-
language: system
10-
pass_filenames: false
11-
stages: [push]
12-
verbose: true
13-
14-
# Clean up test results after hook runs
15-
- id: cleanup-test-results
16-
name: Cleanup Test Results
17-
entry: bash -c 'cd tests && rm -rf .pre-commit-results'
18-
language: system
19-
pass_filenames: false
20-
stages: [push]
21-
always_run: true
22-
232
# Code formatting
243
- repo: https://github.com/psf/black
254
rev: 24.4.2
265
hooks:
276
- id: black
28-
files: ^backends/advanced-backend/src/.*\.py$
7+
exclude: \.venv/
298
- repo: https://github.com/PyCQA/isort
309
rev: 5.13.2
3110
hooks:
3211
- id: isort
33-
files: ^backends/advanced-backend/src/.*\.py$
12+
args: ["--profile", "black"]
13+
exclude: \.venv/
3414

3515
# File hygiene
3616
- repo: https://github.com/pre-commit/pre-commit-hooks
3717
rev: v4.5.0
3818
hooks:
3919
- id: trailing-whitespace
40-
files: ^backends/advanced-backend/src/.*
20+
exclude: \.venv/
4121
- id: end-of-file-fixer
42-
files: ^backends/advanced-backend/src/.*
22+
exclude: \.venv/

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ help: ## Show detailed help for all targets
122122
setup-dev: ## Setup development environment (git hooks, pre-commit)
123123
@echo "🛠️ Setting up development environment..."
124124
@echo ""
125+
@bash scripts/check_uv.sh
125126
@echo "📦 Installing pre-commit..."
126-
@pip install pre-commit 2>/dev/null || pip3 install pre-commit
127+
@uv tool install pre-commit
127128
@echo ""
128129
@echo "🔧 Installing git hooks..."
129130
@pre-commit install --hook-type pre-push

backends/advanced/.env.template

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ LANGFUSE_PUBLIC_KEY=
6262
LANGFUSE_SECRET_KEY=
6363
LANGFUSE_BASE_URL=http://langfuse-web:3000
6464

65+
# Galileo (OTEL-based LLM observability)
66+
GALILEO_API_KEY=
67+
GALILEO_PROJECT=chronicle
68+
GALILEO_LOG_STREAM=default
69+
# GALILEO_CONSOLE_URL=https://app.galileo.ai # Default; override for self-hosted
70+
6571
# Qwen3-ASR (offline ASR via vLLM)
6672
# QWEN3_ASR_URL=host.docker.internal:8767
6773
# QWEN3_ASR_STREAM_URL=host.docker.internal:8769

backends/advanced/Docs/plugin-development-guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ async def on_memory_processed(self, context: PluginContext):
206206

207207
**When**: OMI device button is pressed
208208
**Context Data**:
209-
- `state` (str): Button state (`SINGLE_TAP`, `DOUBLE_TAP`)
209+
- `state` (str): Button state (`SINGLE_PRESS`, `DOUBLE_PRESS`)
210210
- `timestamp` (float): Unix timestamp of the event
211211
- `audio_uuid` (str): Current audio session UUID (may be None)
212212
- `session_id` (str): Streaming session ID (for conversation close)
@@ -222,7 +222,7 @@ friend-lite-sdk (extras/friend-lite-sdk/)
222222
→ parse_button_event() converts payload → ButtonState IntEnum
223223
224224
BLE Client (extras/local-wearable-client/ or mobile app)
225-
→ Formats as Wyoming protocol: {"type": "button-event", "data": {"state": "SINGLE_TAP"}}
225+
→ Formats as Wyoming protocol: {"type": "button-event", "data": {"state": "SINGLE_PRESS"}}
226226
→ Sends over WebSocket
227227
228228
Backend (websocket_controller.py)

backends/advanced/src/advanced_omi_backend/app_factory.py

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -71,26 +71,48 @@ async def initialize_openmemory_user() -> None:
7171

7272
# Get configured user_id and server_url
7373
openmemory_config = memory_provider_config.openmemory_config
74-
user_id = openmemory_config.get("user_id", "openmemory") if openmemory_config else "openmemory"
75-
server_url = openmemory_config.get("server_url", "http://host.docker.internal:8765") if openmemory_config else "http://host.docker.internal:8765"
76-
client_name = openmemory_config.get("client_name", "chronicle") if openmemory_config else "chronicle"
74+
user_id = (
75+
openmemory_config.get("user_id", "openmemory")
76+
if openmemory_config
77+
else "openmemory"
78+
)
79+
server_url = (
80+
openmemory_config.get("server_url", "http://host.docker.internal:8765")
81+
if openmemory_config
82+
else "http://host.docker.internal:8765"
83+
)
84+
client_name = (
85+
openmemory_config.get("client_name", "chronicle")
86+
if openmemory_config
87+
else "chronicle"
88+
)
7789

78-
application_logger.info(f"Registering OpenMemory user: {user_id} at {server_url}")
90+
application_logger.info(
91+
f"Registering OpenMemory user: {user_id} at {server_url}"
92+
)
7993

8094
# Make a lightweight registration call (create and delete dummy memory)
81-
async with MCPClient(server_url=server_url, client_name=client_name, user_id=user_id) as client:
95+
async with MCPClient(
96+
server_url=server_url, client_name=client_name, user_id=user_id
97+
) as client:
8298
# Test connection first
8399
is_connected = await client.test_connection()
84100
if is_connected:
85101
# Create and immediately delete a dummy memory to trigger user creation
86-
memory_ids = await client.add_memories("Chronicle initialization - user registration test")
102+
memory_ids = await client.add_memories(
103+
"Chronicle initialization - user registration test"
104+
)
87105
if memory_ids:
88106
# Delete the test memory
89107
await client.delete_memory(memory_ids[0])
90108
application_logger.info(f"✅ Registered OpenMemory user: {user_id}")
91109
else:
92-
application_logger.warning(f"⚠️ OpenMemory MCP not reachable at {server_url}")
93-
application_logger.info("User will be auto-created on first memory operation")
110+
application_logger.warning(
111+
f"⚠️ OpenMemory MCP not reachable at {server_url}"
112+
)
113+
application_logger.info(
114+
"User will be auto-created on first memory operation"
115+
)
94116
except Exception as e:
95117
application_logger.warning(f"⚠️ Could not register OpenMemory user: {e}")
96118
application_logger.info("User will be auto-created on first memory operation")
@@ -116,7 +138,13 @@ async def lifespan(app: FastAPI):
116138

117139
await init_beanie(
118140
database=config.db,
119-
document_models=[User, Conversation, AudioChunkDocument, WaveformData, Annotation],
141+
document_models=[
142+
User,
143+
Conversation,
144+
AudioChunkDocument,
145+
WaveformData,
146+
Annotation,
147+
],
120148
)
121149
application_logger.info("Beanie initialized for all document models")
122150
except Exception as e:
@@ -133,12 +161,17 @@ async def lifespan(app: FastAPI):
133161
# Initialize Redis connection for RQ
134162
try:
135163
from advanced_omi_backend.controllers.queue_controller import redis_conn
164+
136165
redis_conn.ping()
137166
application_logger.info("Redis connection established for RQ")
138-
application_logger.info("RQ workers can be started with: rq worker transcription memory default")
167+
application_logger.info(
168+
"RQ workers can be started with: rq worker transcription memory default"
169+
)
139170
except Exception as e:
140171
application_logger.error(f"Failed to connect to Redis for RQ: {e}")
141-
application_logger.warning("RQ queue system will not be available - check Redis connection")
172+
application_logger.warning(
173+
"RQ queue system will not be available - check Redis connection"
174+
)
142175

143176
# Initialize BackgroundTaskManager (must happen before any code path uses it)
144177
try:
@@ -153,6 +186,14 @@ async def lifespan(app: FastAPI):
153186
get_client_manager()
154187
application_logger.info("ClientManager initialized")
155188

189+
# Initialize OTEL/Galileo if configured (before LLM client so instrumentor patches OpenAI first)
190+
try:
191+
from advanced_omi_backend.observability.otel_setup import init_otel
192+
193+
init_otel()
194+
except Exception as e:
195+
application_logger.warning(f"OTEL initialization skipped: {e}")
196+
156197
# Initialize prompt registry with defaults; seed into LangFuse in background
157198
try:
158199
from advanced_omi_backend.prompt_defaults import register_all_defaults
@@ -176,6 +217,7 @@ async def _deferred_seed():
176217
# Initialize LLM client eagerly (catch config errors at startup, not on first request)
177218
try:
178219
from advanced_omi_backend.llm_client import get_llm_client
220+
179221
get_llm_client()
180222
application_logger.info("LLM client initialized from config.yml")
181223
except Exception as e:
@@ -186,35 +228,47 @@ async def _deferred_seed():
186228
audio_service = get_audio_stream_service()
187229
await audio_service.connect()
188230
application_logger.info("Audio stream service connected to Redis Streams")
189-
application_logger.info("Audio stream workers can be started with: python -m advanced_omi_backend.workers.audio_stream_worker")
231+
application_logger.info(
232+
"Audio stream workers can be started with: python -m advanced_omi_backend.workers.audio_stream_worker"
233+
)
190234
except Exception as e:
191235
application_logger.error(f"Failed to connect audio stream service: {e}")
192-
application_logger.warning("Redis Streams audio processing will not be available")
236+
application_logger.warning(
237+
"Redis Streams audio processing will not be available"
238+
)
193239

194240
# Initialize Redis client for audio streaming producer (used by WebSocket handlers)
195241
try:
196242
app.state.redis_audio_stream = await redis.from_url(
197-
config.redis_url,
198-
encoding="utf-8",
199-
decode_responses=False
243+
config.redis_url, encoding="utf-8", decode_responses=False
200244
)
201245
from advanced_omi_backend.services.audio_stream import AudioStreamProducer
202-
app.state.audio_stream_producer = AudioStreamProducer(app.state.redis_audio_stream)
203-
application_logger.info("✅ Redis client for audio streaming producer initialized")
246+
247+
app.state.audio_stream_producer = AudioStreamProducer(
248+
app.state.redis_audio_stream
249+
)
250+
application_logger.info(
251+
"✅ Redis client for audio streaming producer initialized"
252+
)
204253

205254
# Initialize ClientManager Redis for cross-container client→user mapping
206255
from advanced_omi_backend.client_manager import (
207256
initialize_redis_for_client_manager,
208257
)
258+
209259
initialize_redis_for_client_manager(config.redis_url)
210260

211261
except Exception as e:
212-
application_logger.error(f"Failed to initialize Redis client for audio streaming: {e}", exc_info=True)
262+
application_logger.error(
263+
f"Failed to initialize Redis client for audio streaming: {e}", exc_info=True
264+
)
213265
application_logger.warning("Audio streaming producer will not be available")
214266

215267
# Skip memory service pre-initialization to avoid blocking FastAPI startup
216268
# Memory service will be lazily initialized when first used
217-
application_logger.info("Memory service will be initialized on first use (lazy loading)")
269+
application_logger.info(
270+
"Memory service will be initialized on first use (lazy loading)"
271+
)
218272

219273
# Register OpenMemory user if using openmemory_mcp provider
220274
await initialize_openmemory_user()
@@ -264,12 +318,15 @@ async def _deferred_seed():
264318
application_logger.info(f"✅ Plugin '{plugin_id}' initialized")
265319
except Exception as e:
266320
plugin_router.mark_plugin_failed(plugin_id, str(e))
267-
application_logger.error(f"Failed to initialize plugin '{plugin_id}': {e}", exc_info=True)
321+
application_logger.error(
322+
f"Failed to initialize plugin '{plugin_id}': {e}",
323+
exc_info=True,
324+
)
268325

269326
health = plugin_router.get_health_summary()
270327
application_logger.info(
271328
f"Plugins initialized: {health['initialized']}/{health['total']} active"
272-
+ (f", {health['failed']} failed" if health['failed'] else "")
329+
+ (f", {health['failed']} failed" if health["failed"] else "")
273330
)
274331

275332
# Store in app state for API access
@@ -281,10 +338,14 @@ async def _deferred_seed():
281338
app.state.plugin_router = None
282339

283340
except Exception as e:
284-
application_logger.error(f"Failed to initialize plugin system: {e}", exc_info=True)
341+
application_logger.error(
342+
f"Failed to initialize plugin system: {e}", exc_info=True
343+
)
285344
app.state.plugin_router = None
286345

287-
application_logger.info("Application ready - using application-level processing architecture.")
346+
application_logger.info(
347+
"Application ready - using application-level processing architecture."
348+
)
288349

289350
logger.info("App ready")
290351
try:
@@ -300,6 +361,7 @@ async def _deferred_seed():
300361
from advanced_omi_backend.controllers.websocket_controller import (
301362
cleanup_client_state,
302363
)
364+
303365
await cleanup_client_state(client_id)
304366
except Exception as e:
305367
application_logger.error(f"Error cleaning up client {client_id}: {e}")
@@ -327,9 +389,14 @@ async def _deferred_seed():
327389

328390
# Close Redis client for audio streaming producer
329391
try:
330-
if hasattr(app.state, 'redis_audio_stream') and app.state.redis_audio_stream:
392+
if (
393+
hasattr(app.state, "redis_audio_stream")
394+
and app.state.redis_audio_stream
395+
):
331396
await app.state.redis_audio_stream.close()
332-
application_logger.info("Redis client for audio streaming producer closed")
397+
application_logger.info(
398+
"Redis client for audio streaming producer closed"
399+
)
333400
except Exception as e:
334401
application_logger.error(f"Error closing Redis audio streaming client: {e}")
335402

@@ -341,6 +408,7 @@ async def _deferred_seed():
341408
from advanced_omi_backend.services.plugin_service import (
342409
cleanup_plugin_router,
343410
)
411+
344412
await cleanup_plugin_router()
345413
application_logger.info("Plugins shut down")
346414
except Exception as e:
@@ -380,7 +448,7 @@ def create_app() -> FastAPI:
380448
# Add WebSocket router at root level (not under /api prefix)
381449
app.include_router(websocket_router)
382450

383-
# Add authentication routers
451+
# Add authentication routers
384452
app.include_router(
385453
fastapi_users.get_auth_router(cookie_backend),
386454
prefix="/auth/cookie",
@@ -403,6 +471,8 @@ def create_app() -> FastAPI:
403471
CHUNK_DIR = Path("/app/audio_chunks")
404472
app.mount("/audio", StaticFiles(directory=CHUNK_DIR), name="audio")
405473

406-
logger.info("FastAPI application created with all routers and middleware configured")
474+
logger.info(
475+
"FastAPI application created with all routers and middleware configured"
476+
)
407477

408-
return app
478+
return app

0 commit comments

Comments
 (0)