// Proyecto · RAG Agéntico · Qdrant · LangGraph
Repo Expert responde preguntas sobre cualquier repositorio GitHub con citas inline que apuntan al documento, fragmento y fuente exacta. Tres fuentes de conocimiento heterogéneas, agente con lógica de fallback y dos instancias configurables desde config. Evaluado con métricas reales, no prometidas: 100% routing y 100% de relevancia hit@6 en la instancia que puedes probar en el chat.
// Evaluación del sistema
El problema
Fuentes heterogéneas sin integrar. Un repo tiene documentación, código fuente e issues activos. Un sistema RAG que indexa solo una de ellas responde peor que un developer con grep bien entrenado.
Sin routing de fuentes. Preguntar "¿dónde está el bug del issue #42?" no es lo mismo que "¿qué hace la función parse_config?". Sin routing inteligente, el sistema mezcla fuentes irrelevantes con las pertinentes.
Respuestas sin trazabilidad. Un LLM que responde sin citar fuentes da la misma confianza que uno que alucina. Si no puedes verificar la respuesta, no sirve para trabajo real.
Instancias hardcodeadas. La mayoría de sistemas RAG son one-repo. Cambiar de repositorio implica reescribir código. Un sistema configurable desde config escala sin rozamiento.
PASO 01
Docs, código fuente e issues (instancia pública) o Career KB (instancia portfolio) se indexan en Qdrant Cloud por separado. El markdown se parte por secciones — con el encabezado repetido en cada trozo — y nunca por encima de la ventana del modelo de embeddings, que trunca en silencio.
PASO 02
El agente LangGraph decide qué fuentes consultar. Los huecos se reparten entre colecciones según lo bien que encaja cada una con la pregunta, no a partes iguales: el RRF clásico ordena dentro de cada colección y acababa dando una cuota fija a cada una.
PASO 03
El LLM (gpt-5-mini) genera la respuesta citando cada afirmación con el fragmento, documento y posición exacta de origen. Las respuestas son verificables, no solo plausibles.
PASO 04
Primero se redacta la respuesta; después un juez LLM la contrasta con las fuentes. Si no se sostiene y quedan fuentes por consultar, el agente amplía la búsqueda. Si no queda nada que ampliar, la respuesta se muestra con la marca «respuesta no verificada». En el chat de esta web (una sola fuente) nunca reintenta: medido, los reintentos repetían la misma búsqueda — 5 disparados, ninguno arregló nada y cada uno costaba 10–20 s.
Qdrant para vector search, LangGraph para la lógica agéntica, FastAPI como backend. Desplegado en Azure Container Apps con escalado a cero: coste ~$0–1/mes. La respuesta llega en streaming (server-sent events) y es idéntica a la no emitida: mismo modelo, mismos tokens; solo cambia cómo se entrega.
Tecnologías
El agente no solo recupera y genera — decide, se verifica y reconoce sus límites. Solo amplía la búsqueda cuando hay algún sitio nuevo donde mirar; si no lo hay, marca la respuesta como no verificada en vez de disimularlo. Lógica de autoevaluación que distingue "no hay suficiente información" de "no sé contestar".
Docs, código e issues indexados por separado con estrategias propias. RRF combina los rankings en inferencia sin perder especificidad de cada fuente.
Instancia portfolio — la que responde en el chat — evaluada en 2026-09 sobre gpt-5-mini: 100% de acierto en routing, 100% de relevancia hit@6, y 100% de respuestas que un juez LLM independiente da por totalmente fundamentadas. Los fallos restantes son de recuperación en entradas largas, no invenciones: el agente responde “no lo sé” antes que rellenar huecos.
Una instancia pública (repo open-source + issues GitHub) y una portfolio (Career KB). El mismo código, dos contextos completamente diferentes, sin tocar lógica del agente.
Lo que responde en la portada es la instancia portfolio de este proyecto.
Qué sabe. 2.828 fragmentos indexados en Qdrant: una base de conocimiento sobre mi carrera, más la documentación y el código fuente de diez repositorios públicos. El décimo es el propio Repo Expert, para que el chat pueda explicar cómo funciona. Nada más.
Qué hace con tu pregunta. Un grafo de LangGraph enruta la pregunta a las fuentes adecuadas, fusiona los resultados por rango (RRF), redacta con citas y después verifica su propia respuesta contra las fuentes. Si no se sostiene, lo marca como «respuesta no verificada» en vez de disimularlo.
Qué verás. La respuesta se escribe en tiempo real: las primeras palabras aparecen a los ~10 s, en vez de la respuesta entera a los ~16 s. La línea de estado dice qué está haciendo el agente (buscando → redactando → comprobando), y el resultado de la verificación aparece como una marca justo después del texto. Los marcadores [1], [2] dentro de la respuesta son enlaces al fragmento exacto en GitHub. Puedes comprobar cada afirmación una por una.
Hay una barrera de alcance configurada a propósito: las preguntas fuera de tema se rechazan. No es un asistente general, y no intenta disimularlo.
Puedes preguntar «explícame más del primero» o «¿y eso cuándo fue?»: el chat tiene en cuenta los turnos anteriores. El servidor no guarda nada — es tu navegador el que reenvía la conversación en cada pregunta, así que al recargar la página se empieza de cero.
Medido, no prometido, sobre un conjunto curado de preguntas. No es infalible: si la pregunta se aleja de lo que hay indexado, dice «no lo sé» en vez de rellenar el hueco.
Y ~16 s hasta la respuesta completa. Es el modelo pensando, no el servidor: la búsqueda en Qdrant tarda ~0.5 s. Además, el backend escala a cero cuando nadie lo usa, para que cueste ~$0/mes: la primera pregunta tras un rato de inactividad suma unos segundos en despertar el contenedor.
Cada respuesta cuesta dinero real en tokens, así que hay un límite por IP. Si lo alcanzas, el chat te dirá cuándo puedes volver.
El índice es una foto, no un espejo: se actualiza a mano, no en tiempo real. Si cambio un repositorio o empiezo algo nuevo, el chat no lo sabrá hasta la siguiente indexación.
Lo que se rompió — y cómo lo arreglé
Seis fallos reales en producción, con el diagnóstico que los aisló y el arreglo que los cerró. Puedes comprobar el resultado tú mismo en el chat.
El modelo de embeddings era solo inglés.
Síntoma: "¿Qué proyectos ha construido Jorge?" devolvía plantillas de prompts de otros repos.
Diagnóstico: mismo índice, misma fusión, cambiando solo el idioma de la pregunta — en inglés devolvía 6/6 fragmentos de carrera correctos; en español, 6/6 irrelevantes. El corpus está en inglés y el chat se usa en español.
Arreglo: cambio a multilingual-e5-small, multilingüe y de la misma dimensión (384), con los prefijos query:/passage: que ese modelo espera. Solo hubo que recalcular los vectores, no migrar el esquema. Recuperación de carrera: 0.6 → 1.0.
La fusión era una cuota disfrazada de ranking.
Síntoma: toda pregunta devolvía exactamente 2 fragmentos de cada colección.
Diagnóstico: RRF puntúa por el puesto dentro de cada colección, así que todos los primeros empataban y la mezcla era un round-robin. Una pregunta sobre la carrera gastaba cuatro de seis huecos en código de repositorios.
Arreglo: reparto proporcional de huecos según lo bien que encaja cada colección, medido contra el rango de puntuaciones de esa consulta — necesario porque las similitudes del nuevo modelo se agrupan en una banda estrecha (~0.78–0.88) donde los cocientes en bruto no dicen nada. Una colección sin nada relevante ahora no ocupa ningún hueco.
El 55% de los fragmentos de carrera se truncaban sin avisar.
Síntoma: datos que existían en el documento no aparecían nunca.
Diagnóstico: 12 de 22 secciones superaban la ventana del modelo (la mayor, ~4×), y el truncado es silencioso: se indexaba solo hasta el corte.
Arreglo: las secciones largas se parten por frases — nunca a mitad de una — repitiendo el encabezado en cada trozo para que el embedding siga en tema y la cita conserve contexto. 22 → 56 fragmentos, el mayor de 4.952 a 899 caracteres, ninguno por encima del límite.
El 40% del corpus era ruido interno.
Síntoma: una pregunta sobre proyectos de ML respondía citando una oferta de empleo ficticia.
Diagnóstico: 1.003 fragmentos de ficheros de tareas y 161 de plantillas de prompts de otros repos, que competían con la documentación real.
Arreglo: excluidos del índice. El patrón de exclusión además no funcionaba en la raíz del repositorio — fnmatch deja que * cruce /, así que **/tasks/** exigía una barra antes de tasks — y se corrigió en el comparador, con tests. Docs: 2.885 → 1.691 fragmentos.
Las respuestas tardaban 16–27 segundos.
Diagnóstico: ~95% del tiempo era el modelo (gpt-5-mini) razonando, no la infraestructura: Qdrant responde en ~0.5 s.
Método: antes de cambiar nada, ~150 ejecuciones de prueba — incluidas 24 preguntas adversariales (premisas falsas, inyección de prompt, negación, «enumera todos») —, cada respuesta calificada por un árbitro con más esfuerzo de razonamiento.
Se quedó: un juez de verificación más rápido con la misma precisión (~2 s menos), y quitar los reintentos que nunca ayudaban.
Se descartó: generar la respuesta más rápido — fallaba en «enumera todos», negaciones y premisas falsas —, y una reescritura de la consulta más rápida, que bajaba la calidad de las respuestas de 9.0 a 7.9.
Resultado: la respuesta llega en streaming y las primeras palabras aparecen a los ~10 s. La evaluación tras el despliegue no cambió: 100% routing, 100% hit@6, fidelidad 1.0.
Y una trampa de medición, que casi me hace "arreglar" lo que no estaba roto.
Síntoma: tras ampliar el contexto del generador, la fidelidad cayó de 0.7 a 0.1.
Diagnóstico: el juez recupera su propia evidencia y seguía haciéndolo con 5 fragmentos recortados a 700 caracteres, mientras el generador ya usaba 12 de 2.400. Marcaba como no fundamentadas respuestas correctas porque el texto que las sostenía estaba fuera de su vista.
Arreglo: el juez ve ahora la misma amplitud que el generador. Documentado como arreglo de medición y no de calidad, para que nadie lea esa subida como una mejora del sistema.
¿Prefieres comprobarlo tú mismo?
Habla con mis repos →Relevante si...
Menos relevante si...
Proyectos relacionados
Multi-asistente con un índice por asistente, búsqueda híbrida y fallback honesto. 56 tests, construida en 7 días.
Ver caso completo → Claude Code · MCP · ASTLos seis puntos de extensión de Claude Code aplicados a un monorepo real, con suite de validación.
Ver caso completo →Contacto
30 minutos. Sin compromiso. Si veo que puedo ayudarte, te lo digo. Si no, también.
Agendar videollamada →