Back in April 2020 I shared how I built a nutrition tracker in Emacs that leveraged org-capture templates and or-ql to record foods, recipes, and meals.
At that time, I relied on an org-mode based database and manual updates to keep track of calories, protein, carbs, and fat. While the system worked, maintaining that data was both tedious and error-prone.
Each time I needed to insert a new food, I had to do an internet search to find the nutritional information and then manually update my org-mode files.
Recently, I discovered gptel which allows Emacs users to easily integrate with ChatGPT or other LLMs. So, I couldn’t resist the opportunity to use it to smarten up nutrition tracker by integrating it with LLMs so that it can fetch nutritional information for me.
The goal is to retain the previously used templates, but add a post processing mechanism that will kick in when a new food entry is captured but is missing the nuttritional information.
A video walkthrough that walks through the this post can be found here:
Creating a function to get nutritional information from ChatGPT
Link to heading
The first thing that we are going to need is a new function that given a food and its quantity, will query ChatGPT via GPTel for all nutrients in a FOOD item with a given QUANTITY. The function will return a map of nutrients to their values.
(defun ic/nutrients-get (food quantity)"Query ChatGPT via GPTel for all nutrients in a FOOD item with a given QUANTITY.
Returns a map of nutrients to their values."(if(or(not food)(string-empty-p food))(make-hash-table);; Return an empty map if food is nil or empty(let*((quantity (or quantity "1 serving"))(prompt (format"Provide the nutritional values (calories, protein, carbs, fat) for %s in %s. Only return a JSON object with the keys 'calories', 'protein', 'carbs', and 'fat', and their numeric values." food quantity))(response (if(fboundp'gptel-request)(let((response ""))(gptel-request prompt :callback(lambda(resp &rest _)(setq response (replace-regexp-in-string "^```json\\|```$""" resp))(message"Response: %s" response)))(while(string-empty-p response)(sleep-for0.1)) response)"{}")))(condition-casenil(json-read-from-string response)(error(progn(message"Error parsing JSON response")nil))))))
Next stop is to create a function that goes to the current org-mode heading, calls the function above to get the nutrients, and then updates the properties of the heading with the nutritional information.
Creating a function that post processes captured food entries
Link to heading
Org-Mode is a really powerful tool that can be used in countless ways.
Combining Org-Mode with LLMs can further enhance the capabilities of Org-Mode.
The functionality added in this demo would be really hard to implement without an LLM, as we would have to:
Find an online source for nutritional information (that exposes an API)
Find a way to 100% match user input with names in the online source (e.g. handling synonyms, typos, etc.)
Find a way to parse the response from the online source and deal with inconsistencies missing data etc.
Using an LLM as to abstract the source and the way we interact with it, we allows us to focus on the core functionality, and not on the intricacies of the data source.
Gptel is a great package that allows us to interact with LLMs from within Emacs, either directly or as libray as demonstrated in this post.