Guía del autoestopista para el trapo: desde los archivos pequeños hasta Tolstoi con la API y Langchain de Operai

, Te guié a través de la creación de una tubería de trapo muy simple en Pythonusando la API de OpenAI, Langchain y sus archivos locales. En esa publicación, cubro los conceptos básicos de la creación de incrustaciones de sus archivos locales con Langchain, almacenándolos en una base de datos vectorial con FAISS, haciendo llamadas de API a la API de OpenAi y, en última instancia, generando respuestas relevantes para sus archivos. 🌟

Imagen del autor

No obstante, en este simple ejemplo, solo demuestro cómo usar un pequeño archivo .txt. En esta publicación, explico aún más cómo puede utilizar archivos más grandes con su tubería de trapo agregando un paso adicional al proceso, fragmento.

¿Qué pasa con la fastidia?

La fragmentación se refiere al proceso de analizar un texto en piezas de texto más pequeñas (chunks) que luego se transforman en incrustaciones. Esto es muy importante porque nos permite procesar y crear incrustaciones para archivos más grandes. Todos los modelos de incrustación vienen con varias limitaciones en el tamaño del texto que se pasa: entraré en más detalles sobre esas limitaciones en un momento. Estas limitaciones permiten un mejor rendimiento y respuestas de baja latencia. En el caso de que el texto que proporcionamos no cumpla con esas limitaciones de tamaño, se truncará o rechazará.

Si queríamos crear una lectura de tuberías de trapo, digamos de Leo Tolstoy’s Guerra y paz Texto (un libro bastante grande), no podríamos cargarlo directamente y transformarlo en una sola incrustación. En cambio, primero debemos hacer el fragmento – Crea trozos de texto más pequeños y crea incrustaciones para cada uno. Cada fragmento está por debajo de los límites de tamaño de cualquier modelo de incrustación que usemos nos permite transformar efectivamente cualquier archivo en incrustaciones. Entonces, un algo mas El paisaje realista de una tubería de trapo se vería de la siguiente manera:

Imagen del autor

Hay varios parámetros para personalizar aún más el proceso de fragmentación y ajustarlo a nuestras necesidades específicas. Un parámetro clave del proceso de fábrica es el Tamaño del trozoque nos permite especificar cuál será el tamaño de cada fragmento (en caracteres o tokens). El truco aquí es que los trozos que creamos deben ser lo suficientemente pequeños como para procesarse dentro de las limitaciones de tamaño de la incrustación, pero al mismo tiempo, también deben ser lo suficientemente grandes como para incorporar información significativa.

Por ejemplo, supongamos que queremos procesar la siguiente oración desde Guerra y pazdonde el príncipe Andrew contempla la batalla:

Imagen del autor

Supongamos también que creamos los siguientes trozos (bastante pequeños):

Imagen del autor

Entonces, si tuviéramos que preguntar algo como “¿Qué quiere decir el Príncipe Andrew por ‘todos los mismos ahora’?”, Es posible que no obtengamos una buena respuesta porque el trozo “¿Pero no es todo lo mismo ahora?” pensó él. no contiene ningún contexto y es vago. En contraste, el significado se dispersa en múltiples fragmentos. Por lo tanto, aunque es similar a la pregunta que hacemos y puede recuperarse, no contiene ningún significado para producir una respuesta relevante. Por lo tanto, seleccionar el tamaño de fragmento apropiado para el proceso de fragmentación en línea con el tipo de documentos que utilizamos para el trapo, puede influir en gran medida en la calidad de las respuestas que recibiremos. En general, el contenido de una fragmentación debería tener sentido para que un humano lo lea sin otra información, para poder tener sentido para el modelo. En última instancia, existe una compensación por el tamaño de la fragmentación: los trozos deben ser lo suficientemente pequeños como para cumplir con las limitaciones de tamaño del modelo de incrustación, pero lo suficientemente grande como para preservar el significado.

• • •

Otro parámetro significativo es la superposición del fragmento. Esa es la superposición que queremos que los trozos tengan entre nosotros. Por ejemplo, en el Guerra y paz Ejemplo, obtendríamos algo como los siguientes fragmentos si elegimos una superposición de fragmentos de 5 caracteres.

Imagen del autor

Esta es también una decisión muy importante que tenemos que tomar porque:

  • Una superposición más grande significa más llamadas y tokens gastados en la creación de incrustación, lo que significa más caro + más lento
  • Superposición más pequeña significa una mayor probabilidad de perder información relevante entre los límites del fragmento

Elegir la superposición correcta de la fragmentación depende en gran medida del tipo de texto que deseamos procesar. Por ejemplo, un libro de recetas donde el idioma es simple y directo probablemente no requerirá una metodología de fragmentación exótica. Por otro lado, un libro de literatura clásico como Guerra y pazdonde el lenguaje es muy complejo y el significado está interconectado en diferentes párrafos y secciones, probablemente requerirá un enfoque más reflexivo para fragmentar para que el trapo produzca resultados significativos.

• • •

Pero, ¿qué pasa si todo lo que necesitamos es un trapo más simple que admire a un par de documentos que se ajustan a las limitaciones de tamaño de cualquier modelo de incrustaciones que usamos en un solo trozo? ¿Todavía necesitamos el paso de fragmentación, o podemos hacer directamente una sola incrustación para todo el texto? La respuesta corta es que siempre es mejor realizar el paso de fragmentación, incluso para una base de conocimiento que se ajuste a los límites de tamaño. Eso es porque, como resultado, cuando se trata de documentos grandes, enfrentamos el problema de conseguir perdido en el medio – Falta de información relevante que se incorpora en documentos grandes y grandes incrustaciones respectivas.

¿Cuáles son esas misteriosas ‘limitaciones de tamaño’?

En general, una solicitud a un modelo de incrustación puede incluir uno o más trozos de texto. Hay varios tipos diferentes de limitaciones que debemos considerar relativamente al tamaño del texto para el que necesitamos crear incrustaciones y su procesamiento. Cada uno de esos diferentes tipos de límites requiere diferentes valores dependiendo del modelo de incrustación que utilizamos. Más específicamente, estos son:

  • Tamaño del trozoo también tokens máximos por entrada o ventana de contexto. Este es el tamaño máximo en las fichas para cada fragmento. Por ejemplo, para OpenAi’s text-embedding-3-small modelo de incrustación, el El límite de tamaño de la fragmentación es de 8,191 fichas. Si proporcionamos un fragmento que es más grande que el límite del tamaño de la fragmentación, en la mayoría de los casos, se truncará silenciosamente‼ ️ (se creará una incrustación, pero solo para la primera parte que cumple con el límite del tamaño de la fragmentación), sin producir ningún error.
  • Número de fragmentos por solicitudo también número de entradas. También hay un límite en el número de fragmentos que se pueden incluir en cada solicitud. Por ejemplo, todos los modelos de incrustación de OpenAI tienen un límite de 2,048 entradas, es decir, Un máximo de 2,048 fragmentos por solicitud.
  • Tokens totales por solicitud: También hay una limitación sobre el número total de tokens de todos los trozos en una solicitud. Para todos los modelos de OpenAi, El número máximo total de tokens en todos los fragmentos en una sola solicitud es de 300,000 tokens.

Entonces, ¿qué sucede si nuestros documentos son más de 300,000 tokens? Como habrá imaginado, la respuesta es que hacemos múltiples solicitudes consecutivas/paralelas de 300,000 tokens o menos. Muchas bibliotecas de Python hacen esto automáticamente detrás de escena. Por ejemplo, Langchain’s OpenAIEmbeddings Que uso en mi publicación anterior, lote automáticamente los documentos que proporcionamos en lotes de menos de 300,000 tokens, dado que los documentos ya se proporcionan en fragmentos.

Leer archivos más grandes en la tubería de trapo

Echemos un vistazo a cómo se desarrollan todos estos en un simple ejemplo de Python, usando el Guerra y paz Texto como documento para recuperar en el trapo. Los datos que estoy usando – Leo Tolstoy’s Guerra y paz texto: tiene licencia como dominio público y se puede encontrar en Proyecto Gutenberg.

Entonces, en primer lugar, intentemos leer del Guerra y paz texto sin ninguna configuración para fragmentos. Para este tutorial, necesitará haber instalado el langchain, openaiy faiss Bibliotecas de Python. Podemos instalar fácilmente los paquetes requeridos de la siguiente manera:

pip install openai langchain langchain-community langchain-openai faiss-cpu

Después de asegurarse de que las bibliotecas requeridas estén instaladas, nuestro código para un trapo muy simple se ve así y funciona bien para un archivo .txt pequeño y simple en el text_folder.

from openai import OpenAI # Chat_GPT API key 
api_key = "your key" 

# initialize LLM
llm = ChatOpenAI(openai_api_key=api_key, model="gpt-4o-mini", temperature=0.3)

# loading documents to be used for RAG 
text_folder =  "RAG files"  

documents = []
for filename in os.listdir(text_folder):
    if filename.lower().endswith(".txt"):
        file_path = os.path.join(text_folder, filename)
        loader = TextLoader(file_path)
        documents.extend(loader.load())

# generate embeddings
embeddings = OpenAIEmbeddings(openai_api_key=api_key)

# create vector database w FAISS 
vector_store = FAISS.from_documents(documents, embeddings)
retriever = vector_store.as_retriever()


def main():
    print("Welcome to the RAG Assistant. Type 'exit' to quit.\n")
    
    while True:
        user_input = input("You: ").strip()
        if user_input.lower() == "exit":
            print("Exiting…")
            break

        # get relevant documents
        relevant_docs = retriever.invoke(user_input)
        retrieved_context = "\n\n".join([doc.page_content for doc in relevant_docs])

        # system prompt
        system_prompt = (
            "You are a helpful assistant. "
            "Use ONLY the following knowledge base context to answer the user. "
            "If the answer is not in the context, say you don't know.\n\n"
            f"Context:\n{retrieved_context}"
        )

        # messages for LLM 
        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_input}
        ]

        # generate response
        response = llm.invoke(messages)
        assistant_message = response.content.strip()
        print(f"\nAssistant: {assistant_message}\n")

if __name__ == "__main__":
    main()

Pero, si agrego el Guerra y paz .txt archivo en la misma carpeta e intente crear directamente una incrustación para él, obtengo el siguiente error:

Imagen del autor

ughh 🙃

Entonces, ¿qué pasa aquí? Langchain’s OpenAIEmbeddingsNo se puede dividir el texto en iteraciones separadas de menos de 300,000 tokens, porque no lo proporcionamos en trozos. No divide el fragmento, que es 777,181 tokens, lo que lleva a una solicitud que excede los 300,000 tokens máximo por solicitud.

• • •

Ahora, intentemos configurar el proceso de fragmentación para crear múltiples incrustaciones desde este archivo grande. Para hacer esto, usaré el text_splitter Biblioteca proporcionada por Langchain, y más específicamente, el RecursiveCharacterTextSplitter. En RecursiveCharacterTextSplitterel tamaño del trozo y los parámetros de superposición de la fragmentación se especifican como varios caracteres, pero otros divisores como TokenTextSplitter o OpenAITokenSplitter También permita configurar estos parámetros como una serie de tokens.

Entonces, podemos configurar una instancia del divisor de texto como se muestra a continuación:

splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)

… y luego úselo para dividir nuestro documento inicial en trozos …

split_docs = []
for doc in documents:
    chunks = splitter.split_text(doc.page_content)
    for chunk in chunks:
        split_docs.append(Document(page_content=chunk))

… y luego usa esos trozos para crear los incrustaciones …

documents= split_docs

# create embeddings + FAISS index
embeddings = OpenAIEmbeddings(openai_api_key=api_key)
vector_store = FAISS.from_documents(documents, embeddings)
retriever = vector_store.as_retriever()

.....

… Y Voila 🌟

Ahora nuestro código puede analizar efectivamente el documento proporcionado, incluso si es un poco más grande, y proporcionar respuestas relevantes.

Imagen del autor

En mi mente

Elegir un enfoque de fragmentación que se ajuste al tamaño y la complejidad de los documentos que queremos alimentar en nuestra tubería RAG es crucial para la calidad de las respuestas que recibiremos. Por supuesto, hay varios otros parámetros y diferentes metodologías de fragmentación que uno debe tener en cuenta. No obstante, la comprensión y el tamaño del trozo y la superposición de fragmentos es la base para construir tuberías de trapo que producen resultados significativos.

• • •

¿Me encantó esta publicación? ¿Tienes datos interesantes o un proyecto de IA?

¡Seamos amigos! Unirse a mí

📰Sustitución 📝Medio 💼LinkedInCómprame un café!

• • •