Todos los artículos
Tecnología · 17 min lectura

pgvector + RAG en clínicas · arquitectura honesta 2026 (cómo lo hacemos)

·Jonatan Contell

Cada vez que alguien me pregunta "¿cómo evitáis que el bot invente precios?" la respuesta es RAG sobre pgvector. Pero el cómo concreto no se cuenta · porque la mayoría del contenido en español sobre RAG es teoría genérica con code samples que no sirven en producción. Este es nuestro stack real · con números reales · trade-offs reales · versión mayo 2026.

Por qué pgvector en lugar de Pinecone · Weaviate · Qdrant

Es una decisión deliberada · no por moda. Los criterios que usamos:

  • Volumen real: clínica típica tiene KB de 200-2000 chunks. Eso cabe sobrado en una tabla Postgres con índice HNSW. No necesitas vector DB dedicada hasta 1M+ vectores.
  • Coste: pgvector va en tu Supabase existente · coste marginal ~€0. Pinecone Starter empieza en $70/mes · Weaviate Cloud ~$25 mínimo.
  • Operativa: 1 menos vendor · 1 menos DPA · 1 menos transferencia internacional · 1 menos backup estrategia. En clínica con RGPD eso importa mucho.
  • JOIN nativo con metadata:filtros como "chunks de servicio dental con precio <300€" se hacen con SQL · sin sync entre vector store y DB relacional.
  • Multi-tenant simple: cada clínica es fila con tenant_id · RLS aplica filtro automáticamente. En Pinecone tendrías que gestionar namespaces y bookkeeping aparte.

Cuándo NO usaríamos pgvector: si tuviéramos >5M vectores por tenant · queries >500/segundo · o latency objetivo <20ms p99. Ninguno aplica a clínicas.

Stack completo · vista general

  • Storage: Supabase Postgres 15 + pgvector 0.6.0 + pgcrypto + pgsodium.
  • Embeddings: OpenAI text-embedding-3-small (1536 dimensiones · $0.02 por 1M tokens).
  • Chunking: recursive character splitter · 300-500 tokens por chunk · overlap 50.
  • Index: HNSW (Hierarchical Navigable Small World) · m=16 · ef_construction=64.
  • Retrieval: cosine similarity threshold 0.78 · top-k 8 · MMR re-ranking lambda 0.7 · final top-3.
  • Generation:gpt-4o-mini con prompt template que cita fuentes y rechaza si confidence <threshold.
  • Evaluación: golden set de ~80 preguntas por clínica · faithfulness + answer relevance + context precision · revisión semanal.

Schema pgvector · la tabla concreta

La tabla central · simplificada:

  • id uuid primary key
  • tenant_id uuid not null · fk a clinic.
  • source_url text · de dónde vino (página web · documento · respuesta humana validada).
  • source_type text · enum: web · pdf · faq · qa_human · price_sheet.
  • content text not null · el chunk en sí.
  • metadata jsonb · service_type · price · category · last_verified_at.
  • embedding vector(1536) · el vector.
  • confidence numeric default 1.0 · score manual de qué fiable es esta fuente.
  • created_at · updated_at · deleted_at · soft-delete para auditoría.

Index: create index on kb_chunks using hnsw (embedding vector_cosine_ops). RLS policy filtra por tenant_id automáticamente.

Chunking strategy · por qué 300-500 tokens

Los extremos fallan los dos:

  • Chunks <200 tokens: pierden contexto. El bot responde con frases cortadas. Especialmente problemático con servicios largos tipo "ortodoncia invisible con financiación a 24 meses".
  • Chunks >800 tokens: dilución. El vector representa un promedio de varios temas · cosine similarity baja para queries específicas. Y consumes más context window.

300-500 tokens con overlap 50 es el sweet spot que medimos empíricamente. El overlap permite que conceptos cercanos a la frontera del chunk no se rompan.

Splitter recursive por separadores en este orden: doble newline · newline · punto · espacio. Evita cortar a mitad de palabra o de número (importante para precios).

Embedding model · text-embedding-3-small vs alternativas

Probamos 4 opciones · este es el resultado al cierre de 2025-Q4:

  • text-embedding-3-small (OpenAI · 1536d):baseline. $0.02/1M tokens. Calidad muy buena en español. Lo usamos.
  • text-embedding-3-large (OpenAI · 3072d):ligeramente mejor en recall · pero 6.5x más caro y vectors 2x más grandes. No vale el coste/beneficio para KBs de clínica.
  • multilingual-e5-large (open source · 1024d):self-hosted en GPU pequeña. Calidad similar a 3-small. Considerado como fallback. Hoy no lo usamos por complejidad operativa.
  • cohere-embed-multilingual-v3 (1024d):bueno · pero 1 vendor más · 1 DPA más. Descartado por simplicidad.

Index · HNSW vs IVFFLAT

pgvector ofrece ambos. Para clínicas vamos HNSW siempre. Razones:

  • HNSW (Hierarchical Navigable Small World):build time alto · query time bajo · recall alto. Excelente para nuestro workload (escritura ocasional KB · lectura intensiva).
  • IVFFLAT (Inverted File con FLAT): build rápido · query algo más lento · recall ligeramente menor sin tunear bien número de listas. Mejor para KBs que cambian mucho.

Parámetros que usamos en HNSW: m=16 (cuántos vecinos en grafo) · ef_construction=64 (calidad de build) · ef_search=40 a nivel de query. Estos son los defaults ajustados un punto · benchmark sobre nuestros datos reales.

Retrieval pipeline · sin trampas

Cuando el bot recibe pregunta del paciente:

  • 1 · Query embedding: embed la pregunta del paciente con mismo modelo.
  • 2 · Búsqueda vectorial:top-8 chunks por cosine similarity · filtrado por tenant_id · filtrado por similarity >= 0.78.
  • 3 · Re-ranking MMR: Maximal Marginal Relevance con lambda 0.7 para diversidad. Evita retornar 3 chunks casi idénticos.
  • 4 · Filtro freshness: si chunk tienelast_verified_at>180 días · degrada score 10%. Si >365 días · degrada 30%.
  • 5 · Top-3 final: esto va al prompt.
  • 6 · Confidence check:si el top-1 <0.78 después de re-rank · bot NO responde con RAG · pasa a handoff humano "voy a confirmar con la clínica".

Generation prompt · cómo citamos fuente

El system prompt esencialmente dice:

  • Eres asistente de [Clínica X]. Responde solo con información del contexto proporcionado.
  • Si la pregunta no se puede responder con el contexto · di explícitamente "voy a confirmar este punto con la clínica y te respondo en breve" · NO inventes.
  • NUNCA des diagnóstico clínico. NUNCA orientación médica más allá de información sobre servicios disponibles.
  • Cita el source_url internamente para auditoría (no se muestra al paciente · va en log).
  • Tono · longitud · idioma según config de la clínica.

Adicionalmente · guardrails post-generación: regex que bloquea menciones de precios no presentes en contexto · regex que detecta vocabulario de diagnóstico ("tienes" · "padeces" · "es probable que sea") · si match · bloquear y reformular.

Evaluación · golden set + métricas continuas

Tener RAG sin medir es ingeniería de ciencia ficción. Nuestra evaluación:

  • Golden set por clínica: 50-100 preguntas reales con respuesta correcta validada por la clínica. Mezcla de fácil (horarios · servicios) y difícil (precio específico · combinación de tratamientos).
  • Métricas (Ragas · open source):
  • Faithfulness: ¿la respuesta usa solo lo del contexto? Target >0.92.
  • Answer relevance: ¿responde a lo que se preguntó? Target >0.88.
  • Context precision: ¿el top-3 contiene info relevante? Target >0.85.
  • Context recall: ¿la info necesaria está en el top-3? Target >0.85.
  • Eval automática semanalen CI. Si cualquier métrica cae >5% vs baseline · alert · investigación manual.
  • Eval humana mensual: revisión de 20 conversaciones reales muestreadas · clasificación buena/regular/mala. Feedback loop a la KB.

Cost real · números mayo 2026

Clínica típica con KB inicial 1500 chunks · 200 mensajes paciente/día:

  • Embedding inicial KB: 1500 chunks × 400 tokens × $0.02/1M = $0.012. Una vez.
  • Embedding queries: 200 queries/día × 30 tokens × $0.02/1M = $0.00012/día = $0.0036/mes.
  • Generation (gpt-4o-mini): 200 turnos/día con context 1.5k tokens + output 200 tokens = ~$0.0003/turno × 200 × 30 = ~$1.8/mes.
  • Postgres pgvector storage: ~10 MB en Supabase free tier.
  • Total bot inference: ~$2/mes por clínica activa típica.

El cost real no está en LLM. Está en infraestructura fija (Cloudflare · Supabase · QStash) que escala lineal con clínicas pero arranca en ~€60/mes total. Por encima de 30 clínicas pagantes · pgvector route comprueba ser significativamente más barata que vector DB dedicada.

Cuándo replantear · señales de que toca mover

  • >5M vectores por tenant: empieza a doler latency. Re-evaluar Pinecone/Weaviate.
  • >500 queries/segundo sostenidas: HNSW se queda corto en una sola DB.
  • Requisitos de filtrado complejo multi-dimensional con attributos high-cardinality: vector DBs especializadas lo hacen mejor.
  • Necesitas sharding por tenant grande: complica vida en Postgres · más natural en vector DB con namespace nativo.

Ninguno de estos aplica hoy a AI Empire. Cuando aplique · la migración a vector DB dedicada se haría con período de paralelo · igual que cualquier migración seria.

Disclaimer: esta arquitectura refleja nuestro stack actual mayo 2026 · evoluciona. Los números son los que medimos en nuestros workloads · no son garantía para casos distintos. Si tu clínica tiene requisitos específicos (multi-idioma intensivo · volúmenes muy grandes · uso clínico real con datos sensibles) habla con tu equipo técnico antes de copiar.

Otros artículos que pueden ayudarte a profundizar en lo mismo.

Deja de regalar ingresos.
Activa tu Revenue OS.

14 días gratis · setup completo incluido · sin permanencia. Si en 14 días no recuperas mínimo 1 cita atribuible al bot · te devolvemos lo pagado y archivamos sin preguntas.

¿Prefieres ver demo grabada antes? · analiza tus reseñas gratis · audit pre-onboarding para tu clínica · 5 min · cero compromiso.