Build a 2026‑Ready LangGraph AI Agent: A Step‑by‑Step Guide
This tutorial walks you through constructing a LangGraph‑based AI agent for automated Strava training plans, covering core concepts like state, nodes, and edges, detailed workflow steps, Python code examples, conditional graph routing, testing, and deployment via GitHub Actions.
What is LangGraph?
LangGraph is a library built on LangChain that focuses on explicit control flow for LLM applications. Instead of a linear pipeline, it models an application as a graph that can branch, loop, and revisit earlier steps.
Why LangGraph over traditional LangChain DAGs?
Traditional LangChain workflows are directed acyclic graphs (DAGs) that move forward and terminate. This works for many use cases, but it limits agents that need iteration, re‑evaluation, or dynamic decision‑making. LangGraph solves this by allowing cycles, enabling nodes to be revisited and decisions to be made repeatedly.
Core abstractions: State, Nodes, and Edges
Understanding state , nodes , and edges is enough to grasp how LangGraph works.
State – Agent memory
State is a shared data structure that flows through the graph. Each node receives the current state and can return updates, acting as the agent’s cumulative context (messages, decisions, tool outputs, etc.).
Node – Focused behavior unit
A node is an executable unit representing a single, well‑defined operation, such as:
Calling an LLM
Calling a tool or API
Transforming or validating data
Making routing decisions
Small, focused nodes are easier to test, reason about, and modify.
Edge – Control flow
Edges define how execution moves from one node to the next. They can be:
Sequential (always move to the next node)
Conditional (choose the next node based on state)
Cyclic (return to a previous node)
Explicit edges make the control flow visible in the graph rather than hidden in prompt logic.
Example: Strava‑based training agent
The tutorial builds an AI agent that connects to a Strava account, summarizes recent training, evaluates progress against goals, generates a weekly plan, adds warnings if needed, composes an email, and optionally sends it.
Step 1 – Map the workflow to discrete steps
Break the automation into clear steps, each becoming a node (a small function with a single responsibility). The diagram below shows how the nodes combine in the Strava training workflow.
Step 2 – Define each node’s purpose
For each step decide its type, required context, and expected output. Example nodes include:
Data step : Sync Strava activities (fetch past 90 days of data).
LLM step : Summarize recent training into high‑level metrics.
LLM step : Evaluate progress vs. goal, producing a status, confidence score, risk flags, and recommendation (keep/adjust/deload).
LLM step : Generate next‑week plan based on goal, summary, and recommendation.
Operation step : Send the weekly email.
State definition (TypedDict)
from typing import TypedDict, Literal, List, Dict
class TrainingEvaluation(TypedDict):
status: Literal["on_track", "behind", "overloaded"]
confidence: float
risk_flags: List[str]
recommendation: Literal["keep", "adjust", "deload"]
class TrainingSession(TypedDict):
day: str
description: str
duration_min: int
intensity: Literal["easy", "moderate", "hard"]
class StravaTrainingAgentState(TypedDict):
activities: List[Dict] | None
training_summary: Dict | None
goal: Dict
sessions_per_week: int
evaluation: TrainingEvaluation | None
next_week_plan: List[TrainingSession] | None
weekly_email: str | None
run_id: str
last_sync_timestamp: strStep 4 – Implement nodes (Python functions)
Nodes are simple Python functions that take the shared state and return updates. The key node generate_next_week_plan uses a minimal ChatOpenAI wrapper to produce a JSON plan.
def generate_next_week_plan(state: StravaTrainingAgentState) -> dict:
"""LangGraph node: generate next week's training plan."""
llm: LLMService = _get_service("llm")
goal = state.get("goal", {})
summary = state.get("training_summary", {})
sessions = state.get("sessions_per_week", 3)
recommendation = (state.get("evaluation") or {}).get("recommendation", "adjust")
system_prompt = (
"You are a running coach. Return ONLY valid JSON: "
"a list of sessions with fields: day (Mon-Sun), description, "
"duration_min, intensity (easy|moderate|hard). "
"Respect sessions_per_week exactly. Cap progression at +10% unless deloading (20–30% down)."
)
user_prompt = (
f"Goal: {json.dumps(goal)}
"
f"Recent summary: {json.dumps(summary)}
"
f"Recommendation: {recommendation}
"
f"Sessions per week: {sessions}"
)
plan = llm.structured_completion(system_prompt, user_prompt)
return {"next_week_plan": plan}Step 5 – Connect the graph
After implementing all nodes, create a StateGraph, add nodes, and define edges. Most edges are sequential; the edge after generate_next_week_plan is conditional based on the recommendation.
# Initiate graph
graph = StateGraph(StravaTrainingAgentState)
# Add nodes
graph.add_node("sync_strava_activities", sync_strava_activities)
graph.add_node("summarize_recent_training", summarize_recent_training)
graph.add_node("evaluate_progress_vs_goal", evaluate_progress_vs_goal)
graph.add_node("generate_next_week_plan", generate_next_week_plan)
graph.add_node("adjust_plan_add_warnings", adjust_plan_add_warnings)
graph.add_node("compose_weekly_email", compose_weekly_email)
graph.add_node("send_email", send_email)
# Add edges
graph.add_edge(START, "sync_strava_activities")
graph.add_edge("sync_strava_activities", "summarize_recent_training")
graph.add_edge("summarize_recent_training", "evaluate_progress_vs_goal")
graph.add_edge("evaluate_progress_vs_goal", "generate_next_week_plan")
graph.add_conditional_edges(
"generate_next_week_plan",
lambda state: (state.get("evaluation") or {}).get("recommendation", "adjust"),
{
"keep": "compose_weekly_email",
"adjust": "adjust_plan_add_warnings",
"deload": "adjust_plan_add_warnings",
},
)
graph.add_edge("adjust_plan_add_warnings", "compose_weekly_email")
graph.add_edge("compose_weekly_email", "send_email")
graph.add_edge("send_email", END)
app = graph.compile()Step 6 – Test the agent
Clone the GitHub repository, set up a virtual environment, install dependencies, and configure a .env file with Strava and OpenAI credentials. Run the agent locally with: python strava_training_agent.py If SMTP credentials are missing, the agent prints the email preview instead of sending.
Automation
For weekly execution, a GitHub Actions workflow ( weekly-agent.yml) uses a cron trigger. Secrets store Strava, OpenAI, and optional SMTP credentials, and the same script runs in CI.
Final thoughts
The tutorial’s goal is to demonstrate how LangGraph enables explicit, testable, and reasoning‑friendly agent workflows. By modeling problems as graphs with shared state, small nodes, and clear edges, you can build robust agents for training plans, customer support, data analysis, or any scenario requiring reasoning, action, and loops.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
