inteligencia_artificial:langchain:documentos
Diferencias
Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa | ||
| inteligencia_artificial:langchain:documentos [2025/01/12 17:13] – [Troceado de documentos] alberto | inteligencia_artificial:langchain:documentos [2025/01/31 00:44] (actual) – [Base de datos vectorial] alberto | ||
|---|---|---|---|
| Línea 60: | Línea 60: | ||
| ===== Troceado de documentos ===== | ===== Troceado de documentos ===== | ||
| - | La información almacenada debe "trocearse" para poder trabajar con ella sin problemas de tener ventanas de contexto demasiado | + | Los modelos de lenguaje, o LLM, sólo pueden examinar unos pocos miles de palabras de una vez, por lo que tenemos que necesitamos una solución para documentos que sean largos. |
| + | {{ : | ||
| + | |||
| + | Podemos | ||
| Puede resultar sencillo conceptualmente, | Puede resultar sencillo conceptualmente, | ||
| + | Es importante que cada " | ||
| + | Cuando especificamos el //text splitter// no sólo indicamos el tamaño del " | ||
| + | El tamaño de las partes puede ser definidas a partir de tokens o de caracteres. Es importante que mantengan una cohesión de metadatos, de forma que partes específicas tengan metadatos específicos, | ||
| + | |||
| + | A continuación algunos tipos de //text splitters// proporcionados por Langchain en **langchain.text_splitter.**: | ||
| + | * **CharacterTextSplitter()** Pensado para textos estructurados con separadores claros, como párrafos o líneas. Divide el texto en chunks basándose en un separador fijo (// | ||
| + | * **MarkdownHeaderTextSplitter** Pensado para documentos en formato Markdown con encabezados jerárquicos (manuales técnicos, blogs, etc.). Divide los textos en función de los encabezados Markdown (como #, ##, ###, etc.), creando una estructura jerárquica de secciones y subsecciones. | ||
| + | * **TokenTextSplitter()** Divide el texto en función del número de tokens. Los parámetros principales son // | ||
| + | * **SentenceTransformersTokenTextSplitter()** Similar a TokenTextSplitter, | ||
| + | * **RecursiveCharacterTextSplitter()** Pensado para textos largos y desestructurados, | ||
| + | * **Language()** Usa el paquete langchain.text_splitter.Language para dividir texto basado en las características específicas de un idioma. Es una herramienta auxiliar, no un splitter completo. Se utiliza como base para otros splitters (por ejemplo, para configurar idiomas específicos en SpacyTextSplitter o NLTKTextSplitter). El parámetro principal es // | ||
| + | * **NLTKTextSplitter()** Pensado para textos donde es importante preservar oraciones completas, como artículos o transcripciones. Usa el módulo NLTK para dividir el texto en oraciones (tokenización basada en oraciones). Es ideal para textos donde el corte en oraciones es importante. Los parámetros principales son // | ||
| + | * **SpacyTextSplitter()** Pensado para textos donde la gramática y las oraciones completas son importantes, | ||
| + | |||
| + | En el siguiente ejemplo vamos a usar 2 //text splitters// comunes, //recursive character text splitter// y //character text splitter//. | ||
| + | Para usar expresiones regulares con //recursive character text splitter// expresiones regulares, con el parámetro // | ||
| + | |||
| + | Lo vemos con unos ejemplos: | ||
| + | |||
| + | <code python> | ||
| + | # Dependencias | ||
| + | from langchain.text_splitter import RecursiveCharacterTextSplitter, | ||
| + | |||
| + | # Configuración de los " | ||
| + | chunk_size = 26 | ||
| + | chunk_overlap = 4 | ||
| + | |||
| + | # Instanciamos los splitters | ||
| + | r_splitter = RecursiveCharacterTextSplitter( | ||
| + | chunk_size = chunk_size, | ||
| + | chunk_overlap = chunk_overlap | ||
| + | ) | ||
| + | |||
| + | c_splitter = CharacterTextSplitter( | ||
| + | chunk_size = chunk_size, | ||
| + | chunk_overlap = chunk_overlap | ||
| + | ) | ||
| + | |||
| + | # Textos de prueba | ||
| + | text1 = ' | ||
| + | text2 = ' | ||
| + | text3 = "a b c d e f g h i j k l m n o p q r s t u v w x y z" | ||
| + | |||
| + | # Ejemplo en el que comprobamos el RecursiveCharacterTextSplitter | ||
| + | print(r_splitter.split_text(text1)) # Si el chunk_size es de mismo tamaño del texto, no se divide | ||
| + | print(r_splitter.split_text(text2)) # Si se divide, se añaden los caracteres de chunk_overlap de la sección anterior | ||
| + | |||
| + | # Ejemplo en el que vemos la diferencia entre RecursiveCharacterTextSplitter y CharacterTextSplitter | ||
| + | print(r_splitter.split_text(text3)) # En este caso, cuenta los espacios como caracteres | ||
| + | print(c_splitter.split_text(text3)) # El CharacterTextSplitter puede " | ||
| + | |||
| + | # Ejemplo en el que especificamos como carácter separador del CharacterTextSplitter el espacio ' ' | ||
| + | print(c_splitter.split_text(text3)) # En este caso, como hemos especificado el espacio como separador, puede dividir el texto en donde se encuentre ' ', por lo que respeta el chunksize, puesto que hay un ' ' para poder " | ||
| + | </ | ||
| + | |||
| + | Para textos normales, funciona mejor el splitter recursivo. Vamos a ver otro ejemplo con texto normal para entender mejor su funcionamiento: | ||
| + | |||
| + | <code python> | ||
| + | # Dependencias | ||
| + | from langchain.text_splitter import RecursiveCharacterTextSplitter, | ||
| + | |||
| + | some_text = """ | ||
| + | Esto puede transmitir al lector qué ideas están relacionadas. Por ejemplo, las ideas estrechamente relacionadas \ | ||
| + | están en oraciones. Las ideas similares están en párrafos. Los párrafos forman un documento. \n\n \ | ||
| + | Los párrafos suelen estar delimitados por uno o dos retornos de carro. \ | ||
| + | Los retornos de carro son la "barra invertida n" que se ve incrustada en esta cadena. \ | ||
| + | Las oraciones tienen un punto al final, pero también tienen un espacio. \ | ||
| + | Y las palabras están separadas por espacios.""" | ||
| + | |||
| + | print(len(some_text)) | ||
| + | |||
| + | # Vamos a ver como se comportan los splitters en este texto | ||
| + | c_splitter = CharacterTextSplitter( | ||
| + | chunk_size=450, | ||
| + | chunk_overlap=0, | ||
| + | separator = ' ' | ||
| + | ) | ||
| + | |||
| + | r_splitter = RecursiveCharacterTextSplitter( | ||
| + | chunk_size=450, | ||
| + | chunk_overlap=0, | ||
| + | separators=[" | ||
| + | ) | ||
| + | |||
| + | print(c_splitter.split_text(some_text)) # En este caso, el texto se divide en chunks de 450 caracteres, y se corta en un espacio ' ' si lo encuentra | ||
| + | |||
| + | print(r_splitter.split_text(some_text)) # En este caso, el texto se divide en chunks de 450 caracteres, y se corta en los separadores que hemos especificado, | ||
| + | |||
| + | # Vamos a probar a cambiar el tamaño de los chunks | ||
| + | r_splitter = RecursiveCharacterTextSplitter( | ||
| + | chunk_size=150, | ||
| + | chunk_overlap=0, | ||
| + | separators=[" | ||
| + | ) | ||
| + | |||
| + | print(r_splitter.split_text(some_text)) # En este caso, el texto se divide en chunks de 150 caracteres, y ocurre un problema: el punto ' | ||
| + | |||
| + | # Vamos a usar una expresión regular para que se tenga en cuenta el punto ' | ||
| + | r_splitter = RecursiveCharacterTextSplitter( | ||
| + | chunk_size=150, | ||
| + | chunk_overlap=0, | ||
| + | is_separator_regex=True, | ||
| + | separators=[" | ||
| + | ) | ||
| + | |||
| + | print(r_splitter.split_text(some_text)) | ||
| + | </ | ||
| ===== Guardado de información ===== | ===== Guardado de información ===== | ||
| Línea 71: | Línea 181: | ||
| ===== Salida ===== | ===== Salida ===== | ||
| ===== Embeddings ===== | ===== Embeddings ===== | ||
| - | Los modelos de lenguaje, o LLM, sólo pueden examinar unos pocos miles de palabras de una vez, por lo que tenemos que buscar una solución | + | Una vez " |
| - | {{ : | + | |
| - | + | Los embeddings | |
| - | Para almacenar la información de los documentos usamos // | + | |
| {{ : | {{ : | ||
| {{ : | {{ : | ||
| - | En el ejemplo de la imagen, los dos primeros casos hablan de mascotas, mientras que el tercero habla de un coche, por lo que los vectores entre los dos primeros son similares (cerca espacialmente, | + | En el ejemplo de la imagen, los dos primeros casos hablan de mascotas, mientras que el tercero habla de un coche, por lo que los vectores entre los dos primeros son similares (cerca espacialmente, |
| - | + | Los embeddings dependen de un modelo de IA. OpenAI tiene sus embeddings, Llama los suyos, etc. De modo que podemos usar sus APIs para generar estos vectores, aunque no se usen sus LLM. \\ | |
| - | ===== Base de datos de vectores | + | ===== Base de datos vectorial |
| - | A la base de datos de vectores | + | Los vectores |
| - | + | ||
| - | Si los documentos son muy grandes, hay que " | + | |
| {{ : | {{ : | ||
| Línea 94: | Línea 200: | ||
| {{ : | {{ : | ||
| - | La consulta | + | Para realizar una búsqueda semántica en la base de datos, |
| {{ : | {{ : | ||
| + | En langchain existen más de 30 tipos de base de datos vectoriales diferentes que pueden [[https:// | ||
| + | |||
| + | En el siguiente ejemplo cargamos el texto de varios PDFs, dividimos la información en partes, usando los embeddings de Llama 3.1 pasamos las partes a vectores, y finalmente los guardamos en una base de datos vectorial de tipo Chroma por su simplicidad: | ||
| + | <code python> | ||
| + | from langchain_community.document_loaders import PyPDFLoader | ||
| + | |||
| + | # Cargamos los documentos | ||
| + | loaders = [ | ||
| + | PyPDFLoader(" | ||
| + | PyPDFLoader(" | ||
| + | PyPDFLoader(" | ||
| + | PyPDFLoader(" | ||
| + | PyPDFLoader(" | ||
| + | ] | ||
| + | |||
| + | docs = [] | ||
| + | for loader in loaders: | ||
| + | docs.extend(loader.load()) | ||
| + | |||
| + | # Troceamos los documentos | ||
| + | from langchain.text_splitter import RecursiveCharacterTextSplitter | ||
| + | text_splitter = RecursiveCharacterTextSplitter( | ||
| + | chunk_size = 500, | ||
| + | chunk_overlap = 50 | ||
| + | ) | ||
| + | |||
| + | splits = text_splitter.split_documents(docs) | ||
| + | |||
| + | # Definimos el modelo de embeddings | ||
| + | from langchain_ollama import OllamaEmbeddings | ||
| + | |||
| + | embedding = OllamaEmbeddings(model=" | ||
| + | |||
| + | # Guardamos los datos en una base de datos vectorial | ||
| + | from langchain_community.vectorstores import Chroma | ||
| + | |||
| + | # Definimos el directorio donde se guardará la base de datos vectorial | ||
| + | persist_directory = ' | ||
| + | |||
| + | # Eliminamos el directorio si ya existe, es decir, borramos la base de datos anterior antes de volver a lanzar el sript | ||
| + | import shutil | ||
| + | |||
| + | path = " | ||
| + | |||
| + | try: | ||
| + | shutil.rmtree(path) | ||
| + | print(f" | ||
| + | |||
| + | except FileNotFoundError: | ||
| + | print(f" | ||
| + | |||
| + | except Exception as e: | ||
| + | print(f" | ||
| + | |||
| + | # Creamos la base de datos vectorial | ||
| + | vectordb = Chroma.from_documents( | ||
| + | documents = splits, | ||
| + | embedding = embedding, | ||
| + | persist_directory = persist_directory | ||
| + | ) | ||
| + | |||
| + | # Vamos a realizar una consulta | ||
| + | question = "¿En qué parte de la península hay seres fantásticos como hadas y gnomos?" | ||
| + | docus = vectordb.similarity_search(question, | ||
| + | print(f" | ||
| + | |||
| + | print(f" | ||
| + | print(f" | ||
| + | print(f" | ||
| + | </ | ||
| + | |||
| + | Al final del ejemplo anterior realizamos una búsqueda en la base de datos. | ||
| + | Al haber documentos duplicados, podemos obtener diferentes respuestas prácticamente iguales. | ||
| + | También puede ocurrir que se obtenga la información de algunos documentos (vectores), cuando hay otros más idóneos para la consulta realizada. | ||
| + | Veremos en el siguiente apartado // | ||
| ===== Métodos de extracción de información ===== | ===== Métodos de extracción de información ===== | ||
| Hay varios métodos a partir de los cuales se puede extraer la información más importante (la más similar vectorialmente) de las diferentes partes en que se dividió la información. | Hay varios métodos a partir de los cuales se puede extraer la información más importante (la más similar vectorialmente) de las diferentes partes en que se dividió la información. | ||
inteligencia_artificial/langchain/documentos.1736698401.txt.gz · Última modificación: por alberto
