Some checks are pending
CI / ruff (push) Waiting to run
CI / mypy (push) Waiting to run
CI / pytest (push) Waiting to run
CI / package (push) Waiting to run
CI / Install smoke (${{ matrix.label }}) (linux, ubuntu-latest) (push) Blocked by required conditions
CI / Install smoke (${{ matrix.label }}) (macos-arm64, macos-14) (push) Blocked by required conditions
340 lines
21 KiB
Markdown
340 lines
21 KiB
Markdown
# speech-to-speech — Descripción del Proyecto
|
||
|
||
> **Paquete**: `speech-to-speech`
|
||
> **Versión**: `0.2.11`
|
||
> **Autor**: Hugging Face
|
||
> **Licencia**: Apache-2.0
|
||
> **Python**: 3.10 – 3.12
|
||
> **Lema**: Pipeline Speech-to-Speech de baja latencia end-to-end para construir agentes de voz en tiempo real.
|
||
|
||
---
|
||
|
||
## 1. ¿Qué es `speech-to-speech`?
|
||
|
||
`speech-to-speech` es una **pipeline de audio modular y de baja latencia** que convierte la entrada hablada del usuario en una respuesta hablada encadenando cuatro etapas de IA:
|
||
**VAD → STT → LLM → TTS**.
|
||
|
||
De fábrica expone un **endpoint WebSocket compatible con el protocolo OpenAI Realtime** (`ws://host:port/v1/realtime`),
|
||
por lo que cualquier navegador o SDK que sepa hablar el protocolo Realtime de OpenAI puede conectarse y empezar a tener
|
||
conversaciones naturales en cuestión de segundos — sin escribir una sola línea de código en el servidor.
|
||
|
||
La pipeline está diseñada para ser **agnóstica del backend en cada etapa**: puedes intercambiar distintos modelos
|
||
de STT / LLM / TTS según el hardware disponible (NVIDIA CUDA, Apple Silicon MLX o solo CPU), los idiomas de tus usuarios
|
||
y el compromiso entre latencia y calidad que necesites.
|
||
|
||
Un subsistema opcional integrado de **RAG (Retrieval Augmented Generation) del lado servidor** permite que las
|
||
respuestas del LLM se basen en tu propia base de conocimientos privada (documentos Markdown / TXT / JSONL),
|
||
mientras permanece 100 % transparente para el cliente. Las actualizaciones dinámicas de la base de conocimientos
|
||
se exponen en el mismo servidor HTTP mediante una API REST de 11 endpoints.
|
||
|
||
---
|
||
|
||
## 2. Características principales
|
||
|
||
| Característica | Descripción |
|
||
|---|---|
|
||
| **Protocolo de voz en tiempo real** | Soporte nativo del WebSocket OpenAI `v1/realtime`: `session.update`, `response.create`, llamadas a funciones, deltas de audio, interrupciones. Compatible directamente con el SDK Realtime oficial y con los "playgrounds" web. |
|
||
| **4 modos de ejecución** | `local` (micrófono → altavoces), `socket` (IPC TCP), `websocket` (WS audio crudo), `realtime` (protocolo OpenAI — predeterminado). |
|
||
| **6 backends de STT intercambiables** | Whisper / Whisper-MLX / MLX-Audio-Whisper / Faster-Whisper / Parakeet TDT (predeterminado) / Paraformer. |
|
||
| **4 backends de LLM intercambiables** | `transformers` (local) · `mlx-lm` (Apple Silicon) · `responses-api` (endpoint OpenAI con tool-calling) · `chat-completions` (cualquier servidor `/v1/chat/completions` compatible con OpenAI). |
|
||
| **5 backends de TTS intercambiables** | ChatTTS · Facebook MMS · Pocket (muy pequeño, CPU) · Kokoro · Qwen3-TTS (predeterminado, voz personalizada de 1.7B). |
|
||
| **Multiplataforma** | NVIDIA CUDA (Linux), Apple Silicon MLX + MPS (macOS), fallback CPU en cada etapa. |
|
||
| **Pool de N pipelines aisladas** | `--num_pipelines N` ejecuta N sesiones independientes en paralelo (cada una con sus propios VAD / STT / LLM / TTS y estado de conversación). Ideal para despliegues pequeños con múltiples conexiones simultáneas. |
|
||
| **VAD e interrupción integrados** | Detección de actividad vocal con umbral configurable; finalización del STT por silencio; el LLM se puede interrumpir mientras habla y se cancela de forma limpia mediante un `CancelScope`. |
|
||
| **Detección automática de idioma** | A través de `lingua-language-detector` para el idioma de la respuesta del asistente. |
|
||
| **Transcripción parcial en vivo** | Parakeet-TDT emite transcripciones parciales cada 500 ms para que los clientes muestren el texto "el usuario está hablando…". |
|
||
| **Soporte completo de tool-calling** | Con el backend `responses-api`: entrega en streaming de argumentos de función, tool calls paralelos, voces personalizadas sobre TTS — todo conforme a la superficie de la Responses API de OpenAI. |
|
||
| **Inyección RAG opcional en el servidor** | Hook transparente de recuperación en cada turno del LLM. Embeddings con Sentence-Transformers, persistencia del índice NPZ, top-k / umbral coseno configurables, inyección como mensaje del sistema o del usuario. Multilingüe por defecto (español / italiano / inglés listos para usar). |
|
||
| **API REST para base de conocimientos dinámica** | 11 endpoints para listar, buscar, hacer upsert, actualizar, eliminar y recargar contenidos de la KB en tiempo real — con persistencia entre reinicios mediante `kb/_dynamic.jsonl`. |
|
||
| **Configuración estructurada CLI / JSON** | Cada parámetro es un argumento `@dataclass` de HfArgumentParser con valores predeterminados razonables, o un fichero JSON de configuración completo que puedes pasar como único argumento. |
|
||
| **Empaquetado y flujo de publicación PyPI** | Configurado `uv build` + `twine check` + GitHub Actions (la etiqueta `vX.Y.Z` activa la subida). |
|
||
|
||
---
|
||
|
||
## 3. Arquitectura a vista de pájaro
|
||
|
||
Un turno único de conversación en modo **realtime** se ve así:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ uvicorn + FastAPI │
|
||
│ ┌───────────────────────────────────────────────────┐ │
|
||
Mic / Navegador ──► WS │ │ RealtimeService (sesión + rutas + eventos) │ │
|
||
(audio entradas + │ │ └───────────────────────────────────────────────────┘ │
|
||
eventos) │ │ │
|
||
│ │ Pool de pipelines ─► PipelineUnit #1 ─► PipelineUnit #N│
|
||
│ └─────────────────────────────────────────────────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌──────────────────────────────────────────────┐
|
||
│ Una unidad de pipeline (una por usuario) │
|
||
│ │
|
||
│ 1. VAD ──► inicio / fin voz │
|
||
│ 2. STT (5 sabores) ──► último texto usuario│
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌────────────────────────┐ │
|
||
│ │ HOOK BÚSQUEDA RAG │ ◄─── kb/_index.npz
|
||
│ │ (inyecta solo si ≥ N) │ + md/txt/jsonl
|
||
│ └────────────────────────┘ + _dynamic.jsonl
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 3. LLM (4 sabores) ──► texto + tool calls│
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 4. TTS (5 sabores) ──► PCM audio streaming│
|
||
└──────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
◄── WS audio / delta events
|
||
```
|
||
|
||
Invariantes clave:
|
||
|
||
- **Cada conexión = una `PipelineUnit`** (asignada de forma atómica; colas y estado aislados; si las N unidades están ocupadas, la conexión N+1 se rechaza).
|
||
- **La etapa LLM nunca ve audio crudo**. La pipeline solo envía buffers de texto al manejador del LLM, por lo que cambiar de proveedor o de modelo sigue siendo transparente.
|
||
- **La etapa RAG es un efecto lateral puro sobre el búfer de texto**: lee el texto más reciente del usuario, ejecuta un embedding + top-k por coseno + filtro por umbral, y antepone el conocimiento relevante como un mensaje adicional del sistema (o del usuario) — con una línea de registro clara para ver siempre exactamente qué se inyectó.
|
||
- **TTS en streaming** (todos los backends modernos): los deltas de audio se emiten a medida que se sintetizan, por lo que el primer byte de una respuesta llega al altavoz mucho antes de que el LLM termine de generar el texto.
|
||
|
||
---
|
||
|
||
## 4. Modos de ejecución (`--mode`)
|
||
|
||
Configurables mediante [ModuleArguments.mode](file:///home/azurian/speech-to-speech/src/speech_to_speech/arguments_classes/module_arguments.py#L11-L16)
|
||
(predeterminado: `realtime`).
|
||
|
||
| Modo | Entrada | Salida | Ideal para |
|
||
|---|---|---|---|
|
||
| `local` | Micrófono local vía `sounddevice` / `miniaudio` | Altavoces locales | Demos de escritorio, prototipado rápido en portátil. |
|
||
| `socket` | Chunks PCM crudos en socket TCP | PCM crudos por socket TCP | Sistemas legacy / embebidos, transporte personalizado. |
|
||
| `websocket` | PCM crudo sobre endpoint WS | PCM crudo sobre WS | Frontend a medida mínimo que solo envía audio. |
|
||
| `realtime` | WS con protocolo OpenAI Realtime (`/v1/realtime`) | Mismo protocolo + API REST RAG en el mismo servidor HTTP | **Producción e integración con SDK.** Todos los clientes que soportan la Realtime API (OpenAI SDK, playgrounds web, wrappers Swift / Kotlin / JS) se conectan aquí. |
|
||
|
||
---
|
||
|
||
## 5. Backends soportados
|
||
|
||
### 5.1 STT — Speech to Text
|
||
|
||
| Nombre `--stt` | Familia de modelos | Modelo predeterminado | Ventajas hardware |
|
||
|---|---|---|---|
|
||
| `whisper` | HuggingFace Transformers Whisper | `distil-whisper/distil-large-v3` | CUDA / CPU |
|
||
| `whisper-mlx` | MLX Whisper | `mlx-community/whisper-large-v3-turbo` | Apple Silicon (macOS) |
|
||
| `mlx-audio-whisper` | Apple `mlx-audio` | `mlx-community/whisper-large-v3-turbo` | Apple Silicon |
|
||
| `faster-whisper` | Whisper cuantizada CTranslate2 | `tiny.en` | CPU con latencia muy baja |
|
||
| `parakeet-tdt` | **(predeterminado)** HuggingFace Parakeet TDT | `parakeet-tdt-1.1b` | Streaming amigable + **transcripción parcial en vivo** sobre CUDA |
|
||
| `paraformer` | Alibaba Paraformer | `paraformer-zh` | Despliegues solo chino |
|
||
|
||
### 5.2 LLM — Modelo de lenguaje
|
||
|
||
| `--llm_backend` | Descripción | Modelo predeterminado |
|
||
|---|---|---|
|
||
| `transformers` | Generación local HF Transformers en proceso | `Qwen/Qwen3-4B-Instruct-2507` |
|
||
| `mlx-lm` | Inferencia local cuantizada 4-bit / 8-bit en Apple Silicon | `mlx-community/...` |
|
||
| `responses-api` | **(predeterminado)** Endpoint remoto compatible con OpenAI Responses API (superficie completa de tool-calling + argumentos de función en streaming) | `gpt-5.4-mini` |
|
||
| `chat-completions` | Cualquier remoto `/v1/chat/completions` compatible con OpenAI — enchufa vLLM, TGI, Ollama, servidor llama.cpp, SGLang, TabbyAPI, etc. | (auto-detecta vía `base_url + /v1/models`) |
|
||
|
||
### 5.3 TTS — Text to Speech
|
||
|
||
| Nombre `--tts` | Backend | Modelo predeterminado | Puntos fuertes |
|
||
|---|---|---|---|
|
||
| `chatTTS` | ChatTTS (grupo opcional `chattts`) | — | Muy conversacional, inglés + chino, prosodia muy expresiva |
|
||
| `facebookMMS` | Facebook MMS (grupo opcional `facebook-mms`) | `facebook/mms-tts-eng` | Ultraligero, cubre más de 1.100 idiomas |
|
||
| `pocket` | PocketTTS (pequeño CPU) | — | Sin dependencias en dispositivo, ideal para embebidos / poca RAM |
|
||
| `kokoro` | Kokoro TTS (grupo opcional `kokoro`) | — | Estado del arte de TTS neural en inglés y japonés |
|
||
| `qwen3` | **(predeterminado)** Qwen3-TTS vía GGML `faster-qwen3-tts` | `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` | Multilingüe, soporta clonación por voz de referencia, tasa de token 12 Hz → latencia ultrabaja. |
|
||
|
||
Todos los backends de TTS se acceden a través de una interfaz de streaming común, por lo que el resto de la pipeline es agnóstica al backend.
|
||
|
||
---
|
||
|
||
## 6. RAG del lado servidor (complemento opcional)
|
||
|
||
El subsistema RAG está completamente documentado en:
|
||
|
||
- 🌍 Guía lógica IT/EN: [RAG_SERVER_SIDE.md](file:///home/azurian/speech-to-speech/docs/RAG_SERVER_SIDE.md)
|
||
- 🇪🇸 Traducción al español: [RAG_SERVER_SIDE.es.md](file:///home/azurian/speech-to-speech/docs/RAG_SERVER_SIDE.es.md)
|
||
|
||
En resumen:
|
||
|
||
```
|
||
$ ./start_pipeline_rag.sh # arranca pipeline + API REST RAG
|
||
RAG: Inizializzazione modello embedding=paraphrase-multilingual-MiniLM-L12-v2 device=cuda su kb_path=/home/.../kb
|
||
RAG: Índice cargado desde disco: 9 chunk (shape=(9, 384)).
|
||
RAG: Activo. kb=... top_k=3 umbral=0.250 inject_as=system idioma=es
|
||
RAG HTTP API montata su prefix='/v1/rag'
|
||
```
|
||
|
||
Puntos clave:
|
||
|
||
- **Cero cambios en el cliente** — la inyección ocurre en el servidor, dentro del búfer de chat del LLM, antes de cada turno.
|
||
- **Modelos de embedding intercambiables** con un modelo multilingüe predeterminado (`paraphrase-multilingual-MiniLM-L12-v2`, 384 dims) y alternativas recomendadas para español-only y para bases de conocimientos muy grandes.
|
||
- **Persistencia del índice NPZ** — se reconstruye solo si el contenido cambió. Soporte de `--rag_force_rebuild`.
|
||
- **Tres formatos de fuente**: Markdown / TXT con chunking recursivo automático (tamaño + solapamiento configurables), o JSONL para chunks artesanales.
|
||
- **Comportamiento de recuperación configurable**: `--rag_top_k`, `--rag_threshold`, `--rag_inject_as (system/user)`, `--rag_language (es/it/en)`.
|
||
- **API REST dinámica thread-safe** (11 endpoints): `/status`, `/sources`, `/chunks`, `/chunks/list`, `/chunks/update`, `/upsert/document`, `/search`, `/add/document`, `/add/chunks`, `/remove`, `/reload`.
|
||
- **Persistencia entre reinicios**: las llamadas a la API con `persist=true` se adjuntan atómicamente a `kb/_dynamic.jsonl` y se re-indexan automáticamente en el siguiente arranque.
|
||
- **Patrón CRUD idempotente**: `/upsert/document` gestiona crear / reemplazar / eliminar (`text=""`) de forma atómica para que los clientes solo necesiten una URL.
|
||
- **Falla con elegancia**: cualquier excepción de recuperación se registra pero nunca rompe el turno de conversación.
|
||
|
||
---
|
||
|
||
## 7. Arranque rápido
|
||
|
||
### 7.1 Instalación (instalación editable estilo PyPI)
|
||
|
||
```bash
|
||
git clone https://github.com/huggingface/speech-to-speech.git
|
||
cd speech-to-speech
|
||
|
||
# Paquete base (incluye STT parakeet-tdt + TTS qwen3 por defecto)
|
||
uv pip install -e .
|
||
|
||
# Opcional: añade el subsistema RAG para recuperación en la KB
|
||
uv pip install -e ".[rag]"
|
||
|
||
# Opcional: selecciona los extras de TTS / STT que quieras
|
||
uv pip install -e ".[rag,chattts,kokoro,faster-whisper]"
|
||
```
|
||
|
||
### 7.2 Arrancar en modo Realtime (predeterminado) con un LLM externo
|
||
|
||
Esta es la configuración más común: el LLM corre en un servidor remoto compatible con OpenAI
|
||
(p.ej. `http://127.0.0.1:8001/v1` con vLLM o TGI), STT + TTS corren localmente sobre CUDA / MLX.
|
||
|
||
Crea un pequeño *wrapper* de shell (véase [start_pipeline.sh](file:///home/azurian/speech-to-speech/start_pipeline.sh) para la plantilla completa):
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -euo pipefail
|
||
|
||
LLM_BASE_URL="${LLM_BASE_URL:-http://127.0.0.1:8001/v1}"
|
||
LLM_MODEL="${LLM_MODEL:-Qwen/Qwen2.5-14B-Instruct-GPTQ-Int4}"
|
||
LLM_API_KEY="${LLM_API_KEY:-placeholder}"
|
||
TTS_MODEL="${TTS_MODEL:-Qwen/Qwen3-TTS-12Hz-1.7B-Base}"
|
||
STT_MODEL="${STT_MODEL:-distil-whisper/distil-large-v3}"
|
||
|
||
cd /home/azurian/speech-to-speech
|
||
|
||
uv run speech-to-speech \
|
||
--mode realtime \
|
||
--ws_host 0.0.0.0 \
|
||
--ws_port 12345 \
|
||
--num_pipelines 2 \
|
||
\
|
||
--llm_backend chat-completions \
|
||
--chat_completions_handler_base_url "$LLM_BASE_URL" \
|
||
--chat_completions_handler_model_name "$LLM_MODEL" \
|
||
--chat_completions_handler_api_key "$LLM_API_KEY" \
|
||
\
|
||
--stt whisper \
|
||
--whisper_stt_model_name "$STT_MODEL" \
|
||
\
|
||
--tts qwen3 \
|
||
--qwen3_tts_model_name "$TTS_MODEL"
|
||
```
|
||
|
||
Ejecútalo y después conecta cualquier cliente Realtime de OpenAI a:
|
||
|
||
```
|
||
ws://<host>:12345/v1/realtime
|
||
```
|
||
|
||
### 7.3 Misma pipeline + RAG activado
|
||
|
||
Añade estos flags (véase §6 para la referencia completa y el script auxiliar [start_pipeline_rag.sh](file:///home/azurian/speech-to-speech/start_pipeline_rag.sh)):
|
||
|
||
```bash
|
||
--rag_enabled \
|
||
--rag_kb_path ./kb \
|
||
--rag_top_k 3 \
|
||
--rag_threshold 0.25 \
|
||
--rag_language es \
|
||
--rag_inject_as system
|
||
```
|
||
|
||
La API REST de RAG aparece inmediatamente en el mismo servidor HTTP:
|
||
|
||
```bash
|
||
curl http://127.0.0.1:12345/v1/rag/status | jq
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Estructura del proyecto (puntos destacados)
|
||
|
||
```
|
||
speech-to-speech/
|
||
├── pyproject.toml ← metadatos del paquete + grupos de deps opcionales
|
||
├── LICENSE ← Apache-2.0
|
||
├── start_pipeline.sh ← script de arranque de referencia
|
||
├── start_pipeline_rag.sh ← script de arranque con RAG activado
|
||
│
|
||
├── src/speech_to_speech/
|
||
│ ├── s2s_pipeline.py ← punto de entrada (main), constructor de pipeline, pool realtime
|
||
│ ├── baseHandler.py
|
||
│ ├── chat.py
|
||
│ ├── pipeline/ ← tipos de cola, CancelScope, tipos de handler
|
||
│ │
|
||
│ ├── arguments_classes/ ← args HfArgumentParser @dataclass (uno por handler)
|
||
│ │ ├── module_arguments.py ← mode / stt / tts / llm_backend / num_pipelines
|
||
│ │ ├── rag_arguments.py ← 12 flags específicos de RAG
|
||
│ │ └── ... (Whisper, Qwen3, ChatTTS, VAD, …)
|
||
│ │
|
||
│ ├── STT/ ← seis manejadores STT
|
||
│ ├── TTS/ ← cinco manejadores TTS
|
||
│ ├── LLM/
|
||
│ │ ├── base_openai_compatible_language_model.py ← Hook de inyección RAG + tool-calling completo
|
||
│ │ ├── chat_completions_language_model.py
|
||
│ │ ├── responses_api_language_model.py
|
||
│ │ └── language_model.py ← manejadores locales transformers / mlx-lm
|
||
│ │
|
||
│ ├── RAG/
|
||
│ │ ├── retriever.py ← singleton, embeddings, NPZ, search, CRUD completo
|
||
│ │ └── router.py ← 11 endpoints FastAPI /v1/rag
|
||
│ │
|
||
│ └── api/openai_realtime/
|
||
│ ├── websocket_router.py ← FastAPI + Realtime + montaje condicional RAG
|
||
│ ├── service.py ← Bucle de eventos Realtime + rutas de sesión / handlers
|
||
│ └── ...
|
||
│
|
||
├── kb/
|
||
│ ├── 01_faq_producto.md ← ejemplo FAQ en español (incluido en scaffold)
|
||
│ ├── 02_politicas_internas.md ← ejemplo políticas en español
|
||
│ ├── README.md ← guía de formatos
|
||
│ └── _dynamic.jsonl ← chunks añadidos por API (auto-generado)
|
||
│
|
||
└── docs/
|
||
├── RAG_SERVER_SIDE.md ← Guía RAG en profundidad IT/EN (más de 600 líneas)
|
||
└── RAG_SERVER_SIDE.es.md ← Traducción al español
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Versionado y publicación
|
||
|
||
El repositorio trae una pipeline completa de lanzamiento a PyPI en
|
||
`.github/workflows/publish.yml`:
|
||
|
||
1. Sube la `version` en [pyproject.toml](file:///home/azurian/speech-to-speech/pyproject.toml#L7)
|
||
y `__version__` en [src/speech_to_speech/__init__.py](file:///home/azurian/speech-to-speech/src/speech_to_speech/__init__.py).
|
||
2. Fusiona una PR de release que contenga solo esos dos cambios.
|
||
3. Etiqueta y sube:
|
||
```bash
|
||
git checkout main && git pull origin main
|
||
git tag -a vX.Y.Z -m "Release vX.Y.Z"
|
||
git push origin vX.Y.Z
|
||
```
|
||
4. El workflow ejecuta automáticamente `uv build` + `twine check --strict` + subida a PyPI.
|
||
|
||
Consulta [AGENTS.md](file:///home/azurian/speech-to-speech/AGENTS.md) para las reglas de release a nivel de repositorio.
|
||
|
||
---
|
||
|
||
## 10. Siguientes pasos
|
||
|
||
- Empieza con los scripts de arranque: [start_pipeline.sh](file:///home/azurian/speech-to-speech/start_pipeline.sh) (base) y [start_pipeline_rag.sh](file:///home/azurian/speech-to-speech/start_pipeline_rag.sh) (con RAG).
|
||
- Profundiza en el subsistema RAG: [RAG_SERVER_SIDE.md](file:///home/azurian/speech-to-speech/docs/RAG_SERVER_SIDE.md) / [RAG_SERVER_SIDE.es.md](file:///home/azurian/speech-to-speech/docs/RAG_SERVER_SIDE.es.md).
|
||
- Ajusta los manejadores por etapa leyendo las clases de argumentos en [arguments_classes](file:///home/azurian/speech-to-speech/src/speech_to_speech/arguments_classes) — cada flag tiene su ayuda en línea.
|
||
- Construye tu propio cliente Realtime apoyándote en [service.py](file:///home/azurian/speech-to-speech/src/speech_to_speech/api/openai_realtime/service.py) y [websocket_router.py](file:///home/azurian/speech-to-speech/src/speech_to_speech/api/openai_realtime/websocket_router.py).
|