# PENNY — Asistente Personal de Voz **Especificación Técnica v1.0** **Componente del Sistema DECK** --- ## 1. Definición ### 1.1 ¿Qué es PENNY? PENNY es el **asistente personal de voz** del DECK. Interfaz conversacional hablada 100% natural. ``` ┌─────────────────────────────────────────────────────────────────────┐ │ PENNY │ │ │ │ • ES la voz del DECK │ │ • ES la interfaz hablada con el usuario │ │ • ES quien llama a GRACE cuando necesita datos │ │ • HABLA con el usuario (GRACE no puede) │ │ • REGISTRA todo en el log (planos de información) │ │ │ │ "PENNY habla, GRACE procesa, el Log recuerda." │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 1.2 Principios | Principio | Descripción | |-----------|-------------| | **Privacidad radical** | Modelos autoalojados, zero retención en proceso | | **Conversación natural** | Turnos fluidos, interrupciones, latencia <2s | | **Log como verdad** | Todo persiste en DECK, incluyendo personalidad | | **Planos separados** | Contextos cargables independientemente | | **Proceso stateless** | Servidor remoto no guarda nada | --- ## 2. Arquitectura ### 2.1 Diagrama General ``` ┌─────────────────────────────────────────────────────────────────────┐ │ SERVIDOR DE PROCESO (Remoto) │ │ GPU potente · Pago por uso · ZERO RETENCIÓN │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Silero │─▶│ Whisper │─▶│ LLM │─▶│ TTS │ │ │ │ VAD │ │ Large │ │ (local) │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ Modelos cargados desde almacenamiento ─── NO guardan estado │ │ Al terminar sesión: memoria volátil borrada │ │ │ └─────────────────────────────────────────────────────────────────────┘ ▲ │ │ Audio + Planos │ Audio + Datos │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ DECK │ │ Servidor Personal │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ PLANOS │ │ GRACE │ │ THE VAULT │ │ │ │ (Log) │ │ (Contrato │ │ (Recados) │ │ │ │ │ │ Común) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ Única ubicación con persistencia │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 2.2 Separación de Responsabilidades | Ubicación | Responsabilidad | Persistencia | |-----------|-----------------|--------------| | **Servidor Proceso** | VAD, ASR, LLM, TTS | NINGUNA | | **DECK** | Planos, Log, GRACE, Vault | TOTAL | ### 2.3 Flujo Voice-to-Voice ``` DECK SERVIDOR PROCESO │ │ │──── Audio + Planos ──────────────▶│ │ │ │ 1. VAD detecta voz │ 2. ASR transcribe │ 3. LLM genera respuesta │ 4. TTS sintetiza │ │ │◀──── Audio + Datos ──────────────│ │ │ │ (memoria borrada) │ ├── Log registra turno ├── GRACE procesa si necesario └── Vault guarda si recado ``` ### 2.4 Zero Retención en Proceso ``` ┌─────────────────────────────────────────────────────────────────────┐ │ POLÍTICA ZERO RETENCIÓN │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ SERVIDOR DE PROCESO: │ │ │ │ ✗ NO guarda audio de entrada │ │ ✗ NO guarda transcripciones │ │ ✗ NO guarda prompts ni respuestas │ │ ✗ NO guarda contexto entre sesiones │ │ ✗ NO tiene logs persistentes │ │ │ │ ✓ Modelos cargados desde almacenamiento (solo lectura) │ │ ✓ Procesamiento en memoria volátil │ │ ✓ Al terminar: todo descartado │ │ │ │ DECK: │ │ │ │ ✓ Único lugar con persistencia │ │ ✓ Log completo de conversaciones │ │ ✓ Planos de información │ │ ✓ Control total del usuario sobre sus datos │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 3. Planos de Información Todo persiste en DECK. Los planos se envían al servidor de proceso en cada sesión. ### 3.1 Estructura de Planos ``` ┌─────────────────────────────────────────────────────────────────────┐ │ PLANO 0: SISTEMA INMUTABLE │ │ Capacidades, limitaciones, reglas fundamentales │ ├─────────────────────────────────────────────────────────────────────┤ │ PLANO 1: PERSONALIDAD CONFIGURABLE │ │ Nombre, tono, estilo, configuración voz │ ├─────────────────────────────────────────────────────────────────────┤ │ PLANO 2: CONTEXTO PERSONAL PERSISTENTE │ │ Info usuario, preferencias, historial │ ├─────────────────────────────────────────────────────────────────────┤ │ PLANO 3: CONTEXTO AMBIENTAL DINÁMICO │ │ Fecha, hora, estado sistema, recados pendientes │ ├─────────────────────────────────────────────────────────────────────┤ │ PLANO 4: DATASET BAJO DEMANDA │ │ Datos específicos de tarea (catálogos, documentos) │ ├─────────────────────────────────────────────────────────────────────┤ │ PLANO 5: CONVERSACIÓN VOLÁTIL │ │ Historial de sesión actual │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 3.2 Definición de Planos #### PLANO 0: SISTEMA ```yaml plano: SISTEMA persistencia: permanente modificable: false contenido: version: "1.0" nombre_asistente: "Penny" capacidades: - conversacion_natural - gestion_recados - consulta_datos_via_grace limitaciones: - no_modifica_datos_directamente - no_ejecuta_acciones_externas reglas: - respuestas_concisas_max_3_oraciones - confirmar_antes_de_crear_recados - nunca_inventar_datos ``` #### PLANO 1: PERSONALIDAD ```yaml plano: PERSONALIDAD persistencia: permanente modificable: true contenido: nombre: "Penny" tono: "Cercano pero profesional" estilo: "Directo, sin rodeos" tratamiento: "Tuteo natural" muletillas_evitar: - "¡Claro que sí!" - "¡Por supuesto!" voz: speaker_reference: "penny_base" idioma: "es" ``` #### PLANO 2: CONTEXTO PERSONAL ```yaml plano: CONTEXTO_PERSONAL persistencia: permanente modificable: true player_id: "uuid-usuario" contenido: nombre_usuario: "Carlos" preferencias: formato_hora: "24h" formato_fecha: "dd/mm/yyyy" recordar: - "Prefiere respuestas cortas" - "Reuniones los lunes por la mañana" ``` #### PLANO 3: CONTEXTO AMBIENTAL ```yaml plano: CONTEXTO_AMBIENTAL persistencia: sesion modificable: auto contenido: fecha: "2025-12-16" hora: "10:30" dia_semana: "martes" recados_pendientes: 3 eventos_hoy: - "14:00 - Llamada cliente" ``` #### PLANO 4: DATASET ```yaml plano: DATASET persistencia: sesion carga: bajo_demanda contenido: tipo: "catalogo_productos" fuente: "catalogo_2025" registros: 1547 ``` #### PLANO 5: CONVERSACIÓN ```yaml plano: CONVERSACION persistencia: sesion modificable: append_only contenido: sesion_id: "uuid" turnos: - turno: 1 rol: "usuario" texto: "¿Qué tengo pendiente?" timestamp: "2025-12-16T10:25:15Z" - turno: 2 rol: "penny" texto: "Tienes 3 recados pendientes..." latencia_ms: 1850 ``` --- ## 4. Estructura del Log ### 4.1 Tabla: PENNY_LOG ```sql CREATE TABLE penny_log ( id UUID PRIMARY KEY, sesion_id UUID NOT NULL, turno_index INTEGER NOT NULL, -- Timing timestamp_inicio TIMESTAMPTZ NOT NULL, timestamp_fin TIMESTAMPTZ, -- Contenido rol VARCHAR(10) CHECK (rol IN ('usuario', 'penny', 'sistema')), texto TEXT NOT NULL, texto_hash VARCHAR(64), -- Audio (referencia local en DECK) audio_ref VARCHAR(255), audio_duracion_ms INTEGER, -- Métricas latencia_total_ms INTEGER, latencia_asr_ms INTEGER, latencia_llm_ms INTEGER, latencia_tts_ms INTEGER, tokens_prompt INTEGER, tokens_respuesta INTEGER, -- Trazabilidad trace_id UUID, grace_llamadas JSONB DEFAULT '[]', planos_activos JSONB NOT NULL, -- Estado interrumpido BOOLEAN DEFAULT false ); ``` ### 4.2 Tabla: PENNY_PLANOS ```sql CREATE TABLE penny_planos ( id UUID PRIMARY KEY, plano_tipo VARCHAR(50) NOT NULL, plano_version VARCHAR(20) NOT NULL, player_id UUID, contenido JSONB NOT NULL, contenido_hash VARCHAR(64), fecha_modificacion TIMESTAMPTZ DEFAULT NOW(), activo BOOLEAN DEFAULT true ); ``` ### 4.3 Tabla: PENNY_SESIONES ```sql CREATE TABLE penny_sesiones ( id UUID PRIMARY KEY, player_id UUID NOT NULL, inicio TIMESTAMPTZ NOT NULL, fin TIMESTAMPTZ, total_turnos INTEGER DEFAULT 0, planos_cargados JSONB NOT NULL, resumen TEXT, estado VARCHAR(20) DEFAULT 'activa' ); ``` --- ## 5. Integración con GRACE ### 5.1 Principio ``` PENNY puede HABLAR GRACE puede PROCESAR PENNY no puede PROCESAR GRACE no puede HABLAR Cuando PENNY necesita datos → llama a GRACE con Contrato Común ``` ### 5.2 Módulos Invocables | Módulo | Cuándo | |--------|--------| | `FIELD_EXTRACTOR` | "¿Qué dice esta factura?" | | `SUMMARIZER` | "Resume este documento" | | `CLASSIFIER` | "¿De qué tipo es este email?" | | `TASK_DETECTOR` | "¿Qué tareas hay en esta reunión?" | ### 5.3 Formato de Llamada ```json { "contract_version": "1.2", "envelope": { "trace_id": "penny-trace-uuid", "parent_trace_id": "sesion-uuid", "timestamp_init": "2025-12-16T10:26:00Z", "ttl_ms": 10000 }, "routing": { "module": "FIELD_EXTRACTOR", "provider_preference": ["local"] }, "context": { "lang": "es", "caller": "PENNY", "caller_sesion": "uuid-sesion" }, "payload": { "type": "document", "content_ref": "vault://uploads/factura.pdf" } } ``` ### 5.4 Flujo Ejemplo ``` Usuario: "¿Qué dice la factura que escaneé ayer?" 1. ASR: Audio → Texto 2. PENNY detecta: necesita GRACE.FIELD_EXTRACTOR 3. PENNY → GRACE (Contrato Común) 4. GRACE responde: { proveedor: "Telefónica", total: 45.90 } 5. PENNY + LLM formula respuesta natural 6. TTS: "La factura es de Telefónica por 45,90 euros" 7. Log registra todo (en DECK) ``` --- ## 6. Conversación Natural por Turnos ### 6.1 Turn Detection ```yaml turn_detection: vad: speech_threshold: 0.5 min_speech_ms: 250 min_silence_ms: 700 # Fin de turno end_of_turn: confident_silence_ms: 500 uncertain_silence_ms: 1000 max_silence_ms: 2000 barge_in: enabled: true # Usuario puede interrumpir min_user_speech_ms: 200 cancel_tts: true backchanneling: ignore_patterns: # No son turnos completos - "^(mhm|sí|ajá|vale|ok)$" ``` ### 6.2 Estados ``` IDLE ──────▶ LISTENING ──────▶ PROCESSING ──────▶ SPEAKING ▲ │ │ │ │ (barge-in) │ │ ◀────────────────────────────────────┘ │ │ └───────────────────────────────────────────────────┘ (TTS termina) ``` ### 6.3 Feedback ```yaml feedback: idle: "azul tenue" listening: "azul pulsante" processing: "amarillo" speaking: "verde" error: "rojo" ``` --- ## 7. Stack Técnico ### 7.1 Componentes de Proceso | Componente | Función | Latencia Objetivo | |------------|---------|-------------------| | VAD | Detecta voz/silencio | <50ms | | ASR | Audio → Texto | 200-400ms | | LLM | Genera respuesta | 500ms-2s | | TTS | Texto → Audio | 200-500ms | ### 7.2 Modelos Recomendados ```yaml vad: modelo: silero-vad version: v5 asr: modelo: faster-whisper variantes: - large-v3 # Máxima calidad - distil-large-v2 # Balance - medium # Fallback llm: opciones: - qwen2.5-72b # Máxima calidad - llama3.1-70b # Alternativa - mistral-22b # Balance - qwen2.5-7b # Baja latencia tts: modelo: xtts-v2 fallback: - kokoro-82m - piper ``` ### 7.3 Requisitos GPU (Servidor Proceso) ```yaml minimo: vram: 24GB modelos: whisper-large + llm-7b + xtts recomendado: vram: 48GB+ modelos: whisper-large + llm-70b + xtts distribucion_ejemplo_48gb: whisper_large: ~3GB qwen2.5_72b_q4: ~40GB xtts_v2: ~3GB buffer: ~2GB ``` ### 7.4 Latencias Objetivo | Componente | Objetivo | Máximo | |------------|----------|--------| | VAD | <50ms | 100ms | | ASR | <400ms | 800ms | | LLM (primer token) | <500ms | 1500ms | | TTS (primer audio) | <200ms | 500ms | | **Total voice-to-voice** | **<2s** | **<4s** | --- ## 8. Protocolo de Comunicación ### 8.1 DECK → Servidor Proceso ```json { "type": "session_start", "session_id": "uuid", "planos": { "sistema": { ... }, "personalidad": { ... }, "contexto_personal": { ... }, "contexto_ambiental": { ... } }, "config": { "voz_referencia": "base64_audio_6s", "idioma": "es", "max_tokens": 150 } } ``` ### 8.2 Audio Streaming ``` DECK ──── WebSocket ────▶ Servidor Proceso ◀─── WebSocket ───── Formato: PCM 16kHz mono Chunks: 20ms (320 samples) ``` ### 8.3 Servidor Proceso → DECK ```json { "type": "turn_complete", "turno": { "usuario_texto": "¿Qué tengo pendiente?", "penny_texto": "Tienes 3 recados...", "audio_respuesta": "base64...", "metricas": { "latencia_asr_ms": 350, "latencia_llm_ms": 1200, "latencia_tts_ms": 400, "tokens_prompt": 850, "tokens_respuesta": 42 } } } ``` --- ## 9. Estructura de Proyecto ### 9.1 DECK (Persistencia) ``` /deck/penny/ ├── planos/ │ ├── sistema.yaml │ ├── personalidad.yaml │ └── usuarios/ │ └── {player_id}.yaml ├── voces/ │ └── penny_base.wav ├── db/ │ └── penny.db └── config/ └── penny.yaml ``` ### 9.2 Servidor Proceso (Stateless) ``` /proceso/ ├── models/ # Solo lectura desde almacenamiento │ ├── whisper/ │ ├── llm/ │ ├── tts/ │ └── vad/ ├── engine/ │ ├── vad_processor.py │ ├── asr_processor.py │ ├── llm_processor.py │ ├── tts_processor.py │ └── pipeline.py └── server/ ├── main.py └── websocket.py ``` --- ## 10. Experiencias Reales ``` "Conseguí ~500ms latencia voice-to-voice con streaming de TTS." — KoljaB, RealtimeVoiceChat "En una 4090 con faster-distil-whisper bajamos a 300ms total." — voicechat2 "XTTS-v2 clone de la voz de mi abuelo me hizo llorar." — GitHub contributor "El turn detection es lo más difícil. Silero VAD funciona pero hay que ajustar los thresholds para cada persona." — Reddit r/LocalLLaMA ``` --- ## Resumen PENNY es: - **Voz del DECK** — interfaz hablada natural - **Proceso remoto stateless** — zero retención, modelos autoalojados - **Persistencia solo en DECK** — planos, log, datos - **6 planos** de información cargables independientemente - **Integración GRACE** via Contrato Común - **<2 segundos** voice-to-voice objetivo ``` ┌────────────────────┐ ┌────────────────────┐ │ SERVIDOR PROCESO │ │ DECK │ │ │ │ │ │ • VAD │◀───────▶│ • Planos │ │ • ASR │ Audio │ • Log │ │ • LLM │ Datos │ • GRACE │ │ • TTS │ │ • Vault │ │ │ │ │ │ ZERO RETENCIÓN │ │ PERSISTENCIA │ └────────────────────┘ └────────────────────┘ ``` --- *Diciembre 2025*