🎙️
M10

Multimodal — voz y visión

Módulo 10 · Multimodal — Voz, visión y generación (io.stt, model.vision)

Prerequisito: M1–M6 (LLMs, ingesta, retrieval, generación con citas, agentes). M2 cubre loader.multimodal en ingesta; este módulo profundiza en entrada y salida multimodal.

Nodos RAGorbit: io.stt, model.vision, loader.multimodal, model.llm (multimodal)

Templates ancla: 07-telecom-callcenter-copilot (STT streaming), 04-insurance-claims (visión en siniestros), 08-manufacturing-maintenance-rag (AMM + diagramas ATA)


1. Qué es "multimodal" y por qué importa en RAG

Un sistema multimodal procesa más de un tipo de señal: texto, voz, imagen, video, tablas escaneadas. En RAG y agentes, el objetivo no es "ver bonito" — es convertir esas señales en texto estructurado que el resto del pipeline (retrieval, generación, reglas) ya sabe manejar.

┌─────────────────────────────────────────────────────────────────────┐
│                    PIPELINE MULTIMODAL TÍPICO                       │
│                                                                     │
│  Audio (.wav)  ──▶ [STT] ──▶ texto                                 │
│  Imagen (.jpg) ──▶ [Visión] ──▶ descripción + metadata              │
│  PDF con fotos ──▶ [loader.multimodal] ──▶ chunks enriquecidos     │
│                                                                     │
│         texto + descripciones + metadata                            │
│                    │                                                │
│                    ▼                                                │
│            [Retrieval / RAG / Agente]                               │
│                    │                                                │
│                    ▼                                                │
│  Respuesta texto ◀─┴──▶ [TTS] ──▶ audio (opcional)                 │
│              └──▶ [DALL·E/SDXL] ──▶ imagen (opcional)               │
└─────────────────────────────────────────────────────────────────────┘

1.1 ¿Cuándo usar multimodal?

Usa entrada multimodal cuando:

  • El usuario habla (call center, asistente de voz, dictado en rampa).
  • Los documentos incluyen diagramas, fotos o tablas cuya información no está en el texto OCR.
  • Necesitas clasificar daños en fotos (seguros, inspección industrial).
  • El canal de entrada es foto + pregunta (WhatsApp con imagen de factura).

NO uses multimodal cuando:

  • Todo el contenido relevante ya está en texto limpio (PDF digital, Markdown, SQL).
  • La latencia es crítica (<500 ms) y puedes pedir al usuario que escriba en lugar de hablar o fotografiar.
  • El costo por consulta importa y la señal visual no aporta información nueva (logo decorativo en un PDF).

1.2 El patrón RAGorbit: convertir → recuperar → citar

RAGorbit no indexa píxeles ni ondas de audio directamente en el vector store (salvo embeddings multimodales especializados). El patrón dominante es:

  1. Convertir audio/imagen a texto (STT, visión).
  2. Enriquecer con metadata (ATA chapter, tipo de daño, idioma).
  3. Recuperar con el texto fusionado + filtros duros.
  4. Generar con citas obligatorias (M5).

2. Retos del multimodal en producción

Reto Qué significa Mitigación
Alineación Voz e imagen deben referirse al mismo incidente IDs de sesión; timestamp; fusionar en una sola query
Latencia STT streaming + visión + RAG puede superar 3 s STT streaming (Deepgram); visión async; cache de descripciones
Costo Visión ~$0.01–0.03/img; Whisper API por minuto Batch offline con Whisper local; comprimir imágenes
Idiomas Whisper multilingüe ≠ todos los acentos language hint; fine-tune o modelo regional
Formatos Codecs de audio, HEIC, PDF escaneado Normalizar en ingesta (loader.multimodal)
Alucinación visual El modelo "ve" daños que no existen Citas al AMM; HITL en WARNING; reglas sobre metadata
PII en audio/imagen Voces, placas, rostros en fotos Redacción pre-STT; blur en pipeline de ingesta
Latencia típica (copilot call center, template 07):
  Deepgram STT (streaming)     ~300–800 ms
  model.intent                  ~10 ms
  retrieval + rerank            ~200–400 ms
  logic.prompt + citations      ~500–1500 ms
  ─────────────────────────────────────────
  Total objetivo:               < 1.5 s desde fin de frase

3. STT — Speech-to-Text y el nodo io.stt

3.1 Qué hace

STT convierte audio en texto. En RAGorbit, el nodo io.stt produce fragmentos Message en streaming conforme llega el audio — no espera a que el usuario termine de hablar.

Ver ficha completa: referencia/catalogo-nodos.md §io.stt.

{
  "type": "io.stt",
  "config": {
    "provider": "deepgram",
    "language": "es"
  }
}

3.2 Streaming vs batch

Modo Cuándo Ejemplo
Streaming Tiempo real, copilot, IVR Template 07: fragmento de llamada → sugerencia en panel
Batch Grabación completa, post-proceso Nota de voz de técnico en rampa → transcript → RAG

En batch, Whisper local es la opción de referencia (open-weights, sin costo por token). En streaming, Deepgram Nova-2 o Amazon Transcribe Streaming dominan por latencia.

3.3 Cuándo NO usar io.stt

  • Si ya tienes el transcript (archivo .txt, subtítulos, CRM).
  • Si el audio se procesa en lote nocturno — usa Whisper como paso de io.batch o tool.function.

4. Whisper — el modelo de referencia para STT offline

Whisper (OpenAI, open-weights) es el estándar para transcripción multilingüe offline. Modelos: tiny, base, small, medium, large — más grande = mejor calidad, más lento.

# Conceptual — ver §10 para implementación completa
import whisper
model = whisper.load_model("base")
result = model.transcribe("nota_tecnico.wav", language="es")
print(result["text"])

4.1 Fortalezas

  • Multilingüe sin fine-tune (99 idiomas).
  • Open-weights — corre local vía Hugging Face, Ollama, pip install openai-whisper.
  • Segmentos con timestamps — útil para alinear con video o UI de revisión.
  • Costo cero por token si corre en tu GPU/CPU.

4.2 Limitaciones

  • No es streaming real — procesa el archivo completo (o chunks grandes).
  • Latencia en CPU: ~10–30× tiempo real con modelo base.
  • Ruido de fondo en rampa/aeropuerto degrada la calidad.

4.3 Whisper vs Deepgram

Whisper (local) Deepgram Nova-2
Latencia Alta (batch) Baja (<300 ms streaming)
Costo GPU/CPU fijo ~$0.0043/min
Privacidad Datos no salen Audio va a API
Idiomas 99 36+ con excelente ES
Caso ideal Batch, offline, privado Call center tiempo real (template 07)

Ver también: referencia/glosario.md §Whisper, referencia/glosario.md §STT.


5. Visión — describir imágenes, diagramas y tablas→JSON

5.1 El nodo model.vision

Qué hace: modelo multimodal que describe imágenes, diagramas y tablas en texto para el pipeline RAG. Produce Model y se conecta a loader.multimodal.

{
  "type": "model.vision",
  "config": {
    "model": "anthropic:claude-opus-4-8",
    "apiKeyRef": "ANTHROPIC_API_KEY"
  }
}

Ficha: referencia/catalogo-nodos.md §model.vision.

5.2 Casos de uso en los templates

Template 04 (seguros): fotos de daño en vehículo → descripción → logic.rules + logic.structured con cláusula citada.

Template 08 (manufactura): diagramas hidráulicos del AMM → texto describiendo componentes → chunks indexables con sectionScheme: ATA.

PDF del AMM
    │
    ▼
loader.multimodal (extractTables: true, describeImages: true)
    │                    ▲
    │                    │ Model
    └──── model.vision ──┘
    │
    ▼
"Figura 32-11-05: actuador de retracción MLG con puntos de inspección A, B, C..."
    │
    ▼
ingest.chunker → store.pgvector → retrieval con hardFilters (aircraft_type, ata_chapter)

5.3 Tablas escaneadas → JSON

loader.multimodal con extractTables: true convierte tablas de tolerancias, deducibles o torque en JSON estructurado — no solo texto plano. Eso permite logic.rules deterministas sobre valores numéricos (M5).

5.4 Cuándo NO usar visión

  • PDF con texto seleccionable (usa loader.pdf directo).
  • Imágenes decorativas sin información técnica.
  • Cuando el costo por documento (cientos de páginas con figuras) excede el presupuesto — considera OCR + layout parser (Unstructured) primero.

5.5 Modelos de visión que compiten

Modelo Fortaleza Debilidad
GPT-4o Excelente en diagramas + JSON Costo API
Claude Opus/GPT-4o Razonamiento sobre figuras técnicas Latencia
Gemini Pro Vision Ventana larga, video Ecosistema Google
LLaVA / Qwen-VL Local, open-weights Calidad inferior en tablas densas
IBM Granite Vision Enterprise, watsonx Curva de integración
Pixtral (Mistral) Buen balance EU Menor ecosistema

6. Generación de imagen y audio (salida multimodal)

Hasta ahora cubrimos entrada multimodal (audio→texto, imagen→texto). La generación convierte texto en imagen o audio — útil para respuestas habladas, ilustraciones o IVR.

6.1 Generación de imagen

Modelo Tipo Cuándo usar
DALL·E 3 API OpenAI Ilustraciones, mockups, diagramas conceptuales
Stable Diffusion XL Local/open Alto volumen, control con LoRA, sin API
Sora Video (OpenAI) Clips cortos, prototipos — costo y latencia altos
Midjourney API/discord Calidad artística

En RAG de producción, la generación de imagen es rara — el valor está en recuperar y citar documentos reales. Se usa más en asistentes creativos o para generar diagramas explicativos ad hoc.

6.2 TTS — Text-to-Speech

Convierte la respuesta del LLM en audio sintético:

# Conceptual
client.audio.speech.create(model="tts-1", voice="nova", input=respuesta)

Cuándo usar TTS:

  • Bot de voz bidireccional: STT (entrada) + LLM + TTS (salida).
  • IVR que lee políticas simplificadas.
  • Accesibilidad.

Alternativas: ElevenLabs (voces naturales), Amazon Polly, Google Cloud TTS, Coqui TTS (local).

6.3 Pipeline de voz completo

Usuario habla ──▶ io.stt ──▶ texto ──▶ RAG/agente ──▶ respuesta texto ──▶ TTS ──▶ audio

El template 07 implementa solo la mitad izquierda (STT → copilot para agente humano). Un bot de voz cerrado añadiría TTS al final.


7. Embeddings multimodales y vector DB multimodal

7.1 El problema

Los embeddings de texto (M3) no representan imágenes directamente. Embeddings multimodales proyectan texto e imagen en el mismo espacio vectorial (o espacios alineados):

  • CLIP (OpenAI): imagen ↔ texto
  • ImageBind (Meta): imagen, audio, texto, depth…
  • Cohere embed-multilingual-v3: texto + imagen en API

7.2 Casos de uso

Caso Cómo funciona
Búsqueda "fotos similares a esta descripción" Embed texto query + comparar con embeddings de imágenes
Búsqueda cross-modal "Encontrar diagramas relacionados con esta foto de daño"
Indexación en Weaviate/Milvus multimodal Módulos nativos para imagen + texto

7.3 ¿Cuándo usar embeddings multimodales vs visión→texto?

Enfoque Ventaja Desventaja
Visión → texto → embed texto (patrón RAGorbit) Compatible con cualquier vector store; citas sobre texto Pierde detalle fino; costo de visión en ingesta
Embed imagen directamente Búsqueda por similitud visual Menos interpretable; difícil citar; stores especializados

Para dominios regulados (MRO, seguros, salud), visión→texto→RAG con citas es el patrón preferido porque puedes auditar el texto indexado.

7.4 Vector DB con soporte multimodal

Weaviate y Milvus ofrecen módulos para imagen+texto. Chroma y pgvector en RAGorbit indexan principalmente texto — la vía natural es describir imágenes en ingesta (loader.multimodal).


8. Comparativa de tecnologías — decisión rápida

8.1 STT

Proveedor Latencia Streaming Privacidad Mejor para
Whisper local Alta No ✅ Total Batch, offline, lab
Deepgram Muy baja API Call center (07)
OpenAI Whisper API Media No API Prototipos rápidos
Amazon Transcribe Media AWS Stack AWS

8.2 Visión

Proveedor Calidad técnica Local Mejor para
GPT-4o Alta No Diagramas, JSON, seguros
Claude Opus Alta No Manuales técnicos (08)
LLaVA-1.6 Media Prototipos sin API
watsonx Granite Vision Alta Híbrido Enterprise IBM

8.3 Plataformas open (HF / Ollama / watsonx)

  • Hugging Face: descarga Whisper, LLaVA, CLIP, SDXL; transformers + pipeline.
  • Ollama: ollama run llava para visión local; Whisper vía bindings.
  • watsonx / Granite: modelos enterprise con gobernanza; visión + LLM en mismo contrato.

Ver tabla ampliada: referencia/tecnologias-comparadas.md.


9. Conexión con los templates de industria

Template 07 · Telecom Copilot — STT en streaming

Audio llamada → io.stt (Deepgram) → model.intent → query.rewrite → retrieval.router → io.panel

Template 04 · Seguros — visión en siniestros

io.batch → loader.multimodal (fotos + póliza) → model.vision → logic.rules → logic.structured

Template 08 · Manufactura MRO — AMM multimodal

model.vision → loader.multimodal (sectionScheme: ATA) → retrieval.vector (hardFilters) → hitl.escalate

10. La capa ③ explicada: Whisper, visión y generación desde cero

Prerrequisito: haber implementado la capa ② del taller (lab/solucion_scratch.py) o entender cada pieza mock. Lee esta sección completa antes de escribir lab/solucion_framework.py.

Entorno: en la máquina de estudio no hay pip ni red. El objetivo es que, cuando tengas pip install openai-whisper openai langchain langchain-openai y claves API, puedas escribir la solución framework tú mismo.

10.1 Tabla puente: mocks del scratch → APIs reales

Lo que simulaste con mocks (capa ②) API real (capa ③) Dónde en el lab
Leer transcript fijo de audio_notificacion.json whisper.load_model("base").transcribe(audio_path) transcribe_with_whisper()
Leer description fija de foto_fuga.json client.chat.completions.create con imagen base64 (GPT-4o) describe_image_with_vision()
BoW + coseno sobre amm_chunks.json FAISS + OpenAIEmbeddings + retriever con filtro build_retriever()
Plantilla determinista generate_answer() ChatOpenAI + prompt con citas obligatorias generate_with_llm()
— (no implementado en scratch) client.audio.speech.create (TTS) synthesize_tts()
— (no implementado en scratch) client.images.generate (DALL·E 3) generate_image_dalle()

Modelo mental: en scratch los JSON mock son la salida de STT/visión. En producción reemplazas solo esas dos funciones; retrieval y generación pueden migrar gradualmente (primero embeddings reales, luego LLM real).

10.2 Whisper desde cero — transcribe_with_whisper

Instalación y carga de modelo

pip install openai-whisper
# Requiere ffmpeg en el sistema: brew install ffmpeg (macOS)
import whisper

model = whisper.load_model("base")  # tiny|base|small|medium|large
result = model.transcribe(
    "nota_tecnico.wav",
    language="es",       # hint de idioma mejora precisión
    fp16=False,            # obligatorio en CPU/MPS
)
text = result["text"]
segments = result["segments"]  # [{start, end, text}, ...]

Qué hace internamente (simplificado):

  1. Convierte audio a espectrograma (mel bins).
  2. Encoder Transformer procesa el audio.
  3. Decoder genera tokens de texto autoregresivamente.
  4. Devuelve texto + timestamps por segmento.

Elegir tamaño de modelo

Modelo Params VRAM ~ Calidad ES Velocidad CPU
tiny 39M 1 GB Básica Rápida
base 74M 1 GB Buena Media
small 244M 2 GB Muy buena Lenta
medium 769M 5 GB Excelente Muy lenta

Para notas de técnicos en rampa (ruido, jerga), small es el mínimo recomendable en producción batch.

Gotchas Whisper

  1. No confundir con io.stt streaming — Whisper procesa archivos; para call center usa Deepgram.
  2. language="es" reduce errores vs detección automática en audio corto.
  3. Alucinaciones en silencio — Whisper puede inventar texto en audio vacío; valida no_speech_prob en segmentos.
  4. Primera ejecución descarga el modelo (~150 MB para base) — planifica caché en Docker.

10.3 Visión desde cero — describe_image_with_vision

Patrón OpenAI GPT-4o (el que usa solucion_framework.py)

import base64
from openai import OpenAI

client = OpenAI()

with open("foto_fuga.jpg", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Describe daño aeronáutico. Responde JSON."},
            {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
        ],
    }],
    response_format={"type": "json_object"},
    temperature=0.0,
)
vision = json.loads(response.choices[0].message.content)

Patrón LangChain (equivalente)

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

llm = ChatOpenAI(model="gpt-4o", temperature=0)
msg = HumanMessage(content=[
    {"type": "text", "text": "Describe..."},
    {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
])
result = llm.invoke([msg])

Patrón local — LLaVA via Ollama

ollama pull llava
ollama run llava "Describe esta imagen de tren de aterrizaje" --image foto.jpg

Gotchas visión

  1. Resolución: redimensiona a max 2048px — imágenes enormes aumentan costo sin ganar detalle.
  2. JSON mode (response_format) reduce parsing errors en metadata ATA.
  3. Alucinación de daños — en seguros/MRO, combina con logic.citations sobre documentos, no solo la descripción visual.
  4. PII — fotos pueden tener matrículas, rostros; considera blur previo.

10.4 Retrieval y generación — LangChain sobre chunks del AMM

El scratch usa BoW; en framework:

from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

docs = [Document(page_content=c["text"], metadata=c["metadata"]) for c in chunks]
store = FAISS.from_documents(docs, OpenAIEmbeddings(model="text-embedding-3-small"))
retriever = store.as_retriever(search_kwargs={"k": 2, "filter": {"aircraft_type": "A320"}})

La generación con citas:

from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
# Prompt: "Responde SOLO con evidencia del AMM. Incluye citations: [...]"

10.5 TTS y DALL·E — generación de salida

TTS (OpenAI)

response = client.audio.speech.create(
    model="tts-1",       # o tts-1-hd para mayor calidad
    voice="nova",        # alloy, echo, fable, onyx, nova, shimmer
    input="Procedimiento AMM 32-11-00: inspeccionar actuador...",
)
response.stream_to_file("respuesta.mp3")

Cuándo usar / NO:

  • ✅ Bot de voz, IVR, accesibilidad.
  • ❌ Copilot para agente humano (template 07) — el agente lee el panel, no necesita audio.

DALL·E 3

result = client.images.generate(
    model="dall-e-3",
    prompt="Diagrama técnico esquemático de actuador MLG A320, estilo manual mantenimiento",
    size="1024x1024",
    quality="standard",
)
url = result.data[0].url

Cuándo usar / NO:

  • ✅ Material de formación, ilustración conceptual.
  • ❌ Evidencia regulatoria — nunca sustituye foto real ni documento AMM citado.

10.6 Recorrido bloque a bloque de lab/solucion_framework.py

Abre lab/solucion_framework.py y sigue este mapa:

Bloque 1 — Carga de chunks (líneas ~20–30)

Idéntico al scratch: amm_chunks.json alimenta el retriever. Sin sorpresas.

Bloque 2 — transcribe_with_whisper (líneas ~35–60)

Puente scratch: transcribe_audio() lee JSON → aquí whisper.transcribe() lee .wav.

Detalle: fp16=False en CPU; language="es" para notas en español.

Bloque 3 — describe_image_with_vision (líneas ~65–110)

Puente scratch: describe_image() lee JSON → aquí GPT-4o sobre base64.

Detalle: response_format={"type": "json_object"} para metadata ATA estructurada.

Bloque 4 — build_retriever + retrieve_with_langchain (líneas ~115–155)

Puente scratch: retrieve_chunks() BoW → aquí FAISS con embeddings OpenAI.

Detalle: el filtro {"aircraft_type": "A320", "ata_chapter": "32"} replica los hard-filters del scratch.

Bloque 5 — generate_with_llm (líneas ~160–195)

Puente scratch: generate_answer() plantilla → aquí ChatOpenAI con prompt de citas.

Bloque 6 — TTS y DALL·E (líneas ~200–240)

No tienen equivalente en scratch — son salida multimodal opcional.

Bloque 7 — run_multimodal_pipeline_real (líneas ~245–290)

Orquesta todo con use_mocks_if_missing=True: si no hay .wav/.jpg, cae a JSON del lab (útil para probar sin grabar audio real).

10.7 Cuándo usar cada tecnología — gotchas finales

Situación Usa Evita
Call center tiempo real Deepgram (io.stt) Whisper batch
Notas de voz en batch nocturno Whisper local Deepgram (costo/min)
Fotos de siniestros con JSON GPT-4o + response_format LLaVA si necesitas precisión en daños
Manuales técnicos 1000+ páginas loader.multimodal en ingesta Visión por cada consulta
Bot hablado completo STT + LLM + TTS Solo texto si el canal es chat
Evidencia regulatoria Visión→texto→RAG con citas DALL·E como "prueba"

Gotchas de producción:

  1. Fusionar señales antes de recuperar — no hagas dos RAG separados (voz vs imagen); une transcript + descripción en una query.
  2. Cache de descripciones de imagen — la misma foto no debe pasar por visión dos veces; guarda hash→descripción.
  3. Timeouts en cadena — STT + visión + RAG puede exceder 10 s; paraleliza STT y visión si son independientes.
  4. Formatos de audio — normaliza a 16 kHz mono WAV antes de Whisper; evita codecs exóticos.
  5. Costo de visión en ingesta batch — template 04 con 500 fotos/día × $0.02 ≈ $10/día solo en visión.

10.8 Checklist antes de escribir tu solucion_framework.py

  • ¿transcribe_with_whisper usa language="es" y fp16=False en CPU?
  • ¿describe_image_with_vision pide JSON con aircraft_type, ata_chapter, severity_hint?
  • ¿El retriever FAISS aplica filtro aircraft_type + ata_chapter?
  • ¿El prompt de generación exige citations no vacío o "no determinable"?
  • ¿run_multimodal_pipeline_real degrada a mocks si faltan archivos binarios?
  • (Opcional) ¿synthesize_tts genera audio de la respuesta?
  • (Opcional) ¿Entiendes por qué DALL·E no sustituye evidencia fotográfica real?

Siguiente paso: abre lab/enunciado.md (Parte B) e intenta escribir el archivo tú mismo antes de mirar solucion_framework.py.


11. Nodos RAGorbit de este módulo

io.stt

Puerto de entrada:  Audio (stream)
Puerto de salida:   Message (fragmentos de texto)

Config: provider: deepgram, language: es. Ver template 07.

model.vision

Puerto de salida: Model → (conecta a loader.multimodal o lógica downstream)

Config: model: anthropic:claude-opus-4-8 o openai:gpt-4o.

loader.multimodal

Puertos: Documents → Documents (enriquecidos)
Entrada Model: model.vision (opcional)

Config: extractTables: true, describeImages: true, sectionScheme: ATA.


12. Checkpoint — Lo sabes si puedes…

  • Explicar por qué RAGorbit convierte audio/imagen a texto antes de indexar (en el patrón dominante).
  • Diferenciar STT streaming (io.stt/Deepgram) vs STT batch (Whisper local).
  • Describir qué aporta model.vision en los templates 04 y 08.
  • Justificar cuándo usar visión→texto vs embeddings multimodales directos.
  • Nombrar las alternativas a Whisper, GPT-4o y DALL·E con un trade-off cada una.
  • Implementar un pipeline scratch que fusione transcript mock + descripción mock y recupere con filtros duros.
  • Mapear cada función mock del scratch a su API real (tabla §10.1).
  • Explicar por qué citations sigue siendo obligatorio aunque la entrada sea multimodal.
  • Leer el flow.json del template 07 e identificar dónde entra io.stt.
  • Leer el flow.json del template 08 e identificar model.vision + loader.multimodal.

Si no puedes: repasa §3–§5 (STT y visión), §10 (framework) y el lab/enunciado.md. Compara los tres templates ancla en examples/.