DEV Community

wellallyTech
wellallyTech

Posted on

Build a Smarter Workout: Creating a Self-Adjusting AI Coach with LangGraph and Biofeedback πŸƒβ€β™‚οΈπŸ€–

We’ve all been there: your fitness app tells you it's "Leg Day," but you woke up with a resting heart rate of 80 and a sleep score that looks like a failing grade. Most training plans are static, but our bodies are dynamic. This is exactly where LLM Agents and LangGraph shine.

In this tutorial, we are building a Self-Adjusting AI Coach Agent. This isn't just a chatbot; it’s a closed-loop system that fetches your real-time health data (HRV, Sleep) via the Oura API, reasons about your recovery state, searches for optimized low-impact workouts using Tavily, and dynamically reschedules your Google Calendar.

By leveraging LangGraph, we can handle the cyclic nature of "Check Health -> Decide -> Update -> Verify" much more effectively than a linear chain. We'll be using LangGraph, Oura API, and Google Calendar automation to bring this to life.


The Architecture: A Closed-Loop Fitness Agent

Unlike a simple RAG pipeline, our agent needs to maintain state and potentially loop back if a scheduling conflict occurs. Here is how the data flows:

graph TD
    A[Start: Morning Sync] --> B{Fetch Oura Metrics}
    B --> C[Analyze Readiness Score]
    C -->|High Readiness| D[Keep Original Workout]
    C -->|Low Readiness| E[Consult Tavily for Active Recovery]
    E --> F[Find Low-Intensity Alternative]
    D --> G[Sync with Google Calendar]
    F --> G
    G --> H{Conflict?}
    H -->|Yes| I[Reschedule Logic]
    I --> G
    H -->|No| J[Finalize Schedule & Notify User]
    J --> K[End]
Enter fullscreen mode Exit fullscreen mode

Prerequisites πŸ› οΈ

To follow along, you'll need:

  • Python 3.10+
  • LangGraph & LangChain: For agent orchestration.
  • Oura Personal Access Token: To get your biometric data.
  • Google Cloud Console Access: With Calendar API enabled.
  • Tavily API Key: For searching recovery-focused exercises.

Step 1: Defining the Agent State

In LangGraph, the State is the source of truth passed between nodes. We need to track our health metrics and our current schedule.

from typing import Annotated, TypedDict, List
from langgraph.graph import StateGraph, END

class AgentState(TypedDict):
    health_metrics: dict
    current_schedule: List[str]
    readiness_score: int
    suggested_workout: str
    calendar_updated: bool
    recovery_mode: bool
Enter fullscreen mode Exit fullscreen mode

Step 2: Fetching Real-Time Biofeedback

We’ll create a node that talks to the Oura API. We are specifically looking for the hrv_balance and readiness_score.

import requests

def fetch_health_data(state: AgentState):
    # Mocking the Oura API call for demonstration
    # In production, use: requests.get(f"{OURA_URL}/usercollection/daily_readiness", headers=headers)
    print("πŸš€ Fetching biometric data from Oura...")

    # Example response data
    metrics = {
        "readiness_score": 62,  # Low score
        "hrv": 35,
        "sleep_duration": "5h 20m"
    }

    return {
        "health_metrics": metrics,
        "readiness_score": metrics["readiness_score"],
        "recovery_mode": metrics["readiness_score"] < 70
    }
Enter fullscreen mode Exit fullscreen mode

Step 3: The Reasoning Engine (The "Coach")

If our recovery is low, the agent shouldn't just cancel the workoutβ€”it should pivot. We use Tavily Search to find a mobility flow or yoga routine that fits the user's usual workout duration.

from langchain_community.tools.tavily_search import TavilySearchResults

def plan_workout_node(state: AgentState):
    if state["recovery_mode"]:
        search = TavilySearchResults(k=1)
        query = "best 30 minute active recovery mobility flow for low HRV"
        result = search.run(query)
        suggestion = f"Recovery Flow: {result}"
    else:
        suggestion = "Proceed with scheduled Heavy Strength Training."

    return {"suggested_workout": suggestion}
Enter fullscreen mode Exit fullscreen mode

Step 4: Syncing with Google Calendar

Now, we use the Google Calendar API to find the "Gym" block and update it with our new suggested_workout.

def update_calendar_node(state: AgentState):
    workout = state["suggested_workout"]
    print(f"πŸ“… Updating Google Calendar with: {workout}")

    # Logic to interface with google-api-python-client
    # service.events().patch(calendarId='primary', eventId=event_id, body=updated_event).execute()

    return {"calendar_updated": True}
Enter fullscreen mode Exit fullscreen mode

Advanced Patterns & Production Readiness πŸ₯‘

While this demo covers the basics, building a production-grade AI agent requires handling edge cases like API rate limits, OAuth refreshes, and "hallucination checks" for the LLM's reasoning.

For more advanced agentic patterns, multi-agent collaboration strategies, and production-ready implementation guides, I highly recommend checking out the technical deep-dives at WellAlly Blog. They have fantastic resources on how to scale these "human-in-the-loop" AI systems effectively.


Step 5: Wiring the Graph

This is where the magic happens. We connect the nodes and define the flow logic.

workflow = StateGraph(AgentState)

# Add Nodes
workflow.add_node("fetch_health", fetch_health_data)
workflow.add_node("plan_workout", plan_workout_node)
workflow.add_node("sync_calendar", update_calendar_node)

# Define Edges
workflow.set_entry_point("fetch_health")
workflow.add_edge("fetch_health", "plan_workout")
workflow.add_edge("plan_workout", "sync_calendar")
workflow.add_edge("sync_calendar", END)

# Compile the Graph
app = workflow.compile()

# Run it!
inputs = {"current_schedule": ["08:00 - Weightlifting"]}
for output in app.stream(inputs):
    print(output)
Enter fullscreen mode Exit fullscreen mode

Conclusion: The Era of Personalized Agents πŸš€

By combining LangGraph with real-world health telemetry, we’ve moved past static automation into the realm of context-aware AI. Your AI Coach now knows when you're burnt out before you even open your eyes.

What's next for your agent?

  • Add a Twilio node to SMS you the update.
  • Integrate Garmin's "Body Battery" for even more granularity.
  • Add a feedback loop where you can "veto" the coach's suggestion via Slack.

If you enjoyed this build, drop a comment below! And don't forget to visit WellAlly for more insights into the future of AI-driven wellness and agentic workflows. Happy coding! πŸ’»πŸ”₯

Top comments (0)