{
  "$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."
}