- Introducción
- Pre requisitos
- Lanzar la aplicación de IA Conversacional
- Flujo de procesamiento de documentos
- Uso de los documentos en la consulta
- Secuencia de Ejecución de RAG
- Conclusiones y siguientes pasos
Introducción
Desde manuales de usuario, reportes técnicos e informes de investigación, hasta contratos legales y transcripciones de llamadas, las empresas acumulan una enorme cantidad de contenido en formato de texto no estructurado. Procesar y extraer conocimiento útil de estos documentos suele ser una tarea tediosa y propensa a errores cuando se hace manualmente. Además, encontrar y recuperar información específica entre cientos o miles de documentos puede ser como buscar una aguja en un pajar.
En este artículo, complementarás el asistente de IA conversacional para ayudar a resolver estos desafíos. Utilizando técnicas de búsqueda semántica en documentos utilizando modelos embeddings como Amazon Titan y procesamiento con LLM como Anthropic Claude en Amazon Bedrock.
Manos a la obra!
Pre requisitos:
Para usar esta aplicación deberas contar con un usuario aws con permisos para usar Bedrock, un perfil local con ese usuario y python3. Aca puedes encontrar los pre requisitos.
Asegúrate de poder ejecutar en la aws cli antes de avanzar:
aws bedrock list-foundation-models
Instrucciones para lanzar la aplicación IA Conversacional
Clonar repositorio
Clona este repositorio y accede a la carpeta:
git clone https://github.com/ensamblador/generative-ai-chat-application.git
cd generative-ai-chat-application/03-personal-assistant-add-data
Configuración del entorno virtual
Esto es un entorno independiente de tu entorno python principal, todo lo que instalas en este entorno venv
quedará en la carpeta .venv
Crea y activa entorno virtual con sus dependencias:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Lanzar la aplicación
Inicia la aplicación del asistente (puedes elegir el puerto con --server.port
):
streamlit run chatbot_app.py --server.port 80
La aplicación estará disponible en http://localhost:80
Flujo de carga de documentos
En este proyecto puedes incorporar tres tipos de documentos: pdf, transcripciones de youtube y sitios web. Puede revisar una lista extensiva de loaders acá.
Documentos pdf
Cuando carga la aplicación veremos un nuevo menú lateral load documents
. Ahi podemos seleccionar un archivo pdf cualquiera y cargamos, escribimos un nombre para la colección y le damos al botón Submit
Nota: el gif está acelerado, tomó aproximadamente 5 minutos procesar el archivo.
El procesamiento de los documentos para ser utilizados por el asistente se realiza de la siguiente manera
- El usuario carga archivos PDF a través de un componente File Loader de Streamlit
uploaded_files = st.file_uploader("Elija documentos (pdf)", accept_multiple_files=True)
- Los PDFs se cargan con
PyPDFLoader
y se procesan para dividirlos en pasajes individuales
Es decir, obtenermos un documento dividido por splitby
caracteres (definidos en los parámetros de side bar), con un overlap de 10% aproximadamente.
def load_and_split_pdf(file_path, splitby):
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", "\. ", " ", ""],
chunk_size=splitby,
chunk_overlap=int(splitby/10),
length_function=len
)
loader = PyPDFLoader(file_path)
docs = loader.load_and_split(text_splitter)
return docs
- Cada una de las partes se procesan por un modelo que convierte texto a vectores, llamados embeddings, lo cual permite calcular distancia entre un texto de otro.
Para la base de vectores utilizamos ChromaDB dado que nos brinda la posibilidad de guardar nuestra base de datos de forma local. Te invito a revisar otras opciones que se pueden integrar como un vector database en langchain.
Para el uso en la aplicación creamos una clase de python que se encarga de inicializar, crear, almacenar y devolver las bases vectores.
bedrock_embeddings = BedrockEmbeddings(model_id="amazon.titan-embed-text-v1")
class CustomChromaClass():
def __init__(self) -> None:
if is_local: chroma_path = f"{user_name}/chroma"
else: chroma_path = f"{volume}/{user_name}/chroma"
# chroma (lowercase) client
self.persistent_client = chromadb.PersistentClient(path=chroma_path)
collections = self.persistent_client.list_collections()
self.vectordbs = {}
for collection in collections:
langchain_chroma = Chroma(
client=self.persistent_client,
collection_name=collection.name,
embedding_function=bedrock_embeddings
)
print(collection.name,":",langchain_chroma._collection.count())
self.vectordbs[collection.name] = langchain_chroma
self.vectordb = False
def create_vectordb(self, name):
name = clean_name(name)
# Chroma (capital C) is langchain chroma wrapper
vectordb = Chroma(
client=self.persistent_client,
embedding_function = bedrock_embeddings,
collection_name=name
)
self.vectordbs[name] = vectordb
self.vectordb = vectordb
return vectordb
def get_vectordb(self, name):
return self.vectordbs[name]
def add_docs(self, collection_name, docs, n=10):
add_docs_by_chunks(self.vectordbs[collection_name], docs, n)
En el siguiente diagrama se muestra el proceso completo.
Carga de sitios webs
Como ven, podemos utilizar el mismo concepto para cargar un sitio web completo. Ahora utilizamos WebBaseLoader
(basado en Beautiful Soup y Requests) para cargar todo el texto útil de un sitio web en una base de vectores.
from langchain.document_loaders import WebBaseLoader
...
loader = WebBaseLoader(url)
Carga de transcripciones de videos Youtube
Para cargar un video de youtube, tenemos que extraer la transcripción . Ahora utilizamos YoutubeLoader
que utiliza youtube-transcript-api para obtener las transcripciones o subtítulos existentes de un video de Youtube.
from langchain.document_loaders import YoutubeLoader
...
loader = YoutubeLoader.from_youtube_url(video_url, add_video_info=True )
Uso de los documentos en la consulta
Para consultar la base de datos vectorial, la aplicación busca pasajes similares al texto ingresado como consulta, utilizando la similitud entre embeddings. Luego utiliza estas partes como contexto para reponder. Esta técnica es conocida como Retrieval Augmented Generation. Problemos algunas consultas:
(asegura de hacer click en alguna base de conocimiento disponible que hayas cargado)
Input: cual es la diferencia entre ajuste fino de modelo y continuar el pre entrenamiento?
La aplicación se encarga de manejar la conexión a ChromaDB, ejecutar las consultas y retornar los resultados en texto para que el LLM genere la respuesta. Incluso podemos hacer preguntas de clarificación, gracias a la memoria que posee nuestro asistente:
Input: explícamelo mejor, como una analogía de la vida real
Podemos consultar otras bases creadas, por ejemplo la transcripción del video Youtube cargado (best apps for productivity):
Input: what apps are mentioned and what do they do?
Veamos que nos puede decir del blog que agregamos
de: Welcome to a New Era of Building in the Cloud with Generative AI on AWS
Input: what whas announced around generative ai?
Además se pueden combinar varias collections en una sola pregunta. Intentalo.
Secuencia de ejecución de RAG
A continuación vemos la secuencia cuando utilizamos RAG en nuestra pregunta.
El wrapper de chroma en langchain nos permite utilizar la base de vectores como un retriever, como vemos en vdb_lib.py
def get_retriever(name):
if 'chroma_client' in st.session_state:
vector_db = st.session_state.chroma_client.vectordbs[name]
return vector_db.as_retriever(search_type = "mmr", search_kwargs={"k": 10})
Nota: el retriever utiliza mmr (relevancia maxima margina) una forma de decir que no queremos documentos muy similares o repetidos.
Finalmente, utilizamos ConversationalRetrievalChain
de Lanchain para procesar todo: el retriever, la memoria y la historia dentro de la conversación.
En chatbot_lib.py
se invocará ConversationalRetrievalChain
ConversationChain
dependiendo si vamos a usar o no alguna colección de chroma.
def get_chat_rag_response(prompt, retriever, memory, streaming_callback=None, invocation_kwargs=None, model_id= None):
llm = get_llm(streaming_callback, invocation_kwargs, model_id)
qa = ConversationalRetrievalChain.from_llm(
llm,
verbose=True,
retriever=retriever,
memory=memory,
get_chat_history=lambda h : h # workaround dado que utilizamos la misma memoria para RetrievalChain y ConversationChain
)
return qa.run({"question": prompt})
Ahora nuestro asistente no solo nos sugiere texto sino que también conoce de nuestros documentos y es capaz de responder utilizando la información relevante de ellos. Lo puedes utilizar para saber que existe detrás de toda esa información en un instante.
how cool is that!
Conclusiones y siguientes pasos
En esta sesión, tomamos el Asistente de IA generativo y lo supercargamos con búsqueda semántica y RAG para responder preguntas encontrando pasajes relevantes en los documentos. A partir de aquí, puedes seguir construyendo sobre esta base de muchas maneras:
- Agregar capacidades resumen de documentos al momento de la carga.
- Usar nuevos loaders
- Experimentar diferentes tamaños de splitters y embeddings como Cohere Embed-Multilingual
- Experimentar con Self-Query
Recomendacion para estudio adicional
- Como siempre, si quieres sacar el jugo a los prompts te recomiendo hacer la guia de prompting (usando tu nuevo asistente)
- Este excelente tutorial de Langchain de @gkamradt
- Si tienes tiempo, este workshop paso a paso para construir aplicaciones de IA Generativa con Amazon Bedrock y Langchain
Stay tuned!
Acerca de los autores:
Top comments (0)