# speech-to-speech — Descripción del Proyecto

> **Paquete**: `Vocero speech-to-speech`
> **Versión**: `0.2.11`
> **Autor**: Alessandro Valenti
> **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 `Vocero speech-to-speech`?

`Vocero 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).
