Grandes modelos lingüísticos, conocidos popularmente como LLM, es uno de los temas más debatidos en la industria de la IA. Todos conocemos las posibilidades y capacidades de ChatGPT de OpenAI. Eventualmente usando esos LLMs a nuestro favor descubrimos un montón de nuevas posibilidades usando los datos.
Pero no se puede esperar todo de los LLM debido a sus limitaciones de diseño y a muchos otros factores. Pero utilizando el concepto de Búsqueda vectorial nos ofrece un nuevo tipo de aplicaciones llamadas Aplicaciones de Generación Aumentada de Recuperación (RAG). Así que vamos a ver qué son las aplicaciones RAG, cómo se pueden utilizar para hacer frente a nuevos problemas, cómo se pueden desarrollar utilizando Couchbase y tener una visión detallada de cómo Vector Search ayuda a hacer esas aplicaciones.
Antes de entrar en el fondo de la aplicación, aquí hay un diagrama de arquitectura de lo que estamos construyendo y cómo LangChain se vincula a ella:
Generación aumentada de recuperación (RAG)
El GAR permite aumentar los resultados de un LLM con información específica sin modificar el propio modelo subyacente, de modo que la información específica puede estar más actualizada que el LLM y ser específica de una organización y un sector concretos. Esto significa que el IA generativa puede proporcionar respuestas más adecuadas al contexto y basarlas en datos muy actuales. Comprendamos este concepto con un ejemplo de la vida real.
Digamos que perteneces a la organización X, que tiene una tonelada de datos almacenados en su base de datos, y estás a cargo del desarrollo de una aplicación que pide la entrada del usuario y da la salida basada en los datos presentes en su base de datos.
Al principio puedes pensar que esto parece fácil, ¿verdad? Si conoces los LLMs y cómo aprovecharlos para tus necesidades, entonces es una tarea sencilla. Sólo tienes que elegir los LLMs de OpenAI o los modelos Llama y Mistral si quieres ser rentable, y simplemente disparar preguntas de usuario al LLM y obtener los resultados.
Pero aquí hay un gran problema...
Por ejemplo, supongamos que está utilizando la función Llama 2 LLM 8B tipo.
Ahora bien, este modelo está entrenado con casi todos los datos presentes en la Internet pública. Haces cualquier pregunta, incluso sobre tu org X, y te da la respuesta correcta más cercana.
Ahora hagamos un pequeño cambio en el enunciado de tu problema. La tonelada de datos que hay en tu base de datos ya no son datos públicos, sino privados. Lo que significa que Llama 2 desconoce tus datos y ya no te dará respuestas correctas.
Teniendo en cuenta el escenario anterior, consideremos la pregunta del usuario, "¿cuáles son las actualizaciones del componente C en org X?"
Entonces, ¿cómo solucionarlo?
Podrías pensar, ¿por qué no pasamos todos los datos presentes en la base de datos, junto con la pregunta, para que el LLM pueda utilizar los datos como contexto y responder a la pregunta? Pero aquí está el gran problema, todos los LLM tienen una restricción llamada límite simbólico. Sin entrar en qué son los tokens, etc., por ahora considera que 1 token == 1 palabra.
Lamentablemente, el límite de tamaño de los tokens de Llama 2 es de 4096 tokens (palabras). Supongamos que la totalidad de los datos presentes en su base de datos tiene 10 millones de palabras, por lo que resulta imposible pasar la totalidad de los datos por motivos de contexto.
La solución al problema anterior se llama RAG. En RAG, seleccionamos una proporción de datos presentes en su base de datos que está muy relacionada con la consulta del usuario. El tamaño de la proporción es tal que:
Tamaño de la proporción < límite de fichas
Ahora pasamos los datos extraídos como contexto junto con la consulta y obtenemos buenos resultados. Esto es RAG. Pero, ¿cómo obtenemos la proporción de datos que está estrechamente relacionada con la consulta del usuario y, al mismo tiempo, el tamaño no supera el tamaño del token? Esto se resuelve utilizando el concepto de Búsqueda vectorial.
¿Qué es la búsqueda vectorial?
La búsqueda vectorial aprovecha el aprendizaje automático (AM) para captar el significado y el contexto de los datos no estructurados, incluidos textos e imágenes, transformándolos en una representación numérica. Frecuentemente utilizada para la búsqueda semántica, la búsqueda vectorial encuentra datos similares mediante algoritmos de aproximación al vecino más cercano (RNA).
Couchbase versión 7.6.0 y superior viene con esto Búsqueda vectorial. Lo importante aquí es que no se requieren librerías externas, módulos y configuraciones. Basta con tener al menos 1 busque en hace el trabajo.
Couchbase utiliza internamente Marco FAISS proporcionada por Facebook para realizar la búsqueda vectorial.
Creación de una aplicación RAG
Ahora pasemos a desarrollar una aplicación RAG de principio a fin utilizando la herramienta Funcionalidad de búsqueda vectorial de Couchbase.
En este tutorial, vamos a desarrollar un chatea con tus pdf aplicación.
Antes de seguir adelante, hay varias formas de crear la aplicación. Una de ellas es utilizando el framework LangChain que utilizaremos para desarrollar la aplicación RAG.
Aplicación 1: Construcción con el marco LangChain
Paso 1: Configurar una base de datos Couchbase
Puedes configurar el servidor Couchbase en EC2, Máquina Virtual, tu máquina local, etc.
Siga este enlace para configurar el clúster Couchbase. Asegúrate de tener activados estos servicios, los demás son opcionales:
-
- Datos
- Buscar en
Nota: Asegúrese de instalar Couchbase Server versión 7.6.0 o superior para realizar búsquedas vectoriales. También desarrollaremos esta aplicación utilizando Python en el entorno Mac OS.
Una vez que el cluster esté en marcha, crea un nuevo proyecto <project_name> y crear un nuevo archivo Python llamado app.py.
Ahora, en el terminal del proyecto, ejecute el siguiente comando:
|
1 |
pip install --upgrade --quiet langchain langchain-openai langchain-couchbase sentence-transformers |
Ahora ve a la interfaz de usuario y crea un cubo llamado proyecto. Para este tutorial, utilizaremos el método por defecto alcance y recogida.
-
- Más información cubo, ámbito y colecciones en los documentos.
Ahora hay diferentes maneras de generar incrustaciones vectoriales. El más popular es OpenAI y lo usaremos para generar incrustaciones vectoriales.
Copie el código siguiente en app.py:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import getpass import os from langchain_couchbase.vectorstores import CouchbaseVectorStore from langchain_openai import OpenAIEmbeddings from datetime import timedelta from couchbase.auth import PasswordAuthenticator from couchbase.cluster import Cluster from couchbase.options import ClusterOptions os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:") COUCHBASE_CONNECTION_STRING = "couchbase://localhost" DB_USERNAME = "Administrator" DB_PASSWORD = "Password" BUCKET_NAME = "project" SCOPE_NAME = "_default" COLLECTION_NAME = "_default" SEARCH_INDEX_NAME = "vector-index" auth = PasswordAuthenticator(DB_USERNAME, DB_PASSWORD) options = ClusterOptions(auth) cluster = Cluster(COUCHBASE_CONNECTION_STRING, options) cluster.wait_until_ready(timedelta(seconds=5)) embeddings = OpenAIEmbeddings() |
Si alojaste Couchbase en una VM, entonces asegúrate de reemplazar la palabra localhost a la ip pública de la máquina virtual.
Paso 2: Importar el índice de búsqueda
La función de búsqueda vectorial en Couchbase requiere un índice de búsqueda para funcionar. Hay varias maneras de crear el índice, pero para hacer las cosas fáciles y rápidas, a continuación es el índice JSON. Copia el código de abajo y pégalo:
- INTERFAZ DE USUARIO > Buscar en > Añadir índice (arriba a la derecha) > Importar
Índice.json
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
{ "name": "vector-index", "type": "fulltext-index", "params": { "doc_config": { "docid_prefix_delim": "", "docid_regexp": "", "mode": "type_field", "type_field": "type" }, "mapping": { "default_analyzer": "standard", "default_datetime_parser": "dateTimeOptional", "default_field": "_all", "default_mapping": { "dynamic": true, "enabled": true, "properties": { "metadata": { "dynamic": true, "enabled": true }, "embedding": { "enabled": true, "dynamic": false, "fields": [ { "dims": 1536, "index": true, "name": "embedding", "similarity": "dot_product", "type": "vector", "vector_index_optimized_for": "recall" } ] }, "text": { "enabled": true, "dynamic": false, "fields": [ { "index": true, "name": "text", "store": true, "type": "text" } ] } } }, "default_type": "_default", "docvalues_dynamic": false, "index_dynamic": true, "store_dynamic": true, "type_field": "_type" }, "store": { "indexType": "scorch", "segmentVersion": 16 } }, "sourceType": "gocbcore", "sourceName": "project", "sourceParams": {}, "planParams": { "maxPartitionsPerPIndex": 103, "indexPartitions": 10, "numReplicas": 0 } } |
Paso 3: Cargar los datos
Ahora es el momento de almacenar todos los datos PDF como trozos junto con sus incrustaciones vectoriales en la base de datos.
Nota: Lea esto blog detallado sobre fragmentación, recopilación de datos, etc.. Es muy recomendable consultar el blog para tener una idea clara de lo que vamos a tratar en los pasos posteriores.
Existen bibliotecas para los distintos tipos de documentos que desee cargar. Por ejemplo, si sus datos de origen están en .txt añada el siguiente código a su app.py:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from langchain_community.document_loaders import TextLoader from langchain_text_splitters import CharacterTextSplitter loader = TextLoader("path of your text file") documents = loader.load() text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=0) docs = text_splitter.split_documents(documents) vector_store = CouchbaseVectorStore.from_documents( documents=docs, embedding=embeddings, cluster=cluster, bucket_name=BUCKET_NAME, scope_name=SCOPE_NAME, collection_name=COLLECTION_NAME, index_name=SEARCH_INDEX_NAME, ) |
Pero supongamos que su tipo de fuente es PDF, entonces:
|
1 |
pip install pypdf |
|
1 2 3 4 |
from langchain_community.document_loaders import PyPDFLoader loader = PyPDFLoader("path to your pdf file") pages = loader.load_and_split() |
No sólo es compatible con PDF, LangChain ofrece compatibilidad con múltiples tipos, como:
-
- CSV
- HTML
- JSON
- Markdown y mucho más
Más información Cargadores de documentos Langchain.
Paso 4: Inferir los resultados
Ahora estamos listos para enviar consultas a nuestra aplicación:
|
1 2 3 |
query = "<your question>" results = vector_store.similarity_search(query) print(results[0]) |
App 2: Crear la app desde cero
Antes de empezar, como se describe en la sección anterior, cree su clúster con un bucket llamado proyecto. Siga también el paso 2 de la sección anterior, asegurándose de importar el índice de búsqueda.
Paso 1: Configuración de Couchbase
Si va a utilizar los valores por defecto, entonces su app.py debería tener este aspecto:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from couchbase.auth import PasswordAuthenticator from couchbase.cluster import Cluster from couchbase.options import (ClusterOptions, ClusterTimeoutOptions,QueryOptions) from datetime import timedelta username = "Administrator" password = "password" bucket_name = "project" scope_name = "_default" collection_name = "_default" auth = PasswordAuthenticator( username, password, ) cluster = Cluster(f'couchbase://localhost}', ClusterOptions(auth)) cluster.wait_until_ready(timedelta(seconds=5)) cb = cluster.bucket(bucket_name) cb_coll = cb.scope(scope_name).collection(collection_name) |
Ahora que los enlaces de la colección Couchbase y el índice de búsqueda están listos, pasemos a la parte de carga de datos.
Paso 2: Carga de datos
Para mantener las cosas modularizadas, crea un nuevo archivo Python llamado load.py.
Existen múltiples formas de extraer datos de archivos PDF. Para hacerlo más fácil, vamos a utilizar el pypdf de Langchain:
cargar.py
|
1 2 3 4 |
from langchain_community.document_loaders import PyPDFLoader loader = PyPDFLoader("path to your pdf") pages = loader.load_and_split() |
Ahora bien páginas es una lista de bloques de texto extraídos del pdf. Vamos a fusionar todo el contenido en una variable:
|
1 2 3 |
text = "" for page in pages: text += page['page_content'] |
Antes de hacer el chunking, tenemos que configurar el modelo de incrustación. En este caso, utilizaremos el modelo transformadores de frases/paráfrasis-distilroberta-base-v1 de abrazar la cara.
Este modelo proporciona incrustaciones vectoriales de 768 dimensiones.
cargar.py
|
1 2 |
from sentence_transformers import SentenceTransformer model = SentenceTransformer('sentence-transformers/paraphrase-distilroberta-base-v1') |
Ahora nuestro modelo está listo. Empujemos los documentos. Podemos utilizar el paquete divisor de texto de caracteres recursivos de Langchain.
Este paquete le da trozos del tamaño proporcionado, a continuación, vamos a encontrar la incrustación de vectores para cada trozo utilizando el modelo anterior y empujar el documento en la base de datos.
Así, el documento tendrá dos campos:
|
1 2 3 4 |
{ “Data” : <chunk>, “Vector_data” : <vector embedding of the chunk> } |
cargar.py
|
1 2 3 4 5 6 7 8 9 |
from langchain_text_splitters import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=20, length_function=len, is_separator_regex=False, ) chunks = text_splitter.create_documents(text) |
Ahora que nuestros trozos están listos, podemos encontrar las incrustaciones para cada trozo y enviarlo a la base de datos.
cargar.py
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from json import JSONEncoder from FlagEmbedding import BGEM3FlagModel from sentence_transformers import SentenceTransformer import numpy as np import json import ast class NumpyEncoder(JSONEncoder): def default(self, obj): if isinstance(obj, np.ndarray): return obj.tolist() return JSONEncoder.default(self, obj) docid_counter = 1 for sentence in chunks: emb = model.encode(str(sentence.page_content)) embedding = np.array(emb) np.set_printoptions(suppress=True) json_dump = json.dumps(embedding, cls=NumpyEncoder) document = { "data":str(sentence.page_content), "vector_data":ast.literal_eval(json_dump) } docid = 'docid:' + str(name) + str(docid_counter) docid_counter+=1 try: result = cb_coll.upsert(docid, document) print(result.cas) except Exception as e: print(e) |
Me pregunto dónde está el cb_coll ¿de dónde viene? Es el conector de colección que creamos en app.py. Para pasarlo, vamos a envolver todo esto en cargar.py a una función que acepte cb_coll como parámetro.
Así que, finalmente su cargar.py debería verse así:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from json import JSONEncoder from FlagEmbedding import BGEM3FlagModel from sentence_transformers import SentenceTransformer import numpy as np import json import ast class NumpyEncoder(JSONEncoder): def default(self, obj): if isinstance(obj, np.ndarray): return obj.tolist() return JSONEncoder.default(self, obj) def Load_data(cb_coll): loader = PyPDFLoader("path to your pdf") pages = loader.load_and_split() text = "" for page in pages: text += page['page_content'] text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=20, length_function=len, is_separator_regex=False, ) chunks = text_splitter.create_documents(text) docid_counter = 1 for sentence in chunks: emb = model.encode(str(sentence.page_content)) embedding = np.array(emb) np.set_printoptions(suppress=True) json_dump = json.dumps(embedding, cls=NumpyEncoder) document = { "data":str(sentence.page_content), "vector_data":ast.literal_eval(json_dump) } docid = 'docid:' + str(name) + str(docid_counter) docid_counter+=1 try: result = cb_coll.upsert(docid, document) print(result.cas) except Exception as e: print(e) |
Ahora vamos a app.pyimporta esto y llama al cargar_datos función.
app.py
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from couchbase.auth import PasswordAuthenticator from couchbase.cluster import Cluster from couchbase.options import (ClusterOptions, ClusterTimeoutOptions,QueryOptions) from datetime import timedelta from ./load import Load_data username = "Administrator" password = "password" bucket_name = "project" scope_name = "_default" collection_name = "_default" auth = PasswordAuthenticator( username, password, ) cluster = Cluster(f'couchbase://localhost}', ClusterOptions(auth)) cluster.wait_until_ready(timedelta(seconds=5)) cb = cluster.bucket(bucket_name) cb_coll = cb.scope(scope_name).collection(collection_name) Load_data(cb_coll) |
Ahora esto empujará los documentos en el formato requerido y nuestro índice de búsqueda también será mutado. Ahora es el momento de hacer la búsqueda vectorial.
Paso 3: Búsqueda vectorial
En Couchbase tienes múltiples formas de hacerlo, una de ellas es el método Curl.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import subprocess import json from config import COUCHBASE_URL def vector_search_chat1(index_name, search_vector, k, cb_coll,query): print("query :",query) args = {"index_name":index_name, "search_vector":search_vector,"k":k,"query":query,"couchbase_url":COUCHBASE_URL} curl_command = """ curl -XPOST -H "Content-Type: application/json" -u Administrator:password \ https://{couchbase_url}:8094/api/bucket/project/scope/_default/index/{index_name}/query \ -d '{{ "query": {{ "match_none":{} }}, "size": 6, "knn": [ {{ "field": "vector_data", "k": {k}, "vector":{search_vector} }} ], "from": 0 }}' """.format(**args) try: result = subprocess.run(curl_command, shell=True, check=True,stdout=subprocess.PIPE) return result.stdout |
En resultado.stdout contiene el k más cercano Doc IDs. Puede ampliar el script para realizar una solicitud get en todos los ID devueltos y combinar los resultados para obtener el contexto final. Luego pasamos este contexto junto con la solicitud al LLM para obtener los resultados deseados.
Referencias
-
- Empezar a utilizar Couchbase Capella DBaaS gratis, hoy mismo
- Almacén vectorial LangChain Couchbase
¡Muy buena!