Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/actions/python-uv-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ inputs:
python-version:
description: Python version to install
required: false
default: "3.11.6"
default: "3.13.2"
sync-flags:
description: Extra flags for uv sync (e.g., --locked --group test)
required: false
Expand Down Expand Up @@ -48,7 +48,7 @@ runs:
SUDO=""
fi
$SUDO apt-get update
$SUDO apt-get install -y ffmpeg portaudio19-dev alsa-utils
$SUDO apt-get install -y ffmpeg portaudio19-dev alsa-utils libcap-dev

- name: Install the project dependencies
shell: bash
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ jobs:
install-system-packages: "true"
sync-flags: "--locked --no-default-groups --group lint"
- name: Run Ruff linter
run: uv run ruff check .
run: uv run --no-sync ruff check .
- name: Run Ruff formatter
run: uv run ruff format --check .
run: uv run --no-sync ruff format --check .

test:
name: Test "${{ inputs.marker }}"
Expand All @@ -48,9 +48,9 @@ jobs:
uses: ./.github/actions/python-uv-setup
with:
install-system-packages: "true"
sync-flags: "--locked --no-default-groups --group test"
sync-flags: "--locked --no-default-groups --group test --extra vision"
- name: Run core tests
run: uv run pytest -n auto -m "${{ inputs.marker }}" --cov=src --cov-report=xml tests/
run: uv run --no-sync pytest -n auto -m "${{ inputs.marker }}" --cov=src --cov-report=xml tests/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
Expand All @@ -73,9 +73,9 @@ jobs:
with:
enable-cache: true
install-system-packages: "true"
sync-flags: "--locked --no-default-groups --group test --extra raspberry-pi"
sync-flags: "--locked --no-default-groups --group test --extra vision --extra raspberry-pi"
- name: Run Raspberry Pi profile tests
run: uv run pytest -n auto -m "${{ inputs.marker }}" --cov=src --cov-report=xml tests/
run: uv run --no-sync pytest -n auto -m "${{ inputs.marker }}" --cov=src --cov-report=xml tests/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
Expand Down
11 changes: 11 additions & 0 deletions .gitmessage
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# <type>(<scope>): <subject>
#
# <body>
#
# <footer>

# Type: feat, fix, docs, style, refactor, perf, test, chore, ci
# Scope: zone affectée (stt, vision, tts, etc.)
# Subject: description courte en minuscules
# Body: explication détaillée (optionnel)
# Footer: issues fermées (optionnel)
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.14
rev: v0.15.22
hooks:
- id: ruff
name: ruff check --fix (commit)
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.11
3.13
47 changes: 45 additions & 2 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ audio:
input_chunk_size: 1024
input_device_index: 0 # use system default when a mic is attached
input_device_name: 'USB ENC Audio Device' # Optional name of input device to select (overrides index if found)
volume: 0.5 # half as loud
volume: 0.8 # half as loud
output_device_index: 1 # default to USB PnP Audio Device: Audio (hw:3,0)
output_sample_rate: 44100
output_chunk_ms: 300 # taille des chunks audio en ms
Expand All @@ -73,4 +73,47 @@ audio:
# Platform-specific configuration
platform:
cpu_cores: 2 # Limit CPU cores for multiprocessing on Pi5
pi: false # Automatically detect Raspberry Pi and apply tuning
pi: true # Automatically detect Raspberry Pi and apply tuning

# Vision configuration
vision:
object_model_type: "yolo" # "yolo" (CPU), "yolo_hailo" (Hailo NPU), "yolo_imx500" (Sony IMX500)
object_model_name: "yolo26n.onnx" # Options: "yolo26n.onnx" (default fast), "LibreYOLOXn.onnx" (100% free / MIT license)
object_model_path: null # Let Python code build the path dynamically
object_device: "cpu" # CPU or "hailo"
object_inference_framework: "onnx" # "ncnn"
object_nms: False # Enable NMS in Python (after export)
object_image_size: 640 # Image size for YOLO model
object_recognition_threshold: 0.5
face_detector_type: "insightface" # "cascade", "insightface", "hailo", "imx500"
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

post_processing_model: "arcface_r100_v1.onnx"
post_processing_model_type: "insightface"
post_processing_model_path: null # Let Python code build the path dynamically
post_processing_image_size: 640
enable_face_detection: true
enable_face_recognition: true
enable_object_detection: true
face_dataset_path: "data/face_dataset/"
object_dataset_path: "data/object_dataset/"

camera:
camera_index: 0
frame_width: 1080
frame_height: 720
format: "RGB888" # "YUV420" or "RGB888"
lores_frame_width: 320
lores_frame_height: 240
lores_format: "YUV420"
imx500_frame_width: 640
imx500_frame_height: 480
# Local LLM settings for fallbacks or open-ended queries
llm:
api_type: "ollama"
model: "llama3.2:latest"
url: "http://127.0.0.1:11434/api/generate"
timeout: 30.0
api_key: null
Binary file added data/bus.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions data/responses_en.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# English responses configuration for the voice agent
responses:
# Exit/Stop commands are placed first to take precedence
- keywords: ["stop", "exit", "quit"]
type: "text"
value: "Goodbye! Returning to wake word detection."
exit: true
- keywords: ["bye_bye", "bye"]
type: "text"
value: "See you later! Going back to sleep mode."
exit: true
- keywords: ["hello", "hi"]
type: "text"
value: "Hello! I'm {wake_word}, your AI assistant. How can I help you?"
- keywords: ["time"]
type: "action"
value: "get_time"
- keywords: ["date"]
type: "action"
value: "get_date"
- keywords: ["lights"]
type: "action"
value: "control_lights"
- keywords: ["music"]
type: "action"
value: "play_music"
- keywords: ["help"]
type: "text"
value: "I can respond to simple commands like hello, hi, time, date, lights, music, stop and bye-bye."
# Empty keywords acts as default / fallback
- keywords: []
type: "llm"
value: "You said: {user_input}. I'm still learning how to respond to that."
33 changes: 33 additions & 0 deletions data/responses_fr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# French responses configuration for the voice agent
responses:
# Les commandes d'arrêt sont placées en premier pour être prioritaires
- keywords: ["arrête", "arrete", "quitter", "stop"]
type: "text"
value: "Au revoir ! Retour à l'écoute du mot d'activation."
exit: true
- keywords: ["bye_bye", "bye", "salut", "a plus"]
type: "text"
value: "À plus tard ! Retour en mode veille."
exit: true
- keywords: ["bonjour"]
type: "text"
value: "Bonjour! Je suis {wake_word}, votre assistant IA. Comment puis-je vous aider ?"
- keywords: ["heure"]
type: "action"
value: "get_time"
- keywords: ["date"]
type: "action"
value: "get_date"
- keywords: ["lumière", "lumieres", "lumières"]
type: "action"
value: "control_lights"
- keywords: ["musique"]
type: "action"
value: "play_music"
- keywords: ["aide"]
type: "text"
value: "Je peux répondre à des commandes simples comme bonjour, heure, date, lumière, musique, et arrête."
# Empty keywords acts as default / fallback
- keywords: []
type: "llm"
value: "Vous avez dit : {user_input}. Je suis encore en train d'apprendre comment répondre à cela."
76 changes: 76 additions & 0 deletions docs/LLM_offline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Offline Large Language Model (LLM)

The goal is to enable the assistant to generate intelligent responses offline using local language models.
We have integrated a local LLM client in `src/llm/` that is designed to support both local Ollama and OpenAI-style APIs, configured centrally.

## 1. Ollama

### Description
Ollama is a lightweight, extensible framework for building and running language models locally. It provides a simple API and CLI to download, run, and manage models on your device, making it an excellent choice for offline setups on hardware like the Raspberry Pi.

### Installation

1. **Install Ollama**:
You can use the provided script or install it directly using the official curl command:

```bash
curl -fsSL https://ollama.com/install.sh | sh
```
Alternatively, run our provided script:
```bash
./scripts/install/install_ollama.sh
```

2. **Download Models**:
Once Ollama is installed, you need to pull the models you wish to use offline. For instance, `tinyllama` or `llama3.2:1b` are suitable for lightweight setups.
```bash
ollama pull tinyllama
```
Alternatively, you can run the preloading script to load a set of default models:
```bash
./scripts/models/preload_ollama_models.sh
```

## 2. LLM Client Library

The LLM response generation logic is structured as a generic client package in `src/llm/`.

### Configuration Layer

Configuration is managed via `src/utils/config.py` and `config.yaml`. The LLM configuration block includes:
- `api_type`: Configures the LLM provider (e.g., `ollama` or `openai`).
- `url`: The endpoint URL (e.g., `http://localhost:11434/api/generate` for Ollama).
- `model`: The name of the model to use (e.g., `tinyllama`).
- `api_key`: API key if using a cloud provider (can be null for local Ollama).
- `timeout`: Request timeout in seconds.

### Voice Agent Integration

The offline voice agent (`examples/VAD/voice_agent_offline.py`) has been updated to use this client library:
- Added a `ResponsesConfig` configuration block to handle response rules.
- The agent includes a `use_llm` toggle. If enabled, the agent attempts to generate a response via the LLM API.
- Support is provided for formatting payloads and forwarding custom overrides when using Ollama.
- If the LLM generation fails or times out, it gracefully falls back to basic intent matching or a fallback template.

### Testing and Examples

- **Tests**: The `tests/llm/` and `tests/audio/` directories contain unit and integration tests covering Ollama responses, OpenAI completions, headers, timeout settings, request exceptions, and non-200 responses.
- **Example Script**: A standalone example is provided at `examples/llm/llm_example.py` for end-to-end testing of the LLM library manually using custom prompts or overriding configurations.

```bash
python examples/llm/llm_example.py --prompt "What is the capital of France?" --api-type ollama --model tinyllama
```

## 3. HuggingFace Transformers (Alternative)

An alternative integration for a local LLM involves using HuggingFace Transformers directly.

**Changes required for Transformers integration:**
- Update `llm_model_name`, `llm_device` (e.g. `cpu`), and `llm_torch_dtype` in the configuration.
- Load the model and tokenizer manually.
- Note: This method is significantly more resource-intensive on the Raspberry Pi compared to Ollama.

**Manual Verification:**
```bash
pip install torch transformers accelerate
```
15 changes: 10 additions & 5 deletions docs/STS_VAD_models.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,17 @@ The library exposes three main components that map directly to the recommended a

### Wake Word Detection

`WakeWordDetector` uses **openWakeWord**:
`WakeWordDetector` is implemented by directly running pre-trained openWakeWord ONNX models using `onnxruntime`, bypassing the standard `openwakeword` Python package (which has compatibility issues with newer Python versions).

- Runs fully offline
- Captures microphone input with PyAudio
- Expects 16 kHz audio internally, resamples device audio when necessary
- Uses a cooldown to avoid repeated triggers
Key Details:

- Model designs and weights are derived from [openWakeWord](https://github.com/dscripka/openWakeWord.git).
- Audio feature extraction (log-mel spectrogram and Google speech embeddings) is handled directly via `ONNXAudioFeatures` to reproduce openWakeWord's preprocessing pipeline.
- Performs inference using three chained ONNX sessions: `melspectrogram.onnx`, `embedding_model.onnx`, and the wake word classifier `.onnx` model.
- Runs fully offline.
- Captures microphone input with PyAudio.
- Expects 16 kHz audio internally, resampling device audio when necessary.
- Uses a cooldown to avoid repeated triggers.

### Speech Recognition

Expand Down
Loading
Loading