<?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: batonyzeng-sketch</title>
    <description>The latest articles on DEV Community by batonyzeng-sketch (@batonyzengsketch).</description>
    <link>https://dev.to/batonyzengsketch</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%2F4242255%2F8786136f-c101-4184-991e-3d5134a4f11e.png</url>
      <title>DEV Community: batonyzeng-sketch</title>
      <link>https://dev.to/batonyzengsketch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/batonyzengsketch"/>
    <language>en</language>
    <item>
      <title>Before You Buy AI Tokens for Your Team: The 3 Checks Most Buyers Skip</title>
      <dc:creator>batonyzeng-sketch</dc:creator>
      <pubDate>Sat, 14 Nov 2026 03:14:17 +0000</pubDate>
      <link>https://dev.to/batonyzengsketch/before-you-buy-ai-tokens-for-your-team-the-3-checks-most-buyers-skip-5aa</link>
      <guid>https://dev.to/batonyzengsketch/before-you-buy-ai-tokens-for-your-team-the-3-checks-most-buyers-skip-5aa</guid>
      <description>&lt;p&gt;If your team is shopping for AI tokens — or, more precisely, for reliable access to large-model APIs — the conversation almost always opens the same way: "Which models do you support?" and "What's your price per million tokens?"&lt;/p&gt;

&lt;p&gt;Those are the comfortable questions. They're also the ones that matter least.&lt;/p&gt;

&lt;p&gt;The questions that decide whether the tokens you actually pay for move your business are the ones nobody asks until something breaks: a call times out in another country, an invoice can't be issued in the currency you need, an incident happens and nobody is clearly on the hook. By then the token budget is spent.&lt;/p&gt;

&lt;p&gt;Here are the three checks worth doing before you sign.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Settlement and invoicing — the line item that isn't on the price tag
&lt;/h2&gt;

&lt;p&gt;A per-token price is meaningless if you can't actually reconcile it. Three things decide that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Which pricing caliber&lt;/strong&gt; the quote uses. "Per token" can mean input and output priced separately, or bundled. "Per flow" can mean a failed retry counts as a new flow. The number looks comparable until you realize you're comparing two different cost structures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The exchange-rate basis.&lt;/strong&gt; Is the rate fixed on the quote date, or floating? A fixed rate means both sides absorb a slice of volatility; a floating rate means finance re-explains the variance every month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The settlement-currency boundary.&lt;/strong&gt; Which currencies can be collected, which invoices can be issued locally, and which cannot be promised — these are the lines that surface only when you're ready to move money.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful tell when you're evaluating a supplier: do they publish their price list, pricing caliber, and settlement boundary openly, so both sides reconcile against the same table? Some do. &lt;a href="https://aiokeys.com/tseny/" rel="noopener noreferrer"&gt;aiokeys.com&lt;/a&gt; puts its RUB price list, pricing caliber, and settlement-currency boundary on the site — not because it's the cheapest, but because "we can both see the same number" removes a round of email before you've even started.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Connectivity — where the API call actually originates
&lt;/h2&gt;

&lt;p&gt;This is the one teams in a single market rarely think about, and teams operating across borders learn the hard way.&lt;/p&gt;

&lt;p&gt;You buy the token. You wire up the integration. And in one market the calls sail through; in another they time out, or silently slow to a crawl. The cause is usually not the model. It's the network the calling device registers on.&lt;/p&gt;

&lt;p&gt;Two 5G modes exist: NSA borrows the 4G core network for data; SA runs on an independent 5G core. If the device supports one and the local operator has only commercialized the other, the device quietly downgrades. Lights are on, signal shows full bars, the dashboard says connected — but it's registered on 4G, and your inference call runs on 4G expectations. In a market where SA isn't live yet, a "5G" device simply works as 4G LTE. Usable, but not what you priced for.&lt;/p&gt;

&lt;p&gt;None of this shows up in a model spec. It shows up in the acceptance checklist — if you write one. The token is step two. Confirming the network can actually carry the call is step one. Buy the token first, and you may have bought something you can't invoke.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Responsibility — private deployment moves the request, not the accountability
&lt;/h2&gt;

&lt;p&gt;A common reflex when cross-border data or compliance comes up: "We'll deploy it privately / in our own region." That solves one thing — the request doesn't cross a border. It does not solve the rest.&lt;/p&gt;

&lt;p&gt;Who is notified when something fails? Who can read the request logs? Where is the data-retention period defined? Who is the responsible party when an incident hits? Those remain yours. Technology can be outsourced; the accountability cannot. A private deployment is a real trade-off — you gain a controllable data boundary and you take on operations, scaling, and model updates. Writing "more secure" on the proposal is not the same as writing down what you gained and what you kept.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that's actually the product
&lt;/h2&gt;

&lt;p&gt;Tokens are, at this point, close to a commodity. The model list is a spreadsheet. The price per million is a number you can paste into a calculator.&lt;/p&gt;

&lt;p&gt;What's not a commodity is the set of boundary conditions wrapped around the token: can you settle and invoice the way your business actually runs, will the call reach the model from where your team sits, and when it breaks, who answers. Those are the checks that separate "we bought AI" from "we can use AI."&lt;/p&gt;

&lt;p&gt;So before the next token quote, run the three checks. And when a supplier shows you a transparent price page instead of a sales deck, that transparency is itself part of the deliverable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Golden line:&lt;/strong&gt; &lt;em&gt;The token isn't the risky part. The conditions attached to it are.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: &lt;a href="https://aiokeys.com/tseny/" rel="noopener noreferrer"&gt;aiokeys.com/tseny&lt;/a&gt; — openly published RUB pricing, pricing caliber, and settlement-currency boundary.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
    </item>
    <item>
      <title>The Corporate LLM Gateway: Why GPU-Hour Is the Wrong Metric</title>
      <dc:creator>batonyzeng-sketch</dc:creator>
      <pubDate>Fri, 13 Nov 2026 05:59:12 +0000</pubDate>
      <link>https://dev.to/batonyzengsketch/the-corporate-llm-gateway-why-gpu-hour-is-the-wrong-metric-3occ</link>
      <guid>https://dev.to/batonyzengsketch/the-corporate-llm-gateway-why-gpu-hour-is-the-wrong-metric-3occ</guid>
      <description>&lt;p&gt;An H100 80GB rents for anywhere between $1.30 and $12.29 per hour in public price lists. That is not a cherry-picked sample. It is the spread across providers tracked by Coloprice as of November 6, 2026, where the median sits at $3.90. Runpod lists the same card at $2.89 in Secure Cloud and from $1.99 in Community Cloud, billed by the second. Eight times the price for identical silicon, and that is still not the uncomfortable part of the job.&lt;/p&gt;

&lt;p&gt;The uncomfortable part is that GPU-hour price does not decide anything in a corporate deployment. It does not even make the top three arguments once the project reaches security review and the CFO. I am writing this after several deployments where we calculated savings first and built the gateway second. Every time, the savings turned out not to be the reason the thing got built.&lt;/p&gt;

&lt;p&gt;What follows is a cost model with the assumptions written down, the architecture of an exit point, the code for a minimal proxy, and three failure modes I have seen in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually breaks with direct access
&lt;/h2&gt;

&lt;p&gt;Calling a foreign API straight from a corporate network runs into three problems, and none of them is bandwidth.&lt;/p&gt;

&lt;p&gt;Keys get revoked. Providers see requests from certain regions and treat them as a regional policy violation: first a 403 on individual models, then a full key block. Recovery means correspondence in which you explain why your office IP ended up where it did. Over an eight-month pilot this happens two or three times, and each occurrence is downtime for a specific team rather than an abstract risk.&lt;/p&gt;

&lt;p&gt;Payments do not reconcile. A corporate card does not go through, so you need an intermediary, and an intermediary means a contract, closing documents and VAT. A new cost line appears that was never in the project budget, and it lives separately from the technical work.&lt;/p&gt;

&lt;p&gt;The third one is what usually stops the project. If a prompt carries a name, a phone number or bank details, you cannot prove afterwards that it did not. With direct access from applications there is no place where that would be recorded: every developer put their own key into their own service, and nobody retains a trace of what exactly left the perimeter. That is no longer about a 403. It is about data protection law (152-FZ in Russia) and about having nothing to answer an auditor with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recalculating the cost of a single request
&lt;/h2&gt;

&lt;p&gt;Take a typical profile: an internal assistant with retrieval over company documents. 1.5 million requests a month, averaging 900 input tokens and 350 output tokens. That is 1.35 billion input and 525 million output tokens.&lt;/p&gt;

&lt;p&gt;At an open-model price of roughly $0.15 per million input and $0.60 per million output, you get about $203 for input and $315 for output, so roughly $518 a month. The order of magnitude matters more than the precision here: substitute your own price and the conclusion stays in the same range.&lt;/p&gt;

&lt;p&gt;Then the arithmetic breaks the original framing. If the cache hits 30 percent of the time, you save about $155 a month, which is around thirteen thousand roubles. An engineer who owns the gateway, the routes, the updates and incident triage costs 250,000 to 300,000 roubles a month in Russia, taxes included. That is five percent of their cost, and only with a good cache hit rate. At these volumes a gateway does not pay for itself through token savings. It pays for itself through something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually pays for
&lt;/h2&gt;

&lt;p&gt;A single personal data leak costs a legal entity three to five million roubles under part 12 of article 13.11 of the Administrative Code when 1,000 to 10,000 data subjects are affected, five to ten million for 10,000 to 100,000, and for a repeat offence the fine is calculated from turnover. For comparison, the annual payroll of that same engineer is 3.0 to 3.6 million. One incident costs more than a year of their work, and that number excludes reputational damage and projects frozen during the investigation.&lt;/p&gt;

&lt;p&gt;Which answers the original question. You do not install a gateway to save on GPU-hours. You install it so there is one place where it is visible what left, what stayed, and why a particular decision was made. Savings arrive as a side effect rather than as the goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the exit point does
&lt;/h2&gt;

&lt;p&gt;Put it where traffic is already aggregated but has not yet left the perimeter: between the applications and the outside world. From there it takes on four jobs that cannot be done inside a client application.&lt;/p&gt;

&lt;p&gt;Masking happens first, before anything is sent. Not after the response, not in the logs, but in the request body before the upstream call: entities are replaced with placeholders backed by a reversible dictionary so the answer can be reassembled. It is also fair to admit that masking breaks some workloads. Which ones is covered below, because it is not a detail.&lt;/p&gt;

&lt;p&gt;Next is routing by sensitivity. Ticket classification, field extraction from contracts and everything else on a short context goes to a small model inside the perimeter. Long analysis, code generation and image work go to a large model through a tunnel, but only after masking. The rule lives in configuration rather than in a developer's head, otherwise in a month nobody can say why a given request went where it went.&lt;/p&gt;

&lt;p&gt;Then quotas. A limit per team, a separate counter for experiments, a monthly ceiling per project. Without this one enthusiast with a batch script consumes the budget in a week, and you learn about it from the invoice.&lt;/p&gt;

&lt;p&gt;And the decision log, without which everything above is pointless. For every request: an identifier, which masking was applied, which route was chosen, what the response cost. This log is what you show an auditor. Its absence is usually what "no compliance" actually means, even when everything was formally approved.&lt;/p&gt;

&lt;p&gt;A separate note on streaming, because almost every hand-built gateway trips here. Chat returns the answer over SSE, and if the gateway buffers the whole body to count tokens and write the log, the user stares at nothing for twenty seconds and then gets the entire text at once. That feels worse than a slow but live answer. Read usage from the final chunk and write the log after the stream closes, otherwise you get either added latency or lost records when the connection drops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code: a minimal proxy
&lt;/h2&gt;

&lt;p&gt;Below is the base I usually start from. The token comes from the environment, every request has an identifier, the timeout is bounded, and retries happen only on transport errors so that the same request is not billed twice.&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="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"corporate-llm-gateway"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;UPSTREAM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"UPSTREAM_URL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="no"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"UPSTREAM_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@app&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="s2"&gt;"/v1/chat"&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;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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="s2"&gt;"x-request-id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&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="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_pii&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="c1"&gt;# mask BEFORE leaving the perimeter&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pick_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# small model / large model / reject&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&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;attempt&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&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="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&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="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Bearer {TOKEN}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"x-request-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rid&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="nf"&gt;log_decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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="s2"&gt;"x-usage"&lt;/span&gt;&lt;span class="p"&gt;)&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TransportError&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;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"upstream unavailable"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sanitize_pii&lt;/code&gt;, &lt;code&gt;pick_route&lt;/code&gt; and &lt;code&gt;log_decision&lt;/code&gt; functions are deliberately left out: the first is an entity dictionary and regular expressions for your formats, the second is a rule table, the third writes to your logging system. What matters is not the implementation but the order. Masking and route selection run before the network call, not after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three failure modes I have seen
&lt;/h2&gt;

&lt;p&gt;A cache keyed on the wrong thing. Response caching looks like free money until the key omits the model version, the system prompt and temperature. This surfaces in support: answers meant for the basic tier reach premium customers because the key was built from the question text alone. Semantic caching is more honest about hit rates and more dangerous about consequences. Fifteen to thirty percent hits on a stream of similar requests is entirely achievable, but every hit is an answer to a similar question rather than to the same one.&lt;/p&gt;

&lt;p&gt;Masking breaks code generation. Replace identifiers with placeholders and the model loses the structure: variable names and dictionary keys become identical labels, and the generated code does not compile. The fix is not to disable masking but to use different policies for different task types. Code goes through a route where source is allowed and personal data is not, while documents containing such data go through strict masking.&lt;/p&gt;

&lt;p&gt;Failover that doubles the bill. When a route degrades, the gateway retries on a backup while the first request is neither cancelled nor untariffed, it is simply billed. On a stream of more than a million requests that is visible money and, worse, duplicated answers. You need an idempotency key and an explicit cancellation of the previous call on timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test it
&lt;/h2&gt;

&lt;p&gt;Synthetic pings are useless here. A test that shows something looks like this: record a real day of request traffic, replay it through the gateway, and drop one route halfway through the run. Then look at two numbers: the share of successful responses and the p95 latency. If both stay inside the contracted bounds, the architecture works. If they move, the problem is in the balancer or the timeouts rather than in the model, and that distinction matters during an incident review.&lt;/p&gt;

&lt;p&gt;Test masking separately: run a set of requests with known entities and verify that none of them appears in the outbound log. That test should be written before the first incident rather than after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you do not need a gateway
&lt;/h2&gt;

&lt;p&gt;If you have one team, ten developers, no personal data in prompts and fewer than fifty thousand requests a month, you do not need a gateway. A hundred-line wrapper, a single key in a secret store and a ban on hardcoding keys solve the same problem more cheaply. I have seen projects that built a gateway "for the future" and then spent six months unable to explain who owns it.&lt;/p&gt;

&lt;p&gt;A gateway becomes mandatory at exactly the moment a second application gets model access and the first personal data clause appears in a contract. Before that it only adds a service to your on-call rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;GPU-hour price is a poor metric for this problem because it answers "what does the hardware cost" while the decision is made on "what happens if data goes somewhere it should not". An eightfold spread on the same card is a good illustration of how secondary the first question is. Even if you pick the cheapest price, the answer to the second question does not change.&lt;/p&gt;

&lt;p&gt;The practical conclusion is simple. Count the cost of one successfully handled request, including masking, routing and logging, rather than the cost of an hour of rental. And keep the exit point where the data boundary is: if you cannot show where a request was masked and where it went, you have neither compliance nor an SLA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Prices and spreads were verified on November 11, 2026: the H100 80GB summary on &lt;a href="https://coloprice.com/gpu/h100" rel="noopener noreferrer"&gt;Coloprice&lt;/a&gt;, rates on the &lt;a href="https://www.runpod.io/pricing" rel="noopener noreferrer"&gt;Runpod pricing page&lt;/a&gt;. Fines are quoted from part 12 of article 13.11 of the Russian Administrative Code as amended with effect from May 30, 2025.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>infrastructure</category>
      <category>gateway</category>
      <category>compliance</category>
    </item>
  </channel>
</rss>
