<?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: YaFei</title>
    <description>The latest articles on DEV Community by YaFei (@xuanyi).</description>
    <link>https://dev.to/xuanyi</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%2F4070815%2F359798fd-5fbd-443e-a2f1-61f978fdc0dd.png</url>
      <title>DEV Community: YaFei</title>
      <link>https://dev.to/xuanyi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xuanyi"/>
    <language>en</language>
    <item>
      <title>The cheapest LLM call is the one you don't make: a caching layer that actually pays off</title>
      <dc:creator>YaFei</dc:creator>
      <pubDate>Wed, 19 Aug 2026 02:34:43 +0000</pubDate>
      <link>https://dev.to/xuanyi/the-cheapest-llm-call-is-the-one-you-dont-make-a-caching-layer-that-actually-pays-off-59e</link>
      <guid>https://dev.to/xuanyi/the-cheapest-llm-call-is-the-one-you-dont-make-a-caching-layer-that-actually-pays-off-59e</guid>
      <description>&lt;p&gt;The cheapest LLM call is the one you don't make: a caching layer that actually pays off&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In the last post I wrote about routing across providers to cut our bill ~40%. Caching was the second lever — and honestly the more underrated one. Here's what we learned shipping it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Routing gets most of the attention because it's sexy: traffic dancing across providers, failover kicking in, dashboards lighting up. But the single biggest cost lever we pulled after routing wasn't smarter routing. It was not calling the model at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why caching gets ignored
&lt;/h2&gt;

&lt;p&gt;When people talk about LLM cost, they picture the per-token price. That's the wrong unit. The question is how many of your calls are &lt;em&gt;genuinely new information&lt;/em&gt; versus repeats wearing a costume.&lt;/p&gt;

&lt;p&gt;We were shocked at the overlap. Once we started measuring, a large share of production traffic was re-asking near-identical things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same system prompt + near-identical user input, re-embedded every time.&lt;/li&gt;
&lt;li&gt;The same retrieval-augmented question asked by different users within minutes.&lt;/li&gt;
&lt;li&gt;Deterministic pre/post-processing steps recomputed on every request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that needs a fresh model call. It needs a cache with a brain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three layers that actually paid off
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Exact cache (the boring one that works immediately)
&lt;/h3&gt;

&lt;p&gt;Hash the full request (system + messages + params). If you've seen it, return the stored completion. Obvious, but most teams skip it because "our prompts are dynamic." They usually aren't &lt;em&gt;that&lt;/em&gt; dynamic.&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;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&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="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&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;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&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="n"&gt;k&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;hit&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;hit&lt;/span&gt;  &lt;span class="c1"&gt;# zero tokens spent
&lt;/span&gt;    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&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;out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This alone killed a chunk of bill on our highest-traffic endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Semantic cache (the one people underestimate)
&lt;/h3&gt;

&lt;p&gt;Exact matching misses the real win: &lt;em&gt;similar&lt;/em&gt; prompts returning &lt;em&gt;similar&lt;/em&gt; answers. Embed the user turn, store embeddings in a vector index, and on each request check for a neighbor above a similarity threshold (we use ~0.92). If found, reuse the prior completion.&lt;/p&gt;

&lt;p&gt;The catch: semantic caching is only safe for deterministic-ish tasks (classifications, extractions, stable Q&amp;amp;A). Don't cache creative generation — you'll serve stale voices. We scope it tightly and it still covers a surprising volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Deterministic-step cache
&lt;/h3&gt;

&lt;p&gt;A lot of "LLM calls" are actually deterministic work wrapped in a prompt: parsing, normalization, format conversion. We moved those to pure functions computed once and reused. It's not even a model cache — it's just not pretending the model is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuning without breaking things
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TTL by volatility.&lt;/strong&gt; Stable reference answers: long TTL. Fast-moving data: short or none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token budget for the lookup.&lt;/strong&gt; An embedding + vector search costs tokens too. Make sure the cache check is cheaper than the miss — for us it is, by a wide margin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure hit rate, not just savings.&lt;/strong&gt; Hit rate tells you when caching stopped helping (prompt drift, new use cases) so you can re-scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cache hit rate across cached endpoints: ~35%.&lt;/li&gt;
&lt;li&gt;Additional bill reduction on top of routing: meaningful — combined with routing we're now well past the original 40% on the endpoints that use both.&lt;/li&gt;
&lt;li&gt;p95 latency on cached hits: sub-50ms instead of hundreds of ms. Users notice the speed more than the savings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is exotic. It's the same caching discipline people have applied to databases for decades, applied to model calls where the per-hit savings are bigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits with the rest
&lt;/h2&gt;

&lt;p&gt;Routing moves traffic to the cheapest healthy provider (&lt;a href="https://dev.to/xuanyi/how-we-cut-our-llm-bill-40-with-multi-provider-routing-44l7"&gt;how we cut the bill with routing&lt;/a&gt;). A circuit breaker keeps a flaky provider from turning an outage into a bill explosion (&lt;a href="https://majiafan.hashnode.dev/a-practical-circuit-breaker-for-llm-api-calls-in-production" rel="noopener noreferrer"&gt;the pattern we use&lt;/a&gt;). Caching is the layer underneath both: the call you skip is the call you never have to route or protect.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're optimizing the same thing
&lt;/h2&gt;

&lt;p&gt;Getting reliable, affordable model access set up for a team has its own headaches — provider quotas, region limits, payment friction. If any of that sounds familiar, I'm happy to compare notes. Find me here or DM me; no pitch, just war stories.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>costoptimization</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How we cut our LLM bill 40% with multi-provider routing</title>
      <dc:creator>YaFei</dc:creator>
      <pubDate>Mon, 10 Aug 2026 08:31:12 +0000</pubDate>
      <link>https://dev.to/xuanyi/how-we-cut-our-llm-bill-40-with-multi-provider-routing-44l7</link>
      <guid>https://dev.to/xuanyi/how-we-cut-our-llm-bill-40-with-multi-provider-routing-44l7</guid>
      <description>&lt;p&gt;&lt;em&gt;Running LLM features in production is expensive in ways the pricing page doesn't show. Here's what actually moved the needle for us.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We ship LLM features to production. For the first few months, our bill looked reasonable — until it didn't. The model call price was never the problem. The problem was everything around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost isn't the happy path
&lt;/h2&gt;

&lt;p&gt;When a provider starts rate-limiting or flaking, two things happen that the naive cost estimate ignores:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Retry storms.&lt;/strong&gt; The same prompt gets hammered 3–5 times before anything gives up. Each retry is a full-price call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tail latency.&lt;/strong&gt; Requests queue behind slow providers, users time out, and you retry again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In one bad week, our effective cost per successful request was ~2.4× the listed price. Nobody budgets for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Route across providers by latency + price
&lt;/h3&gt;

&lt;p&gt;Instead of pinning one model to one provider, we route each request to the cheapest healthy endpoint that meets our latency budget.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A request that needs a fast answer goes to the lowest-latency provider currently under quota.&lt;/li&gt;
&lt;li&gt;A batch job goes to the cheapest one.&lt;/li&gt;
&lt;li&gt;Same model family, different provider — the caller doesn't care.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This alone removed most of the "stuck behind a throttled provider" tax.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cache embeddings and deterministic steps
&lt;/h3&gt;

&lt;p&gt;A surprising amount of our traffic re-embeds the same inputs. We added a cache layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embeddings for repeated prompts → served from cache.&lt;/li&gt;
&lt;li&gt;Deterministic pre/post-processing → computed once, reused.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That cut a meaningful chunk of repeat calls without touching quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Health-checked failover
&lt;/h3&gt;

&lt;p&gt;We keep a health-checked pool of upstreams. A provider that returns N consecutive 5xx/429 gets pulled from rotation for ~60s. Retries use exponential backoff with jitter.&lt;/p&gt;

&lt;p&gt;Result: when one provider degrades, traffic moves before users notice. No dropped requests as long as one upstream is alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bill per successful request: down ~40%.&lt;/li&gt;
&lt;li&gt;Retry calls: down ~60%.&lt;/li&gt;
&lt;li&gt;2am incidents where we manually switched providers: roughly zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this required an exotic stack. It's routing, caching, and a health check — the boring infra stuff that nobody writes blog posts about until the bill arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're in the same boat
&lt;/h2&gt;

&lt;p&gt;Getting reliable, affordable model access set up for a team has its own headaches — provider quotas, region limits, payment friction. If any of that sounds familiar, I'm happy to compare notes. Find me here or DM me; no pitch, just war stories.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>devops</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
