Skip to content

Commit 19077d8

Browse files
authored
Merge pull request #25 from roryeckel/copilot/fix-1d0977a5-6a91-4c8a-a123-0f8f86119ad8
Restore missing Voxtral documentation and fix section numbering after main branch merge
2 parents 666ec29 + 8ec3e4a commit 19077d8

File tree

12 files changed

+1932
-477
lines changed

12 files changed

+1932
-477
lines changed

CLAUDE.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Wyoming OpenAI is a proxy middleware that bridges the Wyoming protocol with OpenAI-compatible endpoints for ASR (Automatic Speech Recognition) and TTS (Text-to-Speech) services. It enables Wyoming clients like Home Assistant to use various OpenAI-compatible STT/TTS services.
8+
9+
## Development Commands
10+
11+
### Testing
12+
```bash
13+
# Install development dependencies
14+
pip install -e ".[dev]"
15+
16+
# Run all tests
17+
pytest
18+
19+
# Run tests with coverage
20+
pytest --cov=wyoming_openai
21+
22+
# Run specific test file
23+
pytest tests/test_handler.py
24+
```
25+
26+
### Code Quality
27+
```bash
28+
# Run linting with Ruff
29+
ruff check .
30+
31+
# Auto-fix linting issues
32+
ruff check . --fix
33+
```
34+
35+
### Local Development Setup
36+
```bash
37+
# Install in editable mode
38+
pip install -e .
39+
40+
# Run the server locally
41+
python -m wyoming_openai --uri tcp://0.0.0.0:10300 --stt-models whisper-1 --tts-models tts-1
42+
```
43+
44+
### Docker Development
45+
```bash
46+
# Build and run development container
47+
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
48+
49+
# With local services (e.g., Speaches)
50+
docker compose -f docker-compose.speaches.yml -f docker-compose.dev.yml up -d --build
51+
```
52+
53+
## Architecture
54+
55+
### Core Components
56+
57+
- **`handler.py`**: Contains `OpenAIEventHandler` - the main Wyoming protocol event handler that processes ASR and TTS requests
58+
- **`compatibility.py`**: Provides `CustomAsyncOpenAI` class with backend detection and OpenAI API compatibility layer
59+
- **`__main__.py`**: Entry point with argument parsing and server initialization
60+
- **`utilities.py`**: Helper functions for audio processing and data handling
61+
- **`const.py`**: Version constants and configuration
62+
63+
### Key Architecture Patterns
64+
65+
1. **Async Event Handling**: Uses Wyoming's `AsyncEventHandler` to process incoming protocol events
66+
2. **Backend Abstraction**: `CustomAsyncOpenAI` wraps different backends (OpenAI, Speaches, LocalAI, etc.) with a unified interface
67+
3. **Stream Processing**: Handles both streaming and non-streaming transcription modes
68+
4. **Audio Buffer Management**: Collects audio chunks into complete files for processing
69+
70+
### Wyoming Protocol Flow
71+
72+
The handler processes these Wyoming events:
73+
- `AudioStart/AudioChunk/AudioStop` → STT transcription
74+
- `Transcribe` → Initiate transcription request
75+
- `Synthesize` → TTS audio generation
76+
77+
### Backend Support
78+
79+
The `OpenAIBackend` enum defines supported backends:
80+
- `OPENAI`: Official OpenAI API
81+
- `SPEACHES`: Local Speaches service
82+
- `LOCALAI`: LocalAI service
83+
- `KOKORO_FASTAPI`: Kokoro TTS service
84+
85+
## Configuration
86+
87+
The server accepts both command-line arguments and environment variables. Key configuration includes:
88+
- STT/TTS API keys and URLs
89+
- Model lists for STT and TTS
90+
- Voice configurations
91+
- Backend-specific settings (temperature, speed, etc.)
92+
93+
## Testing Strategy
94+
95+
Tests are organized by module:
96+
- `test_handler.py`: Event handler logic
97+
- `test_compatibility.py`: Backend compatibility
98+
- `test_utilities.py`: Helper functions
99+
- `test_integration.py`: End-to-end scenarios

Dockerfile

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ FROM python:3.12-slim
55
ENV PYTHONDONTWRITEBYTECODE=1
66
ENV PYTHONUNBUFFERED=1
77

8-
# Install system dependencies (if any)
9-
# build-essential and libssl-dev might be needed for some dependencies
10-
RUN apt-get update && \
11-
apt-get install -y --no-install-recommends \
12-
build-essential \
13-
libssl-dev \
14-
&& rm -rf /var/lib/apt/lists/*
8+
# No system dependencies needed - all Python packages have pre-compiled wheels
9+
# Uncomment the following lines if you need to install system dependencies
10+
# RUN apt-get update && \
11+
# apt-get install -y --no-install-recommends \
12+
# build-essential \
13+
# libssl-dev \
14+
# && rm -rf /var/lib/apt/lists/*
1515

1616
# Set the working directory in the container
1717
WORKDIR /app

README.md

Lines changed: 458 additions & 397 deletions
Large diffs are not rendered by default.

docker-compose.openai-edge-tts.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
services:
2+
wyoming_openai:
3+
image: ghcr.io/roryeckel/wyoming_openai:latest
4+
container_name: wyoming_openai
5+
ports:
6+
- "10300:10300"
7+
restart: unless-stopped
8+
environment:
9+
WYOMING_URI: tcp://0.0.0.0:10300
10+
WYOMING_LOG_LEVEL: INFO
11+
WYOMING_LANGUAGES: en
12+
TTS_OPENAI_URL: http://openai-edge-tts:5050/v1
13+
TTS_MODELS: "tts-1"
14+
TTS_BACKEND: "OPENAI"
15+
TTS_VOICES: "en-US-AnaNeural en-US-AndrewMultilingualNeural en-US-AndrewNeural en-US-AriaNeural en-US-AvaMultilingualNeural en-US-AvaNeural en-US-BrianMultilingualNeural en-US-BrianNeural en-US-ChristopherNeural en-US-EmmaMultilingualNeural en-US-EmmaNeural en-US-EricNeural en-US-GuyNeural en-US-JennyNeural en-US-MichelleNeural en-US-RogerNeural en-US-SteffanNeural"
16+
TTS_SPEED: "1.0"
17+
depends_on:
18+
openai-edge-tts:
19+
condition: service_healthy
20+
21+
openai-edge-tts:
22+
image: travisvn/openai-edge-tts:latest
23+
container_name: openai-edge-tts
24+
restart: unless-stopped
25+
ports:
26+
- "5050:5050"
27+
environment:
28+
- PORT=5050
29+
- DEFAULT_SPEED=1.0
30+
- DEFAULT_LANGUAGE=en-US
31+
- REQUIRE_API_KEY=False
32+
- REMOVE_FILTER=False
33+
- EXPAND_API=False
34+
healthcheck:
35+
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5050/v1/models', timeout=5)"]
36+
interval: 30s
37+
retries: 3
38+
start_period: 40s

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "wyoming_openai"
7-
version = "0.3.5"
7+
version = "0.3.6"
88
description = "OpenAI-Compatible Proxy Middleware for the Wyoming Protocol"
99
authors = [
1010
{ name = "Rory Eckel" }
@@ -21,8 +21,8 @@ classifiers = [
2121
"Operating System :: OS Independent",
2222
]
2323
dependencies = [
24-
"openai==1.91.0",
25-
"wyoming==1.7.1"
24+
"openai==1.98.0",
25+
"wyoming==1.7.2"
2626
]
2727

2828
[project.urls]
@@ -35,6 +35,7 @@ dev = [
3535
"pytest==8.4.1",
3636
"pytest-asyncio==1.0.0",
3737
"pytest-mock==3.14.1",
38+
"pytest-cov==6.2.1",
3839
]
3940

4041
[tool.setuptools.packages.find]

src/wyoming_openai/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,5 @@ async def main():
242242
)
243243
)
244244

245-
asyncio.run(main())
245+
if __name__ == "__main__":
246+
asyncio.run(main())

src/wyoming_openai/compatibility.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
from enum import Enum
3-
from typing import override
43

54
from openai import AsyncOpenAI
65
from wyoming.info import AsrModel, AsrProgram, Attribution, Info, TtsProgram, TtsVoice
@@ -277,16 +276,6 @@ def __init__(self, *args, **kwargs):
277276
self.backend: OpenAIBackend = kwargs.pop("backend", OpenAIBackend.OPENAI)
278277
super().__init__(*args, **kwargs)
279278

280-
@property
281-
@override
282-
def auth_headers(self) -> dict[str, str]:
283-
"""
284-
Override the auth_headers property to remove the Authorization header if no API key is provided.
285-
"""
286-
super_headers = super().auth_headers
287-
if not self.api_key:
288-
del super_headers["Authorization"]
289-
return super_headers
290279

291280
# OpenAI
292281

0 commit comments

Comments
 (0)