En la primera parte, abordamos cómo interactuar con un modelo para obtener un diálogo basado en información sobre la cual no ha sido entrenado. Para resumir, añadimos al contexto la información deseada. Pero, ¿qué sucede si quisieras usar una base de conocimientos completa? Eso sería demasiada información para agregar al contexto. Por ello, necesitamos poner en una base de datos toda la información que deseamos proporcionar a los usuarios. Vamos a dividir nuestros contenidos en párrafos y aplicar un índice vectorial. Este índice convierte el texto en vectores numéricos que representan el significado semántico de las palabras y frases. Lo grandioso es que esto permite realizar búsquedas basadas en el significado en lugar de en las palabras exactas.

Entre las bases de datos que permiten realizar este tipo de búsqueda, encontramos:

  1. Elasticsearch: Es un motor de búsqueda y análisis distribuido ampliamente utilizado para búsqueda de texto. Elasticsearch se puede utilizar para crear índices vectoriales gracias a sus complementos e integraciones, especialmente para el procesamiento del lenguaje natural.

  2. Faiss (Facebook AI Similarity Search): Desarrollado por Facebook AI, Faiss es una biblioteca que permite la indexación vectorial eficiente y la búsqueda de similitudes. Es especialmente adecuada para manejar grandes conjuntos de vectores y es a menudo utilizada en sistemas de recomendación y búsqueda semántica.

  3. Milvus: Milvus es un sistema de gestión de bases de datos vectoriales de código abierto. Está diseñado para manejar índices vectoriales a gran escala y es compatible con varios modelos de aprendizaje automático, incluidos los utilizados para el procesamiento del lenguaje natural.

  4. Pinecone: Pinecone es una base de datos de vectores diseñada para aplicaciones de aprendizaje automático. Ofrece gestión y búsqueda de vectores a gran escala, lo que es útil para aplicaciones de búsqueda semántica y NLP (procesamiento del lenguaje natural).

  5. Weaviate: Weaviate es una base de datos de conocimiento vectorial, que permite el almacenamiento de datos en forma vectorial. Admite consultas semánticas y está optimizada para casos de uso que implican procesamiento del lenguaje natural.

  6. Annoy (Approximate Nearest Neighbors Oh Yeah): Annoy es una biblioteca en C++ con enlaces para Python diseñada para buscar los vecinos más cercanos en espacios de alta dimensión. Es utilizada para crear índices vectoriales y es eficaz para consultas de búsqueda rápida.

  7. HNSW (Hierarchical Navigable Small World): HNSW es un algoritmo popular para la búsqueda de vecinos más cercanos en espacios de alta dimensión. Varios sistemas de gestión de bases de datos integran HNSW para la creación de índices vectoriales.

  8. Postgresql (pgvector): Postgresql utiliza HNSW para la creación de índices vectoriales como parte de la extensión pgvector.Pueden encontrar una lista más completa en esta dirección: https://js.langchain.com/docs/integrations/vectorstores Esto ya ofrece bastantes posibilidades.

En cuanto a la parte de almacenamiento, ¿cómo creamos estos vectores? Usamos lo que se llama 'embeddings'. Los embeddings transforman datos complejos, como palabras, en vectores en un espacio multidimensional. Por ejemplo, cada palabra de un texto se puede representar por un vector de 50, 100, 300 dimensiones o más. Estos vectores están diseñados de tal manera que las palabras con significados similares están cerca en este espacio vectorial. Por ejemplo, 'rey' y 'reina' estarían cerca, reflejando su relación semántica. Hay modelos de embeddings preentrenados como Word2Vec, GloVe o BERT, que han sido entrenados en extensos corpus de texto y se pueden usar para obtener embeddings de palabras de alta calidad.

Podrán encontrar una lista de los diferentes sistemas de embeddings disponibles en esta dirección: https://js.langchain.com/docs/integrations/text_embedding

Es importante entender que cuando usas un modelo para crear tus embeddings, deberás usar el mismo modelo para generar tus consultas posteriormente. Es esencial mantener una coherencia con el fin de maximizar las posibilidades de que el motor de la base de datos compare correctamente la distancia entre los vectores de tu consulta y aquellos almacenados en tu base de datos.

En resumen, tienes en la base de datos una serie de vectores, y vas a hacer una consulta con otra serie de vectores. La base de datos va a comparar las distancias; cuanto más pequeñas sean, más similares serán los significados.

Para profundizar, encontré los ejemplos de este artículo bastante ilustrativos: https://medium.com/@aravilliatchutaram/intent-classification-using-vector-database-txtai-sqlite-and-large-language-models-821f939b87ba

Si deseas utilizar un sistema diferente al propuesto estándar por txtai, aquí tienes un recurso definitivo: https://neuml.hashnode.dev/customize-your-own-embeddings-database Aquí, encontrarás ejemplos de generación de embeddings con NumPy, PyTorch, Faiss, HNSW e incluso el uso de una API externa como Huggingface.

Es importante destacar que txtai crea por defecto vectores con el modelo https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2 utilizando Faiss como backend de índice Ann.

Para resumir:

  • Crear un embedding
from txtai.embeddings import Embeddings

embeddings = Embeddings()
  • Crear un embedding con un registro en una base de datos: SQLITE / all-MiniLM-L6-v2 / Faiss
from txtai.embeddings import Embeddings

embeddings = Embeddings(content=True)
  • Para crear un embedding con Postgresql / gte-large / Faiss
pipenv shell
pipenv install psycopg2-binary
from datasets import load_dataset
import txtai

# Load dataset
ds = load_dataset("ag_news", split="train")

embeddings = txtai.Embeddings(
    content="postgresql+psycopg2://testuser:testpwd@localhost:5432/vectordb",
    objects=True,
    backend="faiss",
    path="thenlper/gte-large"
)

# indexer
embeddings.index(ds["text"])
# sauvegarder l'index
embeddings.save("./index")
# charger l'index sauvegardé
embeddings.load("./index")
## ou dans le cloud,compressé
embeddings.save("/path/to/save/index.tar.gz", cloud={...})

En este ejemplo, registramos los contenidos en una base de datos y creamos nuestro índice con FAISS. Esto te obligará a guardar tus índices para poder recargarlos posteriormente. También tienes la opción de almacenarlos en la nube o en Elasticsearch. Es importante anotar que si modificas tus contenidos en la base de datos, deberás recrear tu índice.

import txtai

embeddings = txtai.Embeddings(
    content="postgresql+psycopg2://testuser:testpwd@localhost:5432/vectordb",
    objects=True,
    backend="faiss",
    path="thenlper/gte-large"
)


embeddings.reindex(path="sentence-transformers/all-MiniLM-L6-v2", backend="hnsw")
embeddings.save("./index")

estrategia de chunks o segmentación de tus documentos

Hay varias estrategias cuando se trata de crear tus segmentos de datos (chunks).A continuación, se presenta la traducción solicitada en idioma español (es):

  1. Segmentación de Tamaño Fijo

Es el método más común y simple. Consiste en determinar un número fijo de tokens para cada segmento, con la posibilidad de un cierto solapamiento entre ellos para preservar el contexto semántico.

  1. Segmentación "Consciente del Contenido"

Este enfoque utiliza la naturaleza del contenido para una segmentación más sofisticada. Incluye:

  • División de Frases: Uso de herramientas como NLTK o spaCy para dividir el texto en frases, ofreciendo una mejor preservación del contexto.

  • Segmentación Recursiva: Divide el texto en segmentos más pequeños de forma jerárquica e iterativa, utilizando diferentes separadores o criterios hasta obtener el tamaño o la estructura de segmento deseada.

Estas técnicas varían según el contenido y la aplicación en cuestión, y la elección del método apropiado depende de varios factores, incluida la naturaleza del contenido, el modelo de codificación utilizado y el objetivo de la aplicación.

Configuración simple de la base de datos PostgreSQL:

No hay misterio aquí, lo hacemos simple. Vamos a utilizar la imagen Docker proporcionada por el desarrollador del módulo pgvector.

## docker-compose.yaml
services:
  db:
    hostname: db
    image: ankane/pgvector
    ports:
     - 5432:5432
    restart: always
    environment:
      - POSTGRES_DB=vectordb
      - POSTGRES_USER=testuser
      - POSTGRES_PASSWORD=testpwd
      - POSTGRES_HOST_AUTH_METHOD=trust
    volumes:
     - ./init.sql:/docker-entrypoint-initdb.d/init.sql
## init.sql
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE IF NOT EXISTS embeddings (
  id SERIAL PRIMARY KEY,
  embedding vector,
  text text,
  created_at timestamptz DEFAULT now()
);
docker compose up -d

Así que eso es todo para esta segunda parte, algo teórica. Solo he tocado la superficie de los temas, y pueden imaginar que cada uno de los componentes involucrados tiene una gran cantidad de parámetros de ajuste fino. La belleza de TxtAi radica en que todo es simple y ya está preconfigurado. Estos ejemplos no son realmente útiles si queremos establecer un sistema sencillo. Sin embargo, en la realidad de un proyecto, raras veces se usan las cosas tal cual. Es por eso que quise mostrar para cada elemento las posibilidades ofrecidas por este marco de trabajo. Todo es configurable, nada se deja al azar. Lo que es maravilloso con este marco, especialmente si estás descubriendo el campo, es que uno puede descubrir a través de la documentación los componentes necesarios para tal sistema. Las cosas pueden volverse muy complejas cuando deseamos salir del marco establecido.

En la tercera parte de nuestra serie sobre el sistema RAG, vamos a meternos un poco más en la práctica. Dado que ya hemos tratado el lado tedioso de la teoría, podremos enfocarnos en el código.