AI Workflows

Part 2 — Turn Two Python Functions into Your First LangGraph

CodeTips Academy
October 24, 2026
7 min read
5 views
Turn Two Python Functions into Your First LangGraph

Build and run a StateGraph, then trace how nodes return changes to a shared dictionary-shaped state.

Part 2 of 8 · Basic Python is enough · Tested with Python 3.12.4, LangGraph 1.2.16 and SQLite checkpointer 3.1.2.

Build and run a StateGraph, then trace how nodes return changes to a shared dictionary-shaped state.

Code for Part 2: Download Part 2 and its runnable project ZIP. Download the complete series repository. Extract the ZIP and follow its README before running the examples.

Keep the task small while learning the graph

In article 1, one Python function cleaned a question and returned a fixed reply. We can do that with plain Python, and we still could. This lesson introduces graph vocabulary using the same small task so you do not have to learn a new business problem at the same time.

By the end, you will run two graph nodes and inspect the state they produced. There is no model, database, account, or API key involved.

Install into an isolated environment

Create a virtual environment in the repository root:

python -m venv .venv

On Windows, call that environment's Python explicitly:

.venv\Scripts\python.exe -m pip install -r requirements-lock.txt
.venv\Scripts\python.exe -m pip install --no-deps -e .
.venv\Scripts\python.exe lessons/02_first_graph/main.py

On Linux or macOS:

.venv/bin/python -m pip install -r requirements-lock.txt
.venv/bin/python -m pip install --no-deps -e .
.venv/bin/python lessons/02_first_graph/main.py

The lock file records the versions used for this edition, including development dependencies. The second command installs our own package in editable mode; --no-deps keeps the already installed locked dependencies. Downloading packages requires internet access once. Running the demo after installation does not require a model service.

For brevity, later examples use python. That means your virtual-environment interpreter: either activate the environment or substitute the explicit path shown above. There is no need to change Windows execution-policy settings just to run these commands.

Three names you need

State is the named data available to workflow steps. Nodes are functions that do work and return updates. Edges specify where execution can go next. START and END mark the entry and exit of this graph. Graph API overview

Two graph nodes update different state fields: clean adds cleaned, and draft adds reply.

Here is the complete lesson:

"""Article 2: put the two Python steps into a StateGraph."""
import json
from typing import TypedDict
from langgraph.graph import END, START, StateGraph

class State(TypedDict, total=False):
    text: str
    cleaned: str
    reply: str

def clean(state):
    return {"cleaned": " ".join(state["text"].split())}

def draft(state):
    return {"reply": "Check the environment for this question: " + state["cleaned"]}

def build():
    builder = StateGraph(State)
    builder.add_node("clean", clean)
    builder.add_node("draft", draft)
    builder.add_edge(START, "clean")
    builder.add_edge("clean", "draft")
    builder.add_edge("draft", END)
    return builder.compile()

if __name__ == "__main__":
    print(json.dumps(build().invoke({"text": "  My Python module   is missing.  "}), indent=2))

Generate and explain the graph built by this code

The diagram below is generated from this lesson's compiled get_graph() nodes and edges. Node positions are chosen for readability; the connections come from the Python graph, rather than being copied from a generic illustration.

After installing the project, run this command from its root:

python tools/render_graphs.py --part 2

It writes assets/generated/part-02.svg (the image), .mmd (Mermaid source), and .json (the captured nodes and edges). Rendering is local and does not call a model or a remote image service. If you add nodes, update the layout positions in the renderer as well.

Part 2: the graph generated from this lesson's compiled Python code.

Open the full-size graph.

Read the arrows with the code

  • START → clean: The initial question enters the clean function. It adds the cleaned field.
  • clean → draft: Draft reads cleaned, so this edge puts the data-producing step first.
  • draft → END: The reply has been prepared. This graph has no branch, pause, or delivery step.

Solid lines are fixed transitions. Orange dashed lines mark possible conditional destinations; the routing function chooses the destination for a particular run. A node can contain several Python operations that are not separate graph nodes.

Understand the state schema

State inherits from TypedDict. It describes the keys and value types we intend to use in a dictionary. text, cleaned, and reply are strings. total=False allows some keys to be absent: we begin with text, then later nodes create the other values.

A type hint is not a promise that Python will validate every value at runtime. If a caller passes a list where your function expects text, the annotation does not magically turn it into a string. The final application checks its external inputs explicitly. Here, the fixed example keeps the input simple while we learn the flow.

Notice that clean returns only {"cleaned": ...}. It does not return the entire incoming state. draft returns only {"reply": ...}. Each node states what changed. That makes its responsibility easier to inspect than a function that mutates several unrelated fields in place.

Read the builder as a map

The two add_node calls register functions under names. The name "clean" identifies a graph node; the Python function clean supplies its behavior. The names happen to match because that is easy to read, but they serve different roles.

The three edges establish one path. Entry goes to clean, cleaning goes to draft, and drafting goes to the end. Compiling gives us the runnable graph. Invoking it supplies the initial state and waits for a result.

There are no branches yet. If you can read clean -> draft, you can explain the whole execution. Adding a graph did not make the reply more intelligent. It made the sequence explicit in a form we can extend.

Trace the result yourself

For the supplied question, the final values are:

{
  "text": "  My Python module   is missing.  ",
  "cleaned": "My Python module is missing.",
  "reply": "Check the environment for this question: My Python module is missing."
}

The original text remains unchanged. The cleaned field records a derived value, and reply uses that derived value. Preserving both can be useful when investigating exactly what normalization changed.

Follow the access in draft: state["cleaned"]. That field is available because our edges require clean to run first. If you connect entry straight to draft, you have changed the data dependency. The field would not exist in the initial dictionary, and the function would fail.

Graph construction therefore describes more than visual ordering. It also establishes when a function's required inputs should be available.

Common mistakes

If langgraph cannot be imported, check python -m pip --version and the interpreter path. Installing with one Python and executing with another is a common environment problem. Use the explicit virtual-environment path to remove the ambiguity.

If a node raises a missing-key error, identify the node that should have created the key, then inspect the edge leading to the failing node. Do not fix it by filling every field with meaningless defaults; that can hide an incorrectly ordered graph.

If your reply still contains unexpected whitespace, confirm that you used state["cleaned"], not state["text"]. The graph cannot infer which of two valid fields your business logic intended to use.

Practice

Add a node called count that creates a character_count integer from cleaned. Update the schema and change the edges to clean -> count -> draft. Predict the final dictionary before running the program.

Then move count after draft. Does the count change? Explain why both positions are valid for this particular function. Contrast that with moving draft before clean.

In article 3, we will replace the single path with a decision: technical questions proceed toward documentation, while billing and unknown questions go to a person.


This is an educational project using fictional workshop notes and synthetic tickets. It does not access the real CodeTips Academy website or send email. Downloadable code is provided above. GitHub source links will be added after the repository is published.


Related Articles

Back to Articles