Entry Points, Finish Points, and Graph Compilation
Configure entry and finish points in LangGraph. Understand set_entry_point, set_finish_point, multiple entry points, and what graph compilation does.
Entry Points
Every LangGraph graph needs exactly one entry point — the first node that receives the initial state when you call invoke().
from langgraph.graph import StateGraph, END
from typing import TypedDict
class State(TypedDict):
message: str
result: str
def process(state: State) -> State:
return {**state, "result": f"Processed: {state['message']}"}
graph = StateGraph(State)
graph.add_node("process", process)
# Set the entry point
graph.set_entry_point("process")
graph.add_edge("process", END)
app = graph.compile()
result = app.invoke({"message": "hello", "result": ""})
print(result["result"]) # "Processed: hello"Finish Points
set_finish_point() sets a node as the terminal state — execution ends when this node completes, without needing an explicit edge to END:
graph = StateGraph(State)
graph.add_node("start", start_fn)
graph.add_node("process", process_fn)
graph.add_node("finalize", finalize_fn)
graph.set_entry_point("start")
graph.add_edge("start", "process")
graph.add_edge("process", "finalize")
# finalize is the finish point — no edge to END needed
graph.set_finish_point("finalize")
app = graph.compile()set_finish_point is equivalent to adding graph.add_edge("finalize", END). Use whichever is clearer for your graph structure.
Using END Directly
The most common pattern uses END from langgraph.graph:
from langgraph.graph import StateGraph, END
# Conditional routing to END
graph.add_conditional_edges(
"router",
routing_function,
{
"process": "process_node",
"skip": END, # Goes directly to end
"error": "error_handler",
},
)Multiple Conditional Entry Points
Use add_conditional_edges from START for dynamic entry routing:
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
class QueryState(TypedDict):
query: str
query_type: str # "drug_info", "interaction", "dosing"
result: str
def classify_query(state: QueryState) -> str:
"""Route to different handlers based on query type."""
query = state["query"].lower()
if "interaction" in query:
return "interaction_handler"
elif "dose" in query or "dosing" in query:
return "dosing_handler"
return "drug_info_handler"
def drug_info_handler(state: QueryState) -> QueryState:
return {**state, "result": "General drug info response"}
def interaction_handler(state: QueryState) -> QueryState:
return {**state, "result": "Drug interaction response"}
def dosing_handler(state: QueryState) -> QueryState:
return {**state, "result": "Dosing guidance response"}
graph = StateGraph(QueryState)
graph.add_node("drug_info_handler", drug_info_handler)
graph.add_node("interaction_handler", interaction_handler)
graph.add_node("dosing_handler", dosing_handler)
# Conditional entry from START
graph.add_conditional_edges(
START,
classify_query,
{
"drug_info_handler": "drug_info_handler",
"interaction_handler": "interaction_handler",
"dosing_handler": "dosing_handler",
},
)
# All handlers go to END
for handler in ["drug_info_handler", "interaction_handler", "dosing_handler"]:
graph.add_edge(handler, END)
app = graph.compile()
result = app.invoke({"query": "What is the interaction between warfarin and aspirin?", "query_type": "", "result": ""})
print(result["result"]) # "Drug interaction response"What graph.compile() Does
compile() converts the graph definition into an executable CompiledGraph:
app = graph.compile()Compile-time operations:
- Validation: Checks that all edges reference defined nodes, entry point is set, no unreachable nodes
- Optimization: Pre-computes routing paths where possible
- Checkpointing setup: If a checkpointer is provided, configures persistence
- Interrupt setup: Registers human-in-the-loop interrupt points
Compilation fails with clear errors if the graph is malformed:
- Missing entry point →
ValueError: Entry point not set - Edge to undefined node →
ValueError: Unknown node 'xyz' - No path to END → Detected as potential infinite loop at runtime
Compile Options
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = graph.compile(
checkpointer=checkpointer, # Enable state persistence
interrupt_before=["review"], # Pause before "review" node for human approval
interrupt_after=["generate"], # Pause after "generate" to inspect output
debug=True, # Print detailed execution trace
recursion_limit=50, # Max graph steps (default: 25)
)Inspecting the Compiled Graph
# Get the Mermaid diagram of the graph
print(app.get_graph().draw_mermaid())
# Output:
# %%{init: {'flowchart': {'curve': 'linear'}}}%%
# graph TD;
# __start__([__start__]):::first
# process([process])
# __end__([__end__]):::last
# ...
# Get all nodes
print(list(app.get_graph().nodes))
# Stream execution events (useful for debugging)
for event in app.stream({"message": "hello", "result": ""}):
print(event)Common Entry/Finish Mistakes
Mistake 1: Forgetting to set entry point
graph.compile() # Raises: ValueError: Entry point not setMistake 2: Node with no outgoing edge and no finish point
graph.add_node("orphan", fn) # This node has no edge to END and is not set_finish_point
# Graph may compile but execution gets stuck at "orphan"Mistake 3: Circular import preventing START import
# Wrong: END is always imported, but START may not be in older versions
from langgraph.graph import StateGraph, END # OK for edge-based entry
from langgraph.graph import START # Needed for conditional_edges from STARTAlways explicitly add graph.add_edge(node, END) or graph.set_finish_point(node) for every terminal node in your graph.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.