Routing (notebook)
from agex import Agent, Versioned, connect_llm, MemberSpec
from agex.helpers import register_stdlib
import osmnx as ox
import networkx as nx
import folium
import geopandas as gpd
routy = Agent(
name="routy",
primer="You assist the user finding routes (OSMnx) and visualizing them (Folium).",
llm_client=connect_llm(provider="openai", model="gpt-5"),
timeout_seconds=60.0, # agent will need to download openstreetmap data
)
Now we'll start registration with our agent.
This is an alternative to tool-layer abstractions. Instead we're unlocking Python abilities for our agent and highlighting useful bits as guidance.
# give our agent standard lib modules like 'math'
register_stdlib(routy)
# our agent may not be familiar with osmnx, so we'll highlight parts
routy.module(
ox,
visibility="low",
recursive=True,
configure={
# elevate for high visibility
"geocoder.geocode": MemberSpec(visibility="high"),
"graph.graph_from_bbox": MemberSpec(visibility="high"),
"distance.nearest_nodes": MemberSpec(visibility="high"),
"distance.nearest_edges": MemberSpec(visibility="high"),
"routing.shortest_path": MemberSpec(visibility="high"),
"routing.k_shortest_paths": MemberSpec(visibility="high"),
"routing.route_to_gdf": MemberSpec(visibility="high"),
"convert.graph_to_gdfs": MemberSpec(visibility="high"),
},
)
# our agent may not be familiar with folium, so we'll highlight parts
routy.module(
folium,
visibility="low",
recursive=True,
configure={
# elevate for high visibility
"Map": MemberSpec(visibility="high"),
"Marker": MemberSpec(visibility="high"),
"PolyLine": MemberSpec(visibility="high"),
},
)
# as osmnx uses nx and geopandas artifacts, our agent may want these as well
routy.module(nx, visibility="low", recursive=True)
routy.module(gpd, visibility="low", recursive=True)
Next we make the agent entry point.
We define it just like a regular Python fn. The types are the contracts and the agent gets to build the implementation dynamically when the fn is called.
@routy.task
def route(prompt: str) -> folium.Map: # type: ignore[return-value]
"Find a route given the prompt and return a Folium map."
pass
Now let's call this new task function.
We'll include a Versioned
state in the call so that our agent has memory accross tasks.
state = Versioned()
map = route("I'd like to drive from Albany, OR to Corvallis, OR", state=state)
display(map)
Awesome! The agent found a straight forward route and gave us an interactive folium.Map
.
But oh no, it turns out US20 is closed due to slime eels!
map = route("Hwy 20 is closed between Albany and Corvallis, other options?", state=state)
display(map)
Great, the agent found two viable alternatives!
Under the hood, the agent is downloading openstreetmap data, geocoding locations, searching for best routes, and then converting routes into plottable polylines. It's a considerable amount of work.
But how did it handle the "avoid Hwy 20" constraint?
We were rude. We never gave the agent a tool or any hints for avoiding specific roads. Instead, when faced with this requirement, the agent invented its own tool on the fly by composing primitives from the OSMnx library. It wrote a new helper to filter the road network graph before finding the shortest path.
Here is the actual helper function:
# Helper: compute shortest path while avoiding edges with certain patterns in name
def compute_route_avoiding(G_in, orig, dest, avoid_patterns_upper):
H = G_in.copy()
to_remove = []
for u, v, k, data in H.edges(keys=True, data=True):
ref = str(data.get('ref', '') or '')
name = str(data.get('name', '') or '')
tags = (ref + ' ' + name).upper()
if any(p in tags for p in avoid_patterns_upper):
to_remove.append((u, v, k))
if to_remove:
H.remove_edges_from(to_remove)
route = osmnx.routing.shortest_path(H, orig=orig, dest=dest, weight='length')
return H, route, len(to_remove)
# Alternative 1: Avoid US-20
patterns_us20 = ['US 20', 'US-20', 'US20', 'HWY 20', 'OR 20', 'OR-20', 'OR20']
H1, route1, removed1 = compute_route_avoiding(G, orig_node, dest_node, patterns_us20)
The benefit is two-fold. First, we avoided the need to write new tooling layers for OSMnx & Folium. The agent learns what it needs just from fn sigs and existing docs. Second, by giving the agent a set of primitives it has more space to be creative when solving unexpected problems.
This approach is asking a little more from our agents. But as the example above shows, they're often up to the challenge.