Skip to content

feat: Enhance Voice Agent with Multilingual, Vision Detection Support and CI Improvements - #11

Merged
chcavignx merged 11 commits into
mainfrom
test
Jul 22, 2026
Merged

feat: Enhance Voice Agent with Multilingual, Vision Detection Support and CI Improvements#11
chcavignx merged 11 commits into
mainfrom
test

Conversation

@chcavignx

Copy link
Copy Markdown
Owner

No description provided.

chcavignx added 11 commits June 24, 2026 17:43
LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
…g Tools

LiveReview Pre-Commit Check: ran (iter:2, coverage:0%)
…anced Audio Testing

LiveReview Pre-Commit Check: ran (iter:2, coverage:90%)
LiveReview Pre-Commit Check: ran (iter:2, coverage:88%)
…ts and samples. scripts to preload models to work offline

LiveReview Pre-Commit Check: ran (iter:11, coverage:100%)
LiveReview Pre-Commit Check: vouched (iter:1, coverage:0%)
LiveReview Pre-Commit Check: ran (iter:7, coverage:94%)
LiveReview Pre-Commit Check: ran (iter:3, coverage:100%)
LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
LiveReview Pre-Commit Check: ran (iter:3, coverage:46%)
LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add modular vision pipeline + multilingual voice-agent responses with LLM fallback

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Adds a modular vision subsystem with threaded camera capture and pluggable face/object detectors.
• Adds multilingual (EN/FR) response tables for the offline voice agent, with optional LLM fallback.
• Updates config schema, dependencies, and CI (uv) to support the new vision/LLM features.
• Adds extensive unit tests plus install/model preload scripts for offline + Raspberry Pi camera
 usage.
Diagram

graph TD
  VA["Voice Agent"] --> RC["Responses Rules"] --> LLM["LLM Client"] --> LLMAPI[("Ollama/OpenAI API")]
  VC["VideoCapture"] --> CAM["ThreadedCamera"] --> FACE["Face Pipeline"] --> BDF["BaseDetector"] --> FB{{"Cascade/InsightFace/IMX500"}}
  VC --> OBJ["Object Pipeline"] --> BDO["BaseDetector"] --> OB{{"YOLO CPU/IMX500"}}
  CFG["Config (Vision/LLM)"] --> VA
  CFG --> VC
  subgraph Legend
    direction LR
    _mod["Module"] ~~~ _ext{{"Backend"}} ~~~ _api[("External API")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Split by subsystem (vision / voice-agent+LLM / CI+tooling)
  • ➕ Smaller, easier-to-review PRs
  • ➕ Cleaner rollback/bisect if a subsystem regresses
  • ➕ Lets CI/tooling changes land independently
  • ➖ Requires sequencing shared config/schema changes
  • ➖ Temporary intermediate states may be less usable end-to-end
2. Centralize runtime imports behind optional dependency gates
  • ➕ Avoids importing heavy/optional deps (picamera2/insightface/ultralytics) unless enabled
  • ➕ Reduces CI failures on non-RPi hosts
  • ➖ Adds indirection and some boilerplate
  • ➖ Harder static analysis/type checking
3. Use a single unified detector factory module
  • ➕ One place to map config → detector instance
  • ➕ Consistent error handling and capability reporting
  • ➖ Less explicit wiring inside pipelines
  • ➖ May constrain special-casing (e.g., IMX500 metadata paths)

Recommendation: The BaseDetector + pipeline approach is a strong foundation for hardware portability and testability. The main improvement would be to reduce risk by landing this as multiple PRs or, at minimum, isolating CI/tooling changes from the large feature additions.

Files changed (88) +9778 / -267 · 3 not counted

Enhancement (20) +2368 / -59
bus.jpgUpdate sample image asset for vision demos/tests not counted

Update sample image asset for vision demos/tests

• Updates the bundled sample image used by vision examples/tests.

data/bus.jpg

responses_en.yamlAdd English response rules for offline voice agent +33/-0

Add English response rules for offline voice agent

• Defines keyword/action/LLM fallback rules for English, including exit semantics and a default fallback rule.

data/responses_en.yaml

responses_fr.yamlAdd French response rules for offline voice agent +33/-0

Add French response rules for offline voice agent

• Defines French equivalents of keyword/action/LLM fallback rules, including exit semantics and a default fallback rule.

data/responses_fr.yaml

voice_agent_offline.pyAdd rule-based multilingual responses + optional LLM fallback +190/-40

Add rule-based multilingual responses + optional LLM fallback

• Introduces a YAML-driven response engine ('ResponsesConfig') with language selection (EN/FR), exit semantics, action handling, and optional LLM fallback via 'src.llm.generate_llm_response'. Updates the agent loop to return both response and whether to continue conversation.

examples/VAD/voice_agent_offline.py

__init__.pyExpose LLM client API from package root +5/-0

Expose LLM client API from package root

• Exports 'generate_llm_response' via 'src.llm' for easier consumption by examples/tests.

src/llm/init.py

llm_client.pyAdd HTTP LLM client supporting Ollama and OpenAI-style payloads +65/-0

Add HTTP LLM client supporting Ollama and OpenAI-style payloads

• Implements 'generate_llm_response()' to POST prompts to an LLM endpoint, handling Ollama ('response') and OpenAI chat ('choices[].message.content') response formats with configurable timeouts and payload overrides.

src/llm/llm_client.py

camera.pyAdd threaded camera abstraction with OpenCV and Picamera2/IMX500 support +230/-0

Add threaded camera abstraction with OpenCV and Picamera2/IMX500 support

• Introduces 'ThreadedCamera' for low-latency frame capture with optional IMX500/Picamera2 initialization, plus helpers to discover Pi cameras and detect IMX500 presence.

src/utils/camera.py

config.pyExtend config schema with Vision/Camera/LLM and path helpers +134/-19

Extend config schema with Vision/Camera/LLM and path helpers

• Adds 'CameraConfig', 'VisionConfig', and 'LLMConfig', plus vision model path resolution helpers ('object_model_full_path', 'face_detector_model_path', 'post_processing_model_full_path'). Also adds vision/dataset path properties under 'PathConfig' and wires new sections into the top-level 'Config'.

src/utils/config.py

metrics.pyAdd/expand metrics utilities +110/-0

Add/expand metrics utilities

• Introduces or significantly expands metrics helpers used across subsystems (including vision performance overlays).

src/utils/metrics.py

__init__.pyAdd vision package marker +1/-0

Add vision package marker

• Introduces the 'src.vision' package entry point to group vision modules.

src/vision/init.py

base.pyIntroduce BaseDetector and standardized detection format +34/-0

Introduce BaseDetector and standardized detection format

• Defines 'BaseDetector' and 'DetectionDict' to unify object/face detector outputs across different backends.

src/vision/base.py

face_detector.pyAdd InsightFace and IMX500 face detector wrappers +193/-0

Add InsightFace and IMX500 face detector wrappers

• Implements an InsightFace CPU face detector (RetinaFace via 'FaceAnalysis') and an IMX500 detector that parses detections from Picamera2 metadata.

src/vision/face_detector.py

face_detector_cascade.pyAdd Haar Cascade face detector backend +75/-0

Add Haar Cascade face detector backend

• Implements a lightweight OpenCV Haar Cascade-based face detector conforming to 'BaseDetector'.

src/vision/face_detector_cascade.py

face_in_frame.pyAdd per-frame face detection/recognition processor +140/-0

Add per-frame face detection/recognition processor

• Implements 'FaceInFrame' to run configured face detection/recognition per frame, with drawing and metadata-aware paths for IMX500.

src/vision/face_in_frame.py

face_insight_pipeline.pyAdd unified face detection + recognition pipeline +159/-0

Add unified face detection + recognition pipeline

• Implements 'FaceInsightPipeline' that selects the configured detector backend (InsightFace vs IMX500) and optionally runs ArcFace post-processing for embeddings/recognition.

src/vision/face_insight_pipeline.py

face_recognizer.pyAdd ArcFace embedding extractor and matcher +95/-0

Add ArcFace embedding extractor and matcher

• Implements 'ArcFaceRecognizer' for embedding extraction (aligned via landmarks or bbox) and cosine-similarity matching against registered faces.

src/vision/face_recognizer.py

object_insight_frame.pyAdd per-frame object detection processor with standardized detections +130/-0

Add per-frame object detection processor with standardized detections

• Implements 'ObjectInsightFrame' that runs inference, extracts standardized detections from Ultralytics Results or IMX500 dummy results, and optionally draws annotations.

src/vision/object_insight_frame.py

video_capture.pyAdd high-level vision orchestrator (camera + pipelines) +281/-0

Add high-level vision orchestrator (camera + pipelines)

• Implements 'VideoCapture' to coordinate 'ThreadedCamera', object detection, face recognition, and performance overlays while maintaining some backward-compatible properties.

src/vision/video_capture.py

yolo_cpu.pyAdd YOLO CPU detector module with Ultralytics/LibreYOLO support +272/-0

Add YOLO CPU detector module with Ultralytics/LibreYOLO support

• Implements 'YoloCpuDetector' for CPU inference and standardized detections, plus a YOLO NCNN detector path and benchmarking helpers.

src/vision/yolo_cpu.py

yolo_imx500.pyAdd IMX500 object detector parsing metadata outputs +188/-0

Add IMX500 object detector parsing metadata outputs

• Implements an IMX500 detector that parses on-sensor inference outputs from Picamera2 metadata into standardized detections and provides drawing helpers.

src/vision/yolo_imx500.py

Bug fix (1) +1 / -1
asr.pyFix logging indentation in audio storage path +1/-1

Fix logging indentation in audio storage path

• Ensures the 'audio chunk stored' debug log is emitted consistently after storing audio frames.

src/audio/asr.py

Refactor (5) +72 / -34
__init__.pyAdjust audio package exports/imports +2/-2

Adjust audio package exports/imports

• Minor tweaks to the audio package initialization to align with refactoring and type/lint expectations.

src/audio/init.py

audio_utils.pyClean up type-ignore clutter in audio utilities +17/-17

Clean up type-ignore clutter in audio utilities

• Removes multiple 'pyright' ignores around sounddevice/soundfile/scipy calls while preserving behavior.

src/audio/audio_utils.py

wake_word.pyTighten type usage and inference session setup in wake-word detector +12/-12

Tighten type usage and inference session setup in wake-word detector

• Removes several 'pyright' ignore annotations and clarifies ONNXRuntime session usage and inference plumbing.

src/audio/wake_word.py

log_filters.pyMinor logging filter tweak +1/-1

Minor logging filter tweak

• Small adjustment to logging filter behavior/typing for consistency with updated tooling.

src/utils/log_filters.py

sysutils.pyRefine system utilities for platform/runtime detection +40/-2

Refine system utilities for platform/runtime detection

• Updates system helper utilities used by configuration/platform tuning and hardware-dependent features.

src/utils/sysutils.py

Tests (17) +1750 / -8
test_audio_engine_units.pyAdjust audio engine unit tests for refactor +2/-1

Adjust audio engine unit tests for refactor

• Minor updates to existing audio engine unit tests to align with refactored modules/config.

tests/audio/test_audio_engine_units.py

test_voice_agent_responses.pyAdd tests for multilingual response rules and LLM fallback payloads +264/-0

Add tests for multilingual response rules and LLM fallback payloads

• Adds unit tests covering EN/FR response selection, exit semantics, LLM fallback behavior, and payload templating for both Ollama and OpenAI-style APIs.

tests/audio/test_voice_agent_responses.py

conftest.pyUpdate test fixtures and defaults +8/-7

Update test fixtures and defaults

• Adjusts pytest fixtures/config to support new config schema and vision/LLM tests.

tests/conftest.py

__init__.pyNormalize test package init not counted

Normalize test package init

• Minor adjustment to 'tests.llm' package initialization (formatting/newline consistency).

tests/llm/init.py

test_llm_client.pyAdd unit tests for LLM client behavior +99/-0

Add unit tests for LLM client behavior

• Adds tests for successful/failed LLM calls and response parsing for Ollama/OpenAI-style formats.

tests/llm/test_llm_client.py

test_camera.pyAdd tests for camera utilities +141/-0

Add tests for camera utilities

• Adds tests for camera discovery, IMX500 detection logic, and threaded camera behavior using mocks where needed.

tests/utils/test_camera.py

test_config.pyAdd tests for new config schema and path resolution +39/-0

Add tests for new config schema and path resolution

• Adds tests ensuring 'VisionConfig'/'LLMConfig' validation and model path resolution behave as expected.

tests/utils/test_config.py

test_sysutils.pyExpand sysutils test coverage +28/-0

Expand sysutils test coverage

• Adds/updates tests for platform/system utility functions used by config/hardware detection.

tests/utils/test_sysutils.py

__init__.pyNormalize vision test package init not counted

Normalize vision test package init

• Minor adjustment to 'tests.vision' package initialization (formatting/newline consistency).

tests/vision/init.py

test_face_detector_cascade.pyAdd tests for Haar Cascade face detector +66/-0

Add tests for Haar Cascade face detector

• Adds unit tests validating cascade detector initialization and detection output formatting.

tests/vision/test_face_detector_cascade.py

test_face_in_frame.pyAdd tests for per-frame face pipeline behavior +179/-0

Add tests for per-frame face pipeline behavior

• Adds tests for face pipeline drawing/outputs and correct routing based on detector type.

tests/vision/test_face_in_frame.py

test_face_insight_pipeline.pyAdd tests for the unified face insight pipeline +121/-0

Add tests for the unified face insight pipeline

• Adds tests for pipeline initialization, detector selection, and recognition flow with mocked backends.

tests/vision/test_face_insight_pipeline.py

test_libreyolo.pyAdd tests for LibreYOLO integration path +45/-0

Add tests for LibreYOLO integration path

• Adds tests ensuring LibreYOLO detection path is exercised/falls back appropriately when unavailable.

tests/vision/test_libreyolo.py

test_object_insight_frame.pyAdd tests for object detection frame processor +86/-0

Add tests for object detection frame processor

• Adds tests for standardized detection extraction from results and draw behavior.

tests/vision/test_object_insight_frame.py

test_video_capture_init.pyAdd tests for VideoCapture initialization and wiring +538/-0

Add tests for VideoCapture initialization and wiring

• Adds tests verifying 'VideoCapture' wires camera + processors correctly for configured backends.

tests/vision/test_video_capture_init.py

test_vision_imx500.pyAdd tests for IMX500 vision paths +48/-0

Add tests for IMX500 vision paths

• Adds tests covering IMX500 metadata parsing and detector setup using mocks.

tests/vision/test_vision_imx500.py

test_yolo26_ncnn.pyAdd tests for YOLO NCNN detector path +86/-0

Add tests for YOLO NCNN detector path

• Adds tests exercising the YOLO NCNN integration codepath and output formatting assumptions.

tests/vision/test_yolo26_ncnn.py

Documentation (13) +3642 / -73
.gitmessageUpdate commit message template +11/-0

Update commit message template

• Expands/refreshes the repository commit message template to match the refactored conventions.

.gitmessage

LLM_offline.mdDocument offline LLM (Ollama) setup and usage +76/-0

Document offline LLM (Ollama) setup and usage

• Adds documentation for configuring and running the voice agent with an offline LLM backend, including model preload guidance.

docs/LLM_offline.md

STS_VAD_models.mdRefresh VAD/model documentation +10/-5

Refresh VAD/model documentation

• Updates documentation around STS/VAD models and how they are obtained/used after the refactor.

docs/STS_VAD_models.md

voice_agent_offline.mdUpdate offline voice agent example documentation +23/-17

Update offline voice agent example documentation

• Refreshes the offline voice agent guide to reflect the new responses configuration and LLM fallback options.

examples/VAD/voice_agent_offline.md

AUDIO_TESTS_README.mdRevise audio tests documentation after refactor +35/-51

Revise audio tests documentation after refactor

• Updates the audio hardware/integration tests README to reflect new scripts, flows, and expectations.

examples/audio/AUDIO_TESTS_README.md

llm_example.pyAdd example usage of the LLM client +98/-0

Add example usage of the LLM client

• Provides a runnable example that exercises the new 'src.llm' client against a configured backend.

examples/llm/llm_example.py

face_capture.pyAdd interactive face capture example +454/-0

Add interactive face capture example

• Adds a vision sample that captures faces from a camera stream and supports saving/labeling datasets.

examples/vision/face_capture.py

face_capture_headless.pyAdd headless face capture example +346/-0

Add headless face capture example

• Adds a headless variant of face capture suitable for SSH/headless devices (e.g., Raspberry Pi).

examples/vision/face_capture_headless.py

face_capture_web.pyAdd web-based face capture UI example +1446/-0

Add web-based face capture UI example

• Adds a larger web-based face capture workflow for dataset enrollment and previewing camera output.

examples/vision/face_capture_web.py

face_identify.pyAdd face identification example +367/-0

Add face identification example

• Adds a sample that runs face detection/recognition on captured frames using the modular pipeline.

examples/vision/face_identify.py

face_identify_web.pyAdd web-based face identification example +495/-0

Add web-based face identification example

• Adds a web-oriented face identification demo using the vision pipeline and camera capture.

examples/vision/face_identify_web.py

face_insight_imx500.pyAdd IMX500 + InsightFace example integration +117/-0

Add IMX500 + InsightFace example integration

• Adds a sample showing IMX500 camera integration and InsightFace-based face processing paths.

examples/vision/face_insight_imx500.py

motion_detection_alert.pyAdd motion detection alert example +164/-0

Add motion detection alert example

• Adds a sample that detects motion and can trigger an alert/recording workflow using camera frames.

examples/vision/motion_detection_alert.py

Other (32) +1945 / -92
action.ymlBump default Python and add system dependency for builds +2/-2

Bump default Python and add system dependency for builds

• Updates the action default Python version and installs 'libcap-dev' alongside audio tooling dependencies.

.github/actions/python-uv-setup/action.yml

run_tests.ymlSpeed up CI commands and enable vision extra in test envs +6/-6

Speed up CI commands and enable vision extra in test envs

• Runs 'uv run' with '--no-sync' and installs the new 'vision' extra during test jobs (including Raspberry Pi profile).

.github/workflows/run_tests.yml

.pre-commit-config.yamlAdjust pre-commit configuration +1/-1

Adjust pre-commit configuration

• Tweaks pre-commit tooling configuration (minor version/setting update) to align with updated linting/toolchain.

.pre-commit-config.yaml

.python-versionUpdate default Python version for local tooling +1/-1

Update default Python version for local tooling

• Bumps the repository Python version used by version managers to match CI expectations.

.python-version

config.yamlAdd Vision + LLM sections to default runtime config +45/-2

Add Vision + LLM sections to default runtime config

• Introduces a full 'vision' configuration block (models, thresholds, camera settings) and a 'llm' block (Ollama/OpenAI parameters). Also adjusts some audio/platform defaults for Raspberry Pi usage.

config.yaml

voice_agent_offline.yamlAdd responses configuration for the offline voice agent +11/-0

Add responses configuration for the offline voice agent

• Adds example YAML configuration controlling response language, file mapping, and LLM payload templating.

examples/VAD/voice_agent_offline.yaml

QUICK_START_AUDIO_TESTS.shUpdate audio tests quick-start script +22/-18

Update audio tests quick-start script

• Adjusts the quick-start script for running audio tests (paths/steps updated for the refactor).

examples/audio/QUICK_START_AUDIO_TESTS.sh

run_all_audio_tests.pyAdjust audio test runner behavior +19/-5

Adjust audio test runner behavior

• Updates the audio test orchestrator to match new test structure and runtime expectations.

examples/audio/run_all_audio_tests.py

test_asr_recording_validation.pyAlign ASR recording validation test with refactor +2/-2

Align ASR recording validation test with refactor

• Small adjustments to keep the ASR recording validation example compatible with new configuration/import paths.

examples/audio/test_asr_recording_validation.py

test_asr_with_tts.pyAlign ASR+TTS example test with refactor +2/-2

Align ASR+TTS example test with refactor

• Small compatibility updates to the ASR-to-TTS example test after refactoring.

examples/audio/test_asr_with_tts.py

test_hardware_detection.pyAlign hardware detection test with refactor +2/-2

Align hardware detection test with refactor

• Small compatibility updates for the hardware detection example test.

examples/audio/test_hardware_detection.py

test_playback.pyAlign playback example test with refactor +3/-3

Align playback example test with refactor

• Small compatibility updates for the playback example test.

examples/audio/test_playback.py

test_recorder_standalone.pyAlign recorder standalone example test with refactor +1/-1

Align recorder standalone example test with refactor

• Small compatibility updates for the recorder standalone example test.

examples/audio/test_recorder_standalone.py

test_recording.pyAlign recording example test with refactor +4/-4

Align recording example test with refactor

• Small compatibility updates for the recording example test.

examples/audio/test_recording.py

test_stream_open_close.pyUpdate stream open/close example test for new behavior +18/-5

Update stream open/close example test for new behavior

• Updates the stream lifecycle test to reflect the refactored audio stack and timing/cleanup behavior.

examples/audio/test_stream_open_close.py

test_tts_lifecycle_and_utils.pyAlign TTS lifecycle example test with refactor +1/-1

Align TTS lifecycle example test with refactor

• Small compatibility updates for the TTS lifecycle/utilities test.

examples/audio/test_tts_lifecycle_and_utils.py

test_vad_standalone.pyAlign VAD standalone example test with refactor +1/-1

Align VAD standalone example test with refactor

• Small compatibility updates for the VAD standalone example test.

examples/audio/test_vad_standalone.py

test_wake_word_standalone.pyAlign wake-word standalone example test with refactor +1/-1

Align wake-word standalone example test with refactor

• Small compatibility updates for the wake-word standalone example test.

examples/audio/test_wake_word_standalone.py

pyproject.tomlAdd 'vision' dependency group, relax lint/pytest settings, lower coverage gate +111/-6

Add 'vision' dependency group, relax lint/pytest settings, lower coverage gate

• Introduces a 'vision' extra (insightface/opencv/picamera2/ultralytics), updates Ruff ignore/excludes, adjusts pytest warning filters, adds basedpyright execution environments, and lowers coverage 'fail_under' threshold.

pyproject.toml

ai_camera_setup.shAdd Raspberry Pi AI camera setup script +42/-0

Add Raspberry Pi AI camera setup script

• Adds a setup script for installing and configuring camera dependencies needed for IMX500/vision workflows.

scripts/install/ai_camera_setup.sh

dependencies.shUpdate dependency install script for new features +11/-1

Update dependency install script for new features

• Extends the dependency installer to include tooling required by the new vision/LLM workflows.

scripts/install/dependencies.sh

install_ollama.shAdd/refresh Ollama install helper +5/-0

Add/refresh Ollama install helper

• Adds or updates a helper script to install and configure Ollama for offline LLM usage.

scripts/install/install_ollama.sh

wakeword_model.pyImprove wakeword model helper script +15/-6

Improve wakeword model helper script

• Enhances the wakeword model script used for downloading/preparing audio models for offline execution.

scripts/models/audio/wakeword_model.py

preload_ollama_models.shAdd script to preload Ollama models for offline use +10/-0

Add script to preload Ollama models for offline use

• Adds a script to pre-pull/preload Ollama models so the system can operate without network access.

scripts/models/preload_ollama_models.sh

coco_load.pyAdd COCO label/dataset loader for vision models +141/-0

Add COCO label/dataset loader for vision models

• Adds scripts to fetch/prepare COCO labels/assets used by object detection demos and tests.

scripts/models/vision/coco_load.py

detector_load.pyAdd detector model download/prepare script +131/-0

Add detector model download/prepare script

• Adds model download/prepare logic for vision detectors (YOLO/InsightFace-related assets).

scripts/models/vision/detector_load.py

libre_yolo_onnx.pyAdd LibreYOLO ONNX preparation helper +108/-0

Add LibreYOLO ONNX preparation helper

• Adds utilities to obtain/prepare LibreYOLO ONNX models for CPU inference flows.

scripts/models/vision/libre_yolo_onnx.py

load_all.pyAdd one-shot loader to prepare all vision models +45/-0

Add one-shot loader to prepare all vision models

• Adds a convenience script to preload all required vision models and assets for offline execution.

scripts/models/vision/load_all.py

yolo_onnx.pyAdd YOLO ONNX preparation helper +97/-0

Add YOLO ONNX preparation helper

• Adds utilities to obtain/prepare YOLO ONNX models for CPU inference flows.

scripts/models/vision/yolo_onnx.py

test_ollama.shAdd/expand Ollama smoke test script +113/-0

Add/expand Ollama smoke test script

• Adds a script to validate Ollama installation, model availability, and basic prompt/response behavior.

scripts/test_ollama.sh

ai_camera_verification.pyAdd AI camera verification test utility +225/-0

Add AI camera verification test utility

• Adds a verification script for camera availability and basic capture/inference readiness on supported hardware.

scripts/tests/ai_camera_verification.py

uv.lockLockfile update for new extras and dependencies +749/-22

Lockfile update for new extras and dependencies

• Updates the uv lockfile to include new 'vision' dependencies and other toolchain changes introduced in 'pyproject.toml'.

uv.lock

@chcavignx
chcavignx merged commit 0352e47 into main Jul 22, 2026
6 checks passed
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Logger name split crash 🐞 Bug ☼ Reliability
Description
Multiple new modules compute lib_name via __name__.split('.')[1], which raises IndexError when
the module is executed with python -m (where __name__ == '__main__'). This prevents running the
new vision/LLM entrypoints in module mode.
Code

src/llm/llm_client.py[R9-11]

+module_name = __name__
+lib_name = module_name.split(".")[1]
+logger = logging.getLogger(lib_name)
Evidence
The code indexes the second component of __name__ without checking whether a dot exists, which
will fail when __name__ is __main__.

src/llm/llm_client.py[9-12]
src/vision/video_capture.py[34-36]
src/vision/yolo_cpu.py[26-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Several modules assume `__name__` contains a dot and do `__name__.split('.')[1]`. When run via `python -m ...`, `__name__` becomes `__main__` and this crashes at import time.

### Issue Context
These files include shebangs / are used as runnable components, so module-mode execution is a realistic invocation.

### Fix
Prefer `logger = logging.getLogger(__name__)` (simplest), or use a safe fallback:
```py
module_name = __name__
lib_name = module_name.split(".")[1] if "." in module_name else module_name
logger = logging.getLogger(lib_name)
```
Apply consistently across all new modules using this pattern.

### Fix Focus Areas
- src/llm/llm_client.py[9-11]
- src/vision/video_capture.py[34-36]
- src/vision/yolo_cpu.py[26-28]
- src/utils/camera.py[21-23]
- src/vision/face_detector.py[16-18]
- src/vision/face_detector_cascade.py[17-19]
- src/vision/face_in_frame.py[17-19]
- src/vision/face_insight_pipeline.py[22-25]
- src/vision/face_recognizer.py[14-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unreleased Picamera2 request 🐞 Bug ☼ Reliability
Description
ThreadedCamera.update() only calls req.release() on the success path; if
make_array()/get_metadata() raises, the request is never released. This can leak camera
buffers/requests and degrade or stall capture over time.
Code

src/utils/camera.py[R106-120]

+    def update(self) -> None:
+        """Continuously grab frames from the camera in a background thread."""
+        while self.started:
+            if self.use_picamera2:
+                try:
+                    req = self.picam2.capture_request()  # type: ignore[attr-defined]
+                    frame = req.make_array("main")  # type: ignore[attr-defined]
+                    metadata = req.get_metadata()
+                    with self.read_lock:
+                        self.grabbed = True
+                        self.frame = frame
+                        self.metadata = metadata
+                    req.release()
+                except Exception:
+                    time.sleep(0.01)
Evidence
req.release() is inside the try block and is skipped if an exception occurs after
capture_request() succeeds.

src/utils/camera.py[106-120]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
In the Picamera2 path, `capture_request()` is followed by work that can raise before `req.release()` is reached; the `except` block doesn’t release the request.

### Issue Context
This code runs in a tight background loop, so even occasional exceptions can accumulate unreleased requests/buffers.

### Fix
Wrap request usage in `try/finally`:
- Initialize `req = None`
- After successful `capture_request()`, ensure `req.release()` is called in `finally` if `req` is not `None`.

### Fix Focus Areas
- src/utils/camera.py[106-120]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Vision postprocess key mismatch 🐞 Bug ≡ Correctness
Description
config.yaml sets vision.post_processing_model, but VisionConfig defines
post_processing_model_name, so the configured value won’t populate that field (it will be dropped
or cause validation failure depending on Pydantic extra-field settings). This makes the configured
post-processing model selection ineffective.
Code

config.yaml[R92-95]

+  post_processing_enabled: false
+  post_processing_model: "arcface_r100_v1.onnx"
+  post_processing_model_type: "insightface"
+  post_processing_model_path: null # Let Python code build the path dynamically
Evidence
The YAML uses post_processing_model while the Pydantic model declares
post_processing_model_name, so they do not map to each other during validation.

config.yaml[88-96]
src/utils/config.py[249-265]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`config.yaml` uses `vision.post_processing_model`, but the code reads `VisionConfig.post_processing_model_name`. As a result, the intended model filename from YAML does not actually control the runtime model choice.

### Issue Context
The mismatch is between the YAML key and the Pydantic model field name. There is no alias defined for `post_processing_model_name`.

### Fix
Choose one:
1) Rename the YAML key to `post_processing_model_name`, or
2) Add a Pydantic alias (e.g., `Field(validation_alias="post_processing_model")`) so both keys work.

### Fix Focus Areas
- config.yaml[88-96]
- src/utils/config.py[249-265]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Vision test needs model 🐞 Bug ☼ Reliability
Description
tests/vision/test_video_capture_init.py unconditionally asserts that
cfg.vision.object_model_full_path exists, but the default path resolves under
.cache/vision/models/... and the test does not provision it. This makes the integration test
non-reproducible on clean environments unless an explicit download/setup step is added.
Code

tests/vision/test_video_capture_init.py[R129-132]

+    def test_model_path_exists(self, cfg):
+        model_path = cfg.vision.object_model_full_path
+        assert model_path.exists(), f"Model path does not exist: {model_path}"
+
Evidence
The test requires the model path to exist, while the config resolves that path into .cache by
default and the repo’s model provisioning is implemented as a separate script.

tests/vision/test_video_capture_init.py[129-132]
src/utils/config.py[282-303]
scripts/models/vision/yolo_onnx.py[19-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The integration test asserts the YOLO model file exists at `cfg.vision.object_model_full_path` without ensuring it has been downloaded/created.

### Issue Context
`VisionConfig.object_model_full_path` defaults to `.cache/vision/models/<type>/<name>` when `object_model_path` is unset. The repo provides a separate script to download/convert YOLO models into that cache directory, but the test doesn’t call it.

### Fix
Pick one (prefer fast/reliable CI behavior):
1) Change the test to `pytest.skip(...)` when the model file is missing, with a message pointing to the download script.
2) In a fixture, download/provision the model (or a small test model) before asserting existence.
3) Add a CI step that runs the download/conversion script before executing `-m integration`.

### Fix Focus Areas
- tests/vision/test_video_capture_init.py[129-132]
- src/utils/config.py[282-303]
- scripts/models/vision/yolo_onnx.py[19-34]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Misleading ASR storage log 🐞 Bug ◔ Observability
Description
ASR._store_audio() logs "Audio chunk stored" even when _wav_writer is None, because the log
statement was moved outside the guarded write block. This produces incorrect debug telemetry for
audio storage.
Code

src/audio/asr.py[R525-528]

        with self._wav_writer_lock:
            if self._wav_writer is not None:
                self._wav_writer.writeframes(audio_bytes)
-                logger.debug("Audio chunk stored: %s", self._config.asr.store_audio_path)
+        logger.debug("Audio chunk stored: %s", self._config.asr.store_audio_path)
Evidence
The log statement is unconditionally executed after the guarded writeframes call, so it can fire
even when _wav_writer is None.

src/audio/asr.py[523-528]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`src/audio/asr.py` logs a successful audio-store message even when no writer exists because `logger.debug(...)` is outside the `if self._wav_writer is not None` block.

### Issue Context
This is an observability defect: it can mislead debugging/monitoring by indicating chunks were stored when they were not.

### Fix
Move the `logger.debug("Audio chunk stored...")` call under the `if self._wav_writer is not None:` block (optionally log a different message when storage is disabled).

### Fix Focus Areas
- src/audio/asr.py[523-528]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/audio/asr.py
@@ -525,7 +525,7 @@ def _store_audio(self, audio_bytes: bytes) -> None:
with self._wav_writer_lock:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

1. Misleading asr storage log 🐞 Bug ◔ Observability

ASR._store_audio() logs "Audio chunk stored" even when _wav_writer is None, because the log
statement was moved outside the guarded write block. This produces incorrect debug telemetry for
audio storage.
Agent Prompt
### Issue description
`src/audio/asr.py` logs a successful audio-store message even when no writer exists because `logger.debug(...)` is outside the `if self._wav_writer is not None` block.

### Issue Context
This is an observability defect: it can mislead debugging/monitoring by indicating chunks were stored when they were not.

### Fix
Move the `logger.debug("Audio chunk stored...")` call under the `if self._wav_writer is not None:` block (optionally log a different message when storage is disabled).

### Fix Focus Areas
- src/audio/asr.py[523-528]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread config.yaml
face_model_name: "buffalo_l" # "imx500_yolo11n.rpk" # "buffalo_l" #"imx500_network_mobilenet_v2.rpk" #"buffalo_l" #"haarcascade_frontalface_default.xml" , "buffalo_l" for insightface
face_model_path: null # "/data/imx500_yolo11n.rpk" # for "/data/imx500_yolo11n.rpk" imx500 # null for insightface
face_recognition_threshold: 0.4
post_processing_enabled: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Vision postprocess key mismatch 🐞 Bug ≡ Correctness

config.yaml sets vision.post_processing_model, but VisionConfig defines
post_processing_model_name, so the configured value won’t populate that field (it will be dropped
or cause validation failure depending on Pydantic extra-field settings). This makes the configured
post-processing model selection ineffective.
Agent Prompt
### Issue description
`config.yaml` uses `vision.post_processing_model`, but the code reads `VisionConfig.post_processing_model_name`. As a result, the intended model filename from YAML does not actually control the runtime model choice.

### Issue Context
The mismatch is between the YAML key and the Pydantic model field name. There is no alias defined for `post_processing_model_name`.

### Fix
Choose one:
1) Rename the YAML key to `post_processing_model_name`, or
2) Add a Pydantic alias (e.g., `Field(validation_alias="post_processing_model")`) so both keys work.

### Fix Focus Areas
- config.yaml[88-96]
- src/utils/config.py[249-265]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/llm/llm_client.py

import requests

module_name = __name__

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Logger name split crash 🐞 Bug ☼ Reliability

Multiple new modules compute lib_name via __name__.split('.')[1], which raises IndexError when
the module is executed with python -m (where __name__ == '__main__'). This prevents running the
new vision/LLM entrypoints in module mode.
Agent Prompt
### Issue description
Several modules assume `__name__` contains a dot and do `__name__.split('.')[1]`. When run via `python -m ...`, `__name__` becomes `__main__` and this crashes at import time.

### Issue Context
These files include shebangs / are used as runnable components, so module-mode execution is a realistic invocation.

### Fix
Prefer `logger = logging.getLogger(__name__)` (simplest), or use a safe fallback:
```py
module_name = __name__
lib_name = module_name.split(".")[1] if "." in module_name else module_name
logger = logging.getLogger(lib_name)
```
Apply consistently across all new modules using this pattern.

### Fix Focus Areas
- src/llm/llm_client.py[9-11]
- src/vision/video_capture.py[34-36]
- src/vision/yolo_cpu.py[26-28]
- src/utils/camera.py[21-23]
- src/vision/face_detector.py[16-18]
- src/vision/face_detector_cascade.py[17-19]
- src/vision/face_in_frame.py[17-19]
- src/vision/face_insight_pipeline.py[22-25]
- src/vision/face_recognizer.py[14-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/utils/camera.py
self.thread.start()
return self

def update(self) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Unreleased picamera2 request 🐞 Bug ☼ Reliability

ThreadedCamera.update() only calls req.release() on the success path; if
make_array()/get_metadata() raises, the request is never released. This can leak camera
buffers/requests and degrade or stall capture over time.
Agent Prompt
### Issue description
In the Picamera2 path, `capture_request()` is followed by work that can raise before `req.release()` is reached; the `except` block doesn’t release the request.

### Issue Context
This code runs in a tight background loop, so even occasional exceptions can accumulate unreleased requests/buffers.

### Fix
Wrap request usage in `try/finally`:
- Initialize `req = None`
- After successful `capture_request()`, ensure `req.release()` is called in `finally` if `req` is not `None`.

### Fix Focus Areas
- src/utils/camera.py[106-120]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

def test_model_loaded(self, vc):
assert vc.model is not None, "YOLO model should be loaded"

def test_model_path_exists(self, cfg):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

5. Vision test needs model 🐞 Bug ☼ Reliability

tests/vision/test_video_capture_init.py unconditionally asserts that
cfg.vision.object_model_full_path exists, but the default path resolves under
.cache/vision/models/... and the test does not provision it. This makes the integration test
non-reproducible on clean environments unless an explicit download/setup step is added.
Agent Prompt
### Issue description
The integration test asserts the YOLO model file exists at `cfg.vision.object_model_full_path` without ensuring it has been downloaded/created.

### Issue Context
`VisionConfig.object_model_full_path` defaults to `.cache/vision/models/<type>/<name>` when `object_model_path` is unset. The repo provides a separate script to download/convert YOLO models into that cache directory, but the test doesn’t call it.

### Fix
Pick one (prefer fast/reliable CI behavior):
1) Change the test to `pytest.skip(...)` when the model file is missing, with a message pointing to the download script.
2) In a fixture, download/provision the model (or a small test model) before asserting existence.
3) Add a CI step that runs the download/conversion script before executing `-m integration`.

### Fix Focus Areas
- tests/vision/test_video_capture_init.py[129-132]
- src/utils/config.py[282-303]
- scripts/models/vision/yolo_onnx.py[19-34]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant