vocero-s2s/docs/PROJECT_OVERVIEW.es.md
valenti ecb24e916d
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
sec rel
2026-08-26 12:42:28 +00:00

340 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).