671 lines
22 KiB
Markdown
671 lines
22 KiB
Markdown
# 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*
|