Skip to content

feat(assistant): RAG with pgvector, incremental reindex and LLM fallback (PR 2) - #8

Merged
jcmoro merged 2 commits into
mainfrom
feature/assistant-rag
Oct 27, 2026
Merged

jcmoro merged 2 commits into
mainfrom
feature/assistant-rag

Conversation

@jcmoro

@jcmoro jcmoro commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Qué

PR 2 del plan (docs/plan-assistant-genai.md): RAG con pgvector. El asistente responde a partir del catálogo real.

  • Indexado bajo demanda e incremental: make reindex ejecuta python -m contexts.assistant.ui.cli.reindex, que llama al caso de uso IndexCatalog.
    • Solo recalcula embeddings de lo que cambió (content_hash sobre modelo + contenido), en lotes.
    • Borra un documento huérfano solo después de confirmar con catalog que el producto ya no existe.
    • No hay endpoint de indexado: la API no tiene autenticación.
  • Recuperación: POST /assistant/ask calcula el embedding de la pregunta, recupera los top-k (pgvector, HNSW, distancia coseno), los pone en el prompt y los devuelve como sources.
  • Fallback: si el LLM no está disponible, responde 200 con los productos recuperados, fallback_used=true y usage=null. Sustituye al 503 del PR 1.
  • Embeddings locales y multilingües: fastembed con paraphrase-multilingual-MiniLM-L12-v2 (384 dimensiones).
    • El modelo se descarga al construir la imagen. En ejecución solo se carga de disco (local_files_only) y se calienta en el lifespan.
    • Para calcular embeddings no hace falta red; la llamada al LLM sí la necesita.
  • catalog no cambia. assistant solo lo toca a través de su capa de aplicación (ListProducts, GetProduct y DTOs) y del cableado de dependencias.

Especificación completa (objetivo, alcance, fuera de alcance, 12 criterios y evidencia): docs/step-16-assistant-rag.md.

Cambios de contrato

AnswerResponse gana sources[] (product_id, content, score) y fallback_used. usage pasa a ser nullable. El 503 deja de documentarse porque el fallback lo sustituye; los errores de configuración siguen dando 500.

Code review

/code-review medium dio 10 hallazgos, cada uno comprobado contra el código: 6 corregidos (R1–R6), más R7, que añadió el usuario. 2 repiten decisiones ya documentadas y 2 eran falsos positivos. Cada corrección empieza por un test que fallaba con el código anterior:

# Corrección Rojo → verde
R1 Confirmar las bajas con GetProduct antes de borrar deleted=1 sobre un producto existente → se conserva
R2 El modelo de embeddings entra en el hash indexed=0 al cambiar de modelo → indexed=3
R3 Embeddings y upsert en lotes de 256 una sola llamada → lotes [2, 2, 1]
R4 RAG_TOP_K ≤ 40 (ef_search por defecto de HNSW) 41 aceptado → ValidationError
R5 La búsqueda usa su propia sesión corta 1 conexión ocupada durante la llamada al LLM → 0
R6 El modelo se carga al arrancar (lifespan) el módulo no existía → se calienta al arrancar
R7 Modelo dentro de la imagen; en ejecución nunca se descarga ValueError sin descarga si no está en caché

Verificación

  • make lint: ruff OK, pylint 10.00/10, mypy strict sin errores (72 ficheros).
  • make test: 137 passed, antes 99.
  • make reindex sobre datos reales: indexed=3 unchanged=0 deleted=0 y después indexed=0 unchanged=3 deleted=0.
  • Recuperación real: «¿Cuánto cuesta el widget?», en español, devuelve primero «Real Widget» (en inglés).
  • Contenedor con --network none: calcula embeddings y la app arranca tras cargar el modelo.

Al desplegar

  • db y db-test pasan a pgvector/pgvector:pg16. Se aplica la migración 4aa1b2bf2668, que crea la extensión vector y la tabla product_documents.
  • En la BD de desarrollo se ejecutó REINDEX TABLE products por el cambio de musl a glibc. Los datos se conservan.
  • La imagen pesa unos 240 MB más por el modelo.
  • Hay que ejecutar make reindex para poblar el índice.

Revisar

alembic/env.py añade un import solo para registrar los modelos de assistant, con # noqa: F401, igual que la línea que ya existe para catalog.

Pendiente (fuera de alcance)

  • Indexado automático con eventos de dominio y outbox: está en el plan como evolución futura.
  • Una pregunta completa con el LLM real: no hay ANTHROPIC_API_KEY en el entorno local.

🤖 Generated with Claude Code

@jcmoro
jcmoro merged commit edece6d into main Oct 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants