Laboratorio · en producción

Esta web es mi demo

El asistente de soytrillos.com no es un chatbot genérico: es un sistema RAG completo. Un pipeline de datos en Python prepara y valida mis contenidos, genera embeddings y alimenta a un agente que busca, usa herramientas y cita sus fuentes. El mismo conocimiento está disponible para cualquier IA a través de un servidor MCP.

Arquitectura

  1. Contenido

    Markdown y MDX: perfil, servicios y blog

  2. Pipeline Python

    Extrae, limpia, fragmenta, valida y genera embeddings

  3. Índice vectorial

    30 fragmentos · 384 dimensiones

  4. Agente RAG

    Búsqueda híbrida + herramientas + Gemma 4

Chat de la web con fuentes y pasos visibles
Servidor MCP para Claude, Cursor y otras IAs

01 · Datos

Pipeline de ingesta en Python

Antes de que la IA vea un solo dato, el pipeline lo valida. Si encuentra un error de calidad (títulos duplicados, documentos vacíos o un email que no debería publicarse), se detiene y no genera el índice. Se ejecuta automáticamente en GitHub Actions cada vez que publico contenido, y solo recalcula los embeddings de los fragmentos que cambiaron.

  • ›Extract → Markdown y MDX con frontmatter
  • ›Clean → texto plano sin sintaxis ni JSX
  • ›Chunk → por secciones, con solapamiento
  • ›Quality → duplicados, longitudes, datos sensibles
  • ›Embed → multilingual-e5-small, por lotes y con reintentos
  • ›Load → índice JSON + reporte de calidad
documentos procesados
7
fragmentos indexados
30
duplicados eliminados
0
errores de calidad
0

Reporte real de la última ejecución · intfloat/multilingual-e5-small · 19 de octubre de 2026

rag/soytrillos_rag/chunk.py
def window(text: str, max_chars: int, overlap: int) -> list[str]:
    """Corta textos largos en ventanas, intentando terminar en un párrafo o frase."""
    if len(text) <= max_chars:
        return [text]
    if overlap >= max_chars:
        raise ValueError("overlap debe ser menor que max_chars")
    parts, start = [], 0
    while start < len(text):
        end = min(start + max_chars, len(text))
        if end < len(text):
            cut = max(text.rfind("\n\n", start, end), text.rfind(". ", start, end))
            if cut > start + max_chars // 2:
                end = cut + 1
        parts.append(text[start:end].strip())
        if end >= len(text):
            break
        start = max(end - overlap, start + 1)
    return [p for p in parts if p]
rag/soytrillos_rag/pipeline.py
def load_previous_vectors(index_path: Path, model: str) -> dict[str, list[float]]:
    """Carga incremental: reutiliza embeddings de fragmentos cuyo texto no cambió."""
    if not index_path.exists():
        return {}
    try:
        previous = json.loads(index_path.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        log.warning("Índice anterior ilegible: se regenerará completo")
        return {}
    if previous.get("model") != model:
        log.info("Cambió el modelo de embeddings: se regenerará completo")
        return {}
    return {c["hash"]: c["embedding"] for c in previous.get("chunks", []) if c.get("embedding")}

02 · RAG y agente

Búsqueda híbrida y herramientas

Embeddings + BM25

La búsqueda vectorial entiende el significado; BM25 no pierde términos exactos como «ETL». Reciprocal Rank Fusion combina ambos rankings.

Agente con tool calling

Gemma 4 decide si necesita más contexto, listar artículos o dar el contacto. El servidor ejecuta la herramienta y le devuelve el resultado.

Respuestas verificables

Cada respuesta muestra sus fuentes y los pasos que siguió. Si la información no está en mis datos, lo dice en lugar de inventar.

Pruébalo: haz una pregunta y despliega «Cómo respondí» bajo la respuesta.

03 · MCP

Conecta tu IA a mi servidor MCP

El Model Context Protocol permite que asistentes como Claude o Cursor usen herramientas externas de forma estándar. Mi servidor es público y de solo lectura: tu IA puede buscar en mis contenidos, leer mis artículos y obtener mis datos de contacto.

  • buscar_conocimientobúsqueda híbrida con scores
  • listar_articulosartículos del blog por categoría
  • leer_articulotexto completo de un artículo
  • contactoemail, agenda y redes

Endpoint: https://soytrillos.com/api/mcp

Claude Code

claude mcp add --transport http soytrillos https://soytrillos.com/api/mcp

Claude Desktop, Cursor y otros clientes (mcp.json)

{
  "mcpServers": {
    "soytrillos": { "url": "https://soytrillos.com/api/mcp" }
  }
}

¿Quieres algo así con los datos de tu empresa?

Asistentes internos, búsqueda sobre tus documentos o un servidor MCP para tus sistemas. Empezamos por ordenar los datos.

Agendar consultoría