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