<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hanlin Xiang</title>
    <description>The latest articles on DEV Community by Hanlin Xiang (@ai-gateway-veteran).</description>
    <link>https://dev.to/ai-gateway-veteran</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3988994%2F78c4bdc4-22db-4b3f-83ca-994d4b4d8f20.png</url>
      <title>DEV Community: Hanlin Xiang</title>
      <link>https://dev.to/ai-gateway-veteran</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ai-gateway-veteran"/>
    <language>en</language>
    <item>
      <title>OpenClaw: My AI Home Base That Runs Everywhere</title>
      <dc:creator>Hanlin Xiang</dc:creator>
      <pubDate>Thu, 25 Jun 2026 02:13:38 +0000</pubDate>
      <link>https://dev.to/ai-gateway-veteran/openclaw-my-ai-home-base-that-runs-everywhere-3fk2</link>
      <guid>https://dev.to/ai-gateway-veteran/openclaw-my-ai-home-base-that-runs-everywhere-3fk2</guid>
      <description>&lt;p&gt;Six weeks ago, my AI workflow looked like this: ChatGPT for coding questions, Claude for writing, a Telegram bot for quick mobile queries, a Discord bot I half-built for my server, and a WhatsApp number I'd set up with some random API wrapper that broke every Tuesday. Five apps. Five different conversation histories. Five places where I'd explain who I am, what I'm working on, and what "that project" refers to.&lt;/p&gt;

&lt;p&gt;I'd ask Claude to refactor a function, then switch to ChatGPT on my phone to ask a follow-up — and it had zero context. I'd paste the same code snippet into three different windows. My AI assistants knew nothing about each other. They were five strangers sharing one brain.&lt;/p&gt;

&lt;p&gt;The breaking point came on a Saturday morning. I was at a coffee shop, phone only. I needed to check if a deployment went through, ask about a weird log line, and draft a Slack message to my team. That's three AI conversations across three apps, on a 6-inch screen, while my coffee got cold.&lt;/p&gt;

&lt;p&gt;I thought: &lt;em&gt;There has to be a single thing I can message from anywhere, and it just knows me.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That thing turned out to be &lt;a href="https://openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What OpenClaw Actually Is
&lt;/h2&gt;

&lt;p&gt;OpenClaw is a &lt;strong&gt;self-hosted AI agent gateway&lt;/strong&gt;. It's not another chat app. It's not another AI model. It's the plumbing that connects every messaging platform you already use to a single AI agent that runs on your own hardware.&lt;/p&gt;

&lt;p&gt;Here's the mental model: imagine a central hub sitting on your machine (or a VPS, or a Raspberry Pi — it doesn't care). That hub maintains live connections to WhatsApp, Telegram, Discord, Slack, Signal, iMessage, and a dozen other platforms simultaneously. When you send a message from any of those apps, it routes to one agent. One brain. One memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You (WhatsApp)  ──┐
You (Telegram)  ──┤
You (Discord)   ──┼──→  OpenClaw Gateway  ──→  AI Agent (your config, your tools, your memory)
You (Slack)     ──┤
You (iMessage)  ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Gateway is the control plane. It owns all the messaging sessions, routes inbound messages to the right agent, manages tool execution, handles memory, and streams responses back to whichever app you're using. You talk from WhatsApp, it answers on WhatsApp. You switch to Telegram mid-thought, it picks up the thread.&lt;/p&gt;

&lt;p&gt;It's built by &lt;a href="https://github.com/steipete" rel="noopener noreferrer"&gt;Peter Steinberger&lt;/a&gt; and the community, it's MIT-licensed, and it's one of the fastest-growing open-source AI agent projects on GitHub — which tells you two things: people want this, and it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5-Minute Install
&lt;/h2&gt;

&lt;p&gt;I'm allergic to complex setups. If a tool needs a 40-minute tutorial to get running, I'm out. OpenClaw surprised me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 24 (recommended) or Node 22.19+&lt;/li&gt;
&lt;li&gt;An API key from any model provider (OpenAI, Anthropic, Google, etc.)&lt;/li&gt;
&lt;li&gt;5 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Install and run:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; openclaw@latest
openclaw onboard &lt;span class="nt"&gt;--install-daemon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The onboarding wizard walks you through picking a model provider, pasting your API key, and configuring the Gateway. It installs as a daemon (launchd on macOS, systemd on Linux) so it stays running across reboots.&lt;/p&gt;

&lt;p&gt;Verify it's up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw gateway status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the built-in dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're looking at a browser-based control UI. Type a message. Get a response. You now have a working AI agent.&lt;/p&gt;

&lt;p&gt;The whole thing took me about 4 minutes, including the time I spent squinting at my API key to make sure I copied it right.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting Your First Channel
&lt;/h2&gt;

&lt;p&gt;The dashboard is fine for testing, but the real magic is talking to your agent from apps you already have open. I started with Telegram because it's the fastest to set up — you just need a bot token from &lt;a href="https://t.me/BotFather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In your OpenClaw config (openclaw onboard handles this, but here's the raw version)&lt;/span&gt;
&lt;span class="na"&gt;channels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;telegram&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-bot-token-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the gateway, send a Telegram message to your bot. It replies. Instantly. With the same context, same memory, same personality as the dashboard.&lt;/p&gt;

&lt;p&gt;Then I connected Discord. Then WhatsApp. Each one took under 5 minutes. The docs have step-by-step guides for every channel:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Setup Time&lt;/th&gt;
&lt;th&gt;What You Need&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Telegram&lt;/td&gt;
&lt;td&gt;3-5 min&lt;/td&gt;
&lt;td&gt;Bot token from &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discord&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Bot token from Discord Developer Portal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Slack app + bot token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WhatsApp&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Phone number (via Baileys)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signal&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Signal CLI linked device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iMessage&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;macOS with BlueBubbles or similar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebChat&lt;/td&gt;
&lt;td&gt;0 min&lt;/td&gt;
&lt;td&gt;Built into the dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full list includes Matrix, Google Chat, Microsoft Teams, IRC, LINE, Mattermost, WeChat, QQ, Feishu, Nostr, Twitch, and more. I haven't tried them all, but the ones I have just work.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Agent, Every Platform
&lt;/h2&gt;

&lt;p&gt;Here's what changed after a week of using OpenClaw: I stopped thinking about &lt;em&gt;which app to open&lt;/em&gt;. I'd be on my laptop, hit a question, and just open Telegram (which was already in my dock). Or I'd be on my phone and fire off a WhatsApp message. The agent didn't care. It answered wherever I asked.&lt;/p&gt;

&lt;p&gt;But the real payoff was &lt;strong&gt;context persistence&lt;/strong&gt;. On Monday, I asked the agent about a database migration via the web dashboard. On Wednesday, I referenced "that migration" from Telegram. It knew exactly what I meant. Same agent. Same memory. Different app.&lt;/p&gt;

&lt;p&gt;This sounds small. It's not. It's the difference between having a personal assistant and having five strangers who each know one slice of your life.&lt;/p&gt;

&lt;p&gt;The way OpenClaw handles this is elegant: the Gateway maintains &lt;strong&gt;per-session, per-sender isolation&lt;/strong&gt;. Your WhatsApp conversation is separate from your Discord conversation, but they share the same agent's long-term memory. The agent remembers facts, preferences, and context across all channels — but each conversation thread stays clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Memory System
&lt;/h2&gt;

&lt;p&gt;This is where OpenClaw went from "useful tool" to "I can't go back."&lt;/p&gt;

&lt;p&gt;The agent has two layers of memory:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session memory&lt;/strong&gt; — the conversation you're having right now. It knows what you said 10 messages ago in this thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long-term memory&lt;/strong&gt; — facts, preferences, and context that persist across sessions and channels. Tell it once that you prefer TypeScript over JavaScript, and it remembers. Mention your team's deployment schedule, and it brings it up when relevant.&lt;/p&gt;

&lt;p&gt;The memory system stores everything locally on your machine. Your data doesn't leave your machine unless you explicitly configure it to.&lt;/p&gt;

&lt;p&gt;In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Week 1:&lt;/strong&gt; I told the agent about my project structure, tech stack, and coding preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 2:&lt;/strong&gt; I asked it to review a PR. It already knew our conventions. No re-explaining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 3:&lt;/strong&gt; I asked for help debugging a race condition. It remembered the async patterns we'd discussed before and suggested a fix that matched our style.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It felt like onboarding a new team member who actually paid attention.&lt;/p&gt;

&lt;p&gt;You can also manually manage memories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List stored memories&lt;/span&gt;
openclaw memory list

&lt;span class="c"&gt;# Search memories&lt;/span&gt;
openclaw memory search &lt;span class="s2"&gt;"deployment schedule"&lt;/span&gt;

&lt;span class="c"&gt;# Add a specific memory&lt;/span&gt;
openclaw memory add &lt;span class="s2"&gt;"Production deploys happen Tuesdays at 2pm UTC"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent also does &lt;strong&gt;memory maintenance&lt;/strong&gt; during heartbeats — periodic check-ins where it reviews recent conversations, distills important facts, and updates its long-term memory. It's like watching someone review their journal and update their mental model. Daily files are raw notes; long-term memory is curated wisdom.&lt;/p&gt;

&lt;p&gt;What surprised me most: the agent started noticing patterns I hadn't. After a few weeks, it pointed out that I always asked about deployment status on Monday mornings and suggested it could proactively check and report. I said yes. Now every Monday at 9 AM, I get a Telegram message with the staging and production status. I didn't build that workflow. The agent suggested it because it &lt;em&gt;remembered&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cron Jobs and Automation
&lt;/h2&gt;

&lt;p&gt;One feature I didn't expect to use as much as I do: &lt;strong&gt;scheduled tasks&lt;/strong&gt;. OpenClaw has a built-in cron system that lets the agent run tasks on a schedule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check for new GitHub issues every morning at 8am&lt;/span&gt;
openclaw cron add &lt;span class="nt"&gt;--schedule&lt;/span&gt; &lt;span class="s2"&gt;"0 8 * * *"&lt;/span&gt; &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Check for new GitHub issues in openclaw/openclaw and summarize any critical ones"&lt;/span&gt;

&lt;span class="c"&gt;# Remind me about standup every weekday at 9:45am&lt;/span&gt;
openclaw cron add &lt;span class="nt"&gt;--schedule&lt;/span&gt; &lt;span class="s2"&gt;"45 9 * * 1-5"&lt;/span&gt; &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Standup in 15 minutes. Here's what you worked on yesterday and what's planned for today."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cron system integrates with the agent's full tool suite. A cron job can search the web, check APIs, run shell commands, and then deliver the result to whatever channel you want. It's like having a cron job that can &lt;em&gt;think&lt;/em&gt; about what it finds.&lt;/p&gt;

&lt;p&gt;I have about a dozen cron jobs running. Some are simple reminders. Others are complex — one checks three different monitoring dashboards, cross-references the results, and only messages me if something looks off. Building that as a traditional script would have taken an afternoon. I described it to the agent in two sentences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice: The Underrated Feature
&lt;/h2&gt;

&lt;p&gt;If you're on macOS or have an iOS/Android node paired, OpenClaw supports &lt;strong&gt;voice input&lt;/strong&gt; and &lt;strong&gt;text-to-speech output&lt;/strong&gt;. Speak your question, get a spoken answer. It integrates with TTS providers for high-quality voice output, and falls back to system TTS if you don't have a premium voice key.&lt;/p&gt;

&lt;p&gt;I use this while cooking. Hands covered in flour, need to know if I can substitute baking powder for baking soda. "Hey, can I swap baking powder for baking soda in this recipe?" — spoken answer, no screen required.&lt;/p&gt;

&lt;p&gt;It sounds like a small thing. It's not. Voice changes the relationship with the agent from "tool I use" to "assistant I talk to."&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools: Giving the Agent Hands
&lt;/h2&gt;

&lt;p&gt;An AI that just &lt;em&gt;talks&lt;/em&gt; is a chatbot. An AI that &lt;em&gt;does things&lt;/em&gt; is an agent. OpenClaw is firmly in the second category.&lt;/p&gt;

&lt;p&gt;The agent ships with a built-in tool suite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shell execution&lt;/strong&gt; — run commands, manage files, deploy code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser automation&lt;/strong&gt; — browse websites, fill forms, take screenshots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web search&lt;/strong&gt; — search the web for current information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File operations&lt;/strong&gt; — read, write, edit files on your system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron jobs&lt;/strong&gt; — schedule recurring tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas&lt;/strong&gt; — a live, agent-driven visual workspace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub-agent orchestration&lt;/strong&gt; — spawn specialized agents for complex tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a real example from my workflow. I asked the agent (via Telegram, while walking to get lunch):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Check if the staging deploy went through, and if there are any errors in the last hour of logs."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It ran the commands, parsed the output, and replied:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Staging deploy completed at 14:32 UTC. Two warnings in the last hour — both are the known Redis connection timeout on startup. No new errors. Looks clean."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn't open a terminal. I didn't SSH into anything. I sent a message from my phone and got an answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills: Pluggable Capabilities
&lt;/h3&gt;

&lt;p&gt;OpenClaw has a skill system through &lt;a href="https://clawhub.ai" rel="noopener noreferrer"&gt;ClawHub&lt;/a&gt; — a registry of community-built capabilities. Skills are like plugins that teach the agent new tricks: GitHub integration, email management, calendar access, data analysis, and hundreds more.&lt;/p&gt;

&lt;p&gt;Installing a skill:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw skills &lt;span class="nb"&gt;install &lt;/span&gt;github
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent now knows how to interact with GitHub — create issues, review PRs, check CI status — all through natural language.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Agent Routing
&lt;/h2&gt;

&lt;p&gt;This one's for the power users. OpenClaw supports &lt;strong&gt;multi-agent routing&lt;/strong&gt; — different channels or senders can be routed to different agents, each with their own workspace, personality, and tool access.&lt;/p&gt;

&lt;p&gt;Why would you want this? A few scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Work vs. personal:&lt;/strong&gt; Route your Slack messages to a "work agent" that knows your team's context. Route WhatsApp to a "personal agent" that manages your side projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different expertise:&lt;/strong&gt; Have a "coding agent" with full shell access for your DMs, and a "content agent" with writing tools for your Telegram channel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team sharing:&lt;/strong&gt; If you expose the gateway (with proper security), different team members can hit different agents with isolated sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuration is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;agents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;routing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slack&lt;/span&gt;
        &lt;span class="na"&gt;workspace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;work&lt;/span&gt;
      &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;work-agent&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp&lt;/span&gt;
      &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;personal-agent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each agent gets its own workspace directory, memory namespace, and tool permissions. They're completely isolated.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Compares
&lt;/h2&gt;

&lt;p&gt;I've tried most of the alternatives. Here's my honest take:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs. ChatGPT/Claude apps:&lt;/strong&gt; These are great for one-off conversations. But they're siloed. No cross-platform continuity. No tool execution on your machine. No self-hosting. You're renting an assistant; with OpenClaw, you own it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs. Coze / Dify:&lt;/strong&gt; These are workflow builders — visual drag-and-drop platforms for building AI pipelines. They're powerful but heavy. They're designed for building products, not for having a personal assistant. OpenClaw is opinionated: it's a single-user agent, not an app builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs. n8n / Zapier:&lt;/strong&gt; Automation platforms. Great for connecting APIs, but they're not conversational. You build workflows, not relationships. OpenClaw is an agent you &lt;em&gt;talk to&lt;/em&gt; that happens to be able to run workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs. Building your own:&lt;/strong&gt; You could wire up a Telegram bot, a Discord bot, and a WhatsApp integration yourself. I've done it. It takes weeks and you end up maintaining three codebases. OpenClaw is the "just works" version of that project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs. Open WebUI:&lt;/strong&gt; Open WebUI is a polished frontend for talking to LLMs. It's excellent at what it does. But it's a UI layer — it doesn't execute tasks, manage memory across platforms, or connect to messaging apps. OpenClaw has an &lt;a href="https://docs.openwebui.com/getting-started/quick-start/connect-an-agent/openclaw" rel="noopener noreferrer"&gt;Open WebUI integration&lt;/a&gt; if you want both: OpenClaw as the agent brain, Open WebUI as the chat interface.&lt;/p&gt;

&lt;p&gt;The key differentiator for me: &lt;strong&gt;OpenClaw is local-first.&lt;/strong&gt; Your data stays on your machine. Your agent runs on your hardware. You're not sending your conversation history through someone else's servers (except to the AI model provider, which is unavoidable). For a personal assistant, that matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Canvas: A Surprise Hit
&lt;/h2&gt;

&lt;p&gt;Canvas is an experimental feature, but it's already useful. I didn't expect to use it — it sounded gimmicky — a visual workspace the agent can render to?&lt;/p&gt;

&lt;p&gt;Then I asked it to diagram a system architecture. Instead of generating a wall of text, it rendered an interactive diagram in the Canvas. I could see it, manipulate it, and ask the agent to modify specific parts. It was like pair-designing with someone who could draw.&lt;/p&gt;

&lt;p&gt;The Canvas is served from the Gateway's HTTP server, so it works in any browser. The agent can push HTML, CSS, and JavaScript to it, and you can interact with the rendered output. It's particularly good for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System diagrams&lt;/li&gt;
&lt;li&gt;Data visualizations&lt;/li&gt;
&lt;li&gt;Interactive prototypes&lt;/li&gt;
&lt;li&gt;Dashboards the agent builds on the fly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've since used it for quick data visualizations where I'd normally reach for a Jupyter notebook. Faster, more conversational, and the output is immediately visible.&lt;/p&gt;

&lt;p&gt;The Canvas also supports &lt;strong&gt;A2UI (Agent-to-UI)&lt;/strong&gt; — a protocol where the agent can push structured UI components, not just raw HTML. Think of it as the agent building a mini-app in real time, tailored to exactly what you asked for. I used it to build a live monitoring dashboard for a project in about 30 seconds of conversation. No code from me. Just "show me the health of these services" and the agent rendered it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security: The Uncomfortable Conversation
&lt;/h2&gt;

&lt;p&gt;Let's be real: giving an AI agent shell access to your machine is a big deal. OpenClaw takes this seriously, but you need to understand the trust model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default behavior:&lt;/strong&gt; The agent runs with full access to your host for the main session. This is intentional — it's a &lt;em&gt;personal&lt;/em&gt; assistant, and you're the only user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sandboxing:&lt;/strong&gt; For non-main sessions (group chats, shared channels), you can enable Docker-based sandboxing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;agents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sandbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;non-main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sandbox restricts tool access — by default it allows file operations and session management but blocks browser, canvas, cron, and gateway commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pairing:&lt;/strong&gt; By default, unknown senders get a pairing code. The agent won't process their messages until you approve them. This prevents random people from messaging your WhatsApp bot and getting your agent to do things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remote access:&lt;/strong&gt; If you expose the gateway beyond localhost, use Tailscale or an SSH tunnel. The docs have a detailed &lt;a href="https://docs.openclaw.ai/gateway/security/exposure-runbook" rel="noopener noreferrer"&gt;exposure runbook&lt;/a&gt;. Don't skip it.&lt;/p&gt;

&lt;p&gt;My setup: the gateway runs on a VPS with Tailscale. I access it from my laptop and phone over the Tailnet. No public ports. No Docker (I'm the only user). It's been rock-solid for six weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Change
&lt;/h2&gt;

&lt;p&gt;No tool is perfect. Here's what I'd improve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Initial channel setup could be smoother.&lt;/strong&gt; The onboarding wizard is great for the gateway, but connecting channels still requires digging into platform-specific developer portals (Discord, Slack, etc.). A guided flow for each channel would help.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mobile companion apps are maturing.&lt;/strong&gt; iOS and Android "nodes" exist and work, but they're newer than the desktop experience. Voice wake on macOS is polished; mobile voice is catching up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Documentation is extensive but sometimes scattered.&lt;/strong&gt; The docs site is comprehensive, but I occasionally found myself jumping between three pages to answer one question. A "cookbook" section with common recipes would be useful.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are dealbreakers. The core product is solid, and the community moves fast. I've seen features land in weeks that would take months in corporate projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started Today
&lt;/h2&gt;

&lt;p&gt;If you've read this far, you probably relate to the "five apps, five strangers" problem. Here's my recommendation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install OpenClaw&lt;/strong&gt; — &lt;code&gt;npm install -g openclaw@latest &amp;amp;&amp;amp; openclaw onboard --install-daemon&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect one channel&lt;/strong&gt; — Telegram is the fastest (3-5 minutes, one bot token)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use it for a week&lt;/strong&gt; — ask it things, let it learn your preferences, give it small tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add more channels&lt;/strong&gt; — WhatsApp, Discord, whatever you actually use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explore skills&lt;/strong&gt; — browse &lt;a href="https://clawhub.ai" rel="noopener noreferrer"&gt;ClawHub&lt;/a&gt; and install what fits your workflow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent gets better as it learns about you. The first day is just a chatbot. By the end of the first week, it's an assistant. By the end of the first month, it's the one you reach for first.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;We're in a weird moment in AI. The models are incredible, but the &lt;em&gt;experience&lt;/em&gt; of using them is fragmented. Every platform wants you in their app, their ecosystem, their walled garden. OpenClaw is a bet on a different model: &lt;strong&gt;you own the agent, you own the data, you choose the channels.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's not trying to replace ChatGPT or Claude. It's the layer that makes them work &lt;em&gt;together&lt;/em&gt;, on your terms, from wherever you happen to be.&lt;/p&gt;

&lt;p&gt;Six weeks in, I've deleted three AI apps from my phone. I have one number, one bot, one agent — and it knows me across every screen I own.&lt;/p&gt;

&lt;p&gt;That Saturday morning at the coffee shop? I opened WhatsApp, sent three messages, and had my answers before the barista called my name. One app. One brain. Coffee still hot.&lt;/p&gt;

&lt;p&gt;And the best part? I didn't explain anything. The agent already knew.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;OpenClaw is open source (MIT) and available at &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;github.com/openclaw/openclaw&lt;/a&gt;. Docs at &lt;a href="https://docs.openclaw.ai" rel="noopener noreferrer"&gt;docs.openclaw.ai&lt;/a&gt;. Community on &lt;a href="https://discord.gg/clawd" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you tried self-hosting an AI agent? I'd love to hear about your setup in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>tools</category>
    </item>
    <item>
      <title>The $2,300 Weekend: When Fallback Routing Goes Wrong in AI Gateways</title>
      <dc:creator>Hanlin Xiang</dc:creator>
      <pubDate>Tue, 23 Jun 2026 02:42:14 +0000</pubDate>
      <link>https://dev.to/ai-gateway-veteran/the-2300-weekend-when-fallback-routing-goes-wrong-in-ai-gateways-1iip</link>
      <guid>https://dev.to/ai-gateway-veteran/the-2300-weekend-when-fallback-routing-goes-wrong-in-ai-gateways-1iip</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A single misconfigured fallback line turned a $40/month API bill into $2,300 in 48 hours. Here's what happened, why it's the most common LiteLLM mistake, and how to fix it before it happens to you.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Happened
&lt;/h2&gt;

&lt;p&gt;Last month, I set up LiteLLM Proxy to route traffic across multiple providers. My primary model was DeepSeek-V3 at $0.14/M tokens — cheap, fast, good enough for 90% of my traffic. As a fallback, I configured GPT-4o "just in case DeepSeek goes down."&lt;/p&gt;

&lt;p&gt;Sounds reasonable, right? That's what I thought.&lt;/p&gt;

&lt;p&gt;Friday night, DeepSeek started rate-limiting (429s). My fallback chain kicked in. Every single request that got a 429 rerouted to GPT-4o at $2.50/M input + $10/M output — &lt;strong&gt;18x more expensive on input tokens alone&lt;/strong&gt;, and over 70x on output**.&lt;/p&gt;

&lt;p&gt;By Saturday morning, 5% of my traffic had fallen back to GPT-4o. By Sunday night, I had a $2,300 OpenAI bill.&lt;/p&gt;

&lt;p&gt;The worst part? The gateway was working perfectly. No errors, no alerts, no downtime. The fallback did exactly what I configured it to do. The problem was the configuration itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Happens
&lt;/h2&gt;

&lt;p&gt;The anti-pattern is &lt;strong&gt;capability-based fallback&lt;/strong&gt; — routing traffic to whatever model is "better" when the primary fails. It feels intuitive: if DeepSeek goes down, fall back to GPT-4o, which is more capable anyway.&lt;/p&gt;

&lt;p&gt;But this creates a financial time bomb:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cheap models fail more often&lt;/strong&gt; — they're on shared infrastructure with tighter rate limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expensive models are always available&lt;/strong&gt; — providers prioritize premium tiers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every fallback = 10-20x cost increase&lt;/strong&gt; — and there's no alert when it happens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;429s come in bursts&lt;/strong&gt; — when a provider rate limits, it rate limits &lt;em&gt;everything&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result: your cheapest, highest-volume tier fails, and a tsunami of traffic hits your most expensive model. You won't notice until the billing email arrives Monday morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Price-Tiered Fallback
&lt;/h2&gt;

&lt;p&gt;The principle is simple: &lt;strong&gt;fallbacks go sideways, never upward.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cheap model fails → fall back to another cheap model&lt;/li&gt;
&lt;li&gt;Mid-tier model fails → fall back to another mid-tier model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never&lt;/strong&gt; fall up from cheap to expensive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the production config I run now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.yaml&lt;/span&gt;
&lt;span class="na"&gt;model_list&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Tier 1: cheap models ($0.10-0.30/M tokens)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deepseek-chat&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deepseek/deepseek-chat&lt;/span&gt;
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;os.environ/DEEPSEEK_API_KEY&lt;/span&gt;
      &lt;span class="na"&gt;max_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;        &lt;span class="c1"&gt;# $50/day hard limit per key&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini-flash&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini/gemini-1.5-flash&lt;/span&gt;
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;os.environ/GEMINI_API_KEY&lt;/span&gt;
      &lt;span class="na"&gt;max_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-haiku&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude/claude-3-haiku&lt;/span&gt;
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;os.environ/ANTHROPIC_API_KEY&lt;/span&gt;
      &lt;span class="na"&gt;max_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;

  &lt;span class="c1"&gt;# Tier 2: mid-tier models ($1-3/M tokens)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai/gpt-4o&lt;/span&gt;
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;os.environ/OPENAI_API_KEY&lt;/span&gt;
      &lt;span class="na"&gt;max_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;       &lt;span class="c1"&gt;# $100/day hard limit&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude/claude-3-5-sonnet&lt;/span&gt;
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;os.environ/ANTHROPIC_API_KEY&lt;/span&gt;
      &lt;span class="na"&gt;max_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini-pro&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini/gemini-1.5-pro&lt;/span&gt;
      &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;os.environ/GEMINI_API_KEY&lt;/span&gt;
      &lt;span class="na"&gt;max_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;

&lt;span class="na"&gt;litellm_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;num_retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;allowed_fails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;        &lt;span class="c1"&gt;# circuit breaker: stop after 3 total failures&lt;/span&gt;
  &lt;span class="na"&gt;cooldown_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;       &lt;span class="c1"&gt;# wait 60s before retrying a failed provider&lt;/span&gt;
  &lt;span class="na"&gt;fallbacks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;              &lt;span class="c1"&gt;# fallbacks use model_name from model_list above&lt;/span&gt;
    &lt;span class="c1"&gt;# Tier 1: cheap → cheap (NEVER cheap → expensive)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-chat"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-flash"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-flash"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-chat"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Tier 2: mid → mid&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-pro"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-pro"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# NEVER do this:&lt;/span&gt;
    &lt;span class="c1"&gt;# ✗ - "deepseek-chat": ["gpt-4o"]&lt;/span&gt;

  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;cache_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis"&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis"&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;              &lt;span class="c1"&gt;# 1h cache — catches ~30% of duplicate traffic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key settings explained:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allowed_fails: 3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Circuit breaker — stops retrying after 3 total failures&lt;/td&gt;
&lt;td&gt;Prevents retry storms from amplifying costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cooldown_time: 60&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waits 60s before retrying a failed provider&lt;/td&gt;
&lt;td&gt;Gives rate-limited providers time to recover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_retries: 2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retries on same model before fallback&lt;/td&gt;
&lt;td&gt;Reduces unnecessary fallback triggers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_budget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-key daily spending cap&lt;/td&gt;
&lt;td&gt;Hard stop — even if everything goes wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to Detect Fallback Abuse Before the Bill Arrives
&lt;/h2&gt;

&lt;p&gt;You can't manage what you don't measure. Add this simple check to your monitoring:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Log-based (no extra infra)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example: check fallback rate from LiteLLM logs (adjust fields to your version)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:4000/logs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$LITELLM_MASTER_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'[.[] | select(.metadata.fallback_model != null)] | length'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{if($1 &amp;gt; 50) print "WARNING: High fallback rate: "$1" requests"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Prometheus + Grafana (production)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Fallback rate as % of total requests (metric names may vary by LiteLLM version)
sum(rate(litellm_fallback_count_total[5m]))
  /
sum(rate(litellm_total_requests[5m]))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set an alert at 5% — if more than 1 in 20 requests is falling back, something's wrong with your primary provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3: The $0 solution — daily budget alerts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;max_budget&lt;/code&gt; on every key. LiteLLM will return a &lt;code&gt;429&lt;/code&gt; when the budget is hit. Better to serve errors for 100 requests than to serve 10,000 requests on GPT-4o.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;Fallback routing is &lt;strong&gt;Pitfall #4&lt;/strong&gt; in my production survival map. After 6 months of running LiteLLM Proxy in production, I documented 5 deployment pitfalls and 3 cost traps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;503 on every request after adding a provider&lt;/strong&gt; — model name mismatch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs 3× higher than expected&lt;/strong&gt; — default fallback chain routes to expensive models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keys rotated but old ones still work&lt;/strong&gt; — key cache invalidation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback routing bleeds your wallet dry&lt;/strong&gt; ← &lt;em&gt;this one&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming responses cut off mid-token&lt;/strong&gt; — Nginx/Cloudflare buffering&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these cost me real money or real downtime. The full one-page reference card with all 5 pitfalls, 3 cost traps, a failure decision tree, and config templates is here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://payhip.com/b/S96bB" rel="noopener noreferrer"&gt;AI API Gateway Pitfall Map — $9&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's the page you print and pin next to your monitor — because when your gateway goes down at 2 AM, you won't be reading a 40-page guide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free resource:&lt;/strong&gt; I also put together a &lt;a href="https://payhip.com/PitMapGuides" rel="noopener noreferrer"&gt;Pre-Deployment Checklist&lt;/a&gt; — a 1-page PDF covering the 15 things to check before going live. Free download, no email required.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Independent reference. Not affiliated with LiteLLM, OpenAI, DeepSeek, or any provider named. 7-day money-back guarantee — if the map doesn't save you at least 1 hour of debugging, email for a full refund.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#litellm&lt;/code&gt; &lt;code&gt;#ai&lt;/code&gt; &lt;code&gt;#production&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt;&lt;/p&gt;

</description>
      <category>litellm</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>The 5 Cost Traps That Will Quietly Bleed Your AI API Gateway Dry (And How to Fix Them)</title>
      <dc:creator>Hanlin Xiang</dc:creator>
      <pubDate>Mon, 22 Jun 2026 01:47:21 +0000</pubDate>
      <link>https://dev.to/ai-gateway-veteran/the-5-cost-traps-that-will-quietly-bleed-your-ai-api-gateway-dry-and-how-to-fix-them-326j</link>
      <guid>https://dev.to/ai-gateway-veteran/the-5-cost-traps-that-will-quietly-bleed-your-ai-api-gateway-dry-and-how-to-fix-them-326j</guid>
      <description>&lt;p&gt;In my last post, we talked about key cache invalidation — the silent production killer that turns your gateway into a 502 factory. Today I want to talk about something equally dangerous but far more insidious: &lt;strong&gt;cost traps&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These aren't bugs. They're not crashes. Your gateway runs fine. Your users are happy. Then finance sends you a Slack message: &lt;em&gt;"Why did our OpenAI bill jump 4x last month?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've been running LiteLLM Proxy in production for multiple teams across three companies. Here are the five cost traps I've personally been burned by — each with the config that would have saved me thousands of dollars.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap #1: The Retry Spiral — When &lt;code&gt;num_retries=3&lt;/code&gt; Actually Means 15
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;LiteLLM's retry logic is smart. Too smart. When a request fails, it retries. When the retried request hits a fallback model and &lt;em&gt;that&lt;/em&gt; fails, it retries again. If you've configured a fallback chain of 5 models with 3 retries each, a single user request can trigger up to &lt;strong&gt;15 upstream API calls&lt;/strong&gt; — and you pay for every single one, including the ones that errored out after consuming tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Happens
&lt;/h3&gt;

&lt;p&gt;The default &lt;code&gt;num_retries&lt;/code&gt; in LiteLLM is 3. Most teams set it and forget it. But retries multiply across your fallback chain. Here's the math:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request → Model A fails → Retry A (1) → Retry A (2) → Retry A (3)
       → Fallback to Model B → Fails → Retry B (1) → Retry B (2) → Retry B (3)
       → Fallback to Model C → Fails → Retry C (1) → Retry C (2) → Retry C (3)

Total upstream calls: 9 retries + 3 initial = 12 billable calls for 1 user request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Model C is GPT-4o and each retry consumes 2K input tokens before timing out, that's 24K tokens on a &lt;em&gt;single failed request&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Cap total attempts across the entire chain, not just per-model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# litellm_config.yaml&lt;/span&gt;
&lt;span class="na"&gt;litellm_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;num_retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;           &lt;span class="c1"&gt;# per-model retries&lt;/span&gt;
  &lt;span class="na"&gt;max_fallbacks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;         &lt;span class="c1"&gt;# hard cap on fallback chain depth&lt;/span&gt;
  &lt;span class="na"&gt;retry_after&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;           &lt;span class="c1"&gt;# respect 429 Retry-After headers&lt;/span&gt;
  &lt;span class="na"&gt;allowed_fails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;         &lt;span class="c1"&gt;# circuit breaker: after 3 fails, stop entirely&lt;/span&gt;

&lt;span class="na"&gt;model_list&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;
      &lt;span class="na"&gt;max_retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;       &lt;span class="c1"&gt;# override: fewer retries on expensive models&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o-fallback&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;   &lt;span class="c1"&gt;# cheap fallback, not another expensive model&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;strong&gt;your fallback should be cheaper than your primary&lt;/strong&gt;, not equally expensive. If GPT-4o fails, fall back to GPT-4o-mini, not to Claude Opus.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap #2: Fallback Chains That Funnel Money Into Premium Models
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;This is the trap that cost me $2,300 in a single weekend. A well-meaning engineer configured a fallback chain that looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# THE EXPENSIVE WAY — do not do this&lt;/span&gt;
&lt;span class="na"&gt;router_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fallbacks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-5-sonnet"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-5-sonnet"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-opus"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic seemed sound: "If the cheap model fails, try the better one." But here's what actually happened: GPT-4o-mini was rate-limited during a traffic spike (429s everywhere), so &lt;strong&gt;every single request fell through to GPT-4o and then to Claude 3.5 Sonnet&lt;/strong&gt;. For 6 hours, we were running 100% of our traffic on the most expensive models in the chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Happens
&lt;/h3&gt;

&lt;p&gt;Rate limits are per-model, not per-gateway. When you hit OpenAI's TPM limit on &lt;code&gt;gpt-4o-mini&lt;/code&gt;, LiteLLM dutifully falls back. But if the traffic spike is caused by overall volume (not a model-specific outage), the fallback model gets the same volume that caused the 429 in the first place. You're not solving the problem — you're just paying 10x more to have it on a different model.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Structure fallbacks by &lt;strong&gt;cost tier&lt;/strong&gt;, not by capability tier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# THE SMART WAY — fallback within price tier, not up&lt;/span&gt;
&lt;span class="na"&gt;router_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fallbacks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Tier 1: Cheap models (fallback to other cheap models)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-1.5-flash"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-haiku"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Tier 2: Mid-tier models (fallback to other mid-tier)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-5-sonnet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-1.5-pro"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# NEVER fall up from cheap to expensive&lt;/span&gt;
    &lt;span class="c1"&gt;# If all cheap models fail, return an error, don't escalate&lt;/span&gt;

  &lt;span class="c1"&gt;# Add a cooldown so the same model isn't retried immediately&lt;/span&gt;
  &lt;span class="na"&gt;cooldown_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also add alerting. If your fallback rate exceeds 5% of total traffic, something is structurally wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# prometheus metric in your LiteLLM custom callback
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;litellm.integrations.custom_logger&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CustomLogger&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;litellm&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FallbackAlertLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomLogger&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_pre_api_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fallback_idx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# This is a fallback call, not the primary
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fallback_counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="c1"&gt;# Alert if fallback rate &amp;gt; 5%
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fallback_counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alert_webhook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ Fallback rate &amp;gt;5% — check rate limits on primary models&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Trap #3: Zero Caching = Paying for the Same Answer 1,000 Times
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Most teams don't enable LiteLLM's built-in caching because "our prompts are dynamic." But in practice, a huge percentage of your traffic is &lt;strong&gt;near-identical&lt;/strong&gt;: system prompts are the same, the first 500 tokens of user messages are often boilerplate, and many users ask the exact same questions.&lt;/p&gt;

&lt;p&gt;I audited one team's traffic and found that &lt;strong&gt;34% of their requests were exact duplicates&lt;/strong&gt; of requests made in the last hour. They were paying OpenAI ~$400/day for identical completions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Happens
&lt;/h3&gt;

&lt;p&gt;LiteLLM has Redis caching built in. But it's disabled by default, and the documentation buries it under "Advanced Settings." Most engineers set up the proxy, test it, ship it, and never circle back.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Enable Redis caching with a sensible TTL. This is a 30-second config change that can cut your bill by 30-50%:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;litellm_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;cache_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis"&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-redis-host"&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;litellm_cache"&lt;/span&gt;

    &lt;span class="c1"&gt;# Cache settings&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;              &lt;span class="c1"&gt;# 1 hour for exact matches&lt;/span&gt;
    &lt;span class="c1"&gt;# For semantic caching (similar but not identical prompts):&lt;/span&gt;
    &lt;span class="c1"&gt;# semantic_cache: true&lt;/span&gt;
    &lt;span class="c1"&gt;# similarity_threshold: 0.8&lt;/span&gt;

  &lt;span class="c1"&gt;# Cache based on messages content, not just the full request&lt;/span&gt;
  &lt;span class="na"&gt;cache_key_include_models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;   &lt;span class="c1"&gt;# don't share cache across models&lt;/span&gt;

&lt;span class="na"&gt;model_list&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;
      &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;           &lt;span class="c1"&gt;# enable per-model&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For even bigger savings, use &lt;strong&gt;prompt caching&lt;/strong&gt; with providers that support it (Claude, GPT-4o). LiteLLM supports this natively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;litellm&lt;/span&gt;

&lt;span class="c1"&gt;# Enable prompt caching for Claude
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;litellm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-5-sonnet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;long_system_prompt&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache_control&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ephemeral&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Claude charges 90% less for cached input tokens
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real numbers from my audit&lt;/strong&gt;: After enabling Redis cache with a 1-hour TTL, that team went from $400/day to $180/day. A 55% reduction for a config change that took less than a minute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap #4: No Per-Key Budget Limits — One Runaway Loop Can Bankrupt You
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;An intern pushes a &lt;code&gt;while True&lt;/code&gt; loop to a staging environment. It doesn't crash — it just calls your gateway 4,000 times per minute with a 4K-token prompt. By the time PagerDuty fires, you've spent $847 in 12 minutes.&lt;/p&gt;

&lt;p&gt;This isn't hypothetical. This is a Tuesday.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Happens
&lt;/h3&gt;

&lt;p&gt;LiteLLM's default configuration has &lt;strong&gt;no budget enforcement&lt;/strong&gt;. The &lt;code&gt;max_budget&lt;/code&gt; field exists but most teams never configure it because they're focused on getting the gateway working, not on constraining it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Set budgets at three levels: per-key, per-team, and global:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Per-virtual-key budget (when creating keys via /key/generate)&lt;/span&gt;
&lt;span class="c1"&gt;# This is your first line of defense&lt;/span&gt;

&lt;span class="na"&gt;litellm_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Global budget — emergency brake&lt;/span&gt;
  &lt;span class="na"&gt;max_budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;          &lt;span class="c1"&gt;# $500/day global cap&lt;/span&gt;
  &lt;span class="na"&gt;budget_duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1d"&lt;/span&gt;

  &lt;span class="c1"&gt;# Rate limiting&lt;/span&gt;
  &lt;span class="na"&gt;rpm_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;          &lt;span class="c1"&gt;# requests per minute, global&lt;/span&gt;

&lt;span class="na"&gt;general_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;master_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sk-1234&lt;/span&gt;
  &lt;span class="na"&gt;database_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://..."&lt;/span&gt;

  &lt;span class="c1"&gt;# Enable budget tracking&lt;/span&gt;
  &lt;span class="na"&gt;alerting&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slack"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;alerting_threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;  &lt;span class="c1"&gt;# alert at 80% of budget&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When creating virtual keys for teams or individual developers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a key with a $50 daily budget and 100 RPM&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:4000/key/generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk-1234"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "max_budget": 50,
    "budget_duration": "1d",
    "rpm_limit": 100,
    "tpm_limit": 50000,
    "models": ["gpt-4o-mini", "gpt-4o"],
    "metadata": {"team": "frontend"}
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And set up a webhook to catch budget breaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In your LiteLLM proxy config
&lt;/span&gt;&lt;span class="n"&gt;litellm_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;proxy_budget_respecting_alerting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;webhook_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://hooks.slack.com/services/...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="c1"&gt;# This fires BEFORE the request is sent when a key is over budget
&lt;/span&gt;      &lt;span class="c1"&gt;# LiteLLM will return a 429 to the client, not forward to the provider
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intern's loop? With a $50/day key budget and 100 RPM limit, it would have been throttled after 100 calls and blocked entirely after $50. Total damage: about $0.80.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap #5: The Streaming Tax — You're Paying for Tokens You Never See
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Streaming mode (&lt;code&gt;stream=True&lt;/code&gt;) is great for UX. Users see tokens appear in real-time. But here's what most teams don't realize: &lt;strong&gt;when a streaming request is interrupted mid-stream, you still pay for the entire generation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;User starts a request → GPT-4o begins streaming a 2,000-token response → user navigates away after 50 tokens → the client connection drops → but the upstream API call completes fully → you pay for all 2,000 tokens.&lt;/p&gt;

&lt;p&gt;At scale, this is devastating. I've seen teams where &lt;strong&gt;23% of their token spend was on tokens that no user ever saw&lt;/strong&gt; because the client disconnected early.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Happens
&lt;/h3&gt;

&lt;p&gt;LiteLLM (and most API gateways) doesn't automatically cancel the upstream request when the client disconnects during streaming. The gateway is acting as a proxy — it's happily receiving tokens from OpenAI and trying to forward them, even though nobody's listening.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Enable client disconnect detection and upstream cancellation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;litellm_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Cancel upstream request when client disconnects during streaming&lt;/span&gt;
  &lt;span class="na"&gt;stream_options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;include_usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;     &lt;span class="c1"&gt;# get token counts in the final chunk&lt;/span&gt;

  &lt;span class="c1"&gt;# Custom callback to track abandoned streams&lt;/span&gt;
  &lt;span class="na"&gt;callbacks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stream_cost_logger&lt;/span&gt;

&lt;span class="na"&gt;router_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Close upstream connection when client disconnects&lt;/span&gt;
  &lt;span class="na"&gt;streaming_client_disconnect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;   &lt;span class="c1"&gt;# LiteLLM 1.40+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're on an older version or need more control, add a custom middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;litellm.proxy.custom_proxy_admin_logic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CustomProxyAdminLogic&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamCancellationMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomProxyAdminLogic&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;async_pre_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_api_key_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# Mark the start time
&lt;/span&gt;            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream_start_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;async_log_stream_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logging_obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Log how many tokens were actually consumed vs delivered
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;usage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;total_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# If stream ended early (client disconnect), log it
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;logging_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream_connection_broken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abandoned_stream_tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Abandoned stream: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens paid but undelivered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, consider setting &lt;code&gt;max_tokens&lt;/code&gt; conservatively for streaming endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;model_list&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o-stream&lt;/span&gt;
    &lt;span class="na"&gt;litellm_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;
      &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;        &lt;span class="c1"&gt;# cap generation length&lt;/span&gt;
      &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;stream_options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;include_usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After implementing stream cancellation, that 23% wasted spend dropped to under 2%.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern Behind All Five Traps
&lt;/h2&gt;

&lt;p&gt;Notice the theme: &lt;strong&gt;every one of these traps is a sensible default that becomes dangerous at scale.&lt;/strong&gt; Retries are good — until they multiply across fallbacks. Fallbacks are good — until they funnel traffic to premium models. Caching is optional — until it's costing you 30% of your bill.&lt;/p&gt;

&lt;p&gt;The fix is never "disable the feature." It's always "add constraints." Budgets, caps, cooldowns, TTLs. The gateway works for you, not the other way around.&lt;/p&gt;

&lt;p&gt;If you're deploying LiteLLM or any AI API gateway, do a quick audit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What's your effective retry multiplier?&lt;/strong&gt; (num_retries × fallback_depth)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does your fallback chain ever fall UP in price?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is caching enabled?&lt;/strong&gt; (If you can't answer this in 5 seconds, it's probably not)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does every API key have a budget cap?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What percentage of your streaming tokens are actually delivered?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any of these questions made you nervous, you might want to check out the &lt;strong&gt;&lt;a href="https://payhip.com/b/S96bB" rel="noopener noreferrer"&gt;AI API Gateway Pitfall Map&lt;/a&gt;&lt;/strong&gt; — a one-page production survival guide I put together that covers these traps (and a few more) in a format you can print and pin above your desk. It's the checklist I wish I'd had before I learned these lessons the expensive way.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you hit any of these traps in production? Or found others I missed? Drop a comment — I'm collecting war stories for a follow-up post.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tags: #litellm #ai #devops #costoptimization&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎁 Free: AI API Gateway Pre-Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Before you ship, run through this 43-point checklist covering auth, cost control, caching, fallbacks, security, monitoring, and production readiness. It's free — grab it here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://payhip.com/b/NTQka" rel="noopener noreferrer"&gt;Free Pre-Deployment Checklist (PDF)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And if you want the full pitfall map with detailed fixes for each trap above, that's here: &lt;a href="https://payhip.com/b/S96bB" rel="noopener noreferrer"&gt;AI API Gateway Pitfall Map ($9)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>litellm</category>
      <category>ai</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>The #3 Production Killer in Your LiteLLM Setup: Key Cache Invalidation (and How to Fix It)</title>
      <dc:creator>Hanlin Xiang</dc:creator>
      <pubDate>Fri, 19 Jun 2026 14:18:28 +0000</pubDate>
      <link>https://dev.to/ai-gateway-veteran/the-3-production-killer-in-your-litellm-setup-key-cache-invalidation-and-how-to-fix-it-5af5</link>
      <guid>https://dev.to/ai-gateway-veteran/the-3-production-killer-in-your-litellm-setup-key-cache-invalidation-and-how-to-fix-it-5af5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is the pitfall that cost me 3 hours at 2 AM. If you're running LiteLLM Proxy in production, it will hit you too — usually at the worst possible time.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Happened
&lt;/h2&gt;

&lt;p&gt;I run LiteLLM Proxy + New API in front of 18 provider channels. One night, I rotated an API key for a provider that had been flagged for unusual spending.&lt;/p&gt;

&lt;p&gt;Standard procedure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate new key in provider dashboard&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;config.yaml&lt;/code&gt; with new key&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;litellm --config config.yaml --reload&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reload succeeded. No errors. The config showed the new key. I went to sleep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The next morning, the old key was still being used.&lt;/strong&gt; Every single request was still authenticating with the rotated-out key. The provider's dashboard showed traffic from both keys — the new one (from config validation) and the old one (from actual API calls).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Happens
&lt;/h2&gt;

&lt;p&gt;LiteLLM caches API keys in-memory for performance. When you &lt;code&gt;--reload&lt;/code&gt;, the &lt;strong&gt;config&lt;/strong&gt; is reloaded, but the &lt;strong&gt;key store&lt;/strong&gt; is not purged. The worker process holds the old keys in a dictionary that persists across config reloads.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;config.yaml&lt;/code&gt; shows the new key ✅&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;litellm --model_cost_map&lt;/code&gt; shows the new key ✅&lt;/li&gt;
&lt;li&gt;The actual HTTP requests use the old key ❌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You won't notice until the old key expires or is revoked — at which point every request to that provider starts returning &lt;code&gt;401&lt;/code&gt;, and your fallback chain kicks in, routing traffic to your most expensive model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Purge the cache manually (no downtime)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:4000/cache/purge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$LITELLM_MASTER_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This clears the in-memory key cache. The next request will pull the key from the freshly reloaded config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Use Redis for shared key state (recommended for multi-worker)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;REDIS_HOST&lt;/code&gt; in your environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_HOST=redis://redis:6379&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_CONNECTION_POOL_SIZE=5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Redis, keys are stored externally. A config reload triggers a Redis key update, and all workers pick it up immediately. No stale keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3: Restart the worker (downtime: 2-5 seconds)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker restart litellm-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Brute force, but guaranteed to work. Use this if you're in a hurry and can afford a brief blip.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Detect It Before Users Do
&lt;/h2&gt;

&lt;p&gt;Add this to your monitoring — a simple script that checks whether the key in config matches the key actually being used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check which key is being used for a specific model&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:4000/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$LITELLM_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model": "openai/gpt-4o", "messages": [{"role": "user", "content": "test"}], "max_tokens": 1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.usage'&lt;/span&gt;

&lt;span class="c"&gt;# Compare with the key in config&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"api_key:"&lt;/span&gt; config.yaml | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the provider's response includes a &lt;code&gt;x-api-key-id&lt;/code&gt; header (OpenAI does), you can verify which key was used without guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;Key cache invalidation is &lt;strong&gt;Pitfall #3&lt;/strong&gt; in my production survival map. There are 4 more deployment pitfalls and 3 hidden cost traps that I documented after 6 months of running this stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;503 on every request after adding a provider&lt;/strong&gt; — model name mismatch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs 3× higher than expected&lt;/strong&gt; — fallback chain hits expensive models by default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keys rotated but old ones still work&lt;/strong&gt; ← &lt;em&gt;this one&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming responses cut off mid-token&lt;/strong&gt; — Nginx/Cloudflare buffering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New API channels show "insufficient quota" with balance &amp;gt; 0&lt;/strong&gt; — weight = 0 by default&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these took me 1-2 hours to diagnose in production. The full one-page reference card with all 5 pitfalls, 3 cost traps, a failure decision tree, and a pre-launch security checklist is available here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://payhip.com/b/S96bB" rel="noopener noreferrer"&gt;AI API Gateway Pitfall Map — $9&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's the page you print and pin next to your monitor — because when your gateway goes down at 2 AM, you won't be reading a 40-page guide.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>litellm</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>5 Pitfalls I Hit Running LiteLLM Proxy in Production (with a 1-page survival map)</title>
      <dc:creator>Hanlin Xiang</dc:creator>
      <pubDate>Wed, 17 Jun 2026 12:09:44 +0000</pubDate>
      <link>https://dev.to/ai-gateway-veteran/5-pitfalls-i-hit-running-litellm-proxy-in-production-with-a-1-page-survival-map-4k1h</link>
      <guid>https://dev.to/ai-gateway-veteran/5-pitfalls-i-hit-running-litellm-proxy-in-production-with-a-1-page-survival-map-4k1h</guid>
      <description>&lt;p&gt;I've spent the last 6 months running an 18-channel LLM gateway in production — LiteLLM Proxy backed by Redis and PostgreSQL, routing traffic across OpenAI, Anthropic, Google, DeepSeek, and several smaller providers. What started as a weekend project turned into a 24/7 operation serving multiple AI agents and internal tools.&lt;/p&gt;

&lt;p&gt;This post covers the 5 pitfalls that hit me hardest, with real error examples and the fixes that worked. If you're running LiteLLM Proxy (or considering it), these are the things I wish someone had told me before I went to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfall #1: Silent OOM (Memory Leak + No systemd MemoryMax)
&lt;/h2&gt;

&lt;p&gt;LiteLLM has a known memory leak under high concurrency. Without a hard memory limit, the process will eat all available RAM until the kernel's OOM-killer takes it down — usually at 3 AM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The symptom: requests start timing out intermittently
# Check dmesg for the kill signal
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dmesg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-T&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;litellm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FOUND: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# The fix: systemd unit with MemoryMax
# /etc/systemd/system/litellm.service
# [Service]
# ExecStart=/usr/bin/litellm --config /opt/litellm/config.yaml --port 4000
# MemoryMax=4G
# Restart=always
# RestartSec=10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is simple but easy to miss: set &lt;code&gt;MemoryMax=4G&lt;/code&gt; (or whatever your server can spare) in your systemd unit. The proxy will restart cleanly instead of being force-killed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfall #2: Key Cache Miss (OpenAI 8min Cache vs 24h Cache Key)
&lt;/h2&gt;

&lt;p&gt;This was the single most painful bug I encountered. I rotated a provider API key through the config and ran &lt;code&gt;litellm --reload&lt;/code&gt;. The config file updated, but LiteLLM caches keys in-memory. The old key kept getting used for hours.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What happened: silent 401s that looked like "provider outage"
# The config was correct, but the in-memory cache wasn't purged
&lt;/span&gt;
&lt;span class="c1"&gt;# WRONG: this only reloads config, not the key store
# litellm --reload
&lt;/span&gt;
&lt;span class="c1"&gt;# RIGHT: purge the cache explicitly
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4000/cache/purge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer YOUR_MASTER_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Or just restart the worker entirely
# If using REDIS_HOST for shared state, flush that too:
# redis-cli -h $REDIS_HOST FLUSHDB
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;code&gt;--reload&lt;/code&gt; refreshes the config file but does NOT purge the in-memory key cache. You need to either hit &lt;code&gt;/cache/purge&lt;/code&gt; or restart the worker. If you're using Redis for shared key state, flush that too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfall #3: Retry Storm (4xx Retries Cause Rate-Limit Avalanche)
&lt;/h2&gt;

&lt;p&gt;LiteLLM retries &lt;code&gt;num_retries=3&lt;/code&gt; by default. A single failed call becomes 3x the token spend. Worse: on 4xx errors (which should NOT be retried), the retry logic can trigger rate-limit cascades.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The problem: default config retries everything
# config.yaml (BAD)
# litellm_settings:
#   num_retries: 3  # This retries even 4xx errors!
&lt;/span&gt;
&lt;span class="c1"&gt;# The fix: retry only 5xx, use fallbacks for 4xx
# config.yaml (GOOD)
&lt;/span&gt;&lt;span class="n"&gt;litellm_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;num_retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;retry_policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;InternalServerError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Don't retry rate limits — use fallbacks instead
&lt;/span&gt;
&lt;span class="n"&gt;model_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gpt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="n"&gt;litellm_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gpt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;
      &lt;span class="n"&gt;fallbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic/claude-3.5-sonnet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;num_retries: 1&lt;/code&gt; for non-critical paths. Use fallbacks (cheapest-first) instead of retries for cost control. A retry storm on a rate-limited provider can 3x your spend in 5 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfall #4: Cost Unobserved (Multi-Provider Routing Weights)
&lt;/h2&gt;

&lt;p&gt;When you route across multiple providers, LiteLLM's default &lt;code&gt;fallbacks&lt;/code&gt; are sequential — not cost-sorted. One upstream failure can route all traffic to your most expensive model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The problem: fallbacks hit the most expensive model first
# config.yaml (BAD)
&lt;/span&gt;&lt;span class="n"&gt;model_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gpt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="n"&gt;litellm_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gpt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;
      &lt;span class="n"&gt;fallbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai/gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic/claude-3.5-sonnet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="c1"&gt;# If gpt-4o fails, it tries mini (cheap) then sonnet (expensive)
&lt;/span&gt;      &lt;span class="c1"&gt;# But if mini also fails, ALL traffic goes to sonnet
&lt;/span&gt;
&lt;span class="c1"&gt;# The fix: sort fallbacks cheapest-first + set max_budget per team
# config.yaml (GOOD)
&lt;/span&gt;&lt;span class="n"&gt;litellm_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;max_budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;  &lt;span class="c1"&gt;# Daily budget cap in USD
&lt;/span&gt;  &lt;span class="n"&gt;budget_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;model_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gpt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="n"&gt;litellm_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gpt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;
      &lt;span class="n"&gt;allowed_fails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
      &lt;span class="n"&gt;fallbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai/gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Only fall back to cheaper models
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor per-provider spend daily. The LiteLLM UI shows cost breakdowns, but only if you've configured &lt;code&gt;master_key&lt;/code&gt; and &lt;code&gt;database_url&lt;/code&gt; properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfall #5: Metric Blindness (Incomplete Prometheus Metrics)
&lt;/h2&gt;

&lt;p&gt;LiteLLM's built-in Prometheus metrics don't cover per-provider latency percentiles or cost attribution. You're flying blind on the most important signals for production operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What LiteLLM exposes by default:
# - litellm_requests_total
# - litellm_request_duration_seconds (aggregate, not per-provider)
# - litellm_spend_total (only if database is configured)
&lt;/span&gt;
&lt;span class="c1"&gt;# What's MISSING:
# - Per-provider P95/P99 latency
# - Per-provider error rate
# - Per-team cost breakdown
# - Cache hit/miss ratio
&lt;/span&gt;
&lt;span class="c1"&gt;# The fix: add a custom middleware to emit per-provider metrics
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;litellm.integrations.custom_logger&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CustomLogger&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;prometheus_client&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;prom&lt;/span&gt;

&lt;span class="n"&gt;provider_latency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;litellm_provider_latency_seconds&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Latency by provider&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;provider&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PerProviderMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomLogger&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_success_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;litellm_params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom_llm_provider&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;litellm_params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;provider_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without per-provider metrics, you can't tell if DeepSeek is slow today or if OpenAI is throttling you. Add a custom logger to fill the gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: A 1-Page Survival Map
&lt;/h2&gt;

&lt;p&gt;After hitting all 5 of these pitfalls in production (and losing too many weekends to debugging), I compiled everything into a single-page survival map. It covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All 5 deployment pitfalls with symptoms, root causes, and fixes&lt;/li&gt;
&lt;li&gt;3 hidden cost traps (retry amplification, embedding tax, idle-connection keep-alive)&lt;/li&gt;
&lt;li&gt;A failure decision tree for any error code you'll see&lt;/li&gt;
&lt;li&gt;A pre-launch security checklist&lt;/li&gt;
&lt;li&gt;Copy-paste diagnostic commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full map is available here: &lt;a href="https://payhip.com/b/S96bB" rel="noopener noreferrer"&gt;https://payhip.com/b/S96bB&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's $9, no email signup, no affiliate. Just the thing I wish I had when I started.&lt;/p&gt;




&lt;p&gt;Have you hit any of these pitfalls? Or did I miss something that's bitten you? Drop a comment — I'll be responding to everyone. You can find me at &lt;a class="mentioned-user" href="https://dev.to/ai-gateway-veteran"&gt;@ai-gateway-veteran&lt;/a&gt; on Reddit and X.&lt;/p&gt;

</description>
      <category>python</category>
      <category>litellm</category>
      <category>devops</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
