Crear un ChatGPT para videos de YouTube con Langchain.

Create a ChatGPT for YouTube videos with Langchain.

Introducción

¿Alguna vez te has preguntado lo bueno que sería chatear con un video? Como persona que escribe en un blog, a menudo me aburre ver un video de una hora para encontrar información relevante. A veces parece un trabajo ver un video para obtener información útil. Así que construí un chatbot que te permite chatear con videos de YouTube o cualquier video. Esto fue posible gracias a GPT-3.5-turbo, Langchain, ChromaDB, Whisper y Gradio. Por lo tanto, en este artículo, haré una revisión de código de construcción de un chatbot funcional para videos de YouTube con Langchain.

Objetivos de aprendizaje

  • Construir la interfaz web utilizando Gradio
  • Manejar videos de YouTube y extraer datos textuales de ellos utilizando Whisper
  • Procesar y formatear textos adecuadamente
  • Crear embeddings de datos de texto
  • Configurar Chroma DB para almacenar datos
  • Inicializar una cadena de conversación Langchain con chatGPT de OpenAI, ChromaDB y función de embeddings
  • Finalmente, consultar y transmitir respuestas al chatbot de Gradio

Antes de llegar a la parte de codificación, familiaricémonos con las herramientas y tecnologías que usaremos.

Este artículo fue publicado como parte de Data Science Blogathon.

Langchain

Langchain es una herramienta de código abierto escrita en Python que hace que los Modelos de Lenguaje Grande sean conscientes de los datos y agentes. Entonces, ¿qué significa eso? La mayoría de los LLM comerciales disponibles, como GPT-3.5 y GPT-4, tienen un límite en los datos en los que están entrenados. Por ejemplo, ChatGPT solo puede responder preguntas que ya ha visto. Cualquier cosa después de septiembre de 2021 es desconocida para él. Este es el problema principal que resuelve Langchain. Ya sea un documento de Word o cualquier PDF personal, podemos alimentar los datos a un LLM y obtener una respuesta similar a la humana. Tiene envoltorios para herramientas como Vector DBs, modelos de chat y funciones de embedding, lo que hace que sea fácil construir una aplicación de inteligencia artificial utilizando solo Langchain.

Langchain también nos permite construir Agentes: bots LLM. Estos agentes autónomos se pueden configurar para múltiples tareas, incluido el análisis de datos, las consultas SQL e incluso la escritura de códigos básicos. Hay muchas cosas que podemos automatizar con estos agentes. Esto es útil ya que podemos subcontratar el trabajo de conocimientos de bajo nivel a un LLM, lo que nos ahorra tiempo y energía.

En este proyecto, usaremos herramientas de Langchain para construir una aplicación de chat para videos. Para obtener más información sobre Langchain, visite su sitio oficial.

Whisper

Whisper es otra progenie de OpenAI. Es un modelo de conversión de habla a texto de propósito general que puede convertir audio o videos en texto. Está entrenado en una gran cantidad de audio diverso para realizar traducciones multilingües, reconocimiento de voz y clasificación.

El modelo está disponible en cinco tamaños diferentes: tiny, base, Zepes, small y large, con compensaciones de velocidad y precisión. El rendimiento de los modelos también depende del lenguaje. La figura a continuación muestra una desglose de WER (tasa de error de palabras) por idiomas del conjunto de datos de Fleur utilizando el modelo large-v2.

Source: https://github.com/openai/whisper

Bases de datos vectoriales

La mayoría de los algoritmos de aprendizaje automático no pueden procesar datos sin estructurar como imágenes, audio, video y texto. Deben convertirse en matrices de embeddings de vector. Estos embeddings de vector representan los datos en un plano multidimensional. Para obtener embeddings, necesitamos modelos de aprendizaje profundo altamente eficientes capaces de capturar el significado semántico de los datos. Esto es muy importante para hacer cualquier aplicación de inteligencia artificial. Para almacenar y consultar estos datos, necesitamos bases de datos capaces de manejarlos de manera efectiva. Esto resultó en la creación de bases de datos especializadas llamadas bases de datos vectoriales. Hay múltiples bases de datos de código abierto disponibles. Chroma, Milvus, Weaviate y FAISS son algunos de los más populares.

Otra ventaja de las tiendas vectoriales es que podemos realizar operaciones de búsqueda de alta velocidad en datos sin estructurar. Una vez que obtenemos los embeddings, podemos usarlos para agrupar, buscar, ordenar y clasificar. Como los puntos de datos están en un espacio vectorial, podemos calcular la distancia entre ellos para saber cuán relacionados están. Se utilizan múltiples algoritmos como la similitud del coseno, la distancia euclidiana, KNN y ANN (vecino más cercano aproximado) para encontrar puntos de datos similares.

Usaremos Chroma vector store – una base de datos de vectores de código abierto. Chroma también tiene integración con Langchain, lo cual será muy útil.

Gradio

El cuarto jinete de nuestra aplicación Gradio es una biblioteca de código abierto para compartir modelos de aprendizaje automático fácilmente. También puede ayudar a construir aplicaciones web de demostración con sus componentes y eventos con Python.

Si no está familiarizado con Gradio y Langchain, lea los siguientes artículos antes de continuar.

  • Construyamos ChatGPT con Gradio
  • Construya un ChatGPT para PDFs

Comencemos a construirlo ahora.

Configurar el entorno de desarrollo

Para configurar el entorno de desarrollo, cree un entorno virtual de Python o cree un entorno de desarrollo local con Docker.

Ahora instale todas estas dependencias

pytube==15.0.0
gradio == 3.27.0
openai == 0.27.4
langchain == 0.0.148
chromadb == 0.3.21
tiktoken == 0.3.3
openai-whisper==20230314

Importar bibliotecas

import os
import tempfile
import whisper
import datetime as dt
import gradio as gr
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain
from pytube import YouTube
from typing import TYPE_CHECKING, Any, Generator, List

Crear interfaz web

Usaremos Gradio Block y componentes para construir la interfaz de nuestro aplicación. Así es como puede hacer la interfaz. Siéntase libre de personalizarla como desee.

with gr.Blocks() as demo:
    
        with gr.Row():
            # with gr.Group():
                with gr.Column(scale=0.70):
                    api_key = gr.Textbox(placeholder='Ingrese la clave de la API de OpenAI', 
                    show_label=False, interactive=True).style(container=False)
                with gr.Column(scale=0.15):
                    change_api_key = gr.Button('Cambiar clave')
                with gr.Column(scale=0.15):
                    remove_key = gr.Button('Eliminar clave')
        
        with gr.Row():
            with gr.Column():
                
                chatbot = gr.Chatbot(value=[]).style(height=650)
                query = gr.Textbox(placeholder='Ingrese la consulta aquí', 
                                    show_label=False).style(container=False)
     
            with gr.Column():
                video = gr.Video(interactive=True,) 
                start_video = gr.Button('Iniciar transcripción')
                gr.HTML('O')
                yt_link = gr.Textbox(placeholder='Pegue aquí un enlace de YouTube', 
                                     show_label=False).style(container=False)
                yt_video = gr.HTML(label=True)
                start_ytvideo = gr.Button('Iniciar transcripción')
                gr.HTML('Por favor, reinicie la aplicación después de haber terminado con la aplicación para eliminar recursos')
                reset = gr.Button('Reiniciar aplicación')
                

if __name__ == "__main__":
    demo.launch()  

La interfaz aparecerá así

Aquí, tenemos una caja de texto que toma la clave de OpenAI como entrada. Y también dos claves para cambiar la clave de la API y eliminar la clave. También tenemos una interfaz de chat a la izquierda y una caja para renderizar videos locales a la derecha. Inmediatamente debajo de la caja de video, tenemos una caja que pide un enlace de YouTube y botones que dicen “Iniciar transcripción”.

Eventos de Gradio

Ahora definiremos eventos para hacer que la aplicación sea interactiva. Agregue los siguientes códigos al final del Gradio Blocks().

start_video.click(fn=lambda :(pause, update_yt), 
                     outputs=[start2, yt_video]).then(
                     fn=embed_video, inputs=

, 
                     outputs=

).success(
                     fn=lambda:resume, 
                     outputs=[start2])
       
start_ytvideo.click(fn=lambda :(pause, update_video), 
                     outputs=[start1,video]).then(
                    fn=embed_yt, inputs=[yt_link], 
                    outputs = [yt_video, chatbot]).success(
                    fn=lambda:resume, outputs=[start1])
        
query.submit(fn=add_text, inputs=[chatbot, query], 
                     outputs=[chatbot]).success(
                     fn=QuestionAnswer, 
                    inputs=[chatbot,query,yt_link,video], 
                    outputs=[chatbot,query])
        
api_key.submit(fn=set_apikey, inputs=api_key, outputs=api_key)
change_api_key.click(fn=enable_api_box, outputs=api_key)  
remove_key.click(fn = remove_key_box, outputs=api_key)
reset.click(fn = reset_vars, outputs=[chatbot,query, video, yt_video, ])
  • start_video: Al hacer clic, activará el proceso de obtención de textos del video y creará una cadena de conversación.
  • start_ytvideo: Al hacer clic hará lo mismo pero ahora desde el video de YouTube, y cuando se complete, mostrará el video de YouTube justo debajo de él.
  • query: Responsable de la transmisión de la respuesta desde LLM a la interfaz de chat.

El resto de los eventos se encargan de la clave de API y de reiniciar la aplicación.

Hemos definido los eventos pero no hemos definido las funciones responsables de activar los eventos.

Backend

Para no complicarlo y hacerlo desordenado, delinearemos los procesos con los que trataremos en el backend.

  • Manejar claves de API.
  • Manejar video cargado.
  • Transcribir videos para obtener textos.
  • Crear fragmentos de textos de video.
  • Crear incrustaciones a partir de textos.
  • Almacenar incrustaciones vectoriales en el almacén de vectores ChromaDB.
  • Crear una cadena de recuperación conversacional con Langchain.
  • Enviar documentos relevantes al modelo de chat OpenAI (gpt-3.5-turbo).
  • Obtener la respuesta y transmitirla en la interfaz de chat.

Haremos todas estas cosas junto con algunos manejos de excepciones.

Definir algunas variables de entorno.

chat_history = []
result = None
chain = None
run_once_flag = False
call_to_load_video = 0

enable_box = gr.Textbox.update(value=None,placeholder='Cargar su clave de API de OpenAI',
                               interactive=True)
disable_box = gr.Textbox.update(value='La clave de API de OpenAI está establecida',
                               interactive=False)
remove_box = gr.Textbox.update(value='Su clave de API se eliminó correctamente', 
                               interactive=False)
                               
pause = gr.Button.update(interactive=False)
resume = gr.Button.update(interactive=True)
update_video = gr.Video.update(value=None)  
update_yt = gr.HTML.update(value=None) 

Manejar Claves de API

Cuando un usuario envía una clave, se establece como variable de entorno, y también deshabilitaremos el cuadro de texto para más entradas. Si se presiona el botón de cambio de clave, se volverá mutable nuevamente. Al hacer clic en el botón de eliminación de clave, se eliminará la clave.

enable_box = gr.Textbox.update(value=None,placeholder='Cargar su clave de API de OpenAI',
                               interactive=True)
disable_box = gr.Textbox.update(value='La clave de API de OpenAI está establecida',interactive=False)
remove_box = gr.Textbox.update(value='Su clave de API se eliminó correctamente', 
                               interactive=False)

def set_apikey(api_key):
    os.environ['OPENAI_API_KEY'] = api_key
    return disable_box
def enable_api_box():
    return enable_box
def remove_key_box():
    os.environ['OPENAI_API_KEY'] = ''
    return remove_box

Manejar Videos

A continuación, nos ocuparemos de los videos cargados y los enlaces de YouTube. Tendremos dos funciones diferentes que tratan cada caso. Para los enlaces de YouTube, crearemos un enlace de incrustación de iframe. Para cada caso, llamaremos a otra función make_chain() responsable de crear cadenas.

Estas funciones se activan cuando alguien carga un video o proporciona un enlace de YouTube y presiona el botón de transcripción.

def embed_yt(yt_link: str):
    # Esta función incrusta un video de YouTube en la página.

    # Verifique si el enlace de YouTube es válido.
    if not yt_link:
        raise gr.Error('Pegue un enlace de YouTube')

    # Establecer la variable global `run_once_flag` en Falso.
    # Esto se usa para evitar que la función sea llamada más de una vez.
    run_once_flag = False

    # Establecer la variable global `call_to_load_video` en 0.
    # Esto se usa para realizar un seguimiento de cuántas veces se ha llamado la función.
    call_to_load_video = 0

    # Cree una cadena utilizando el enlace de YouTube.
    make_chain(url=yt_link)

    # Obtenga la URL del video de YouTube.
    url = yt_link.replace('watch?v=', '/embed/')

    # Cree el código HTML para el video de YouTube incrustado.
    embed_html = f"""<iframe width="750" height="315" src="{url}"
                     title="Reproductor de video de YouTube" frameborder="0"
                     allow="acelerómetro; reproducción automática; escritura de portapapeles;
                     medios cifrados; giroscopio; imagen en imagen"
                     allowfullscreen></iframe>"""

    # Devuelve el código HTML y una lista vacía.
    return embed_html, []


def embed_video(video=str | None):
    # Esta función incrusta un video en la página.

    # Verifique si el video es válido.
    if not video:
        raise gr.Error('Cargue un video')

    # Establecer la variable global `run_once_flag` en Falso.
    # Esto se usa para evitar que la función sea llamada más de una vez.
    run_once_flag = False

    # Cree una cadena utilizando el video.
    make_chain(video=video)

    # Devuelve el video y una lista vacía.
    return video, []

Crear cadena

Este es uno de los pasos más importantes de todos. Esto implica crear una tienda de vectores Chroma y una cadena Langchain. Utilizaremos una cadena de recuperación conversacional para nuestro caso de uso. Utilizaremos incrustaciones de OpenAI, pero para implementaciones reales, utilice cualquier modelo de incrustación gratuito como Huggingface sentence encoders, etc.

def make_chain(url=None, video=None) -> (ConversationalRetrievalChain | Any | None):
    global chain, run_once_flag

    # Compruebe si se proporciona un enlace de YouTube o un video
    if not url and not video:
        raise gr.Error('Por favor proporcione un enlace de YouTube o cargue un video')
    
    if not run_once_flag:
        run_once_flag = True
        # Obtenga el título del enlace de YouTube o del video
        title = get_title(url, video).replace(' ','-')
        
        # Procese el texto del video
        grouped_texts, time_list = process_text(url=url) if url else process_text(video=video)
        
        # Convierta time_list al formato de metadatos
        time_list = [{'source': str(t.time())} for t in time_list]
        
        # Cree tiendas de vectores a partir de los textos procesados con metadatos
        vector_stores = Chroma.from_texts(texts=grouped_texts, collection_name='test', 
                                                embedding=OpenAIEmbeddings(), 
                                                metadatas=time_list)
        
        # Cree una ConversationalRetrievalChain a partir de las tiendas de vectores
        chain = ConversationalRetrievalChain.from_llm(ChatOpenAI(temperature=0.0), 
                                                     retriever=
                                                     vector_stores.as_retriever(
                                                     search_kwargs={"k": 5}),
                                                     return_source_documents=True)
        
        
    return chain
  • Obtenga textos y metadatos de la URL de YouTube o del archivo de video.
  • Cree una tienda de vectores Chroma a partir de textos y metadatos.
  • Construya una cadena usando OpenAI gpt-3.5-turbo y la tienda de vectores Chroma.
  • Devuelva la cadena.

Procesar textos

En este paso, haremos un corte apropiado de los textos de los videos y también crearemos el objeto de metadatos que usamos en el proceso de construcción de cadena anterior.

def process_text(video=None, url=None) -> tuple[list, list[dt.datetime]]:
    global call_to_load_video

    if call_to_load_video == 0:
        print('sí')
        # Llame a la función process_video en función del video o URL dado
        result = process_video(url=url) if url else process_video(video=video)
        call_to_load_video += 1

    texts, start_time_list = [], []

    # Extraiga texto y hora de inicio de cada segmento en el resultado
    for res in result['segments']:
        start = res['start']
        text = res['text']

        start_time = dt.datetime.fromtimestamp(start)
        start_time_formatted = start_time.strftime("%H:%M:%S")

        texts.append(''.join(text))
        start_time_list.append(start_time_formatted)

    texts_with_timestamps = dict(zip(texts, start_time_list))

    # Convierta las cadenas de tiempo en objetos de fecha y hora
    formatted_texts = {
        text: dt.datetime.strptime(str(timestamp), '%H:%M:%S')
        for text, timestamp in texts_with_timestamps.items()
    }

    grouped_texts = []
    current_group = ''
    time_list = [list(formatted_texts.values())[0]]
    previous_time = None
    time_difference = dt.timedelta(seconds=30)

    # Agrupe los textos en función de la diferencia de tiempo
    for text, timestamp in formatted_texts.items():

        if previous_time is None or timestamp - previous_time <= time_difference:
            current_group += text
        else:
            grouped_texts.append(current_group)
            time_list.append(timestamp)
            current_group = text
        previous_time = time_list[-1]

    # Agregar el último grupo de textos
    if current_group:
        grouped_texts.append(current_group)

    return grouped_texts, time_list
  • La función process_text toma una URL o una ruta de video. Luego, este video se transcribe en la función process_video, y obtenemos los textos finales.
  • Luego obtenemos la hora de inicio de cada oración (de Whisper) y las agrupamos en 30 segundos.
  • Finalmente, devolvemos los textos agrupados y la hora de inicio de cada grupo.

Procesar video

En este paso, transcribimos archivos de video o audio y obtenemos textos. Utilizaremos el modelo base Whisper para la transcripción.

def process_video(video=None, url=None) -> dict[str, str | list]:
    
    if url:
        file_dir = load_video(url)
    else:
        file_dir = video
    
    print('Transcribiendo video con el modelo base de Whisper')
    model = whisper.load_model("base")
    result = model.transcribe(file_dir)
    
    return result

Para videos de YouTube, como no podemos procesarlos directamente, tendremos que manejarlos por separado. Usaremos una biblioteca llamada Pytube para descargar el audio o el video del video de YouTube. Entonces, así es como puedes hacerlo.

def load_video(url: str) -> str:
    # Esta función descarga un video de YouTube y devuelve la ruta al archivo descargado.

    # Crea un objeto de YouTube para la URL dada.
    yt = YouTube(url)

    # Obtener el directorio de destino.
    target_dir = os.path.join('/tmp', 'Youtube')

    # Si el directorio de destino no existe, créelo.
    if not os.path.exists(target_dir):
        os.mkdir(target_dir)

    # Obtener la transmisión de audio del video.
    stream = yt.streams.get_audio_only()

    # Descarga la transmisión de audio al directorio de destino.
    print('----DESCARGANDO ARCHIVO DE AUDIO----')
    stream.download(output_path=target_dir)

    # Obtener la ruta del archivo descargado.
    path = target_dir + '/' + yt.title + '.mp4'

    # Devuelve la ruta del archivo descargado.
    return path
  • Crea un objeto de YouTube para la URL dada.
  • Crea una ruta de directorio de destino temporal
  • Comprueba si la ruta existe, de lo contrario, crea el directorio
  • Descarga el audio del archivo.
  • Obtener el directorio de ruta del video

Este fue el proceso de abajo hacia arriba para obtener textos de videos y crear la cadena. Ahora, todo lo que queda es configurar el chatbot.

Configurar Chatbot

Todo lo que necesitamos ahora es enviar una consulta y un historial de chat para obtener nuestras respuestas. Entonces, definiremos una función que solo se activa cuando se envía una consulta.

def add_text(history, text):
    if not text:
         raise gr.Error('ingrese texto')
    history = history + [(text,'')] 
    return history

def QuestionAnswer(history, query=None, url=None, video=None) -> Generator[Any | None, Any, None]:
    # Esta función responde una pregunta usando una cadena de modelos.

    # Comprueba si se proporciona un enlace de YouTube o un archivo de video local.
    if video and url:
        # Si se proporciona tanto un enlace de YouTube como un archivo de video local, genere un error.
        raise gr.Error('Cargue un video o un enlace de YouTube, no ambos')
    elif not url and not video:
        # Si no se proporciona ninguna entrada, genere un error.
        raise gr.Error('Proporcione un enlace de YouTube o cargue un video')

    # Obtener el resultado del procesamiento del video.
    result = chain({"question": query, 'chat_history': chat_history}, return_only_outputs=True)

    # Agrega la pregunta y la respuesta al historial de chat.
    chat_history += [(query, result["answer"])]

    # Para cada carácter en la respuesta, agréguelo al último elemento del historial.
    for char in result['answer']:
        history[-1][-1] += char
        yield history, ''

Proporcionamos el historial de chat con la consulta para mantener el contexto de la conversación. Finalmente, transmitimos la respuesta de vuelta al chatbot. Y no olvide definir la funcionalidad de reinicio para restablecer todos los valores.

Entonces, esto fue todo al respecto. Ahora, lance su aplicación y comience a chatear con videos.

Así es como se ve el producto final

Video demostración:

Casos de uso en la vida real

Una aplicación que permite al usuario final chatear con cualquier video o audio puede tener una amplia gama de casos de uso. Aquí hay algunos de los casos de uso reales de este chatbot.

  • Educación: Los estudiantes a menudo pasan por largas conferencias en video. Este chatbot puede ayudar a los estudiantes a aprender de videos de conferencias y extraer información útil rápidamente, ahorrando tiempo y energía. Esto mejorará significativamente la experiencia de aprendizaje.
  • Legal: Los profesionales del derecho a menudo pasan por largos procedimientos legales y deposiciones para analizar el caso, preparar documentos, investigar o monitorear el cumplimiento. Un chatbot como este puede ser de gran ayuda para despejar esas tareas.
  • Resumen de contenido: Esta aplicación puede analizar el contenido de video y generar versiones de texto resumidas. Esto permite al usuario comprender los aspectos más destacados del video sin verlo por completo.
  • Interacción con el cliente: Las marcas pueden incorporar una función de chatbot de video para sus productos o servicios. Esto puede ser útil para empresas que venden productos o servicios que son de alto valor o que requieren mucha explicación.
  • Traducción de video: Podemos traducir el corpus de texto a otros idiomas. Esto puede facilitar la comunicación entre idiomas, el aprendizaje de idiomas o la accesibilidad para hablantes no nativos.

Estos son algunos de los posibles casos de uso que se me ocurrieron. Puede haber muchas más aplicaciones útiles de un chatbot para videos.

Conclusión

Entonces, esto fue todo sobre la construcción de una aplicación web de demostración funcional para un chatbot para videos. Cubrimos muchos conceptos a lo largo del artículo. Aquí están las principales conclusiones del artículo.

  • Aprendimos sobre Langchain, una herramienta popular para crear aplicaciones de IA con facilidad.
  • Whisper es un potente modelo de texto a voz de OpenAI. Un modelo de código abierto que puede convertir audio y videos a texto.
  • Aprendimos cómo las bases de datos de vectores facilitan el almacenamiento y la consulta efectivos de vectores de incrustación.
  • Construimos una aplicación web completamente funcional desde cero utilizando modelos de Langchain, Chroma y OpenAI.
  • También discutimos posibles casos de uso en la vida real de nuestro chatbot.

Eso fue todo, espero que les haya gustado, y consideren seguirme en Twitter para más cosas relacionadas con el desarrollo.

Repositorio de GitHub: sunilkumardash9/chatgpt-for-videos. Si encuentran esto útil, marquen el repositorio con una ⭐.

Preguntas Frecuentes

Los medios mostrados en este artículo no son propiedad de Analytics Vidhya y se utilizan bajo la discreción del autor.