Base de conocimiento astrológico personal construida desde canales de YouTube en castellano, con búsqueda semántica RAG y consultas en lenguaje natural vía LLM local.
AstroExtracto es una herramienta 100% local que permite construir una base de conocimiento astrológico personal a partir de transcripciones de canales de YouTube seleccionados por el usuario.
Dado un corpus de decenas o cientos de horas de contenido, permite consultar ese conocimiento acumulado por significado semántico, no por palabras clave exactas. Una pregunta como "¿qué dice Isabel Pareja sobre Júpiter en casa 2?" localiza los fragmentos exactos aunque el astrólogo no haya usado esas palabras literales.
| Característica | Detalle |
|---|---|
| 📥 Descarga masiva | yt-dlp descarga subtítulos sin bajar el vídeo |
| 📄 Soporte VTT y SRT | Sin dependencia de ffmpeg |
| 🗄️ Base vectorial local | ChromaDB persistente, sin datos en la nube |
| 🔍 Búsqueda semántica | multilingual-e5-large (1024 dims, estado del arte multilingüe) |
| 🖥️ Interfaz visual | Gradio en http://localhost:7860 |
| 🤖 RAG con LLM local | Pipeline completo con Qwen via LM Studio |
| 🔬 Startup self-check | Valida BD + modelo + retrieval real al arrancar |
| 📊 SQLite metadatos | Trazabilidad completa: canal, vídeo, URL, fecha |
YouTube Channels (URLs seleccionadas por el usuario)
│
▼
┌─────────────┐
│ yt-dlp │ ← descarga subtítulos (.vtt/.srt) + metadatos (.json)
└──────┬──────┘
│
▼
┌─────────────┐
│ Limpieza │ ← elimina timestamps, tags HTML, ruido VTT/SRT
└──────┬──────┘
│
┌─────┴──────┐
│ │
▼ ▼
┌─────────┐ ┌──────────────────────────────────────────┐
│ SQLite │ │ ChromaDB │
│ videos │ │ colección: astro_corpus │
│ metad. │ │ hnsw:space = cosine │
└─────────┘ │ chunks ~500 chars, overlap 50 │
│ embeddings: e5-large 1024 dims │
│ prefijo indexado: "passage: título+txt" │
└──────────────┬───────────────────────────┘
│
┌─────────────▼──────────────┐
│ Consulta del usuario │
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ multilingual-e5-large │
│ normalizar_query() │ ← prefijo "query: " + unidecode
│ normalize_embeddings=True │ ← OBLIGATORIO para e5
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ ChromaDB query │
│ top_k + 4 candidatos │
│ filtro chunks < 150 chars │
└─────────────┬──────────────┘
│
┌─────────────▼──────────────┐
│ LM Studio — Qwen │
│ RAG: contexto + pregunta │ ← 127.0.0.1:1234
│ → respuesta con fuentes │
└────────────────────────────┘
| Capa | Tecnología | Notas |
|---|---|---|
| Descarga | yt-dlp |
Sin API key, activamente mantenido |
| Interfaz | Gradio |
Web local, streaming de logs |
| Embeddings | intfloat/multilingual-e5-large |
1024 dims, superior a MiniLM para castellano |
| Vector DB | ChromaDB |
Persistente, local, espacio coseno |
| Metadatos | SQLite |
Sin servidor, portable |
| LLM (RAG) | Qwen3-coder-30b via LM Studio |
100% local, sin API keys |
- Python 3.9–3.12 (no 3.14 — sin wheels CUDA para PyTorch)
- ~3 GB disco para el modelo e5-large
- LM Studio corriendo en
http://127.0.0.1:1234con modelo Qwen cargado
git clone https://github.com/eduardoddddddd/AstroExtracto.git
cd AstroExtracto
pip install -r requirements.txt
python astro_corpus_ui.pyLa interfaz se abre en http://localhost:7860. Antes de abrir el navegador verás el self-check en consola (ver sección más abajo).
yt-dlp>=2024.1.0
chromadb>=0.4.0
sentence-transformers>=2.2.0
gradio>=4.0.0
unidecode>=1.3.0
Al arrancar, el script valida automáticamente el entorno antes de abrir la interfaz:
════════════════════════════════════════════════════════════
ASTRO CORPUS v4 — SELF-CHECK DE INICIO
════════════════════════════════════════════════════════════
[OK] ChromaDB: 10363 chunks | hnsw:space=cosine
[OK] Modelo: intfloat/multilingual-e5-large | dim=1024
[OK] Retrieval test: top-3 resultados:
1. Abundancia emocional y material: Júpiter en tu Casa 2
2. Los planetas en casa 2: recursos y valores
3. Conjunción Júpiter-Venus: expansión material
════════════════════════════════════════════════════════════
TODO OK — arrancando interfaz en http://localhost:7860
════════════════════════════════════════════════════════════
Si algo falla (dimensión incorrecta, colección inexistente, retrieval erróneo), el proceso aborta con un mensaje claro antes de mostrar la interfaz.
Los tres checks son:
- ChromaDB — verifica chunks, colección y espacio de distancia
- Modelo — carga e5-large y verifica que produce vectores de 1024 dims
- Retrieval real — hace una query de prueba y muestra los top-3 títulos
Pestaña 📡 Ingestar canales → introduce URLs → ▶ Descargar e indexar
Formatos válidos:
https://www.youtube.com/@NombreCanal/videos
https://www.youtube.com/playlist?list=PLxxxx
https://www.youtube.com/watch?v=VIDEO_ID
Si ya tienes los VTTs descargados: botón 🔄 Reindexar lo ya descargado.
Pestaña 🔍 Consultar — retrieval puro sin LLM. Ideal para verificar que los chunks correctos se recuperan antes de pasar al RAG.
Ejemplos:
Jupiter casa 2 abundancia dinero
Saturno Neptuno conjuncion 2026
Luna casa 12 emociones ocultas
nodo norte mision de vida
💡 La búsqueda es semántica. "restricción afectiva Saturno-Luna" encuentra fragmentos sobre "el bloqueo emocional de Saturno en aspecto a la Luna".
Pestaña 💬 Preguntar a Qwen — recupera fragmentos + genera respuesta sintetizada.
Requiere LM Studio activo en http://127.0.0.1:1234 con modelo qwen3-coder-30b-a3b-instruct.
Pestaña 📊 Estado — muestra total de vídeos, chunks y desglose por canal.
Variables en la cabecera de astro_corpus_ui.py:
DIRECTORIO = "C:/Users/Edu/Downloads/corpus_astro"
DB_PATH = "C:/Users/Edu/Downloads/astro_knowledge.db"
CHROMA_PATH = "C:/Users/Edu/Downloads/chroma_db"
MODELO_NAME = "intfloat/multilingual-e5-large"
CHUNK_SIZE = 500 # caracteres por chunk
CHUNK_OVERLAP= 50 # solapamiento
LM_STUDIO = "http://127.0.0.1:1234/v1/chat/completions"
LM_MODEL = "qwen3-coder-30b-a3b-instruct"
RAG_TOP_K = 8 # fragmentos a recuperar
PORT = 7860 # fijo, sin acumulación de puertosObjetivo: MVP funcional: descargar VTTs e indexarlos.
Lo que se hizo:
- Descarga masiva con
yt-dlp(subtítulos + metadatos JSON) - Limpieza básica de archivos VTT (timestamps, tags HTML)
- Indexación en ChromaDB con
paraphrase-multilingual-MiniLM-L12-v2(384 dims) - Interfaz Gradio con 3 pestañas: Ingestar, Consultar, Estado
- SQLite para metadatos de vídeos
Problemas detectados:
- El texto de los VTTs llegaba triplicado — la misma frase aparecía repetida 3 veces consecutivas porque los subtítulos automáticos de YouTube solapan frases para animar el texto
- 46.289 chunks indexados, pero más de 2/3 eran duplicados ruidosos
- Sin normalización de texto: tildes, mayúsculas y palabras de relleno interferían con la relevancia
Conclusión: La calidad del retrieval era baja porque el corpus estaba contaminado. Necesario limpiar antes de indexar.
Objetivo: Corpus limpio + mejor relevancia de búsqueda.
Lo que se hizo:
- Reescritura del parser VTT para eliminar duplicados: deduplica líneas consecutivas idénticas
- Normalización de queries:
unidecode(elimina tildes) + lowercase + filtro de palabras de relleno ("bueno", "pues", "entonces"...) - Reducción de chunk size a 120 palabras para mayor precisión
- Script independiente
reindex_v2.pypara reindexar desde cero
Resultado: 12.918 chunks limpios vs 46.289 ruidosos. El corpus bajó de tamaño pero ganó calidad.
Problemas restantes:
- MiniLM-L12-v2 (384 dims) tiene rendimiento mediocre en castellano técnico
- Las búsquedas encontraban fragmentos temáticamente relacionados pero no el vídeo específico correcto
- Ejemplo: query "Júpiter casa 2" devolvía vídeos sobre "conjunción Júpiter-Neptuno" en lugar del vídeo dedicado a Júpiter en Casa 2
Conclusión: El modelo era el cuello de botella. MiniLM es rápido pero no suficientemente preciso para terminología astrológica en castellano.
Objetivo: Mejor modelo de embeddings + pipeline RAG funcional con LLM local.
Lo que se hizo:
- Cambio de modelo a
intfloat/multilingual-e5-large(1024 dims, 560M parámetros) - Protocolo e5: prefijo
"passage: "al indexar,"query: "al buscar — obligatorio para este modelo normalize_embeddings=Trueen el retrieval (eval_e5.py) — obligatorio para e5- Reindexado completo: 10.363 chunks con el nuevo modelo
- Pipeline RAG completo: retrieval → prompt estructurado → Qwen via LM Studio → respuesta con fuentes
- Filtro de chunks cortos (< 150 chars) para eliminar solapamientos truncados
Problemas graves descubiertos en producción:
Bug 1 (crítico): consultar() llamaba encode() sin normalize_embeddings=True. El código correcto estaba en eval_e5.py y en rag_consultar(), pero se perdió en la función de búsqueda directa. Resultado: el retrieval de la pestaña "Consultar" producía vectores en un espacio diferente al de la BD → resultados incorrectos.
Bug 2 (crítico): get_col() usaba get_or_create_collection() en lugar de get_collection(). Esto significa que si ChromaDB recreaba el objeto colección en una nueva sesión, podía hacerlo sin el metadato hnsw:space: cosine, cayendo a distancia euclidiana y rompiendo la similitud semántica.
Bug 3 (menor): RAG_TOP_K = 6 — demasiado bajo. Con 6 fragmentos, la probabilidad de que el fragmento exacto entre los top-6 era insuficiente para queries específicas.
Bug 4 (no detectado hasta v4): _indexar_carpeta() llamaba modelo.encode(chunks) también sin normalize_embeddings=True. Los chunks indexados y los vectores de query estaban en espacios ligeramente distintos.
Diagnóstico de los bugs: Se detectaron ejecutando el check de diagnóstico directamente contra ChromaDB:
chromadb.errors.InvalidArgumentError:
Collection expecting embedding with dimension of 1024, got 384
Este error reveló que en algún punto del pipeline se estaba usando el modelo MiniLM (384 dims) en lugar de e5-large (1024 dims) — probablemente por un objeto _modelo cacheado de una sesión anterior.
Conclusión: Los fixes parciales aplicados en v3 no resolvieron el problema porque el bug de _indexar_carpeta seguía contaminando los índices. La solución real requería un self-check integrado que verificara el pipeline completo en cada inicio.
Objetivo: Eliminar todos los bugs de embeddings y hacer el sistema autodiagnósticable.
Lo que se hizo:
-
startup_check()integrado enmain— ejecuta tres validaciones antes de abrir la interfaz:- Conecta a ChromaDB y verifica chunks +
hnsw:space - Carga e5-large y verifica que produce vectores de exactamente 1024 dims
- Hace una query de prueba real y muestra los top-3 títulos recuperados
- Si cualquier check falla →
sys.exit(1)con mensaje claro
- Conecta a ChromaDB y verifica chunks +
-
normalize_embeddings=Trueen los 4 sitios del código:consultar()— búsqueda directa (era el bug principal no resuelto en v3)rag_consultar()— ya estaba correcto en v3, conservado_indexar_carpeta()— nuevo en v4, el bug más silenciosostartup_check()— en el test de retrieval
-
get_collection()en lugar deget_or_create_collection()— evita recrear la colección con metadatos incorrectos -
PORT = 7860fijo — elimina la acumulación en puertos 7861, 7862, 7863... causada por procesos anteriores que no se limpiaban -
Reescritura completa del archivo — elimina inconsistencias acumuladas entre parches
Verificación pre-deploy:
[OK] startup_check definida
[OK] normalize x4 (4 ocurrencias de normalize_embeddings=True)
[OK] normalize en indexar: encode(chunks, normalize_embeddings=True)
[OK] get_collection sin or_create
[OK] PORT=7860
[OK] startup_check en main
Cambios en la interfaz:
- Puerto fijo 7860 (antes acumulaba: 7860→7861→7862→7863)
- Slider RAG por defecto: 8 fragmentos (antes 6)
- Pestaña "Preguntar a Qwen" con slider visible de fragmentos
Lección aprendida: Los bugs de embeddings son especialmente difíciles porque no dan error en tiempo de ejecución — el sistema devuelve resultados, pero incorrectos. La única forma de detectarlos es comparar dimensiones explícitamente o hacer un test de retrieval con ground truth conocido. Por eso el self-check es parte permanente del código, no un script externo.
| Script | Propósito | Estado |
|---|---|---|
reindex_e5.py |
Reindexado completo con e5-large | ✅ Usar este |
check_chroma.py |
Diagnóstico rápido de la BD | ✅ |
check_chroma2.py |
Inspección de calidad con búsquedas de prueba | ✅ |
test_busqueda.py |
Validación post-reindexado | ✅ |
rag_query.py |
CLI standalone del pipeline RAG | ✅ |
reindex_limpio.py |
Reindexado v1 (obsoleto, MiniLM) | |
reindex_v2.py |
Reindexado v2 (obsoleto, MiniLM) | |
demo_embeddings.py |
Demo educativa similitud coseno | ℹ️ |
Si necesitas reindexar (cambio de modelo, corpus nuevo):
# 1. Hacer backup de la BD actual (opcional)
# 2. Eliminar la colección existente
python -c "import chromadb; chromadb.PersistentClient('C:/Users/Edu/Downloads/chroma_db').delete_collection('astro_corpus')"
# 3. Reindexar
python scripts/reindex_e5.py
⚠️ Tras reindexar con un modelo diferente, el startup_check verificará automáticamente que las dimensiones coinciden antes de arrancar la interfaz.
| Modelo | Dims | Parámetros | Castellano | Velocidad |
|---|---|---|---|---|
paraphrase-multilingual-MiniLM-L12-v2 |
384 | 118M | Aceptable | Rápido |
intfloat/multilingual-e5-large |
1024 | 560M | Excelente | Medio |
e5-large fue entrenado específicamente con pares (query, passage) en múltiples idiomas. Para terminología técnica como la astrológica en castellano, la diferencia de calidad es significativa.
El modelo e5 requiere prefijos específicos para distinguir el tipo de texto:
# Al INDEXAR cada chunk:
texto_a_indexar = "passage: " + titulo_normalizado + " " + texto_chunk
# Al BUSCAR:
query_a_buscar = "query: " + texto_normalizado_de_la_preguntaSin estos prefijos, el modelo no funciona correctamente aunque las dimensiones coincidan.
e5-large produce vectores que deben estar normalizados a longitud unitaria para que la similitud coseno funcione correctamente. Sin normalización, los vectores de diferentes longitudes producen rankings incorrectos.
# MAL — puede dar resultados incorrectos con e5
emb = modelo.encode([texto]).tolist()
# BIEN — obligatorio para e5-large
emb = modelo.encode([texto], normalize_embeddings=True).tolist()get_or_create_collection crea la colección si no existe. Si la crea en una sesión nueva, puede hacerlo sin el metadato {"hnsw:space": "cosine"} → la BD usa distancia euclidiana → el retrieval semántico falla silenciosamente.
get_collection lanza una excepción si la colección no existe, lo que es correcto — si no existe hay que reindexar explícitamente.
- Descarga masiva con yt-dlp
- Soporte VTT nativo
- ChromaDB + embeddings MiniLM
- Interfaz Gradio básica
- Deduplicación de texto VTT triplicado
- Normalización: unidecode + lowercase + filtro fillers
- Chunks más pequeños (120 palabras)
- Upgrade a multilingual-e5-large (1024 dims)
- Protocolo de prefijos e5
- Pipeline RAG completo con LM Studio / Qwen
- Filtro de chunks cortos
-
startup_check()integrado — valida BD + modelo + retrieval al inicio -
normalize_embeddings=Trueen los 4 puntos del pipeline -
get_collectionen lugar deget_or_create_collection - Puerto fijo 7860
- Reescritura limpia del archivo principal
- Cambio de modelo LLM: Qwen-coder → modelo de conversación general
- Reranking de fragmentos por relevancia (cross-encoder)
- Filtros en UI: por canal, por fecha de vídeo
- Deduplicación de fragmentos muy similares en el contexto RAG
- Whisper para vídeos sin subtítulos automáticos
- Resumen automático por vídeo al indexar
- Clustering semántico de temas
- Comparativa de perspectivas entre astrólogos sobre un mismo tema
- Exportación de fragmentos a Markdown/PDF
- Soporte podcasts (MP3/RSS)
AstroExtracto/
├── astro_corpus_ui.py ← aplicación principal (Gradio) — v4
├── requirements.txt ← dependencias Python
├── README.md ← este archivo
├── .gitignore ← excluye corpus, BD y modelos
│
├── scripts/
│ ├── README.md ← documentación del pipeline de scripts
│ ├── reindex_e5.py ← reindexado con e5-large ← USAR ESTE
│ ├── check_chroma.py ← diagnóstico rápido BD
│ ├── check_chroma2.py ← inspección de calidad
│ ├── test_busqueda.py ← validación post-reindexado
│ ├── rag_query.py ← CLI standalone RAG
│ ├── demo_embeddings.py ← demo educativa
│ ├── reindex_limpio.py ← v1 histórico (MiniLM)
│ └── reindex_v2.py ← v2 histórico (MiniLM)
│
├── corpus_astro/ ← subtítulos descargados (NO en git)
│ └── NombreCanal/
│ ├── fecha_id_titulo.es.vtt
│ └── fecha_id_titulo.info.json
│
├── astro_knowledge.db ← SQLite metadatos (NO en git)
└── chroma_db/ ← base vectorial e5-large 1024d (NO en git)
¿Por qué el retrieval devolvía resultados incorrectos si no había error?
Los bugs de embeddings son silenciosos: el sistema devuelve resultados, pero son los incorrectos. Sin error en consola, sin excepción. La única forma de detectarlos es verificar las dimensiones explícitamente y hacer un test de retrieval con ground truth conocido (un vídeo cuyo título conoces y puedes verificar). Por eso el self-check es parte permanente del código en v4.
¿Por qué los procesos se acumulaban en puertos 7861, 7862, 7863...?
Gradio, al encontrar el puerto 7860 ocupado por un proceso anterior no terminado correctamente, escala al siguiente disponible. Con PORT = 7860 y server_name="127.0.0.1" en app.launch(), el proceso falla si el puerto está ocupado en lugar de escalar silenciosamente. Solución: matar los procesos Python antes de relanzar.
¿Cuánto espacio ocupa todo?
| Elemento | Tamaño aproximado |
|---|---|
| Modelo e5-large | ~2.1 GB |
| 277 VTTs (corpus Isabel Pareja) | ~40 MB |
| ChromaDB (10.363 chunks, 1024 dims) | ~400 MB |
| SQLite metadatos | ~5 MB |
¿Funciona en macOS/Linux?
Sí. Cambiar las rutas absolutas de Windows en la cabecera de astro_corpus_ui.py a rutas Unix.
¿Puedo usar otro LLM además de Qwen?
Sí. Cambia LM_MODEL a cualquier modelo disponible en LM Studio. Para texto generativo (no código) se recomienda un modelo conversacional como Mistral-7B-Instruct o Llama-3-8B-Instruct.
- El proyecto descarga subtítulos generados automáticamente por YouTube
- Uso estrictamente personal — no redistribuir el corpus
- Respetar los términos de servicio de YouTube
- Si un creador solicita eliminación de su contenido de tu BD local, hazlo
- yt-dlp — descarga de subtítulos
- ChromaDB — base vectorial local
- sentence-transformers — embeddings
- intfloat/multilingual-e5-large — modelo de embeddings
- Gradio — interfaz web
- LM Studio — servidor LLM local
MIT License — úsalo, modifícalo, compártelo.
Proyecto desarrollado para investigación astrológica personal.