{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreihw7l36tb52mjc2l6pgc5ksr6nebfv7fikqzir3yn3cynfh4w4jma",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mp4l6o5pazn2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreidr7meylnms6gacxgbzc2ui6opqes7shrw7kvhofue6wn3nwdzyxq"
},
"mimeType": "image/webp",
"size": 75532
},
"path": "/chex0210crypto/langgraphs-routing-is-llm-guessing-i-wrote-50-lines-of-code-to-make-it-deterministic-24oj",
"publishedAt": "2026-06-25T13:18:34.000Z",
"site": "https://dev.to",
"tags": [
"langgraph",
"agents",
"python",
"ai",
"github.com/chex0210-crypto/decide-router"
],
"textContent": "Every time your LangGraph agent sees \"check nginx logs\", it might call a different tool.\n\nThat's not an exaggeration. LangGraph's routing is driven by an LLM prompt — and prompts aren't reproducible. Same input, different day, different LLM mood, different tool selected.\n\nI spent months debugging this. Every wrong route meant tweaking a prompt, hoping the next call would be better. It never was.\n\nSo I wrote something different.\n\n## The Problem: Routing by LLM is fragile\n\nLangGraph is great at orchestrating multi-step agent workflows. But the first step — \"what tool should I use?\" — is a black box.\n\nYou define a prompt, the LLM decides. If it chooses wrong, you can't debug it. You can only guess: was the prompt not specific enough? Too specific? Wrong example?\n\nThis is the **routing problem** : given user input, which domain or tool should handle it? LangGraph leaves this to the LLM. I think it shouldn't.\n\n## The Fix: 50 Lines of YAML + Python\n\n\n from decide_router import RouteTable\n\n rt = RouteTable(\"routes.yaml\")\n rule, _ = rt.match(\"check nginx error logs\")\n # rule.domain → \"monitoring\" — always, every time\n\n\nNo LLM call. No prompt. Just a YAML file and 50 lines of matching logic.\n\nThe YAML defines domains with keywords and regex patterns:\n\n\n\n domains:\n monitoring:\n priority: 100\n keywords: [log, error, nginx, monitor, health, status]\n patterns: [\"(check|look).*(log|status|error)\"]\n coding:\n priority: 100\n keywords: [code, script, deploy, write]\n human:\n priority: 100\n keywords: [delete production, restart cluster]\n require_confirm: true\n\n\nPlug it into LangGraph as a node:\n\n\n\n from langgraph.graph import Graph\n from decide_router import RouteTable\n\n rt = RouteTable(\"routes.yaml\")\n\n def routing_node(state):\n rule, _ = rt.match(state[\"input\"])\n state[\"domain\"] = rule.domain if rule else \"unknown\"\n return state\n\n graph = Graph()\n graph.add_node(\"decide_route\", routing_node)\n graph.set_entry_point(\"decide_route\")\n\n\nYour LangGraph agent now routes with **rules** , not guesses. Same input → same domain. Every time.\n\n## The Surprising Part: It Learns From Corrections\n\nThe best part wasn't planned — it emerged from using it.\n\nI noticed I kept correcting wrong routes. \"That's monitoring, not coding.\" Every correction was a signal. So I added a feedback cache:\n\n\n\n # User corrects once → remembers immediately\n rt.record_feedback(\"check nginx errors\", \"monitoring\")\n\n\nSame correction 3 times → becomes a permanent routing rule at priority 70. Rules not used in 30 days auto-delete. The system gets smarter with every correction. No fine-tuning. No prompt engineering.\n\nThe core module is one file, 200 lines of Python. Read the whole thing in 5 minutes.\n\n## Try It\n\n\n pip install decide-router\n\n\nOr browse the code: github.com/chex0210-crypto/decide-router\n\n_I built this because I was tired of guessing why my agent picked the wrong tool. If you've had the same frustration, star the repo — it helps other people find it._",
"title": "LangGraph's Routing Is LLM-Guessing. I Wrote 50 Lines of Code to Make It Deterministic."
}