Incorporating Andrew Huberman’s Expertise into an AI-Powered Chatbot Interface using GPT-3
As someone who’s a big fan of Andrew Huberman’s podcast, I often find myself wanting to ask him questions or delve deeper into certain topics that he covers. But with the fast-paced nature of podcasts and the limited time for audience questions, it’s not always possible to get the answers I’m looking for. However, with the recent advancements in AI, especially in the field of generative AI, it’s becoming increasingly feasible to create a chatbot that can interact with experts like Andrew and provide us with the information we seek. In this blog post, we’ll explore the possibilities of creating a chatbot interface with Andrew Huberman and how it can enhance the listening experience for fans like me.
To build our chatbot, we will be utilizing a variety of technologies including Python libraries and APIs. Some of the key technologies include pytube, pydub, and ffmpeg-python for processing and manipulating audio and video files. We will also be using langchain and gpt-index for natural language processing and generating responses. For building the chatbot interface, we will be using Gradio, a Python library for creating customizable web interfaces.
In this guide, we will walk through the step-by-step process of building a chatbot interface with Andrew Huberman using these technologies. We will cover everything from setting up the necessary Python libraries and APIs to creating and training the natural language processing models. We will also explore how to incorporate audio and video files into the chatbot interface and how to customize the interface using Gradio. By the end of this guide, you will have all the tools and knowledge needed to create your own chatbot interface with any public figure or expert.
Pre-requisites
Before you start building your chatbot interface with Andrew Huberman, there are a few things you’ll need to have in place.
Firstly, you’ll need to have some experience with Python programming language as we’ll be using it extensively to build the chatbot. Additionally, you should have a basic understanding of API calls and how they work, as we’ll be using the Google API to authenticate our project and fetch data.
Furthermore, you’ll need to ensure that you have all the necessary libraries installed on your machine. As per the requirements.txt file provided, make sure to have the following libraries installed: pytube
, pydub
, ffmpeg-python
, langchain
, gpt-index
, gradio
, google-api-python-client
, and whisper-openai
.
pytube==12.1.2
pydub==0.25.1
ffmpeg-python==0.2.0
langchain==0.0.91
gpt-index==0.4.8
gradio==3.19.1
google-api-python-client==2.79.0
whisper-openai==1.0.0
Data Collection
To train our chatbot, we need a substantial amount of audio data from Andrew Huberman’s podcast channel. Here’s our approach to collecting the data:
- Get list of all long-form podcasts from the channel: We first need to get a list of all the podcast episodes from Andrew Huberman’s channel. We can do this by visiting the channel’s website or using YouTube’s API to get the data programmatically. For our implementation, we used the latter approach and retrieved the data using the
google-api-python-client
library. - Save it in a text file for us to later iterate over: Once we have the list of podcast episodes, we save it to a text file for easy iteration. We can use Python’s built-in file handling operations to achieve this.
- Download the audio using pytube: Next, we need to download the audio files for each of the podcast episodes. We use the
pytube
library to download the audio files in.mp4
format. - Transcribe the audio using OpenAI’s Whisper: Finally, we need to transcribe the audio files into text format. For this, we use OpenAI’s
Whisper
API. It uses state-of-the-art models to perform speech-to-text transcription with high accuracy. We use thewhisper-openai
library to interface with the API and transcribe the audio files. The resulting transcriptions are saved in text files for further processing.
Below is the code snippet for collecting all the videos
from googleapiclient.discovery import build
import json
# Set up the API client
api_key = 'your_key_here'
youtube = build('youtube', 'v3', developerKey=api_key)
# Make a request to the API
channel_id = 'UC2D2CMWXMOVWx7giW1n3LIg'
folder = './data/AndrewHuberMan/'
# Request the channel's about information
channel_response = youtube.channels().list(
part='snippet',
id=channel_id
).execute()
# Extract the channel's about information
channel_about = channel_response['items'][0]['snippet']['description']
request = youtube.search().list(
part='snippet',
channelId=channel_id,
type='video',
videoDuration='long',
maxResults=500
)
response = request.execute()
# Parse the response
videos = []
for item in response['items']:
video_id = item['id']['videoId']
video_title = item['snippet']['title']
video_description = item['snippet']['description']
videos.append({'id': video_id,
'title': video_title,
'description': video_description})
# Output the video data
print(json.dumps(videos, indent=2))
with open(folder+'about.txt', 'w') as f:
f.write('Channel About:\n{}\n\n'.format(channel_about))
# Write the video data to a text file
with open(folder+'videos.txt', 'w') as f:
json.dump(videos, f, indent=2)
Similarly for downloading and transcribing
import json
from pytube import YouTube
import os
import whisper
whisper_model = whisper.load_model("small")
class Transcriber:
def __init__(self, video_id, video_title, video_description):
self.video_id = video_id
self.video_title = video_title
self.video_description = video_description
self.audio_file = ''
self.destination = './data/AndrewHuberMan/audios/'
def download_mp3(self):
video_id = self.video_id
title = self.video_title
file_path = os.path.join(self.destination, f"{title}.mp3")
if os.path.exists(file_path):
print(f"{title}.mp3 already exists. Skipping download.")
return
try:
yt = YouTube(f"https://www.youtube.com/watch?v={video_id}")
video = yt.streams.filter(only_audio=True).first()
print(f"Downloading audio for {title}...")
out_file = video.download(output_path=self.destination)
base, ext = os.path.splitext(out_file)
new_file = base + '.mp3'
self.audio_file = new_file
os.rename(out_file, new_file)
print(f"{title}.mp3 has been successfully downloaded.")
except Exception as e:
print(f"Error downloading audio for {title}: {e}")
def transcribe_audio(self):
result = whisper_model.transcribe(self.audio_file)
self.transcription = result["text"]
print(f"{self.video_title} has been successfully transcribed.")
def save_transcription(self):
transcriptions_path = "./data/AndrewHuberMan/transcriptions/"
if not os.path.exists(transcriptions_path):
os.makedirs(transcriptions_path)
with open(f"{transcriptions_path}{self.video_title}.txt", "w") as f:
f.write(f"Video Title: {self.video_title}\n")
f.write(f"Video Description: {self.video_description}\n")
f.write("Transcription:\n")
f.write(self.transcription)
print(f"{self.video_title} transcription has been successfully saved.")
# Load videos list from file
with open("./data/AndrewHuberMan/videos.txt", "r") as f:
videos_list = json.load(f)
# Process each video
for video in videos_list:
transcriber = Transcriber(
video["id"], video["title"], video["description"])
transcriber.download_mp3()
transcriber.transcribe_audio()
transcriber.save_transcription()
Construct Index
In this section, we will be constructing an index using the gpt_index
library. The purpose of this index is to allow our chatbot to search through the transcriptions of Andrew Huberman's podcast episodes quickly and efficiently.
First, we import the necessary libraries and define the MakeIndex
class, which takes in the directory path where our transcriptions are stored, the GPT model to use for language modeling, and the maximum number of tokens for each document. We set the default model to text-ada-001
and the default token limit to 256.
We then define the construct_index
function, which first sets several parameters for the index, such as the maximum input size, the number of output tokens, and the maximum chunk overlap. We then create a PromptHelper
object to help with constructing the prompts for the GPT model. Next, we define the language model using the LLMPredictor
class, which takes in our OpenAI API key and the GPT model. We load in the transcriptions using the SimpleDirectoryReader
class, and finally, we create the index using the GPTSimpleVectorIndex
class, passing in the documents, language model, and prompt helper. We save the index to disk in JSON format and return it.
import os
from langchain import OpenAI
from gpt_index import SimpleDirectoryReader, GPTSimpleVectorIndex, LLMPredictor, PromptHelper
os.environ["OPENAI_API_KEY"] = 'Your Key here'
class MakeIndex:
def __init__(self, directory_path, model_name='text-ada-001', max_tokens=256):
self.directory_path = directory_path
self.model_name = model_name
self.max_tokens = max_tokens
def construct_index(self):
# set maximum input size
max_input_size = 4096
# set number of output tokens
num_outputs = self.max_tokens
# set maximum chunk overlap
max_chunk_overlap = 20
# set chunk size limit
chunk_size_limit = 600
prompt_helper = PromptHelper(
max_input_size, num_outputs, max_chunk_overlap, chunk_size_limit=chunk_size_limit)
# define LLM
llm_predictor = LLMPredictor(llm=OpenAI(
temperature=0, model_name=self.model_name, max_tokens=num_outputs))
documents = SimpleDirectoryReader(self.directory_path).load_data()
index = GPTSimpleVectorIndex(
documents, llm_predictor=llm_predictor, prompt_helper=prompt_helper)
index.save_to_disk(self.directory_path+'/avatar.json')
return index
print('Indexing...')
directory_path = 'transcriptions/AndrewHuberMan'
make_index = MakeIndex(directory_path)
index = make_index.construct_index()
print('Indexed!')
The Andrew Huberman Bot
Next we write a code snippet called IndexBot that uses the GPTSimpleVectorIndex from the gpt_index library to construct an index and provide an interface for querying it. The class takes the path to the index file as a parameter and loads the index from disk. It also sets some parameters for the PromptHelper, LLMPredictor, and GPTSimpleVectorIndex objects, which are used to generate responses to queries.
The ask_bot method is the main interface for querying the index. It takes a query string and a history list as input and returns the response from the index as a string. The history list is used to provide context for the query, and the flattened_history variable is created to flatten the nested list into a single list. The user_query variable concatenates the flattened history list and the current query.
Finally, the response is generated using the query method of the GPTSimpleVectorIndex object. The response_mode is set to “compact” to return a single response rather than a list of responses. The response is then stripped of any leading or trailing whitespace before being returned. Overall, the IndexBot class provides a convenient way to interact with the index and generate responses to queries.
import os
from gpt_index import GPTSimpleVectorIndex, LLMPredictor, PromptHelper
from langchain import OpenAI
os.environ["OPENAI_API_KEY"] = 'your_key'
class IndexBot:
def __init__(self, index_file_path):
self.index = GPTSimpleVectorIndex.load_from_disk(index_file_path)
self.max_input_size = 4096
self.num_outputs = 256
self.max_chunk_overlap = 20
self.chunk_size_limit = 600
self.prompt_helper = PromptHelper(
self.max_input_size, self.num_outputs, self.max_chunk_overlap, chunk_size_limit=self.chunk_size_limit)
self.llm_predictor = LLMPredictor(llm=OpenAI(
temperature=0, model_name="text-ada-001", max_tokens=self.num_outputs))
def ask_bot(self, query, history):
# flattened_history = [item for sublist in history for item in sublist]
# user_query = flattened_history + [query]
# print(user_query)
response = self.index.query(
query, response_mode="compact")
return response.response.strip()
Gradio Output
Finally, a code to define a Gradio interface for a chatbot that allows users to ask questions to their favorite podcast hosts. The interface has a title and description, followed by a chatbot element that displays the responses to the user’s queries. The state variable keeps track of the chat history.
The predict function takes in the user’s query, selected podcast host, and current chat history as inputs. It initializes an instance of the IndexBot class for the selected host and calls the ask_bot method to retrieve a response to the query. The function then updates the chat history with the new query-response pair and returns the updated history.
The Gradio interface includes a textbox for the user to enter their query, a dropdown menu to select the podcast host, and a submit button to initiate the query. When the user submits a query, the predict function is called with the textbox input, dropdown value, and current chat history as arguments. The chatbot and state variables are passed as outputs to update the interface with the latest response and chat history. The app is launched at the end of the script.
import gradio as gr
from index_bot import IndexBot
# Load the index
index_bot = IndexBot('transcriptions/AndrewHuberMan/avatar.json')
def predict(query, person, state):
index_bot = IndexBot(person)
response = index_bot.ask_bot(query, state)
state = state + [(query, response)]
return state, state
# Define the Gradio interface
title = "Q&A with Your Favorite Podcast Host / Philishoper"
description = "Ask any question with: Andrew Huberman/LexFridman/Terence Mckenna"
with gr.Blocks() as app:
markdown_text = "# {}\n{}".format(title, description)
gr.Markdown(markdown_text)
chatbot = gr.Chatbot(elem_id="chatbot")
state = gr.State([])
with gr.Column():
person = gr.Dropdown(["transcriptions/AndrewHuberMan/avatar.json",
"transcriptions/LexFridman/avatar.json",
"transcriptions/TerenceMckenna/avatar.json"], value="transcriptions/AndrewHuberMan/avatar.json")
default = ""
txt = gr.Textbox(show_label=False, placeholder="Enter text and press enter", value=default).style(
container=False)
txt.submit(predict, [txt, person, state], [chatbot, state])
app.launch()
Finally, now we can chat and have Q&A sessions with almost any podcaster we want.