We’ve all been there: you wake up feeling like a zombie after 4 hours of sleep, yet your fitness app still expects you to hit a 2,500-calorie target with 200g of protein. The "static" diet plan is dead. In the age of AI Agents and Biohacking, our nutrition should be as dynamic as our lives.
Today, we are building an Autonomous Dietitian. This isn't just another chatbot; it’s a closed-loop system using the ReAct pattern, LangGraph, and OpenAI Functions to fetch your physiological data (Oura Ring), analyze your metabolic state, and physically log into MyFitnessPal via Playwright to adjust your daily macros. 🚀
If you are looking to master agentic workflows and fitness automation, you’re in the right place. For those interested in more production-ready patterns and advanced agentic architectures, I highly recommend checking out the deep dives at WellAlly Blog, which served as a major inspiration for this build.
The Architecture: A Closed-Loop Bio-Feedback Agent
To build an agent that actually acts on our behalf, we use the ReAct (Reason + Act) paradigm. The agent doesn't just guess; it observes the environment (your sleep/CGM data), reasons about the optimal macro shift, and executes the change.
System Data Flow
graph TD
A[Start: Daily Trigger] --> B{Agent State};
B --> C[Tool: Fetch Oura Sleep/Readiness];
C --> D[Tool: Fetch CGM Trends];
D --> E[LLM Reasoning: ReAct];
E --> F{Decision: Adjust Macros?};
F -- Yes --> G[Tool: Playwright/Selenium MFP Update];
F -- No --> H[Notify User];
G --> I[Final State: Macros Updated];
I --> J[End];
Prerequisites
To follow along, you'll need:
- LangGraph & LangChain: For stateful, multi-turn agent logic.
- OpenAI GPT-4o: For complex reasoning and tool calling.
- Oura API: To get your recovery data.
- Playwright: To automate the MyFitnessPal web interface (since they lack a public API for macro settings).
Step 1: Defining the Agent State
In LangGraph, the state is the source of truth. We need to track the user's current metrics and whether the update was successful.
from typing import TypedDict, Annotated, List
import operator
class AgentState(TypedDict):
sleep_score: int
readiness_score: int
cgm_trend: str
current_macros: dict
target_macros: dict
messages: Annotated[List[str], operator.add]
status: str
Step 2: The Tools (Bio-Sensors & Browser Automation)
We need a way for our agent to "see" and "touch" the world. First, let’s define the tool that fetches Oura Ring data.
import requests
from langchain.tools import tool
@tool
def get_oura_readiness(api_key: str):
"""Fetches the sleep and readiness score for the current day."""
# Simplified API call
headers = {'Authorization': f'Bearer {api_key}'}
response = requests.get('https://api.ouraring.com/v2/usercollection/daily_readiness', headers=headers)
data = response.json()['data'][-1]
return {
"readiness": data['score'],
"contributors": data['contributors']
}
Now for the "Action" part of ReAct: updating MyFitnessPal. Since MFP doesn't have an open API for macro adjustments, we use Playwright to simulate a human user.
from playwright.sync_api import sync_playwright
@tool
def update_mfp_macros(calories: int, protein: int, carbs: int, fat: int):
"""Updates MyFitnessPal goals using browser automation."""
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://www.myfitnesspal.com/account/login")
# Logic for login and navigating to 'Goals' goes here...
# page.fill("#username", "my_user")
# ...
print(f"✅ Successfully updated MFP to {calories} kcal")
browser.close()
return "Success"
Step 3: The Reasoning Engine (LangGraph Logic)
This is where the magic happens. We define a node that interprets the data. If the Readiness Score is below 60, the agent might decide to increase "Recovery Carbs" and decrease "Intensity Protein."
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [get_oura_readiness, update_mfp_macros]
llm_with_tools = llm.bind_tools(tools)
def dietitian_logic(state: AgentState):
prompt = f"""
User Readiness: {state['readiness_score']}
Sleep Score: {state['sleep_score']}
CGM Trend: {state['cgm_trend']}
If readiness is < 70, reduce workout intensity. Increase carbs by 15% to aid recovery.
Update MyFitnessPal goals accordingly.
"""
response = llm_with_tools.invoke(prompt)
return {"messages": [response]}
The "Official" Way: Advanced Patterns
While this script works for a single user, building this at scale requires handling session persistence, token refreshing, and complex error handling (what if the MFP UI changes?).
For a deep dive into building production-grade healthcare agents and handling sensitive biometric data, you absolutely must check out the engineering guides at WellAlly Blog. They cover how to move from a script to a distributed agentic system that can handle thousands of concurrent users safely.
Step 4: Compiling the Graph
Finally, we connect the dots. The agent starts, checks Oura, decides on macros, and executes the update via Playwright.
from langgraph.graph import StateGraph, END
workflow = StateGraph(AgentState)
workflow.add_node("agent", dietitian_logic)
workflow.add_node("action", ToolNode(tools))
workflow.set_entry_point("agent")
workflow.add_conditional_edges("agent", lambda x: "action" if x["messages"][-1].tool_calls else END)
workflow.add_edge("action", "agent")
app = workflow.compile()
Conclusion: The Autonomous Future 🚀
By combining LangGraph's state management with Playwright's ability to interact with legacy web platforms, we've turned a simple LLM into a functional Autonomous Dietitian. This agent doesn't just give advice; it executes the plan.
This is the core of the "Learning in Public" philosophy: taking messy, real-world problems (like manual calorie tracking) and solving them with cutting-edge AI orchestration.
What’s next?
- Add Strava API integration to adjust macros based on yesterday's actual calorie burn.
- Implement a "Human-in-the-loop" node to ask for confirmation before updating MFP.
Are you building AI Agents for health? Drop a comment below or join the conversation over at WellAlly! 🥑💻
Top comments (0)