-
Notifications
You must be signed in to change notification settings - Fork 24
Feature/annotations #270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: fix/conversation-view-mongo
Are you sure you want to change the base?
Feature/annotations #270
Conversation
- Introduced a new annotation model to support user edits and AI-powered suggestions for memories and transcripts. - Added annotation routes for CRUD operations, enabling the creation and management of annotations via the API. - Enhanced the audio processing workflow to support fetching audio segments from the backend, improving speaker recognition accuracy. - Updated the speaker recognition client to handle conversation-based audio fetching, allowing for better management of large audio files. - Implemented a cron job for generating AI suggestions on potential errors in transcripts and memories, improving user experience and content accuracy. - Enhanced the web UI to support inline editing of transcript segments and memory content, providing a more interactive user experience. - Updated configuration files to support new features and improve overall system flexibility.
- Introduced a new configuration loader using OmegaConf for unified management of backend settings. - Updated existing configuration functions to leverage the new loader, enhancing flexibility and maintainability. - Added support for environment variable interpolation in configuration files. - Refactored various components to retrieve settings from the new configuration system, improving consistency across the application. - Updated requirements to include OmegaConf as a dependency. - Enhanced documentation and comments for clarity on configuration management.
- Updated the .env.template to clarify its purpose for secret values and streamline setup instructions. - Removed the deprecated diarization_config.json.template file, as it is no longer needed. - Added new environment variables for Langfuse and Tailscale integration to enhance observability and remote service access.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 📝 WalkthroughWalkthroughThis pull request introduces a centralized OmegaConf-based configuration system, implements a polymorphic annotation framework for memory and transcript corrections, adds background job scheduling for AI-powered suggestions, refactors speaker recognition to use token-based backend authentication, and enhances the API and frontend with annotation management and inline editing capabilities. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
backends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.py (1)
139-140: Bug:settingsis undefined – this will raiseNameErrorat runtime.Line 140 references
settings.retention_days, butsettingswas never defined in this function. It appears this line wasn't updated during the migration to dictionary-based config access.🐛 Proposed fix
if retention_days is None: - retention_days = settings.retention_days + retention_days = settings_dict['retention_days']backends/advanced/src/advanced_omi_backend/speaker_recognition_client.py (2)
91-93: Inconsistent error return shapes.When disabled (line 92-93), the method returns
{}. On HTTP errors (line 163), it also returns{}. However, exception handlers (lines 175-184) return{"error": ..., "message": ..., "segments": []}.Callers expecting the
segmentskey will fail when receiving{}. Consider standardizing the return shape:♻️ Suggested fix for consistency
if not self.enabled: logger.info(f"🎤 Speaker recognition disabled, returning empty result") - return {} + return {"segments": []}if response.status != 200: response_text = await response.text() logger.error( f"🎤 ❌ Speaker service returned status {response.status}: {response_text}" ) - return {} + return {"error": "http_error", "message": response_text, "segments": []}Also applies to: 163-163, 175-184
336-347: Inconsistent config access: uses private_diarization_settingsinstead ofget_diarization_settings().This method imports
_diarization_settingsdirectly fromsystem_controller, whilediarize_identify_match(line 99) anddiarize_and_identify(line 222) use the newget_diarization_settings()function.Update for consistency with the new OmegaConf-based config:
🐛 Suggested fix
# Get current diarization settings - from advanced_omi_backend.controllers.system_controller import _diarization_settings + from advanced_omi_backend.config import get_diarization_settings + _diarization_settings = get_diarization_settings()
🤖 Fix all issues with AI agents
In `@backends/advanced/src/advanced_omi_backend/controllers/system_controller.py`:
- Around line 181-182: The call to save_cleanup_settings is not checking its
boolean return value, so failures still produce a success response; update the
controller that calls save_cleanup_settings to capture its return (e.g., ok =
save_cleanup_settings(settings)) and mirror the error handling used in
save_diarization_settings_controller: if ok is False, log the error and return a
failure response (HTTP error or structured error body) instead of proceeding to
a success response; ensure you reference save_cleanup_settings in the
log/response to aid debugging.
- Line 79: Router still calls the old function name
system_controller.save_diarization_settings which was renamed to
save_diarization_settings_controller; update the router handler to call and
await the new function name, e.g. replace
system_controller.save_diarization_settings(settings) with await
system_controller.save_diarization_settings_controller(settings) so the router
invokes the correct async function.
In
`@backends/advanced/src/advanced_omi_backend/routers/modules/annotation_routes.py`:
- Around line 286-298: The Conversation.find_one call that fetches by
Conversation.conversation_id should also filter by the owner to enforce
defense-in-depth: change the lookup in the annotation update path to include
Conversation.user_id == annotation.user_id (i.e., use
Conversation.find_one(Conversation.conversation_id ==
annotation.conversation_id, Conversation.user_id == annotation.user_id)); keep
the subsequent get_active_transcript, segment bounds check, text assignment to
transcript.segments[annotation.segment_index].text = annotation.corrected_text,
and await conversation.save() unchanged.
- Around line 74-90: The current flow marks and saves the annotation as ACCEPTED
before calling memory_service.update_memory, leading to inconsistency if
update_memory fails; change the flow in the route handling code that checks
annotation.status == AnnotationStatus.ACCEPTED so that you first call await
memory_service.update_memory(...) using annotation_data.memory_id and
annotation_data.corrected_text, ensure it succeeds, and only then set
annotation.status = AnnotationStatus.ACCEPTED and persist/save the annotation
(or alternatively, if you must save earlier, implement a rollback that resets
annotation.status to PENDING and re-saves the annotation when update_memory
raises); update logging to record success/failure tied to annotation.id and
include the chosen rollback or save-after-update behavior consistently.
In
`@backends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.py`:
- Around line 259-267: Replace the broad except block around the call to
reconstruct_audio_segment with more precise handling: catch expected errors
(e.g., ValueError, IOError or a domain-specific ReconstructionError if present)
instead of bare Exception, log the failure with logger.exception(...) to
preserve the traceback and context for conversation_id, and re-raise the FastAPI
HTTPException using "raise HTTPException(... ) from e" to preserve the exception
chain; keep references to reconstruct_audio_segment, logger, HTTPException and
conversation_id when making these changes.
In
`@backends/advanced/src/advanced_omi_backend/services/memory/providers/chronicle.py`:
- Around line 365-374: The code incorrectly calls
self.llm_provider.generate_embedding(...) which doesn't exist; replace both
calls to generate_embedding with the correct method generate_embeddings and take
the first embedding from the returned list (e.g., call
generate_embeddings(new_content) and assign new_embedding = result[0]); update
both places in the conditional around existing_memory.embedding so new_embedding
is always set from either existing_memory.embedding or the first element
returned by generate_embeddings.
In `@backends/advanced/src/advanced_omi_backend/speaker_recognition_client.py`:
- Around line 69-75: The user_id parameter of diarize_identify_match is unused
and the form data currently hardcodes "1"; either remove user_id from the
function signature (and update all callers/tests) or use it in the form payload
by replacing the hardcoded "1" values with the validated user_id (ensure
non-empty/expected format), update the form field name where transcript_data is
packaged, and adjust any unit/integration tests and callers that pass user_id
accordingly; reference the diarize_identify_match method and the form-data
assembly spots that currently use the literal "1" to make these changes.
In `@backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py`:
- Around line 531-549: The validation of start_time/end_time runs before
clamping end_time to the conversation duration, which can turn a previously
valid range into an invalid one (silently producing empty audio); after fetching
total_duration from Conversation.get and clamping end_time (and optionally
clamping start_time to <= total_duration), re-run the range check and raise a
ValueError if start_time >= end_time (or alternatively clamp both start_time =
min(start_time, total_duration) and end_time = min(end_time, total_duration) and
then validate), so update the logic around Conversation.get, total_duration, and
the clamping of end_time to ensure you validate the final clamped values before
proceeding.
In `@backends/advanced/src/advanced_omi_backend/workers/annotation_jobs.py`:
- Around line 104-106: The log call is printing PII via user.email; update the
logger.debug invocation used in the annotation job (the line calling
logger.debug with f" Analyzed user {user.email} (placeholder)") to avoid
logging emails—use a non-PII identifier such as user.id or an anonymized token
instead (e.g., reference user.id in the message), and ensure any tests or other
code that reads that log are adjusted accordingly.
In `@backends/advanced/webui/src/pages/MemoryDetail.tsx`:
- Around line 94-129: The client currently assumes
annotationsApi.createMemoryAnnotation succeeded and updates local state
immediately in handleSaveEdit; change handleSaveEdit to inspect the API response
from annotationsApi.createMemoryAnnotation (e.g., a returned annotation object
or a memory_updated flag) and only call setMemory / setIsEditing(false) when the
response explicitly indicates the memory was updated; if the response indicates
a failure to apply the memory change (or returns an error), setSaveError with
the backend detail and do not update local state. Locate handleSaveEdit and the
annotationsApi.createMemoryAnnotation call to implement this check and handle
both success and failure paths (and consider falling back to retry or refreshing
the memory from the server if no explicit flag is returned).
In `@config/defaults.yml`:
- Around line 254-255: The default backend_api_url currently points to
host.docker.internal which doesn't resolve on many native Linux Docker setups;
change the default value for backend_api_url to use http://127.0.0.1:8000 (or
localhost) and add a brief note in the config/comments that Linux users who rely
on host.docker.internal must add an explicit --add-host or override the value
via environment/config, referencing the backend_api_url key so maintainers can
locate and document/override it.
- Around line 242-258: The OmegaConf environment interpolations for optional
secrets (hf_token and deepgram_api_key) use `${oc.env:VAR}` which throws
InterpolationKeyError if unset; update those keys (hf_token and
deepgram_api_key) to include explicit defaults using the correct syntax like
`${oc.env:HF_TOKEN,""}` or `${oc.env:DEEPGRAM_API_KEY,""}` (or ,null if you want
null) so the config loads when the env vars are absent. Ensure you only change
the hf_token and deepgram_api_key lines to include the default value and leave
other keys (similarity_threshold, max_diarize_duration, diarize_chunk_overlap,
backend_api_url) untouched.
In
`@extras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.py`:
- Around line 306-309: The log call using log.info with the literal "Processing
diarize-identify-match request" incorrectly uses an f-string prefix; remove the
leading "f" on that specific log.info invocation so the string is a plain
literal while leaving the other log.info calls (which use
expressions/placeholders like the mode conditional and transcript length)
unchanged.
- Around line 319-325: Remove the unused local full_text assignment and preserve
exception chaining: in the JSON parsing block that reads transcript_data into
transcript and words, drop the unused full_text = transcript.get("text", "")
line and change the except handler to raise HTTPException(400, f"Invalid
transcript_data JSON: {str(e)}") from e so the JSONDecodeError is chained; keep
the variable name e for the caught JSONDecodeError to reference in the chained
raise.
- Around line 336-366: The code assumes metadata['duration'] exists and will
KeyError if missing; update the conversation branch in identification.py to use
metadata.get('duration') and validate it is a number (e.g., not None and >0),
and if invalid raise an HTTPException(400) with a clear message about
missing/invalid duration; also wrap the calls to
BackendClient.get_conversation_metadata and get_audio_segment in a try/except
that converts backend errors into user-friendly HTTPException messages (while
leaving the finally await backend_client.close() intact) so failures return
meaningful HTTP errors instead of raw exceptions.
- Around line 368-386: The temporary file created with
tempfile.NamedTemporaryFile (tmp_path) can leak if get_audio_info() raises or
any other exception occurs; wrap the file creation and subsequent processing
(call to get_audio_info and duration validation against MAX_AUDIO_DURATION) in a
try/finally (or try/except where finally unlinks tmp_path) so that
tmp_path.unlink(missing_ok=True) is always called on error/exit, or perform
get_audio_info and validation inside the with-block before breaking the context,
ensuring tmp_path is deleted in the finally block when an exception is raised.
🧹 Nitpick comments (24)
backends/advanced/src/advanced_omi_backend/controllers/audio_controller.py (1)
117-117: Dead code: unusedversion_idassignment.This
version_idis never used before being overwritten at line 180. Consider removing it to avoid confusion.♻️ Suggested fix
- version_id = str(uuid.uuid4()) - # Generate title from filenamebackends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (1)
503-528: Consider documenting that returned audio may exceed the requested time range.The function returns complete overlapping chunks rather than trimming to exact
start_time/end_timeboundaries. For example, requesting 5.0-15.0 seconds from a conversation with 10-second chunks would return 20 seconds of audio (chunks 0-10 and 10-20). The existingreconstruct_audio_segmentsdocuments this behavior but this function's docstring doesn't mention it.Suggested docstring addition
Returns: WAV audio bytes (16kHz mono or original format) + + Note: + The returned audio includes complete overlapping chunks and may be + slightly longer than the requested range. Callers should trim or + merge overlapping regions during post-processing if exact boundaries + are required.backends/advanced/src/advanced_omi_backend/services/memory/providers/chronicle.py (1)
324-331: Unuseduser_emailparameter.The
user_emailparameter is declared but never used in the method body. If it's kept for API consistency with other providers, prefix it with an underscore to suppress the linter warning and signal intent:async def update_memory( self, memory_id: str, content: Optional[str] = None, metadata: Optional[dict[str, Any]] = None, user_id: Optional[str] = None, - user_email: Optional[str] = None + _user_email: Optional[str] = None ) -> bool:Alternatively, if it might be used in the future (e.g., for audit logging), consider adding a brief comment explaining its purpose.
extras/speaker-recognition/src/simple_speaker_recognition/core/backend_client.py (2)
14-24: Consider passingbase_urltoAsyncClientfor proper URL handling.The
AsyncClientis created without thebase_urlparameter, requiring you to construct full URLs manually in each method. Usinghttpx.AsyncClient(base_url=..., timeout=...)would simplify the URL construction and is the idiomatic httpx pattern.Additionally, consider implementing async context manager support (
__aenter__/__aexit__) for safer resource management.♻️ Suggested improvement
def __init__(self, base_url: str, timeout: float = 30.0): """ Initialize backend client. Args: base_url: Backend API base URL (e.g., http://host.docker.internal:8000) timeout: Request timeout in seconds (default: 30.0) """ self.base_url = base_url.rstrip('/') self.timeout = timeout - self.client = httpx.AsyncClient(timeout=self.timeout) + self.client = httpx.AsyncClient(base_url=self.base_url, timeout=self.timeout) + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close()
100-103: Consider adding exception handling inclose()for resilience.Based on the pattern in
mycelia.py(lines 820-829), theclosemethod catches exceptions duringaclose()and logs them without re-raising. This prevents cleanup failures from masking the original exception when called infinallyblocks.♻️ More resilient close implementation
async def close(self): """Close HTTP client and release resources.""" - await self.client.aclose() - logger.debug("Backend client closed") + try: + await self.client.aclose() + logger.debug("Backend client closed") + except Exception as e: + logger.warning(f"Error closing backend client: {e}")backends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.py (1)
15-15: Unused import:CleanupSettingsis imported but never used.The dataclass
CleanupSettingsis imported but not referenced anywhere in this file.♻️ Proposed fix
-from advanced_omi_backend.config import get_cleanup_settings, CleanupSettings +from advanced_omi_backend.config import get_cleanup_settingsbackends/advanced/src/advanced_omi_backend/workers/speaker_jobs.py (2)
211-231: Token-based authentication approach is sound.The shift to having the speaker service fetch audio from the backend using a JWT is a good architectural improvement – it decouples audio reconstruction from the job and lets the speaker service manage its own memory constraints.
Minor: Remove the extraneous
fprefix on line 231 since the string has no placeholders.♻️ Proposed fix for static analysis warning
- logger.info(f"🔐 Generated backend token for speaker service") + logger.info("🔐 Generated backend token for speaker service")
243-257: Speaker service call pattern looks good.The clear comments explaining the speaker service's responsibilities (fetching metadata, chunking decisions, audio segment requests) improve maintainability.
Minor: Remove the extraneous
fprefix on line 249.♻️ Proposed fix for static analysis warning
- logger.info(f"🎤 Calling speaker recognition service with conversation_id...") + logger.info("🎤 Calling speaker recognition service with conversation_id...")backends/advanced/src/advanced_omi_backend/models/annotation.py (1)
46-53: Consider validation for polymorphic field consistency.The model allows creating annotations where neither
memory_idnorconversation_idis set (or both are set), which could lead to inconsistent data. Consider adding a model validator to enforce that the appropriate reference field is set based onannotation_type.♻️ Example validator
from pydantic import model_validator class Annotation(Document): # ... existing fields ... `@model_validator`(mode='after') def validate_polymorphic_refs(self) -> 'Annotation': if self.annotation_type == AnnotationType.MEMORY: if not self.memory_id: raise ValueError("memory_id required for MEMORY annotations") elif self.annotation_type == AnnotationType.TRANSCRIPT: if not self.conversation_id or self.segment_index is None: raise ValueError("conversation_id and segment_index required for TRANSCRIPT annotations") return selfconfig/config.yml.template (1)
220-223: Consider using environment variable interpolation forbackend_api_url.The diarization chunking configuration looks reasonable. However,
host.docker.internalworks out-of-the-box on Docker Desktop (macOS/Windows) but requires extra configuration on Linux Docker (--add-host=host.docker.internal:host-gateway).For consistency with other URLs in this file (e.g.,
model_urlentries), consider using environment variable interpolation:Suggested change
max_diarize_duration: 60 # Maximum audio duration (seconds) for single PyAnnote call diarize_chunk_overlap: 5.0 # Overlap (seconds) between chunks - backend_api_url: http://host.docker.internal:8000 # Backend API URL + backend_api_url: ${BACKEND_API_URL:-http://host.docker.internal:8000} # Backend API URLThis allows users on Linux or non-Docker environments to override the URL without modifying the config file.
backends/advanced/docker-compose.yml (1)
118-143: Consider adding a healthcheck for operational visibility.The
annotation-cronservice lacks a healthcheck, unlikechronicle-backendand other services. While cron jobs don't serve HTTP requests, a healthcheck can help detect if the scheduler is stuck or has crashed.Additionally, the inline comment on line 143 mentions "Optional profile - enable with: docker compose --profile annotation up" which is accurate, but ensure this aligns with documentation if users expect different profile names.
💡 Optional healthcheck example
restart: unless-stopped + healthcheck: + test: ["CMD", "pgrep", "-f", "advanced_omi_backend.cron"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 30s profiles: - annotation # Optional profile - enable with: docker compose --profile annotation upconfig/defaults.yml (1)
268-290: Backend secrets usingoc.envwithout defaults will fail if unset.Similar to the speaker_recognition section, the backend auth and API keys use strict
oc.envinterpolation that will throw errors if the corresponding environment variables are not defined. This includesAUTH_SECRET_KEY,ADMIN_EMAIL,ADMIN_PASSWORD,OPENAI_API_KEY, andDEEPGRAM_API_KEY.If these are truly required, consider adding validation with clear error messages. If optional, provide empty defaults.
💡 Suggested fix for optional keys
llm: provider: openai # or ollama - api_key: ${oc.env:OPENAI_API_KEY} + api_key: ${oc.env:OPENAI_API_KEY,} base_url: https://api.openai.com/v1 model: gpt-4o-mini timeout: 60 transcription: provider: deepgram # or parakeet - api_key: ${oc.env:DEEPGRAM_API_KEY} + api_key: ${oc.env:DEEPGRAM_API_KEY,} base_url: https://api.deepgram.combackends/advanced/webui/src/pages/Conversations.tsx (1)
928-936: Verify behavior whenconversation_idis missing but edit is initiated.Line 930 checks
conversation.conversation_idbefore callinghandleStartSegmentEdit, but the first argument passed isconversationKeywhich falls back toaudio_uuid(line 853-854). This guard is correct, but the click will silently do nothing whenconversation_idis missing.Consider adding a visual indicator (e.g., tooltip or cursor style) to signal that editing is unavailable for conversations without a
conversation_id.extras/speaker-recognition/src/simple_speaker_recognition/api/service.py (1)
56-57: Consider reducing log verbosity for loaded config.Logging the entire resolved config at INFO level could expose sensitive information if configuration values (like URLs or tokens) are included. Consider logging at DEBUG level or only logging non-sensitive keys.
📝 Suggested change
- log.info(f"Loaded speaker_recognition config: {resolved}") + log.debug(f"Loaded speaker_recognition config: {list(resolved.keys())}") return resolvedbackends/advanced/src/advanced_omi_backend/cron.py (3)
53-64: Database client is never closed.The
AsyncIOMotorClientis created but never stored for later cleanup. If the scheduler is restarted or stopped, the connection may leak.Consider storing the client reference and closing it gracefully:
♻️ Suggested improvement
+# Module-level client reference for cleanup +_mongo_client: Optional[AsyncIOMotorClient] = None async def init_db(): """Initialize database connection""" + global _mongo_client try: - client = AsyncIOMotorClient(MONGODB_URI) + _mongo_client = AsyncIOMotorClient(MONGODB_URI) await init_beanie( - database=client.chronicle, + database=_mongo_client.chronicle, document_models=[Annotation, Conversation, User], ) logger.info("✅ Database connection initialized") except Exception as e: - logger.error(f"❌ Failed to initialize database: {e}") + logger.exception(f"❌ Failed to initialize database: {e}") raiseAlso, per static analysis hint, use
logger.exceptionon line 63 to include the stack trace automatically.
74-76: Jobs will run immediately on first startup.Setting
last_suggestion_runandlast_training_runtonowmeans the elapsed time is ~0 seconds on the first loop iteration. This is actually correct behavior (jobs won't run immediately). However, if the intent is to run jobs immediately on startup, this would need adjustment.If immediate execution on startup is desired:
♻️ Optional: Run jobs immediately on startup
- last_suggestion_run = datetime.now(timezone.utc) - last_training_run = datetime.now(timezone.utc) + # Set to epoch to trigger immediate run on startup + last_suggestion_run = datetime.min.replace(tzinfo=timezone.utc) + last_training_run = datetime.min.replace(tzinfo=timezone.utc)
104-110:KeyboardInterrupthandling inside async loop may be unreliable.In
asyncio.run(), SIGINT typically cancels the running task viaCancelledErrorbefore raisingKeyboardInterruptat the top level. The inner handler (lines 104-106) may not reliably catch keyboard interrupts; the outer handler at lines 117-118 is more appropriate.Consider catching
asyncio.CancelledErrorinstead for graceful shutdown within the coroutine:♻️ Suggested improvement
- except KeyboardInterrupt: - logger.info("⛔ Scheduler stopped by user") - break + except asyncio.CancelledError: + logger.info("⛔ Scheduler cancelled") + raise # Re-raise to allow proper cleanupbackends/advanced/src/advanced_omi_backend/workers/annotation_jobs.py (1)
11-13: Unused imports:timedeltaandConversation.These imports are only used in commented-out TODO code. Consider removing them until the implementation is complete to avoid confusion:
♻️ Optional cleanup
-from datetime import datetime, timezone, timedelta +from datetime import datetime, timezone from typing import List from advanced_omi_backend.models.annotation import ( Annotation, AnnotationSource, AnnotationStatus, AnnotationType, ) -from advanced_omi_backend.models.conversation import Conversation from advanced_omi_backend.models.user import UserAlso applies to: 21-22
extras/speaker-recognition/src/simple_speaker_recognition/core/audio_backend.py (1)
157-163:soundfileimport inside function may fail silently.The
import soundfile as sfat line 159 is inside thewithblock. Ifsoundfileis not installed, this will raiseImportErrorwhich gets caught by the outer exception handling and returns an empty list, potentially masking a configuration issue.Consider importing at module level or providing a clearer error:
♻️ Option 1: Import at module level (preferred)
Add to imports at the top of the file:
import soundfile as sfThen remove the inline import at line 159.
♻️ Option 2: Explicit error handling
# Write chunk to temp file for PyAnnote with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp: - import soundfile as sf + try: + import soundfile as sf + except ImportError: + raise ImportError("soundfile library required for audio chunking. Install with: pip install soundfile") # Extract tensor data and write as WAV audio_tensor = chunk_audio.squeeze().cpu().numpy() sf.write(tmp.name, audio_tensor, 16000) chunk_path = Path(tmp.name)backends/advanced/src/advanced_omi_backend/config_loader.py (2)
65-66: Uselogger.exceptionfor stack traces on config load errors.Per static analysis hint,
logger.exceptionautomatically includes the stack trace:♻️ Suggested fix
except Exception as e: - logger.error(f"Error loading config from {config_path}: {e}") + logger.exception(f"Error loading config from {config_path}: {e}")
119-152: Consider thread/process safety for config file writes.
save_config_sectionperforms read-modify-write without file locking. In multi-process deployments, concurrent calls could cause data loss.If runtime config updates are expected to be rare and single-threaded, this is acceptable. Otherwise, consider adding file locking:
♻️ Optional: Add file locking for safety
+import fcntl + def save_config_section(section_path: str, values: dict) -> bool: ... try: config_path = get_config_dir() / "config.yml" - # Load existing config - existing_config = {} - if config_path.exists(): - existing_config = OmegaConf.load(config_path) + # Use file locking for concurrent access safety + with open(config_path, 'r+' if config_path.exists() else 'w+') as f: + fcntl.flock(f, fcntl.LOCK_EX) + try: + existing_config = OmegaConf.load(config_path) if config_path.exists() else {} + OmegaConf.update(existing_config, section_path, values, merge=True) + OmegaConf.save(existing_config, config_path) + finally: + fcntl.flock(f, fcntl.LOCK_UN)Also, use
logger.exceptionon line 151 per static analysis hint.backends/advanced/src/advanced_omi_backend/routers/modules/annotation_routes.py (1)
13-13: Unused import:JSONResponse.
JSONResponseis imported but never used in this module. All endpoints return Pydantic models or dictionaries.🧹 Remove unused import
from fastapi import APIRouter, Depends, HTTPException -from fastapi.responses import JSONResponsebackends/advanced/src/advanced_omi_backend/config.py (1)
115-116: Consider movingasdictimport to top of file.
dataclassesis already imported at line 10 for the@dataclassdecorator. Theasdictfunction can be imported alongside it rather than inside the function body.🧹 Move import to top of file
-from dataclasses import dataclass +from dataclasses import asdict, dataclassThen remove the inline import:
def save_cleanup_settings(settings: CleanupSettings) -> bool: ... - from dataclasses import asdict return save_config_section('backend.cleanup', asdict(settings))backends/advanced/.env.template (1)
17-18: Consider adding a generation command hint forAUTH_SECRET_KEY.Adding an example command helps users generate a secure key without searching for how to do it.
📝 Add generation hint
# JWT signing key (generate a long random string) +# Generate with: openssl rand -hex 32 AUTH_SECRET_KEY=
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (35)
backends/advanced/.env.templatebackends/advanced/diarization_config.json.templatebackends/advanced/docker-compose.ymlbackends/advanced/pyproject.tomlbackends/advanced/src/advanced_omi_backend/app_factory.pybackends/advanced/src/advanced_omi_backend/config.pybackends/advanced/src/advanced_omi_backend/config_loader.pybackends/advanced/src/advanced_omi_backend/controllers/audio_controller.pybackends/advanced/src/advanced_omi_backend/controllers/system_controller.pybackends/advanced/src/advanced_omi_backend/cron.pybackends/advanced/src/advanced_omi_backend/models/annotation.pybackends/advanced/src/advanced_omi_backend/routers/api_router.pybackends/advanced/src/advanced_omi_backend/routers/modules/__init__.pybackends/advanced/src/advanced_omi_backend/routers/modules/admin_routes.pybackends/advanced/src/advanced_omi_backend/routers/modules/annotation_routes.pybackends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.pybackends/advanced/src/advanced_omi_backend/services/memory/providers/chronicle.pybackends/advanced/src/advanced_omi_backend/speaker_recognition_client.pybackends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.pybackends/advanced/src/advanced_omi_backend/workers/annotation_jobs.pybackends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.pybackends/advanced/src/advanced_omi_backend/workers/speaker_jobs.pybackends/advanced/webui/src/pages/Conversations.tsxbackends/advanced/webui/src/pages/MemoryDetail.tsxbackends/advanced/webui/src/services/api.tsconfig/config.yml.templateconfig/defaults.ymlextras/speaker-recognition/.env.templateextras/speaker-recognition/docker-compose.ymlextras/speaker-recognition/pyproject.tomlextras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.pyextras/speaker-recognition/src/simple_speaker_recognition/api/service.pyextras/speaker-recognition/src/simple_speaker_recognition/core/audio_backend.pyextras/speaker-recognition/src/simple_speaker_recognition/core/backend_client.pysetup-requirements.txt
💤 Files with no reviewable changes (1)
- backends/advanced/diarization_config.json.template
🧰 Additional context used
🧬 Code graph analysis (17)
backends/advanced/src/advanced_omi_backend/app_factory.py (1)
backends/advanced/src/advanced_omi_backend/models/annotation.py (1)
Annotation(36-100)
backends/advanced/webui/src/pages/MemoryDetail.tsx (1)
backends/advanced/webui/src/services/api.ts (1)
annotationsApi(149-177)
backends/advanced/webui/src/pages/Conversations.tsx (1)
backends/advanced/webui/src/services/api.ts (1)
annotationsApi(149-177)
backends/advanced/src/advanced_omi_backend/services/memory/providers/chronicle.py (4)
backends/advanced/src/advanced_omi_backend/services/memory/base.py (4)
update_memory(187-210)update_memory(425-443)initialize(81-90)initialize(356-362)backends/advanced/src/advanced_omi_backend/services/memory/providers/openmemory_mcp.py (2)
update_memory(322-365)initialize(71-104)backends/advanced/src/advanced_omi_backend/services/memory/providers/vector_stores.py (2)
update_memory(315-358)initialize(52-118)backends/advanced/src/advanced_omi_backend/services/memory/providers/mcp_client.py (1)
update_memory(407-452)
backends/advanced/src/advanced_omi_backend/workers/speaker_jobs.py (4)
backends/advanced/src/advanced_omi_backend/auth.py (1)
generate_jwt_for_user(116-145)backends/advanced/src/advanced_omi_backend/models/user.py (1)
user_id(71-73)backends/advanced/src/advanced_omi_backend/speaker_recognition_client.py (1)
diarize_identify_match(69-184)extras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.py (1)
diarize_identify_match(270-498)
backends/advanced/src/advanced_omi_backend/routers/modules/admin_routes.py (2)
backends/advanced/src/advanced_omi_backend/config.py (1)
get_cleanup_settings(94-102)backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (1)
get_cleanup_settings(61-65)
backends/advanced/src/advanced_omi_backend/models/annotation.py (1)
backends/advanced/src/advanced_omi_backend/models/user.py (1)
user_id(71-73)
extras/speaker-recognition/src/simple_speaker_recognition/api/service.py (1)
backends/advanced/src/advanced_omi_backend/models/annotation.py (1)
Config(138-139)
backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (2)
backends/advanced/src/advanced_omi_backend/models/conversation.py (1)
Conversation(17-328)backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (1)
AudioChunkDocument(16-158)
extras/speaker-recognition/src/simple_speaker_recognition/core/backend_client.py (2)
backends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.py (1)
get_conversation_metadata(170-205)backends/advanced/src/advanced_omi_backend/services/memory/providers/mycelia.py (1)
aclose(821-830)
extras/speaker-recognition/src/simple_speaker_recognition/core/audio_backend.py (2)
extras/speaker-omni-experimental/qwen_speaker_diarizer.py (1)
chunk_audio(142-192)extras/speaker-recognition/webui/src/services/api.ts (1)
delete(67-70)
backends/advanced/src/advanced_omi_backend/config_loader.py (1)
backends/advanced/src/advanced_omi_backend/config.py (1)
reload_config(50-52)
backends/advanced/src/advanced_omi_backend/speaker_recognition_client.py (3)
backends/advanced/src/advanced_omi_backend/config.py (1)
get_diarization_settings(59-67)backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (1)
get_diarization_settings(65-76)backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (1)
get_diarization_settings(46-48)
extras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.py (7)
extras/speaker-recognition/src/simple_speaker_recognition/core/backend_client.py (4)
BackendClient(11-103)get_conversation_metadata(26-55)get_audio_segment(57-98)close(100-103)backends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.py (2)
get_conversation_metadata(170-205)get_audio_segment(209-278)extras/speaker-recognition/src/simple_speaker_recognition/utils/audio_processing.py (1)
get_audio_info(53-87)extras/speaker-recognition/src/simple_speaker_recognition/api/routers/websocket_wrapper.py (1)
get_audio_backend(45-48)extras/speaker-recognition/src/simple_speaker_recognition/api/routers/deepgram_wrapper.py (1)
get_audio_backend(35-38)extras/speaker-recognition/src/simple_speaker_recognition/api/routers/enrollment.py (1)
get_audio_backend(36-39)extras/speaker-recognition/src/simple_speaker_recognition/core/audio_backend.py (1)
async_diarize(110-197)
backends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.py (4)
backends/advanced/src/advanced_omi_backend/models/conversation.py (1)
Conversation(17-328)extras/speaker-recognition/src/simple_speaker_recognition/core/backend_client.py (2)
get_conversation_metadata(26-55)get_audio_segment(57-98)backends/advanced/src/advanced_omi_backend/models/user.py (1)
user_id(71-73)backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (1)
reconstruct_audio_segment(498-597)
backends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.py (2)
backends/advanced/src/advanced_omi_backend/config.py (2)
get_cleanup_settings(94-102)CleanupSettings(88-91)backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (1)
get_cleanup_settings(61-65)
backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (2)
backends/advanced/src/advanced_omi_backend/config.py (4)
get_diarization_settings(59-67)save_diarization_settings(70-80)CleanupSettings(88-91)save_cleanup_settings(105-116)backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (3)
get_diarization_settings(46-48)save_diarization_settings(52-57)save_cleanup_settings(69-79)
🪛 Ruff (0.14.11)
backends/advanced/src/advanced_omi_backend/services/memory/providers/chronicle.py
330-330: Unused method argument: user_email
(ARG002)
389-389: Consider moving this statement to an else block
(TRY300)
backends/advanced/src/advanced_omi_backend/workers/speaker_jobs.py
231-231: f-string without any placeholders
Remove extraneous f prefix
(F541)
249-249: f-string without any placeholders
Remove extraneous f prefix
(F541)
backends/advanced/src/advanced_omi_backend/workers/annotation_jobs.py
109-109: String contains ambiguous ℹ (INFORMATION SOURCE). Did you mean i (LATIN SMALL LETTER I)?
(RUF001)
159-159: String contains ambiguous ℹ (INFORMATION SOURCE). Did you mean i (LATIN SMALL LETTER I)?
(RUF001)
198-198: Comment contains ambiguous ℹ (INFORMATION SOURCE). Did you mean i (LATIN SMALL LETTER I)?
(RUF003)
209-209: String contains ambiguous ℹ (INFORMATION SOURCE). Did you mean i (LATIN SMALL LETTER I)?
(RUF001)
backends/advanced/src/advanced_omi_backend/models/annotation.py
79-85: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
extras/speaker-recognition/src/simple_speaker_recognition/api/service.py
57-57: Consider moving this statement to an else block
(TRY300)
59-59: Do not catch blind exception: Exception
(BLE001)
backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py
533-533: Avoid specifying long messages outside the exception class
(TRY003)
535-535: Avoid specifying long messages outside the exception class
(TRY003)
541-541: Avoid specifying long messages outside the exception class
(TRY003)
546-546: Avoid specifying long messages outside the exception class
(TRY003)
557-557: Avoid specifying long messages outside the exception class
(TRY003)
backends/advanced/src/advanced_omi_backend/cron.py
63-63: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
backends/advanced/src/advanced_omi_backend/routers/modules/annotation_routes.py
36-36: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
55-55: Abstract raise to an inner function
(TRY301)
56-56: Do not catch blind exception: Exception
(BLE001)
57-57: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
58-58: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
85-85: Do not catch blind exception: Exception
(BLE001)
86-86: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
98-101: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
100-100: Use explicit conversion flag
Replace with conversion flag
(RUF010)
107-107: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
124-124: Abstract raise to an inner function
(TRY301)
132-132: Abstract raise to an inner function
(TRY301)
175-175: Do not catch blind exception: Exception
(BLE001)
176-176: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
188-191: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
190-190: Use explicit conversion flag
Replace with conversion flag
(RUF010)
197-197: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
211-214: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
213-213: Use explicit conversion flag
Replace with conversion flag
(RUF010)
220-220: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
234-237: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
236-236: Use explicit conversion flag
Replace with conversion flag
(RUF010)
244-244: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
257-257: Abstract raise to an inner function
(TRY301)
280-280: Do not catch blind exception: Exception
(BLE001)
281-281: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
302-302: Do not catch blind exception: Exception
(BLE001)
303-303: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
309-309: Consider moving this statement to an else block
(TRY300)
315-318: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
317-317: Use explicit conversion flag
Replace with conversion flag
(RUF010)
backends/advanced/src/advanced_omi_backend/config_loader.py
56-56: Do not catch blind exception: Exception
(BLE001)
65-65: Do not catch blind exception: Exception
(BLE001)
66-66: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
148-148: Consider moving this statement to an else block
(TRY300)
150-150: Do not catch blind exception: Exception
(BLE001)
151-151: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
backends/advanced/src/advanced_omi_backend/speaker_recognition_client.py
74-74: Unused method argument: user_id
(ARG002)
extras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.py
271-271: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
306-306: f-string without any placeholders
Remove extraneous f prefix
(F541)
323-323: Local variable full_text is assigned to but never used
Remove assignment to unused variable full_text
(F841)
325-325: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
325-325: Use explicit conversion flag
Replace with conversion flag
(RUF010)
backends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.py
172-172: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
213-213: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
265-265: Do not catch blind exception: Exception
(BLE001)
266-266: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
267-267: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
267-267: Use explicit conversion flag
Replace with conversion flag
(RUF010)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: robot-tests
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
|
|
||
|
|
||
| async def save_diarization_settings(settings: dict): | ||
| async def save_diarization_settings_controller(settings: dict): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "save_diarization_settings" backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 185
🏁 Script executed:
rg -n "save_diarization_settings" backends/advanced/src/advanced_omi_backend/controllers/system_controller.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 232
🏁 Script executed:
cat -n backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py | grep -A 10 -B 5 "save_diarization"Repository: SimpleOpenSoftware/chronicle
Length of output: 871
Fix router call to use renamed function.
The function was renamed from save_diarization_settings to save_diarization_settings_controller in system_controller.py (line 79), but the router in system_routes.py (line 57) still calls system_controller.save_diarization_settings(settings). Update the router to call the renamed function to prevent an AttributeError at runtime:
return await system_controller.save_diarization_settings_controller(settings)
🤖 Prompt for AI Agents
In `@backends/advanced/src/advanced_omi_backend/controllers/system_controller.py`
at line 79, Router still calls the old function name
system_controller.save_diarization_settings which was renamed to
save_diarization_settings_controller; update the router handler to call and
await the new function name, e.g. replace
system_controller.save_diarization_settings(settings) with await
system_controller.save_diarization_settings_controller(settings) so the router
invokes the correct async function.
| # Save using OmegaConf | ||
| save_cleanup_settings(settings) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for save_cleanup_settings return value.
Unlike save_diarization_settings_controller (lines 108-122), this function doesn't check the boolean return value of save_cleanup_settings(). If the save fails, the function will still return a success response.
🔧 Suggested fix
# Save using OmegaConf
- save_cleanup_settings(settings)
+ if not save_cleanup_settings(settings):
+ raise ValueError("Failed to save cleanup settings")
logger.info(f"Admin {user.email} updated cleanup settings: auto_cleanup={auto_cleanup_enabled}, retention={retention_days}d")📝 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.
| # Save using OmegaConf | |
| save_cleanup_settings(settings) | |
| # Save using OmegaConf | |
| if not save_cleanup_settings(settings): | |
| raise ValueError("Failed to save cleanup settings") |
🤖 Prompt for AI Agents
In `@backends/advanced/src/advanced_omi_backend/controllers/system_controller.py`
around lines 181 - 182, The call to save_cleanup_settings is not checking its
boolean return value, so failures still produce a success response; update the
controller that calls save_cleanup_settings to capture its return (e.g., ok =
save_cleanup_settings(settings)) and mirror the error handling used in
save_diarization_settings_controller: if ok is False, log the error and return a
failure response (HTTP error or structured error body) instead of proceeding to
a success response; ensure you reference save_cleanup_settings in the
log/response to aid debugging.
| # Update memory content if accepted | ||
| if annotation.status == AnnotationStatus.ACCEPTED: | ||
| try: | ||
| await memory_service.update_memory( | ||
| memory_id=annotation_data.memory_id, | ||
| content=annotation_data.corrected_text, | ||
| user_id=current_user.user_id, | ||
| ) | ||
| logger.info( | ||
| f"Updated memory {annotation_data.memory_id} with corrected text" | ||
| ) | ||
| except Exception as e: | ||
| logger.error(f"Error updating memory: {e}") | ||
| # Annotation is saved, but memory update failed - log but don't fail the request | ||
| logger.warning( | ||
| f"Memory annotation {annotation.id} saved but memory update failed" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Data inconsistency risk when memory update fails.
If the memory update fails (lines 85-90), the annotation is already saved with ACCEPTED status, but the memory content remains unchanged. The user sees the annotation as accepted, believing the correction was applied, when it actually wasn't.
Consider either:
- Rolling back the annotation status to
PENDINGon failure - Saving the annotation only after successful memory update
- Adding a field to track whether the correction was actually applied
🔧 Suggested fix: Save annotation after memory update succeeds
# Create annotation
annotation = Annotation(
annotation_type=AnnotationType.MEMORY,
user_id=current_user.user_id,
memory_id=annotation_data.memory_id,
original_text=annotation_data.original_text,
corrected_text=annotation_data.corrected_text,
status=annotation_data.status,
)
- await annotation.save()
- logger.info(
- f"Created memory annotation {annotation.id} for memory {annotation_data.memory_id}"
- )
# Update memory content if accepted
if annotation.status == AnnotationStatus.ACCEPTED:
try:
await memory_service.update_memory(
memory_id=annotation_data.memory_id,
content=annotation_data.corrected_text,
user_id=current_user.user_id,
)
logger.info(
f"Updated memory {annotation_data.memory_id} with corrected text"
)
except Exception as e:
logger.error(f"Error updating memory: {e}")
- # Annotation is saved, but memory update failed - log but don't fail the request
- logger.warning(
- f"Memory annotation {annotation.id} saved but memory update failed"
- )
+ # Memory update failed - save annotation as PENDING instead
+ annotation.status = AnnotationStatus.PENDING
+ logger.warning(
+ f"Memory update failed, saving annotation as PENDING"
+ )
+
+ await annotation.save()
+ logger.info(
+ f"Created memory annotation {annotation.id} for memory {annotation_data.memory_id}"
+ )
return AnnotationResponse.model_validate(annotation)🧰 Tools
🪛 Ruff (0.14.11)
85-85: Do not catch blind exception: Exception
(BLE001)
86-86: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
🤖 Prompt for AI Agents
In
`@backends/advanced/src/advanced_omi_backend/routers/modules/annotation_routes.py`
around lines 74 - 90, The current flow marks and saves the annotation as
ACCEPTED before calling memory_service.update_memory, leading to inconsistency
if update_memory fails; change the flow in the route handling code that checks
annotation.status == AnnotationStatus.ACCEPTED so that you first call await
memory_service.update_memory(...) using annotation_data.memory_id and
annotation_data.corrected_text, ensure it succeeds, and only then set
annotation.status = AnnotationStatus.ACCEPTED and persist/save the annotation
(or alternatively, if you must save earlier, implement a rollback that resets
annotation.status to PENDING and re-saves the annotation when update_memory
raises); update logging to record success/failure tied to annotation.id and
include the chosen rollback or save-after-update behavior consistently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
| # If accepting a pending suggestion, apply the correction | ||
| if ( | ||
| status == AnnotationStatus.ACCEPTED | ||
| and old_status == AnnotationStatus.PENDING | ||
| ): | ||
| if annotation.is_memory_annotation(): | ||
| # Update memory | ||
| try: | ||
| memory_service = get_memory_service() | ||
| await memory_service.update_memory( | ||
| memory_id=annotation.memory_id, | ||
| content=annotation.corrected_text, | ||
| user_id=current_user.user_id, | ||
| ) | ||
| logger.info( | ||
| f"Applied suggestion to memory {annotation.memory_id}" | ||
| ) | ||
| except Exception as e: | ||
| logger.error(f"Error applying memory suggestion: {e}") | ||
| # Don't fail the status update if memory update fails | ||
| elif annotation.is_transcript_annotation(): | ||
| # Update transcript segment | ||
| try: | ||
| conversation = await Conversation.find_one( | ||
| Conversation.conversation_id == annotation.conversation_id | ||
| ) | ||
| if conversation: | ||
| transcript = conversation.get_active_transcript() | ||
| if ( | ||
| transcript | ||
| and annotation.segment_index < len(transcript.segments) | ||
| ): | ||
| transcript.segments[ | ||
| annotation.segment_index | ||
| ].text = annotation.corrected_text | ||
| await conversation.save() | ||
| logger.info( | ||
| f"Applied suggestion to transcript segment {annotation.segment_index}" | ||
| ) | ||
| except Exception as e: | ||
| logger.error(f"Error applying transcript suggestion: {e}") | ||
| # Don't fail the status update if segment update fails | ||
|
|
||
| await annotation.save() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Annotation status saved as ACCEPTED even when correction fails.
If the memory update (lines 270-282) or transcript update (lines 285-304) fails, the exception is caught and logged, but annotation.save() at line 306 still persists the ACCEPTED status. This creates the same data inconsistency issue noted in create_memory_annotation - the user sees the annotation as accepted but the correction wasn't applied.
Consider only saving the ACCEPTED status if the correction was successfully applied, or tracking application status separately.
🧰 Tools
🪛 Ruff (0.14.11)
280-280: Do not catch blind exception: Exception
(BLE001)
281-281: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
302-302: Do not catch blind exception: Exception
(BLE001)
303-303: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
| conversation = await Conversation.find_one( | ||
| Conversation.conversation_id == annotation.conversation_id | ||
| ) | ||
| if conversation: | ||
| transcript = conversation.get_active_transcript() | ||
| if ( | ||
| transcript | ||
| and annotation.segment_index < len(transcript.segments) | ||
| ): | ||
| transcript.segments[ | ||
| annotation.segment_index | ||
| ].text = annotation.corrected_text | ||
| await conversation.save() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defense-in-depth: Add user_id filter when fetching conversation.
The conversation lookup at line 287 doesn't include a user_id filter. While the annotation itself is already validated to belong to the current user (line 254), adding the user filter provides defense-in-depth against any future bugs that might allow cross-user annotation references.
🛡️ Add user_id filter
try:
conversation = await Conversation.find_one(
- Conversation.conversation_id == annotation.conversation_id
+ Conversation.conversation_id == annotation.conversation_id,
+ Conversation.user_id == current_user.user_id,
)📝 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.
| conversation = await Conversation.find_one( | |
| Conversation.conversation_id == annotation.conversation_id | |
| ) | |
| if conversation: | |
| transcript = conversation.get_active_transcript() | |
| if ( | |
| transcript | |
| and annotation.segment_index < len(transcript.segments) | |
| ): | |
| transcript.segments[ | |
| annotation.segment_index | |
| ].text = annotation.corrected_text | |
| await conversation.save() | |
| conversation = await Conversation.find_one( | |
| Conversation.conversation_id == annotation.conversation_id, | |
| Conversation.user_id == current_user.user_id, | |
| ) | |
| if conversation: | |
| transcript = conversation.get_active_transcript() | |
| if ( | |
| transcript | |
| and annotation.segment_index < len(transcript.segments) | |
| ): | |
| transcript.segments[ | |
| annotation.segment_index | |
| ].text = annotation.corrected_text | |
| await conversation.save() |
🤖 Prompt for AI Agents
In
`@backends/advanced/src/advanced_omi_backend/routers/modules/annotation_routes.py`
around lines 286 - 298, The Conversation.find_one call that fetches by
Conversation.conversation_id should also filter by the owner to enforce
defense-in-depth: change the lookup in the annotation update path to include
Conversation.user_id == annotation.user_id (i.e., use
Conversation.find_one(Conversation.conversation_id ==
annotation.conversation_id, Conversation.user_id == annotation.user_id)); keep
the subsequent get_active_transcript, segment bounds check, text assignment to
transcript.segments[annotation.segment_index].text = annotation.corrected_text,
and await conversation.save() unchanged.
| # Backend API URL for fetching audio segments (used by speaker service) | ||
| backend_api_url: http://host.docker.internal:8000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
host.docker.internal may not resolve on Linux without additional setup.
The backend_api_url defaults to http://host.docker.internal:8000, which works out-of-the-box on Docker Desktop (macOS/Windows) but requires explicit --add-host configuration on native Linux Docker. This could cause connectivity issues for Linux users.
Consider documenting this requirement or providing a fallback mechanism.
🤖 Prompt for AI Agents
In `@config/defaults.yml` around lines 254 - 255, The default backend_api_url
currently points to host.docker.internal which doesn't resolve on many native
Linux Docker setups; change the default value for backend_api_url to use
http://127.0.0.1:8000 (or localhost) and add a brief note in the config/comments
that Linux users who rely on host.docker.internal must add an explicit
--add-host or override the value via environment/config, referencing the
backend_api_url key so maintainers can locate and document/override it.
| log.info(f"Processing diarize-identify-match request") | ||
| log.info(f"Mode: {'conversation' if conversation_id else 'file upload'}") | ||
| log.info(f"Parameters - user_id: {user_id}, min_duration: {min_duration}, similarity_threshold: {similarity_threshold}, min_speakers: {min_speakers}, max_speakers: {max_speakers}, collar: {collar}, min_duration_off: {min_duration_off}") | ||
| log.info(f"Transcript data length: {len(transcript_data) if transcript_data else 0}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unnecessary f-string prefix.
Line 306 has an f-string without any placeholders. This was flagged by static analysis.
🔧 Fix
- log.info(f"Processing diarize-identify-match request")
+ log.info("Processing diarize-identify-match request")📝 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.
| log.info(f"Processing diarize-identify-match request") | |
| log.info(f"Mode: {'conversation' if conversation_id else 'file upload'}") | |
| log.info(f"Parameters - user_id: {user_id}, min_duration: {min_duration}, similarity_threshold: {similarity_threshold}, min_speakers: {min_speakers}, max_speakers: {max_speakers}, collar: {collar}, min_duration_off: {min_duration_off}") | |
| log.info(f"Transcript data length: {len(transcript_data) if transcript_data else 0}") | |
| log.info("Processing diarize-identify-match request") | |
| log.info(f"Mode: {'conversation' if conversation_id else 'file upload'}") | |
| log.info(f"Parameters - user_id: {user_id}, min_duration: {min_duration}, similarity_threshold: {similarity_threshold}, min_speakers: {min_speakers}, max_speakers: {max_speakers}, collar: {collar}, min_duration_off: {min_duration_off}") | |
| log.info(f"Transcript data length: {len(transcript_data) if transcript_data else 0}") |
🧰 Tools
🪛 Ruff (0.14.11)
306-306: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
In
`@extras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.py`
around lines 306 - 309, The log call using log.info with the literal "Processing
diarize-identify-match request" incorrectly uses an f-string prefix; remove the
leading "f" on that specific log.info invocation so the string is a plain
literal while leaving the other log.info calls (which use
expressions/placeholders like the mode conditional and transcript length)
unchanged.
| # Parse transcript data | ||
| try: | ||
| transcript = json.loads(transcript_data) | ||
| words = transcript.get("words", []) | ||
| full_text = transcript.get("text", "") | ||
| except json.JSONDecodeError as e: | ||
| raise HTTPException(400, f"Invalid transcript_data JSON: {str(e)}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add exception chaining and remove unused variable.
Two issues in this block:
full_textis assigned but never used (F841)- The
HTTPExceptionshould useraise ... from eto preserve the exception chain (B904)
🔧 Fix
# Parse transcript data
try:
transcript = json.loads(transcript_data)
words = transcript.get("words", [])
- full_text = transcript.get("text", "")
+ # Note: full_text = transcript.get("text", "") available if needed
except json.JSONDecodeError as e:
- raise HTTPException(400, f"Invalid transcript_data JSON: {str(e)}")
+ raise HTTPException(400, f"Invalid transcript_data JSON: {e!s}") from e🧰 Tools
🪛 Ruff (0.14.11)
323-323: Local variable full_text is assigned to but never used
Remove assignment to unused variable full_text
(F841)
325-325: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
325-325: Use explicit conversion flag
Replace with conversion flag
(RUF010)
🤖 Prompt for AI Agents
In
`@extras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.py`
around lines 319 - 325, Remove the unused local full_text assignment and
preserve exception chaining: in the JSON parsing block that reads
transcript_data into transcript and words, drop the unused full_text =
transcript.get("text", "") line and change the except handler to raise
HTTPException(400, f"Invalid transcript_data JSON: {str(e)}") from e so the
JSONDecodeError is chained; keep the variable name e for the caught
JSONDecodeError to reference in the chained raise.
| # Mode 1: Conversation mode - fetch audio from backend | ||
| if conversation_id: | ||
| from simple_speaker_recognition.core.backend_client import BackendClient | ||
|
|
||
| backend_client = BackendClient(settings.backend_api_url) | ||
| try: | ||
| # Get conversation metadata | ||
| metadata = await backend_client.get_conversation_metadata(conversation_id, backend_token) | ||
| total_duration = metadata['duration'] | ||
|
|
||
| log.info(f"Conversation {conversation_id[:12]}: duration={total_duration:.1f}s") | ||
|
|
||
| # Validate: 2 hour maximum | ||
| if total_duration > MAX_AUDIO_DURATION: | ||
| raise HTTPException(400, f"Audio duration {total_duration:.1f}s exceeds maximum allowed duration of {MAX_AUDIO_DURATION}s (2 hours)") | ||
|
|
||
| # Fetch full audio from backend | ||
| log.info(f"Fetching audio from backend for conversation {conversation_id[:12]}") | ||
| wav_bytes = await backend_client.get_audio_segment( | ||
| conversation_id, | ||
| backend_token, | ||
| start=0.0, | ||
| duration=total_duration | ||
| ) | ||
|
|
||
| # Write to temp file | ||
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file: | ||
| tmp_file.write(wav_bytes) | ||
| tmp_path = Path(tmp_file.name) | ||
| finally: | ||
| await backend_client.close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential KeyError if metadata lacks 'duration' field.
If the backend returns metadata without a duration key (e.g., due to API version mismatch or corrupted data), line 344 will raise a KeyError. Consider using .get() with validation.
Also, the try/finally block correctly ensures backend_client.close() is called, but if an exception occurs during get_conversation_metadata or get_audio_segment, the error message may not be user-friendly.
🔧 Safer metadata access
try:
# Get conversation metadata
metadata = await backend_client.get_conversation_metadata(conversation_id, backend_token)
- total_duration = metadata['duration']
+ total_duration = metadata.get('duration')
+ if total_duration is None:
+ raise HTTPException(400, f"Conversation {conversation_id[:12]} has no duration metadata")
log.info(f"Conversation {conversation_id[:12]}: duration={total_duration:.1f}s")📝 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.
| # Mode 1: Conversation mode - fetch audio from backend | |
| if conversation_id: | |
| from simple_speaker_recognition.core.backend_client import BackendClient | |
| backend_client = BackendClient(settings.backend_api_url) | |
| try: | |
| # Get conversation metadata | |
| metadata = await backend_client.get_conversation_metadata(conversation_id, backend_token) | |
| total_duration = metadata['duration'] | |
| log.info(f"Conversation {conversation_id[:12]}: duration={total_duration:.1f}s") | |
| # Validate: 2 hour maximum | |
| if total_duration > MAX_AUDIO_DURATION: | |
| raise HTTPException(400, f"Audio duration {total_duration:.1f}s exceeds maximum allowed duration of {MAX_AUDIO_DURATION}s (2 hours)") | |
| # Fetch full audio from backend | |
| log.info(f"Fetching audio from backend for conversation {conversation_id[:12]}") | |
| wav_bytes = await backend_client.get_audio_segment( | |
| conversation_id, | |
| backend_token, | |
| start=0.0, | |
| duration=total_duration | |
| ) | |
| # Write to temp file | |
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file: | |
| tmp_file.write(wav_bytes) | |
| tmp_path = Path(tmp_file.name) | |
| finally: | |
| await backend_client.close() | |
| # Mode 1: Conversation mode - fetch audio from backend | |
| if conversation_id: | |
| from simple_speaker_recognition.core.backend_client import BackendClient | |
| backend_client = BackendClient(settings.backend_api_url) | |
| try: | |
| # Get conversation metadata | |
| metadata = await backend_client.get_conversation_metadata(conversation_id, backend_token) | |
| total_duration = metadata.get('duration') | |
| if total_duration is None: | |
| raise HTTPException(400, f"Conversation {conversation_id[:12]} has no duration metadata") | |
| log.info(f"Conversation {conversation_id[:12]}: duration={total_duration:.1f}s") | |
| # Validate: 2 hour maximum | |
| if total_duration > MAX_AUDIO_DURATION: | |
| raise HTTPException(400, f"Audio duration {total_duration:.1f}s exceeds maximum allowed duration of {MAX_AUDIO_DURATION}s (2 hours)") | |
| # Fetch full audio from backend | |
| log.info(f"Fetching audio from backend for conversation {conversation_id[:12]}") | |
| wav_bytes = await backend_client.get_audio_segment( | |
| conversation_id, | |
| backend_token, | |
| start=0.0, | |
| duration=total_duration | |
| ) | |
| # Write to temp file | |
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file: | |
| tmp_file.write(wav_bytes) | |
| tmp_path = Path(tmp_file.name) | |
| finally: | |
| await backend_client.close() |
🤖 Prompt for AI Agents
In
`@extras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.py`
around lines 336 - 366, The code assumes metadata['duration'] exists and will
KeyError if missing; update the conversation branch in identification.py to use
metadata.get('duration') and validate it is a number (e.g., not None and >0),
and if invalid raise an HTTPException(400) with a clear message about
missing/invalid duration; also wrap the calls to
BackendClient.get_conversation_metadata and get_audio_segment in a try/except
that converts backend errors into user-friendly HTTPException messages (while
leaving the finally await backend_client.close() intact) so failures return
meaningful HTTP errors instead of raw exceptions.
| # Mode 2: File upload mode | ||
| else: | ||
| # Create temporary file from upload | ||
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file: | ||
| tmp_file.write(await file.read()) | ||
| tmp_path = Path(tmp_file.name) | ||
|
|
||
| # Get audio duration for validation | ||
| from simple_speaker_recognition.utils.audio_processing import get_audio_info | ||
| audio_info = get_audio_info(str(tmp_path)) | ||
| total_duration = audio_info.get('duration_seconds', 0) | ||
|
|
||
| log.info(f"Uploaded file: {file.filename}, duration={total_duration:.1f}s") | ||
|
|
||
| # Validate: 2 hour maximum | ||
| if total_duration > MAX_AUDIO_DURATION: | ||
| tmp_path.unlink(missing_ok=True) | ||
| raise HTTPException(400, f"Audio duration {total_duration:.1f}s exceeds maximum allowed duration of {MAX_AUDIO_DURATION}s (2 hours)") | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resource leak: temp file not cleaned up if validation fails.
In file upload mode, if get_audio_info() fails or the duration exceeds the limit, the temporary file at tmp_path may not be cleaned up. Line 384 handles the duration exceeded case, but any exception from get_audio_info() would leave the file behind.
🔧 Ensure cleanup on all error paths
# Mode 2: File upload mode
else:
# Create temporary file from upload
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file:
tmp_file.write(await file.read())
tmp_path = Path(tmp_file.name)
- # Get audio duration for validation
- from simple_speaker_recognition.utils.audio_processing import get_audio_info
- audio_info = get_audio_info(str(tmp_path))
- total_duration = audio_info.get('duration_seconds', 0)
-
- log.info(f"Uploaded file: {file.filename}, duration={total_duration:.1f}s")
-
- # Validate: 2 hour maximum
- if total_duration > MAX_AUDIO_DURATION:
- tmp_path.unlink(missing_ok=True)
- raise HTTPException(400, f"Audio duration {total_duration:.1f}s exceeds maximum allowed duration of {MAX_AUDIO_DURATION}s (2 hours)")
+ try:
+ # Get audio duration for validation
+ from simple_speaker_recognition.utils.audio_processing import get_audio_info
+ audio_info = get_audio_info(str(tmp_path))
+ total_duration = audio_info.get('duration_seconds', 0)
+
+ log.info(f"Uploaded file: {file.filename}, duration={total_duration:.1f}s")
+
+ # Validate: 2 hour maximum
+ if total_duration > MAX_AUDIO_DURATION:
+ raise HTTPException(400, f"Audio duration {total_duration:.1f}s exceeds maximum allowed duration of {MAX_AUDIO_DURATION}s (2 hours)")
+ except HTTPException:
+ tmp_path.unlink(missing_ok=True)
+ raise
+ except Exception as e:
+ tmp_path.unlink(missing_ok=True)
+ raise HTTPException(400, f"Failed to read audio file: {e!s}") from e🤖 Prompt for AI Agents
In
`@extras/speaker-recognition/src/simple_speaker_recognition/api/routers/identification.py`
around lines 368 - 386, The temporary file created with
tempfile.NamedTemporaryFile (tmp_path) can leak if get_audio_info() raises or
any other exception occurs; wrap the file creation and subsequent processing
(call to get_audio_info and duration validation against MAX_AUDIO_DURATION) in a
try/finally (or try/except where finally unlinks tmp_path) so that
tmp_path.unlink(missing_ok=True) is always called on error/exit, or perform
get_audio_info and validation inside the with-block before breaking the context,
ensuring tmp_path is deleted in the finally block when an exception is raised.
… loader
- Added custom OmegaConf resolvers to handle legacy ${VAR:-default} syntax for backward compatibility.
- Introduced a preprocessing function to convert legacy syntax in YAML files to OmegaConf-compatible format.
- Updated the load_config function to utilize the new preprocessing for loading defaults and user configurations.
- Enhanced documentation for clarity on the new legacy syntax handling.
- 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.
Plugin terminology: subscriptions→events, trigger→condition Memory jobs: no longer blocked by disabled speaker recognition
0xrushi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also have some tests around the new annotation routes, specifically around the CRUD operation making sure annotation is rolled back to PENDING etc
|
|
||
| # Test OmegaConf configuration loading | ||
| try: | ||
| from advanced_omi_backend.config_loader import load_config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move this to the top
|
|
||
| # Test model registry | ||
| try: | ||
| from advanced_omi_backend.model_registry import get_models_registry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to top
| # Update memory content if accepted | ||
| if annotation.status == AnnotationStatus.ACCEPTED: | ||
| try: | ||
| await memory_service.update_memory( | ||
| memory_id=annotation_data.memory_id, | ||
| content=annotation_data.corrected_text, | ||
| user_id=current_user.user_id, | ||
| ) | ||
| logger.info( | ||
| f"Updated memory {annotation_data.memory_id} with corrected text" | ||
| ) | ||
| except Exception as e: | ||
| logger.error(f"Error updating memory: {e}") | ||
| # Annotation is saved, but memory update failed - log but don't fail the request | ||
| logger.warning( | ||
| f"Memory annotation {annotation.id} saved but memory update failed" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
| # If accepting a pending suggestion, apply the correction | ||
| if ( | ||
| status == AnnotationStatus.ACCEPTED | ||
| and old_status == AnnotationStatus.PENDING | ||
| ): | ||
| if annotation.is_memory_annotation(): | ||
| # Update memory | ||
| try: | ||
| memory_service = get_memory_service() | ||
| await memory_service.update_memory( | ||
| memory_id=annotation.memory_id, | ||
| content=annotation.corrected_text, | ||
| user_id=current_user.user_id, | ||
| ) | ||
| logger.info( | ||
| f"Applied suggestion to memory {annotation.memory_id}" | ||
| ) | ||
| except Exception as e: | ||
| logger.error(f"Error applying memory suggestion: {e}") | ||
| # Don't fail the status update if memory update fails | ||
| elif annotation.is_transcript_annotation(): | ||
| # Update transcript segment | ||
| try: | ||
| conversation = await Conversation.find_one( | ||
| Conversation.conversation_id == annotation.conversation_id | ||
| ) | ||
| if conversation: | ||
| transcript = conversation.get_active_transcript() | ||
| if ( | ||
| transcript | ||
| and annotation.segment_index < len(transcript.segments) | ||
| ): | ||
| transcript.segments[ | ||
| annotation.segment_index | ||
| ].text = annotation.corrected_text | ||
| await conversation.save() | ||
| logger.info( | ||
| f"Applied suggestion to transcript segment {annotation.segment_index}" | ||
| ) | ||
| except Exception as e: | ||
| logger.error(f"Error applying transcript suggestion: {e}") | ||
| # Don't fail the status update if segment update fails | ||
|
|
||
| await annotation.save() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
|
|
||
|
|
||
| # Get settings for chunking configuration | ||
| from simple_speaker_recognition.api.service import auth as settings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to top
|
|
||
| # Mode 1: Conversation mode - fetch audio from backend | ||
| if conversation_id: | ||
| from simple_speaker_recognition.core.backend_client import BackendClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to top
- Updated Docker Compose files to mount the entire config directory, consolidating configuration management. - Refactored the `save_diarization_settings` function to improve clarity and maintainability by renaming it to `save_diarization_settings_controller`. - Enhanced the System component in the web UI to include configuration diagnostics, providing better visibility into system health and issues.
- Updated the testing documentation to reflect a new Makefile-based approach for running tests and managing containers. - Introduced new scripts for container management, including starting, stopping, restarting, and cleaning containers while preserving logs. - Added a cleanup script to handle data ownership and permissions correctly. - Implemented a logging system that saves container logs automatically before cleanup. - Enhanced the README with detailed instructions for running tests and managing the test environment.
Summary by CodeRabbit
Release Notes
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.