Aristotle – The WhatsApp AI Bot in Python

AI Aristotle

Playing around with the Chat GPT, Bard user interfaces has been fun but I end up using the Meta AI chat a lot more since the interface it uses seems to slide smoothly into my daily workflow.  The MetaAI is great at this and is super useful for daily tasks and lookups. I also wanted to have the option of using the OpenAI API as the GPT models seems to give me the best results atleast for some of my more random queries. An unexpected benefit is the ability of using this on Whatsapp when I’m traveling internationally as the MetaAI on whatsapp seems to work only in the US market currently.

Enter Aristotle – a Whatsapp-based bot that plugs into the open AI model of your choosing to give you the result you want. Granted its as easy to flip to a browser on your phone and get the same info but I decided to replicate this functionality for kicks as this seems to be the form-factor of choice for me instead of loading a browser and logging into the interface etc.

This was also a good opportunity to test out the Assistant API and function calling. I also had a Ubuntu machine lying around at home that I wanted to put some use to and considering a small simple usecase like this – all it needs to do is stay awake and make the API calls when triggered. Credit to the openAI cookbook for a lot of the boiler plate.


The basic flow was something like this:

  1. Use the open AI playground  and set up the Assistant. Create a get_news function with the variables you can reference in the code
  2. Grab your Open AI Key and the Assistant API ID
  3. Create a python function to grab the latest news from an API (I used
  4. Use the Assistant API to write your script that will respond to your query and trigger the function if asked for news
  5. Wrap the function in a flask server that you can run on a server
  6. Set up a Twilio sandbox to obtain a sandbox Whatsapp number and connect to it via your phone
  7. Start chatting!


Setting up the playground was pretty easy with something like below.

Get News

# Function to fetch news articles
def fetch_news(skeyword):
    url = f'{skeyword}&sortBy=popularity&pageSize=5&apiKey={news_api_key}'
    response = requests.get(url)
    if response.status_code == 200:
        answer = response.json()
        return answer
    return "No response from the news API"

This function etches news articles from the News API. It takes a single argument, `skeyword`, which is the search keyword used to find relevant news articles.

Set up the AI bot

chat_with_bot function is the core of the chatbot. It starts by getting the user’s message from a form submission. It then creates a new thread with OpenAI and appends the user’s message to the thread to keep the context.. The assistant is then run with the given instructions that give it the personality. The function then waits until the assistant’s run is no longer queued or in progress. If the run status is “requires_action”, it executes the required functions and submits their outputs back to the assistant. This process is repeated until the run is no longer queued, in progress, or requires action. After the run is complete, it retrieves the messages from the thread and adds them to the conversation history. Finally, it gets a response from ChatGPT and returns it. The comments in the code should be self-explanatory. I did notice the whatsapp messages returning empty if the token size was not limited to whatever length Twilio can handle. I finally settled on 250 tokens as that seemed to work for most of my queries.

def chat_with_bot():
   # Obtain the request's user message
    question = request.form.get('Body', '').lower()
    print("user query ", question)
    thread = openaiclient.beta.threads.create()
       # Insert user message into the thread
        message = openaiclient.beta.threads.messages.create(
        # Run the assistant
        run = openaiclient.beta.threads.runs.create(
        # Show assistant response
        run = openaiclient.beta.threads.runs.retrieve(
        # Pause until it is not longer queued
        count = 0
        while (run.status == "queued" or run.status == "in_progress" and count < 5):
            run = openaiclient.beta.threads.runs.retrieve(
            count = count + 1
        if run.status == "requires_action":
            # Obtain the tool outputs by running the necessary functions
            aristotle_output = run_functions(run.required_action)
            aristotle_output = run_functions(run.required_action)
            # Submit the outputs to the Assistant
            run = openaiclient.beta.threads.runs.submit_aristotle_output(
        # Wait until it is not queued
        count = 0
        while (run.status == "queued" or run.status == "in_progress" or run.status == "requires_action" and count < 5):
            run = openaiclient.beta.threads.runs.retrieve(
            count = count + 1
        # After run is completed, fetch thread messages
        messages = openaiclient.beta.threads.messages.list(
        # TODO look for method call in data in messages and execute method
        print(f"THREAD MESSAGES: {messages}")
        # With every message, include user's message into the conversation history
        for message in messages:  # Loop through the paginated messages
            system_message = {"role": "system",
                              "content": message.content[0].text.value}
            # Append to the conversation history
        # Get the response from ChatGPT
        answer = chat_with_bot(question, conversation_history, instructions)
        print(f"ARISTOTLE: {answer}")
        return str(answer)
    except Exception as e:
        answer = 'Sorry, I could not process that.'
        print(f"An error occurred: {e}")


This function is used to execute any required actions that the assistant needs to perform. It loops through the tool calls in the required actions, gets the function name and arguments, and calls the corresponding Python function if it exists. The function’s output is then serialized to JSON and added to the list of tool outputs, which is returned at the end of the function.

The functions are wrapped in a flask server that runs 24 7 on a server I have running around at home. Considering the whopping number of active users (i.e. 1, server load is not really a concern.

TWILIO is super easy to setup a whatsapp connection. Twilio’s documentation and interface makes it super easy to set this up.

Once you have the whatsapp Sandbox set up and the free credits make it super easy to get start, the below code helps connect the application with twilio and whatsapp.

twilio_response = MessagingResponse()
    reply = twilio_response.message()
    answer = chat_with_bot()
    # add all the numbers to which you want to send a message in this list
    for number in numbers:
        message = client.messages.create(
    message = client.messages.create(

So far, this has been useful to switch out models especially during travel. Some things I would love to do to add to this:

  1. Add more custom functions
  2. Speed up the backend by running on a beefier server
  3. Tune the application for faster responses

Code here:

Deep Learning generated guitar solo on Hi Phi Nation podcast

One of my older tracks (trained on a LSTM) to generate a 80s shred inspired guitar solo ( mostly done for laughs and quite embarrassing in hindsight 🙂 ) got featured on the Hi Phi Nation podcast. You can listen to the episode here:

Great episode exploring AI and machine-generated music technologies and AI-generated compositions. My AI-generated solo ( around the 45 minute mark) faced off against two guitarists which was fun and also hilarious with comments from the guitarists on the AI solo such as “reminded  me  of  like droids  from  Star  Wars”, “No structure to it” etc).

Generating AI-driven creativity is an extremely intriguing topic and my personal research continues on embedding AI models in chord or note sequences to enhance creativity. I think the peak experience of personalized music composition using AI has to be a human/machine collaboration (atleast until singularity occurs and blows our collective understanding of reality away). This is an exciting space with my latest muse being the open source libraries being released such as audiocraft that are powered by dynamic music generation language model. More experiments incoming.

PS: I had a whole blog post on the mechanics of generating the solo here.

Thrills of Post-Apocalyptic Fiction

I am legend book

With  ‘The Last of Us‘ becoming everyone’s favorite live-action adaptation of a video game franchise, I fondly remember playing the game years ago along with related apocalyptic/zombie favorites like Resident Evil, Left4Dead etc. Trying to survive the hordes of the undead and boss fights after a long day of work was a trip back in the day ( and mostly less stressful than dealing with race conditions and deadlocks!) . Thanks to a full schedule (aka life), my gaming schedule has taken a backseat over the years (though with the arrival of the Steamdeck, this has seen an uptick recently).  28 Days Later rebooted the genre in the big screen and set the template for things to follow. TV Series like the Walking Dead (which I abandoned after 2 seasons once it became apparent that the story wasn’t really going anywhere), Black Summer, Kingdom etc had some good moments. However, post-apocalyptic fiction writing got me interested and playing those games and watching those series/movies in the first place. The genre is rich with amazing books and a lot of mediocre ones as well. The gloomy Seattle weather plays a perfect conduit to huddling up with desolate dystopian stories. Some of my favorites listed below and could be considered essential reading for this genre.

World War Z by Max Brooks – The “oral” history about an apocalyptic war with the undead that almost wiped out the human race as we know it. This reads almost like your favorite travel vlogger visiting various sites decimated by the undead. The book actually offers an interesting commentary of how governments could (mis)manage an event of catastrophic proportions while the world is amidst a meltdown. The movie was nothing great frankly and the book offered a much more interesting and sometimes humorous view of how a society could adapt against the zombie apocalypse.

The Road by Cormac McCarthy – One of the most hopeless, desolate, heart-breaking stories in the genre. I’ve read this one twice and its one of those books where the writing is alive and makes you plod till the end with the wretched protagonists despite the utter futility you already know that awaits in the end. A father and son trudging through a devastated wasteland trying to survive against a world that includes diseases, starvation, cannibals and more. Its a road to nowhere with only your humanity to treasure. The movie was one of the rare well-made book-to-TV adaptions and remained sincere to the book.

“Borrowed time and borrowed world and borrowed eyes with which to sorrow it.” -Cormac McCarthy, The Road

The Stand by Stephen King – Not part of zombie genre but the 1000 page uncut version of the ultimate post-apocalyptic storyline by master of the macabre is essentially reading for this genre. The battle of good versus evil against a background of disease and complex human relationship finally ending with a showdown in Vegas. Nothing not to enjoy on this one. As a huge king fan, I’ve enjoyed most of his books during my teenage and adult life and this one left a big impression on me. This one is due a re-read.

I am legend – Richard Matheson – Post-apocalyptic vampire-like zombies against the last man on earth who locks his doors and prays for dawn every night. This was one of those books where the second read was more enjoyable than the first. The main characters search for purpose and the utter isolation is one of the more enjoyable aspects of this book. Its a lot different than the Will Smith movie where he slaps the zombies around and goes out in a blaze of glory ( in one of the endings at least).

The Dog Stars by Peter Heller – No zombies on this one but the narrator ( a pilot) and his friend protect their abandoned airport from human scavengers in a post-pandemic situation. Things pick up when the main character has enough and starts to search out for civilization. This one deals with friendship, loneliness, trust and the insignificance of mankind against the power of nature.

My reading list here:

DQ Framework

I came across the excellent draft of the new EU data quality framework that aims to align stakeholders. While it focuses on medicines regulation and procedures that apply across the European Medicines Regulatory , it definitely applies to larger data governance policies and serves as a great template for setting your own strategy.

Data quality is one of the biggest asks from consumers of the data in large scale implementations. As the scale of the data grows, so does complexity and the plethora of usecases that are now dependent on this data. In a large organization, there are typically multiple degrees of separation i.e. siloes between producers of the data and consumers. While the technology to detect and provide feedback loops to data anomalies exist, the fundamental problem of minimizing the value of clean data at the source persists.

A downstream data engineering team stitching together various sources of data is caught between a rock and a hard place i.e. between ever ravenous consumers of data/dashboards/models versus producers who throw over application data over the fence ensuring operational needs are met while downplaying analytics needs.  This also leads to minimal understanding of the data downstream by teams building and using these data products based on resourcing as the focus is on throwing data into the data lake hoarding data. The lack of understanding  is  further compounded by an ever increasing backlog of balancing the tight wire between new data products and tech debt.

This is where organizational structures play a huge part in determining the right business outcome and usage of the data. The right org structure would enforce data products conceptualized and built at source with outcomes and KPIs planned at the outset with room for evolution. 

Some of the better implementations I have been fortunate to be involved in treated data as a product where the data consumption has evolved though deliberation of usecases with KPIs defined at the outset in terms of value of this data as opposed to ‘shovel TBs into the lake’ and then we figure out what to do with it.

Call it what you want ( data mesh etc) , but fundamentally the data-driven approach to plan data usage and governance with the right executive sponsorship holds a ton of value especially in the age of massive data generation across connected systems. The advantage of being KPI-driven at the outset means you can set taxonomy at the beginning, the taxonomy helps feed the domain-based usecase which has implications for consumption of the data and sets the foundation for a holistic permissioning matrix, least privileges clearly implemented and policy-based data access. More to come on this subject but great to see a well defined manifesto defined by policy-makers at the highest levels.

Tenacity for retries

Most times when I have taken over new roles or added more portfolios to my current workstreams, it has usually involved decision making to either build on or completely overhaul legacy code. I’ve truly encountered some horrific code bases that are no longer understandable, secure or  scaleable. Often if there is no ownership of the code or if attrition has rendered it an orphan, it usually sits unnoticed ( like an argiope spider waiting for its prey) until it breaks (usually on a friday afternoon) when someone has to take ownership of it and bid their weekend plans goodbye. Rarely do teams have  a singular coding style with patterns and practices clearly defined that are repeatable and can withstand change people or technology change – if you are in such an utopian situation, consider yourself truly lucky!

A lot of times,  while you have to understand and reengineer the existing codebase, it is imperative to keep the lights on while you are figuring out the right approach, a sorta situation while you got to keep the car moving while changing its tires. I discovered Tenacity while  encountering a bunch of  gobbledygook shell and python scripts crunched together that ran a critical data pipeline and had to figure out a quick retry logic to keep the job running  while it randomly failed due to environment issues ( exceptions, network timeouts, missing files, low memory and every other entropy-inducing situation on the platform). The library can handle different scenarios based on your usecase.

  • Continuous retrying for your commands. This usually works if its a trivial call where you have minimal concerns over overwhelming target systems. Here, the function can be retried until no exception is returned.
  • Stop after a certain number of tries. Example – if you have a shell script like shellex below that needs to execute and you anticipate delays or issues with the target URL or if a command you run raises an exception, you can have Tenacity retry based on your requirement .
  • Exponential backoff patterns – To handle cases where the delays maybe too long or too short for a fixed wait time, tenacity has a wait_exponential algo that can ensure that if a request cant succeed in a short time after a retry, the application can wait longer and longer with each retry thus alleviating the target system of repetitive fixed time retries.

The library handles plenty of others uses cases like Error handling, custom callbacks and tons more.

Overall, this has been a great find to use a functional library like Tenacity to for various usecases instead of writing custom retry logic or implementing a new orchestration engine for handling retries.

Book Review – Machine Learning with PyTorch and Scikit-Learn

by Sebastian Raschka & Vahid Mirjalili

I primarily wanted to read this book due to the Pytorch section and pretty much flipped through the Scikit learn section while absorbing and practicing with the Pytorch section. So my review largely is based on chapter 13 and beyond. Apart from the Pytorch official documentation, there are not too many comprehensive sources that can serve as a good reference with practical examples for Pytorch in my view. This book aims to do that and pretty much hits the mark.

Ch 1-11 is a good refresher on SciKit -learn and sets up all the foundational knowledge you need for the more advanced concepts with Pytorch. I wish though the authors had different examples and not resorted to ubiquitous examples like MNIST ( like in chapter 11/ 13 etc) for explaining neural network concepts. While these are good for understanding foundational concepts, I find the really good books usually veer away from standard examples found online and get creative.

Chapter 12 provides an excellent foundation in Pytorch and a primer for building a NN model in Pytorch. The code examples are precise with the data sources clearly defined so I could follow along without any issues. I did not need a GPU/collab to run examples . Good to see the section on writing custom loss functions as those are useful practical skills to have.

Ch-14 which has us training a smile classifier to explain convolution neural networks is a useful example especially for tricks like data augmentation that can be applied to other usecases.

I skipped through the chapter on RNNs as transformers are the rage now ( Ch-16) and Pytorch already has everything implemented in its wrapper function for structures like LSTMs. Still , a lot of dense and useful material explaining the core concepts behind RNNs and some interesting text generation models using Categorical pytorch classes to draw random samples.

The chapter on Transformers is a must-read and will clear up a lot of foundational concepts. Another thing to mention is that the book has well depicted color figures that make some of the dense material more understandable. Contrasting the transformers approach to RNNs using concepts like attention mechanisms is clearly explained. More interestingly, the book dwells into building larger language models with unlabeled data such as BERT and BART. I plan to re-read this chapter to enhance my understanding of transformers and the modern libraries such as HuggingFace that they power.

The chapter of GANs was laborious with more MNIST examples and could have had a lot more practical examples.

Ch-18 on Graph Neural Network is a standout section in the book and provides useful code examples to build pytorch graphs to treat as datasets defining edges and nodes. For example, libraries like Torch Drug are mentioned that use pytorch Geometric framework for drug discovery. Spectral graph convolution layers, graph pooling layers, and
normalization layers for graphs are explained and I found this chapter to be a comprehensive summary that would save one hours of searching online for the fundamental concepts. GNNs definitely have a ton of interesting applications and a lot of recent papers with links are provided.

Ch-19 on reinforcement learning adds another dimension to the book which is largely focused on supervised and unsupervised learning in prior chapters. Dynamic programming to Monte Carlo to Temporal Difference methods are clearly articulated. The standard open AI gym examples are prescribed for implementing grids to specify actions and rewards. I thought this chapter was great explaining the theoretical concepts but the examples were all the standard Q-learning fare you would find online. Would have loved to see a more realistic example or pointers to apply to your own usecases.

All in all, I enjoyed the Pytorch examples and clearly explained concepts in this book and it would be a good Pytorch reference to add to your library.

Asynchronicity with asyncio and aiohttp

The usual Synchronous versus Asynchronous versus Concurrent versus Parallel is a topic in technical interviews that usually leads to expanded conversations in the candidates overall competency on scaling and leads to interesting rabbit holes and examples. While keeping the conversations open-ended, I’ve noticed when candidates usually incorporate techniques to speed up parallelism or enhance concurrency or mention other ways of speeding up processing, its a great sign.

Its also important to distinguish between CPU-bound and IO-bound tasks in such situations since parallelism is effective on CPU-bound tasks (example preprocessing data, running ensemble of models) while concurrency works best for IO-bound tasks (web scraping, database calls). CPU-bound tasks are great for parallelization using multiple CPUs where a task can be split into multiple subtasks while IO-bound are not CPU dependent but depend on time reading/writing from disk.

Key standard libraries in Python for concurrency include:

  • AsyncIO – for concurrency with coroutines and eventloops. This is most similar to a pub-sub model.
  • Concurrent.futures –  for concurrency via threading

Both are limited by the global interpreter lock (GIL) and single process, multi-threaded.
Note for parallelism, the library I’ve usually used is multiprocessing which is another post.

AsyncIO  is a great tool to execute tasks concurrently and is a great way to add asynchronous calls to your program. However, the right usecase matters as it can also lead to unpredictability since tasks can start, run and complete in overlapping times using context switching between threads. Threads can be blocked using asyncio and the next available thread in the queue can be processed until it completes or is blocked. The key here is the lack of any manual checking if the thread is freed up, as asyncio will announce the availability of the thread when it does actually free up.

This post has a couple of quick examples of asyncio using the async/await syntax  for event-loop management. Plenty of other libraries available but async is usually sufficient for most workloads and a couple of simple examples go a long way in explaining the concept.

The key calls here are –

  • async – tells the Python interpreter to run the coroutine asynchronously with an event loop
  • await – while waiting for the result to be returned, this passes control back to the event loop, suspending the execution of the current coroutine to let the event loop run other things until the await has a result returned
  • run – Schedule a coroutine (python 3.7+) . In earlier versions (3.5/3.6) you  can a
  • sleep – suspends execution of a task to switch to the other task
  • gather – Execute multiple coroutines that need to finish before resuming the current context with the list of responses from each coroutine

Example of using async for multiple tasks:

A trivial example like above goes a long way in explaining the core usage of the sync library especially while bolting it onto long running python processes that are primarily slow due to IO.

aiohttp is another great resource for synchronous HTTP requests which by nature are a great usecase for asynchronicity while requests wait for servers to respond to do other tasks. This basically works by creating a client session that can be used to support multiple individual requests and make connections upto 100 different servers at the same time.

Non async example

A quick example to handle requests from a website ({country}/status/confirmed) that provides a JSON string based on the specific request. The specific request is not important here and used only for demonstrative purposes to demonstrate the async functionality.

Async example using aiohttp which will be needed in order to asynchronously call the same endpoint.

The example clearly shows the time difference where the async calls halve the time taken for the same calls. Granted its a trivial example but it shows the benefit of the non-blocking Ascync call and can be applied to any situation that deals with multiple requests and calls to different servers.

Key Points

  • AsyncIO is usually a great fit for IP bound problems
  • Putting async before every function will not be beneficial as the blocking calls can slow down the code so validate the usecase first
  • async await support a specific set of methods only so for specific calls (say to databases), the specific python wrapper library you use will need to support async await.
  • is the go-to place for higher level async APIs along with

Genesis at MSG

last Domino

I’ve enjoyed the different eras of Genesis regardless of the Peter Gabriel-era prog-rock or the later more commercial Collin-era hits. Regardless of the timeline, the Hackett-Rutherford guitar soundscapes made me love the catalog whether it was the theatrics of Nursery Cryme, the sonic bliss of Selling England by the Pound, the massive hooks of Invisible Touch, or the 90s heyday of We cant Dance ( a personal guilty pleasure).

On the spur of the moment, a couple of high school buddies and I decided to fly to NYC to see the Genesis – ‘The Last Domino’ tour at Madison Square Garden. Being progheads, the motivating factor was to catch Genesis on what could be their final tour . Being someone who spent his teens in the 90s in a cloud of personal angst, the guitar was an escape and I have many fond memories of spending many a gray bleak day muddling my way through a Genesis guitar tab to manifest some poorly played yet soul satisfying covers. With so many of my musical heroes passing over the last few years ( Neil Peart, Eddie Van Halen, Allan Holdsworth…this list goes on sadly), its an imperative now to see the acts I have never had the chance to see before or want to see again.

The Last Domino

The setlist was average and largely meant to accommodate Phil Collins’ current state of performance prowess. I had no expectations of them belting out “”Get ‘Em Out By Friday” or “Can Utility And The Coastliners'” . However, with Nic Collins being stellar on drums and Phil still belting out the songs in top form backed by the genius of Rutherford/Banks , I was glad I made the trek and it was a show to remember not merely for nostalgia but that we could actually make it out to a legit concert in 2021 after an year(s) of pandemic-induced seclusion.


Zone of Proximal Development

The best times of productivity often happen when in a state of “flow” – say a sustained state of seamless productivity while coding, a burst of self-expression while composing music, a sublime feeling of sentient exhilaration while working out and more. However, as great as that state is, its become increasingly hard over the years to achieve for me with all the various dopamine rollercoaster of distractions and the overwhelming demands of time. I came across constructivist methods while researching active learning methods and Vygotsky is a name that is prominent in this subject.

ZPD was a theory proposed by Lev Vygotsky in the 1930s. The crux of his proposal was that giving children experiences that better supported their learning enhanced their development. The 1930s were an interesting time where there was much debate about the the best methods of education and development for children. Vygotsky introduced the concept of ZPD to criticize the psychometric-based testing in Russian schools. He argued that academic tests where students displayed the same academic level were not the best way to determine intelligence. Instead, he focused on the augmented ability of children via social interaction to solve problems via interacting with more knowledgeable persons. He also inferred that while self-directed curiosity based approached worked for some subjects like languages , their learning in other subjects like Mathematics benefited by interacting with more “knowledgeable” folks.

Essentially ZPD is the place where once cannot progress in their learning without the interaction of a person who is high skilled at that learning objective. In a group setting, if some individuals grasp the concept, while other individuals are still in ZPD, the peer interaction between may create the most conducive environment for learning. While it makes sense at first glance, the nuance here is the ability to recognize and set learning objectives that are within the range of ZPD to enhance learning as well as ensure the right subject matter experts around in order to help complete that objective. ZPD can be applicable to various situations, everything ranging from the learning patterns of my 8-year old to setting appropriate goals to someone on my team for their advancement.

More interestingly, reflecting upon my learning blocks over the years, I have noticed multiple instances when serendipitous encounters/conversations have unblocked what seemed an impasse. It may not be too much of a stretch to apply this to religious philosophies like say Buddhism where the role of a teacher important for attainment  of one’s own “inner guru” to become a guru.

ZPD and leading teams

One of the key areas for being successful leaders is goal setting for your teams. Analyzing the tasks that are in ZPD for your team with a view to their future aspirations is invaluable.

Before goal setting, its worth the time to analyze the tasks that are within ZPD.  Then validate if the team has a variety of skill sets and expertise that can help mentorship for team members that are within ZPD but need that extra expertise to help them through it. For example, if a team member is tasked with an important large scale organizational objective, pair them with a partner that is more experienced to help them accomplish that task.

Delegating your own tasks to folks who are interested in being managers and being the  subject matter expert that helps them navigate ZPD. Challenging oneself to think about the types of learning we might or might not be doing on a daily basis and the effect of ZPD can help enhance that learning process.

Contemplating on my own learning, as it currently stands, is a daily dance around various concepts based on time, interest and compulsions – the patchwork quilt of topics consumed (tech, news etc) could use a recipe with ZPD in mind on topics that need to be reenforced or are important to enhance.

Interesting reads:

Learning Computer Science in the “Comfort Zone of Proximal Development”

Great paper on Vygotsky’s Zone of Proximal Development: Instructional Implications and Teachers’ Professional Development