Sourcely

Asistente RAG para hacer preguntas a tus propios PDFs y audios, con respuestas citadas que enlazan a la página o al segundo exacto de la fuente.

Next.jsFastAPITypeScriptSupabase
Sourcely

El Desafío

Preguntar a un PDF con un LLM genérico es un ejercicio de fe: el modelo contesta con soltura pero las páginas que cita no existen. En un RAG casero se sumaban tres problemas reales:

  • Alucinación de citas. Los LLM inventan referencias. Sin un sistema que ate la cita al chunk recuperado, el usuario no puede verificar nada.
  • Fuga entre cuentas. La recuperación por similitud sin filtro de user_id hace que un prompt manipulado del usuario A devuelva los chunks del usuario B.
  • Multiformato real. PDFs y MP3s requieren pipelines distintos que deben terminar en la misma representación vectorial. Y la cita tiene que ser exacta: página N para PDF, segundo M:SS para audio.

La Solución

Citas que apuntan al byte exacto. El prompt le dice al LLM: “responde solo del contexto, cita como [1], [2]…”. Cada [n] corresponde a una tarjeta de fuente. Para PDFs, la tarjeta enlaza a la URL firmada con #page=N. Para audios, salta al currentTime del elemento. El usuario siempre puede hacer click y comprobar.

Aislamiento por usuario en la capa SQL. Cada consulta hace JOIN documents ON chunks.document_id = documents.id WHERE documents.user_id = current_user.id. Lo escribo en la query, no en el código, precisamente porque el código puede tener bugs. Como segunda capa: Row-Level Security en las cinco tablas.

Multiformato con un solo pipeline. PDFs (pypdf) y audio (faster-whisper con timestamps) pasan por el mismo chunking, las mismas embeddings Gemini a 768 dim, el mismo índice HNSW. La cita cambia el formato, no el pipeline.

Streaming para que se sienta en vivo. La respuesta viaja por Server-Sent Events, no por una respuesta completa. Es la diferencia entre “esperar 8 segundos” y “ver la respuesta construirse”.

Decisiones técnicas que defendí

  • 768 dimensiones, no 1536. Gemini text-embedding-001 permite dimensions arbitrario (truncamiento Matryoshka). La pérdida de calidad es <2% y los embeddings caben el doble en memoria.
  • HNSW, no IVFFlat. IVFFlat requiere reentrenar el índice periódicamente. HNSW es más lento de construir pero no necesita reentrenamiento.
  • JOIN explícito a documents, no filtrar por chunk.user_id. El JOIN garantiza el aislamiento aunque alguien olvide el filtro. Test dedicado: usuario A sube, usuario B pregunta, B recibe lista vacía.
  • Gemini en vez de OpenAI para embeddings. OpenAI cobra por token; Gemini tiene tier gratuito con 1500 req/día.
  • Groq en vez de OpenAI/Anthropic para el LLM. Tier gratuito con latencia <500ms. La calidad de llama-3.3-70b-versatile es comparable a GPT-4-mini para RAG con citas.

Lo que aprendí

  • El “impact” no está en el modelo, está en la tubería. Un LLM mejor no arregla citas falsas. La cita fiable viene de: prompt estricto + retrieval con JOIN explícito + UI que muestra el fragmento exacto.
  • Row-Level Security es barato y pocos lo hacen. Activarlo fue 30 líneas de SQL.
  • El cold start de Whisper pesa. La primera llamada descarga ~460 MB del modelo. En Render free son 30-60 segundos extra.
  • SQLite para tests, Postgres para prod. Los unit tests usan sqlite; los integration usan Supabase real y detectan drift.

Licencia y costos

  • Coste por uso: ~$0.0001 por consulta (embeddings Gemini + free-tier Groq).
  • Infraestructura: Supabase free + Render free + Vercel free. Coste total: $0/mes.
  • Repositorio: github.com/k1k3cb/Sourcely.
  • Licencia: MIT.

analyticsImpacto

73 + 9
Tests (unit + integration)
768d
Embeddings Gemini
HNSW
Búsqueda pgvector
RLS
Aislamiento por usuario

Highlights de la Interfaz

Hero de Sourcely

Hero de Sourcely

La propuesta: haz preguntas sobre tus propios PDFs y audios, obtén respuestas citadas.

Chat con citas

Chat con citas

Respuestas en streaming con marcadores [1] [2] que enlazan a la fuente exacta.

Zona de documentos

Zona de documentos

Subida drag-and-drop de PDFs y audio con validación por magic bytes.

Arquitectura

Arquitectura

Next.js habla con FastAPI; embeddings Gemini, pgvector HNSW y generación en Groq.

arrow_forward