<?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: Moksh Gupta</title>
    <description>The latest articles on DEV Community by Moksh Gupta (@moksh).</description>
    <link>https://dev.to/moksh</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%2F2457679%2F32485ee6-614e-4050-bd8e-e22536e1f2b5.png</url>
      <title>DEV Community: Moksh Gupta</title>
      <link>https://dev.to/moksh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moksh"/>
    <language>en</language>
    <item>
      <title>OmniRoute: The Free LLM Gateway With a Terms-of-Service Catch</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Tue, 18 Aug 2026 02:33:51 +0000</pubDate>
      <link>https://dev.to/moksh/omniroute-the-free-llm-gateway-with-a-terms-of-service-catch-374o</link>
      <guid>https://dev.to/moksh/omniroute-the-free-llm-gateway-with-a-terms-of-service-catch-374o</guid>
      <description>&lt;p&gt;If you run Claude Code, Cursor and Cline against different accounts, you already know the failure mode: one provider hits a quota mid-task and the whole session stalls while you go dig up another key. OmniRoute tries to solve that by putting every provider behind one endpoint on your own machine. I wrote a longer breakdown of it on &lt;a href="https://devtoollab.com/blog/omniroute-free-ai-gateway" rel="noopener noreferrer"&gt;DevToolLab&lt;/a&gt;, but the short version covers the parts worth knowing before you install it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmy9jikm7d8swyge2cl1u.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmy9jikm7d8swyge2cl1u.webp" alt="The diegosouzapw/OmniRoute repository on GitHub showing 48.1k stars, 6.5k forks, 6,845 commits with the most recent an hour earlier, and a description of a free MIT AI gateway covering 339 providers and 1200+ models" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OmniRoute is an MIT-licensed proxy that runs locally on &lt;code&gt;localhost:20128&lt;/code&gt; and speaks the OpenAI API format at &lt;code&gt;/v1&lt;/code&gt;. Point any OpenAI-compatible client at it and it forwards requests to whichever of roughly 300 providers its router selects, then fails over automatically when one runs dry. It picked up just over 48,000 GitHub stars in about six months, which is fast growth against LiteLLM, the incumbent in this space, sitting at 56,000-plus stars built up since mid-2023.&lt;/p&gt;

&lt;p&gt;Because it runs on your machine rather than someone else's server, API keys stay encrypted on local disk with AES-256-GCM and your prompts never pass through a third-party cloud. That is a real difference from a hosted gateway, not a marketing line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failover logic is the actual product
&lt;/h2&gt;

&lt;p&gt;Once you get past the free-token pitch, what you are really getting is a well-built retry system. Requests move through four tiers: subscriptions you already pay for first, then your own API keys, then cheap models, then free tiers, so a coding session degrades gracefully instead of dying outright.&lt;/p&gt;

&lt;p&gt;Under that sit three separate recovery mechanisms. A circuit breaker only trips on 408 and 5xx errors (three failures for OAuth connections, five for API keys, two for local models) before resetting on a timer and probing again. A per-connection cooldown backs off exponentially and respects &lt;code&gt;Retry-After&lt;/code&gt; on 429s instead of guessing at a delay. A per-model lockout benches just the failing model rather than the whole provider. Anyone who has hand-written retry logic against a flaky LLM API will recognize these as sensible, specific defaults, and there are 19 routing strategies on top, including one that pins a prompt to a single account so caching actually hits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 1.53 billion token number needs context
&lt;/h2&gt;

&lt;p&gt;OmniRoute's headline stat sums the documented free tiers of 43 provider pools across 516 models, not a pool anyone actually hands you, and shows the running total on a built-in dashboard. To its credit, the methodology is public: shared pools count once, one-time signup credits are separated from recurring ones, and the maintainers openly decline to publish an inflated ~10 billion figure that would come from counting every rate-limit window around the clock. Mistral alone accounts for roughly 1 billion tokens a month of it.&lt;/p&gt;

&lt;p&gt;That number moves. In 2026, Chutes, Phind and Kluster all ended or paused their free tiers, and the project re-audits its figures every two weeks because of exactly that churn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the terms-of-service table before enabling everything
&lt;/h2&gt;

&lt;p&gt;This is the part that gets skipped in most coverage, and it lives right in the repo at &lt;code&gt;docs/reference/FREE_TIERS.md&lt;/code&gt;: a table of 15 providers whose terms explicitly restrict proxy access. Google Antigravity bans third-party tools accessing its service via OAuth. Fireworks prohibits proxy or intermediary use outright. A handful of consumer chat products reached through session tokens ban automated access entirely. The maintainers label each entry &lt;code&gt;ok&lt;/code&gt;, &lt;code&gt;caution&lt;/code&gt;, or &lt;code&gt;ambiguous&lt;/code&gt; and are upfront that this is informational, not legal advice, which is more candor than this category usually offers.&lt;/p&gt;

&lt;p&gt;The practical risk is account suspension, and a suspended provider account can take a subscription you actually depend on with it. I go through the exact wording of the flagged terms in the &lt;a href="https://devtoollab.com/blog/omniroute-free-ai-gateway" rel="noopener noreferrer"&gt;full article&lt;/a&gt;, which is worth five minutes before you flip every connector on.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it stacks up
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Created&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OmniRoute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;48.1k&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Feb 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/BerriAI/litellm" rel="noopener noreferrer"&gt;LiteLLM&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;56.4k&lt;/td&gt;
&lt;td&gt;Non-standard&lt;/td&gt;
&lt;td&gt;Jul 2023&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/songquanpeng/one-api" rel="noopener noreferrer"&gt;one-api&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;36.4k&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Apr 2023&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/Portkey-AI/gateway" rel="noopener noreferrer"&gt;Portkey Gateway&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.7k&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Aug 2023&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;LiteLLM is still the safer pick for a regulated or contract-bound environment purely on track record. For a solo developer juggling agents or a cost-sensitive side project, OmniRoute's failover is worth having on its own, independent of the free-tier math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it safely
&lt;/h2&gt;

&lt;p&gt;Check that port 20128 is actually free before you assume the gateway is running (our &lt;a href="https://devtoollab.com/tools/port-checker" rel="noopener noreferrer"&gt;Port Checker&lt;/a&gt; confirms this in a browser tab), then send one request straight to &lt;code&gt;/v1/chat/completions&lt;/code&gt; before wiring an agent to it, so you know whether a problem is the gateway or your editor config. Read the ToS table and turn off anything you are not personally entitled to use. And before leaning on the free tiers, price the same workload on a paid API with something like our &lt;a href="https://devtoollab.com/tools/llm-token-cost-calculator" rel="noopener noreferrer"&gt;LLM Token Cost Calculator&lt;/a&gt;, because free tiers disappear with no warning and you want to know what the fallback actually costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;The routing, the fallback tiers, and the local-first key storage are genuinely good engineering, and the MIT license means you can verify all of it yourself. The 1.53 billion free tokens are real arithmetic on paper, but a chunk of it sits behind provider terms that prohibit exactly this kind of proxy access. Use OmniRoute for what it is actually good at, and treat the token count as a ceiling with some doors marked "do not open."&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/blog/omniroute-free-ai-gateway" rel="noopener noreferrer"&gt;Original article on DevToolLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/diegosouzapw/OmniRoute" rel="noopener noreferrer"&gt;OmniRoute on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/BerriAI/litellm" rel="noopener noreferrer"&gt;LiteLLM on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Safely Run AI-Generated Code: E2B, Modal and Piston in Python</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Sun, 16 Aug 2026 17:43:48 +0000</pubDate>
      <link>https://dev.to/moksh/how-to-safely-run-ai-generated-code-e2b-modal-and-piston-in-python-584c</link>
      <guid>https://dev.to/moksh/how-to-safely-run-ai-generated-code-e2b-modal-and-piston-in-python-584c</guid>
      <description>&lt;p&gt;The moment you let an LLM write code and then run it for real, you have handed a decision to a model that has no idea what your &lt;code&gt;.env&lt;/code&gt; file contains. A chatbot that only talks is safe by construction. One that executes its own Python to check its math or plot a chart is a different animal, and something has to decide where that code is allowed to run.&lt;/p&gt;

&lt;p&gt;Most people's first instinct is &lt;code&gt;exec()&lt;/code&gt;, and that is exactly the wrong instinct: &lt;code&gt;exec()&lt;/code&gt; runs inside your own process, with your environment variables and your filesystem permissions along for the ride. &lt;a href="https://devtoollab.com/blog/build-ai-code-interpreter-e2b-modal" rel="noopener noreferrer"&gt;I wrote a longer version of this walkthrough on DevToolLab&lt;/a&gt;, covering E2B, Modal, and Piston, the open source engine you can self-host instead of depending on either vendor. Every snippet below was checked against the SDK versions actually installed via pip, not copied from a marketing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exec() problem, quickly
&lt;/h2&gt;

&lt;p&gt;Drop a model-generated string straight into &lt;code&gt;exec()&lt;/code&gt; and it can read anything your process can read:&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="n"&gt;model_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
import os
print(f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I can see {len(os.environ)} environment variables from this process.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On an ordinary laptop that prints something like &lt;code&gt;I can see 61 environment variables from this process&lt;/code&gt;, and nothing about that call needed elevated permissions. It is just a normal Python process reading its own environment, which is usually where API keys and secrets live. A sandbox exists to give the model's code its own throwaway machine so that reading &lt;code&gt;os.environ&lt;/code&gt; returns nothing interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  E2B: microVMs built for this exact loop
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://e2b.dev" rel="noopener noreferrer"&gt;E2B&lt;/a&gt; runs each sandbox as a Firecracker microVM, the same virtualization AWS built for Lambda. Install the code interpreter SDK, grab an API key from the E2B dashboard, and export it as &lt;code&gt;E2B_API_KEY&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;e2b-code-interpreter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;e2b_code_interpreter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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="mi"&gt;60&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;sandbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;execution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data = [4, 8, 15, 16, 23, 42]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;print(sum(data))&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always set an explicit &lt;code&gt;timeout&lt;/code&gt;. &lt;code&gt;run_code()&lt;/code&gt; hands back an &lt;code&gt;Execution&lt;/code&gt; object, not a plain string, with &lt;code&gt;.text&lt;/code&gt; for the printed result, &lt;code&gt;.results&lt;/code&gt; for richer outputs like PNGs from &lt;code&gt;matplotlib&lt;/code&gt;, &lt;code&gt;.logs&lt;/code&gt; for stdout/stderr, and &lt;code&gt;.error&lt;/code&gt; for a structured exception (&lt;code&gt;.name&lt;/code&gt;, &lt;code&gt;.value&lt;/code&gt;, &lt;code&gt;.traceback&lt;/code&gt;) instead of a thrown Python exception. Checking &lt;code&gt;.error&lt;/code&gt; is the idiomatic move here, since a failing snippet is not a transport failure, it is a normal result the SDK reports back correctly.&lt;/p&gt;

&lt;p&gt;E2B's Hobby tier is free with a one-time $100 usage credit, 20 concurrent sandboxes, and one-hour sessions. Pro is $150/month, stretching sessions to 24 hours and concurrency to 100.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9hv9u631o45qt7d9plb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9hv9u631o45qt7d9plb.webp" alt="E2B's pricing page showing the free Hobby tier and the $150/mo Pro tier" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Modal: containers instead of microVMs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://modal.com" rel="noopener noreferrer"&gt;Modal&lt;/a&gt; takes a different route: a &lt;code&gt;Sandbox&lt;/code&gt; is a container on Modal's general compute platform, so the same image-building tools you'd use for a Modal function work here too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;modal
modal setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;modal setup&lt;/code&gt; opens a browser and writes a token to &lt;code&gt;~/.modal.toml&lt;/code&gt;, no manual API key needed. A Modal sandbox starts idle with no entrypoint, and you run commands inside it with &lt;code&gt;.exec()&lt;/code&gt;:&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;modal&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;create_if_missing&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;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debian_slim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;python_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sandbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&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="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_network&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;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&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;-c&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;print(sum([4, 8, 15, 16, 23, 42]))&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;process&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;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Sandbox.create()&lt;/code&gt; defaults its own timeout to 300 seconds if you skip it, and &lt;code&gt;block_network=True&lt;/code&gt; is worth setting explicitly for anything that only needs to compute, not call out. Modal's Starter plan is free with $30/month in credits and no idle charges; Sandboxes bill at roughly 3x the standard per-second rate since they're non-preemptible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Piston: the open source, self-hosted option
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/engineer-man/piston" rel="noopener noreferrer"&gt;Piston&lt;/a&gt; is a genuinely open source (MIT licensed) code execution engine that's powered Discord code bots for years. Its public demo API used to be free to hit, but as of February 15, 2026 the execute endpoint went whitelist-only, confirmed directly:&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;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://emkc.org/api/v2/piston/execute"&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;'{"language":"python","version":"3.10.0","files":[{"name":"main.py","content":"print(1)"}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns a message pointing you to self-hosting instead. The good news is Piston ships a ready Docker image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--privileged&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;:/piston &lt;span class="nt"&gt;-dit&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 2000:2000 &lt;span class="nt"&gt;--name&lt;/span&gt; piston_api ghcr.io/engineer-man/piston
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's running, point requests at your own host instead of &lt;code&gt;emkc.org&lt;/code&gt;, using the exact same request shape:&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;requests&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;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:2000/api/v2/execute&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&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;python&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;version&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;3.10.0&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;files&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;name&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;main.py&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;print(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hi&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&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;print&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run&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;stdout&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;Piston sandboxes each execution with &lt;code&gt;isolate&lt;/code&gt;, the same tool competitive-programming judges use, but you own patching the host and scoping container privileges yourself, work E2B and Modal absorb as part of the price.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8823afsxoin3yk24j00f.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8823afsxoin3yk24j00f.webp" alt="The engineer-man/piston GitHub repo showing 2.8k stars and its MIT license" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking one
&lt;/h2&gt;

&lt;p&gt;If you want the least infrastructure to think about, start with E2B, its &lt;code&gt;run_code()&lt;/code&gt; model is purpose-built for this loop. If you need a custom image, mounted volumes, or you're already deploying on Modal, its container-based &lt;code&gt;Sandbox&lt;/code&gt; fits naturally. If neither vendor works for your situation, Piston self-hosted is the real fallback, at the cost of running it yourself. Whichever you pick, the &lt;a href="https://devtoollab.com/blog/build-ai-code-interpreter-e2b-modal" rel="noopener noreferrer"&gt;full DevToolLab guide&lt;/a&gt; has the complete code for all three plus the exact pricing math, and DevToolLab's &lt;a href="https://devtoollab.com/tools/env-file-generator" rel="noopener noreferrer"&gt;.env File Generator&lt;/a&gt; is a quick way to scaffold the file holding your API keys without hand-typing it.&lt;/p&gt;

&lt;p&gt;The pattern that matters more than which SDK you choose: always set an explicit timeout, always check the structured error instead of guessing, decide deliberately whether the sandbox gets network access, and always tear it down when you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/blog/build-ai-code-interpreter-e2b-modal" rel="noopener noreferrer"&gt;Building an AI Code Interpreter: E2B and Modal Sandboxes in Python - the original, full-length guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://e2b.dev/docs" rel="noopener noreferrer"&gt;E2B code interpreter documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modal.com/docs/guide/sandboxes" rel="noopener noreferrer"&gt;Modal Sandboxes guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/engineer-man/piston" rel="noopener noreferrer"&gt;Piston on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Much VRAM Does a Local LLM Actually Need?</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Sun, 16 Aug 2026 07:18:18 +0000</pubDate>
      <link>https://dev.to/moksh/how-much-vram-does-a-local-llm-actually-need-4g9k</link>
      <guid>https://dev.to/moksh/how-much-vram-does-a-local-llm-actually-need-4g9k</guid>
      <description>&lt;p&gt;"Half a gig of VRAM per billion parameters" is the rule everyone repeats when picking hardware for a local model, and it works fine right up until the model loads, runs for a few minutes, and then dies mid-conversation with an out-of-memory error. The model fit. The context didn't. &lt;a href="https://devtoollab.com/blog/local-llm-vram-requirements" rel="noopener noreferrer"&gt;I wrote up the full math on DevToolLab&lt;/a&gt;, and it comes down to two numbers most people never check.&lt;/p&gt;

&lt;p&gt;First, quantized weights are bigger than the quant name implies. Second, at long context the KV cache can weigh more than the model itself. Here's what actually happens, checked against real GGUF files on Hugging Face rather than the arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quant name isn't the bit count
&lt;/h2&gt;

&lt;p&gt;Q4 sounds like 4 bits per weight. Multiply that by parameter count and divide by 8, and an 8B model should land at 4.01 GB. Download the actual &lt;code&gt;bartowski/Meta-Llama-3.1-8B-Instruct-GGUF&lt;/code&gt; Q4_K_M file and it's 4.92 GB, 23 percent heavier than the math promised.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F231z4367if2a197y8exo.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F231z4367if2a197y8exo.webp" alt="Hugging Face file listing for bartowski/Meta-Llama-3.1-8B-Instruct-GGUF showing Q4_K_M at 4.92 GB and other quant sizes" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That gap isn't a rounding error. K-quants store per-block scale and minimum values next to the quantized weights, and the "M"/"L" variants keep a handful of sensitive tensors (embeddings, output layer) at higher precision. The suffix names the dominant format, not the average across the whole file. The overhead shrinks as precision goes up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quant&lt;/th&gt;
&lt;th&gt;Naive size&lt;/th&gt;
&lt;th&gt;Real size&lt;/th&gt;
&lt;th&gt;Overhead&lt;/th&gt;
&lt;th&gt;Real bits/weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;4.01 GB&lt;/td&gt;
&lt;td&gt;4.92 GB&lt;/td&gt;
&lt;td&gt;+23%&lt;/td&gt;
&lt;td&gt;4.90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q5_K_M&lt;/td&gt;
&lt;td&gt;5.02 GB&lt;/td&gt;
&lt;td&gt;5.73 GB&lt;/td&gt;
&lt;td&gt;+14%&lt;/td&gt;
&lt;td&gt;5.71&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q6_K&lt;/td&gt;
&lt;td&gt;6.02 GB&lt;/td&gt;
&lt;td&gt;6.60 GB&lt;/td&gt;
&lt;td&gt;+10%&lt;/td&gt;
&lt;td&gt;6.58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;td&gt;8.03 GB&lt;/td&gt;
&lt;td&gt;8.54 GB&lt;/td&gt;
&lt;td&gt;+6%&lt;/td&gt;
&lt;td&gt;8.51&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you need one planning number, use &lt;strong&gt;0.61 GB per billion parameters at Q4_K_M&lt;/strong&gt;. That's where the "0.6 GB per billion" folklore actually comes from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The KV cache is what actually runs you out of memory
&lt;/h2&gt;

&lt;p&gt;Weights are a one-time cost. The KV cache grows with every token in the conversation and never shrinks until you start a new one. The formula is two tensors (K and V), per layer, per token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cache_bytes_per_token = 2 x layers x kv_heads x head_dim x bytes_per_element
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Llama 3.1 8B has 32 layers, a head dimension of 128, and only 8 KV heads (not 32 - grouped-query attention shares key/value projections across query heads, which is a 4x cache saving over full multi-head attention). Plug those in at fp16 and you get 128 KB per token:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;KV cache (fp16)&lt;/th&gt;
&lt;th&gt;KV cache (q8)&lt;/th&gt;
&lt;th&gt;Total with Q4_K_M weights&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4,096&lt;/td&gt;
&lt;td&gt;0.54 GB&lt;/td&gt;
&lt;td&gt;0.27 GB&lt;/td&gt;
&lt;td&gt;5.46 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8,192&lt;/td&gt;
&lt;td&gt;1.07 GB&lt;/td&gt;
&lt;td&gt;0.54 GB&lt;/td&gt;
&lt;td&gt;5.99 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;td&gt;4.29 GB&lt;/td&gt;
&lt;td&gt;2.15 GB&lt;/td&gt;
&lt;td&gt;9.21 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;131,072&lt;/td&gt;
&lt;td&gt;17.18 GB&lt;/td&gt;
&lt;td&gt;8.59 GB&lt;/td&gt;
&lt;td&gt;22.10 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at the last row. The model itself is under 5 GB. The cache for its advertised 128k context window is 17.18 GB on top of that - 22.10 GB total for a model everyone calls "runs on anything." That doesn't fit on a 16 GB card and leaves nothing free on a 24 GB one. &lt;a href="https://devtoollab.com/blog/local-llm-vram-requirements" rel="noopener noreferrer"&gt;The full breakdown, including the runnable script&lt;/a&gt;, walks through deriving this for any model from its &lt;code&gt;config.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrency multiplies the cache, not the weights
&lt;/h2&gt;

&lt;p&gt;Everything above is one conversation. Put a model behind an API two people hit at once, and the cache multiplies by however many sequences are in flight, because each one keeps its own K/V state. Four concurrent 8k sessions on this model is 4.29 GB of cache against 4.92 GB of weights. Sixteen sessions is 17.2 GB, gone, on a 24 GB card, while any single conversation still looks perfectly modest. This is why serving frameworks like vLLM invest so heavily in paged attention: naive allocation reserves space for tokens nobody has generated yet.&lt;/p&gt;

&lt;p&gt;If you're sizing for a team instead of your own laptop, multiply the cache column by peak concurrency, and quantize the cache before you do anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rough hardware guide
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;VRAM&lt;/th&gt;
&lt;th&gt;Comfortable at Q4_K_M&lt;/th&gt;
&lt;th&gt;Realistic context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;td&gt;7B-8B&lt;/td&gt;
&lt;td&gt;8k-16k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12 GB&lt;/td&gt;
&lt;td&gt;8B-13B&lt;/td&gt;
&lt;td&gt;16k-32k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;td&gt;13B-14B&lt;/td&gt;
&lt;td&gt;32k, or 8B at 64k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24 GB&lt;/td&gt;
&lt;td&gt;24B-32B&lt;/td&gt;
&lt;td&gt;32k comfortably, 8B at full 128k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48 GB&lt;/td&gt;
&lt;td&gt;70B at Q4&lt;/td&gt;
&lt;td&gt;32k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96 GB+&lt;/td&gt;
&lt;td&gt;70B at Q6+&lt;/td&gt;
&lt;td&gt;long context on large models&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Apple Silicon breaks this table a little because memory is unified, not dedicated - a 32 GB M-series Mac can hold a model plus cache that would need a 32 GB discrete GPU. It's slower per token than an equivalent NVIDIA card, but the ceiling is total RAM, not what fits on a board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four ways to buy headroom without a new GPU
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quantize the KV cache.&lt;/strong&gt; The single biggest lever. &lt;code&gt;llama.cpp&lt;/code&gt; exposes &lt;code&gt;--cache-type-k&lt;/code&gt; / &lt;code&gt;--cache-type-v&lt;/code&gt;, Ollama has an equivalent flag, and going to q8 roughly halves cache memory for a quality hit most people won't notice in chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set context deliberately.&lt;/strong&gt; Runtimes often reserve the full advertised window whether you use it or not. Dropping from 128k to 32k on this model frees nearly 13 GB, and most chat/coding sessions never get near 32k anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop a quant level before dropping model size.&lt;/strong&gt; A 13B at Q4_K_M usually beats an 8B at Q8_0 for similar memory - bigger model, cheaper weights, wins until you go below Q3, where quality visibly falls off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offload layers instead of giving up.&lt;/strong&gt; &lt;code&gt;llama.cpp&lt;/code&gt; splits layers between GPU and CPU. It gets slower, sometimes a lot slower, but a slow model beats one that won't load.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Measure it, don't just estimate it
&lt;/h2&gt;

&lt;p&gt;Once the model's loaded, check the real number instead of trusting the arithmetic - allocator overhead, CUDA context and fragmentation all sit on top of the theoretical figure. On NVIDIA, &lt;code&gt;nvidia-smi --query-gpu=memory.used,memory.total --format=csv&lt;/code&gt; run during a long conversation shows the cache filling in real time. On Apple Silicon, Activity Monitor's memory tab shows the same thing, and watch for the machine starting to swap, which shows up as tokens/sec collapsing rather than a clean error.&lt;/p&gt;

&lt;p&gt;The tell-tale sign of a cache problem: generation is snappy for the first few exchanges, then slows hard or crashes as the conversation grows. Weights load once at startup, so a failure that shows up later is almost always the cache eating into memory you didn't budget for.&lt;/p&gt;

&lt;p&gt;To size a model you haven't downloaded yet, pull four fields from its &lt;code&gt;config.json&lt;/code&gt; on Hugging Face: &lt;code&gt;num_hidden_layers&lt;/code&gt;, &lt;code&gt;num_key_value_heads&lt;/code&gt;, &lt;code&gt;hidden_size&lt;/code&gt;, and &lt;code&gt;num_attention_heads&lt;/code&gt; (head dimension is &lt;code&gt;hidden_size / num_attention_heads&lt;/code&gt;). Watch &lt;code&gt;num_key_value_heads&lt;/code&gt; especially - when it equals &lt;code&gt;num_attention_heads&lt;/code&gt;, the model uses full multi-head attention and its cache is several times larger per token than a GQA model of the same size. Two 8B models can differ 4x on cache memory for exactly that reason.&lt;/p&gt;

&lt;p&gt;Sizing a local model is really two separate calculations: parameters times real bits-per-weight for the weights, and &lt;code&gt;2 x layers x kv_heads x head_dim x bytes&lt;/code&gt; per token for the cache. At long context the second number usually matters more than the first. Check both before you buy a card - &lt;a href="https://devtoollab.com/tools/data-storage-converter" rel="noopener noreferrer"&gt;DevToolLab's Data Storage Converter&lt;/a&gt; handles the GB-vs-GiB conversion, and the &lt;a href="https://devtoollab.com/tools/file-size-converter" rel="noopener noreferrer"&gt;File Size Converter&lt;/a&gt; is useful for checking a downloaded GGUF against the size the repo advertises - and quantize the cache before you compromise on the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/blog/local-llm-vram-requirements" rel="noopener noreferrer"&gt;Local LLM VRAM Requirements: How Much Memory You Actually Need - the original, with the full runnable sizing script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF" rel="noopener noreferrer"&gt;bartowski/Meta-Llama-3.1-8B-Instruct-GGUF on Hugging Face&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Neon vs Supabase: The Real Math Behind Scale-to-Zero Postgres in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Sat, 15 Aug 2026 17:13:40 +0000</pubDate>
      <link>https://dev.to/moksh/neon-vs-supabase-the-real-math-behind-scale-to-zero-postgres-in-2026-3a92</link>
      <guid>https://dev.to/moksh/neon-vs-supabase-the-real-math-behind-scale-to-zero-postgres-in-2026-3a92</guid>
      <description>&lt;p&gt;Provisioned Postgres charges you by the hour whether anyone is querying it or not, which is a bad deal the moment your traffic is spiky, or you want a fresh database per pull request. That is the whole pitch for serverless Postgres, and in 2026 the numbers actually back it up. I ran the real arithmetic on Neon and Supabase's published rates in a longer piece on &lt;a href="https://devtoollab.com/blog/best-serverless-postgres" rel="noopener noreferrer"&gt;DevToolLab&lt;/a&gt;, and the short version is below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Serverless" Actually Changes
&lt;/h2&gt;

&lt;p&gt;Three things happen when compute and storage split apart. Compute can suspend to zero cost when idle, which is the entire economic argument. Storage keeps living on its own, which is what makes suspend-and-resume a few seconds instead of a restore-from-backup. And you can spin up a copy-on-write branch per PR, seeded with real data, without provisioning a second instance. For most teams the branching is the bigger workflow change; the bill is just the reason it got funded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Crossover Actually Sits
&lt;/h2&gt;

&lt;p&gt;The interesting question isn't whether usage billing is cheaper, it's at what utilization it stops being cheaper. That's a two-line calculation from the vendors' own rate cards.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HOURS_PER_MONTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;730&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NEON_CU_HOUR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.106&lt;/span&gt;       &lt;span class="c1"&gt;// neon.com/pricing, Launch plan, Aug 2026&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FLAT_PLAN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;             &lt;span class="c1"&gt;// supabase.com/pricing, Pro, Aug 2026&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;monthlyCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;utilization&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;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;HOURS_PER_MONTH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;utilization&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;NEON_CU_HOUR&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breakEvenUtilization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FLAT_PLAN&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HOURS_PER_MONTH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;NEON_CU_HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`always-on: $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;monthlyCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&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="s2"&gt;/mo`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`break-even vs $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;FLAT_PLAN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/mo flat: &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;breakEvenUtilization&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;always-on: $77.38/mo
break-even vs $25/mo flat: 32.3%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A database that's genuinely busy for roughly a third of the month costs the same either way. Below that line, usage billing wins by a widening margin, an ephemeral preview branch that wakes for ten minutes per PR is basically free. Above it, an always-on single compute unit runs $77.38 a month, over three times a flat plan. I walk through the full utilization table (5% through 100%) in the &lt;a href="https://devtoollab.com/blog/best-serverless-postgres#the-crossover-is-arithmetic" rel="noopener noreferrer"&gt;original breakdown&lt;/a&gt;, worth a look if you're sizing a specific workload rather than eyeballing the crossover.&lt;/p&gt;

&lt;p&gt;One catch: Supabase's $25 includes 8 GB of disk, 250 GB of egress, auth and object storage, so it's a full backend product, not a database line item. The crossover tells you about compute, not about what you'd otherwise pay to assemble the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Neon: Cheap Compute, a Quiet Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://neon.com" rel="noopener noreferrer"&gt;Neon&lt;/a&gt; dropped its monthly minimum entirely. Launch plan is pure usage billing at $0.106 per compute-hour and $0.35 per GB-month, suspending after 5 minutes idle (configurable down to 1 minute on Scale). A paid project sitting idle all month now costs nothing for compute, which makes per-branch, per-agent databases a rounding error instead of a line item somebody has to approve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyp480tp0bivbemb44wj4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyp480tp0bivbemb44wj4.webp" alt="Neon pricing page showing usage-based Launch and Scale plans with no monthly minimum" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Databricks closed its roughly $1 billion acquisition of Neon in May 2025, and the stated reason explains the pricing: about 80% of databases on Neon were being spun up by AI agents, not people, a workload that only makes sense on per-second billing with a zero floor.&lt;/p&gt;

&lt;p&gt;The part almost nobody checks is the open source repo. &lt;code&gt;neondatabase/neon&lt;/code&gt; is Apache 2.0 with around 22,900 stars, but it landed exactly four commits in all of 2026, one of which just swapped a README link. Most recent tagged release: July 2025.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvtsjsc48jwy8p1ryjl28.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvtsjsc48jwy8p1ryjl28.webp" alt="Neon GitHub commit history showing only four commits in 2026" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use Neon as the excellent managed service it is. Don't build a plan around self-hosting it, or around upstream fixes landing on your timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supabase: A Platform That Happens to Include Postgres
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://supabase.com" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; Pro is $25 a month flat, 8 GB disk, 250 GB egress, a Micro compute instance included. No scale-to-zero on paid plans, the flat fee is a floor, not a cap, since the database stays up continuously.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6bocpw12hg9wfm9ijdof.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6bocpw12hg9wfm9ijdof.webp" alt="Supabase pricing page showing Free, Pro at $25/month, Team and Enterprise tiers" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Free tier projects pause after a week of inactivity, fine for a side project, annoying for a demo you show a client monthly. What you're really paying for on Pro is everything wrapped around the database: auth, row-level security, realtime, edge functions, a generated REST layer. Its GitHub repo (&lt;code&gt;supabase/supabase&lt;/code&gt;, ~108,000 stars) had a commit the same day this was checked, a sharp contrast to Neon's.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Neon&lt;/th&gt;
&lt;th&gt;Supabase&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;Unbundled Postgres&lt;/td&gt;
&lt;td&gt;Backend platform with Postgres&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid entry&lt;/td&gt;
&lt;td&gt;No minimum, $0.106/CU-hour&lt;/td&gt;
&lt;td&gt;$25/month flat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale to zero on paid&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repo activity (2026)&lt;/td&gt;
&lt;td&gt;4 commits&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to Pick
&lt;/h2&gt;

&lt;p&gt;Per-branch or per-preview databases: Neon, not close. Agent or batch workloads that spike and vanish: Neon, for the same reason Databricks bought it. Need auth, storage and realtime too: Supabase, and stop comparing raw compute rates, price the whole stack. Steady traffic above roughly a third utilization: run your own version of the arithmetic above with your real compute size before assuming usage billing wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Things Worth Getting Right
&lt;/h2&gt;

&lt;p&gt;Measure your actual utilization (a week of CPU-active time divided by hours in that week) before picking a billing model, that single number decides which shape wins. Use a pooled connection from serverless functions, not a direct one per invocation, or you'll exhaust Postgres connection slots before you exhaust budget. Our &lt;a href="https://devtoollab.com/tools/connection-string-parser" rel="noopener noreferrer"&gt;Connection String Parser&lt;/a&gt; is handy for confirming which endpoint and &lt;code&gt;sslmode&lt;/code&gt; you actually pasted. And don't cron a keepalive ping to dodge cold starts, that quietly converts a usage bill back into an always-on bill, the one move that erases the whole point of scale-to-zero; if you do need scheduled jobs, size the interval with something like a &lt;a href="https://devtoollab.com/tools/cron-expression-generator" rel="noopener noreferrer"&gt;Cron Expression Generator&lt;/a&gt; instead of guessing.&lt;/p&gt;

&lt;p&gt;Neon is the better database, Supabase is the better platform, and comparing them on compute price alone will mislead you in whichever direction you were already leaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/blog/best-serverless-postgres" rel="noopener noreferrer"&gt;Best Serverless Postgres in 2026 (original, DevToolLab)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.prnewswire.com/news-releases/databricks-agrees-to-acquire-neon-to-deliver-serverless-postgres-for-developers--ai-agents-302454992.html" rel="noopener noreferrer"&gt;Databricks agrees to acquire Neon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/neondatabase/neon" rel="noopener noreferrer"&gt;neondatabase/neon on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/supabase/supabase" rel="noopener noreferrer"&gt;supabase/supabase on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Microsoft Now Rejects Unauthenticated Email. Here Is the DNS That Fixes It.</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Thu, 13 Aug 2026 17:32:42 +0000</pubDate>
      <link>https://dev.to/moksh/microsoft-now-rejects-unauthenticated-email-here-is-the-dns-that-fixes-it-233o</link>
      <guid>https://dev.to/moksh/microsoft-now-rejects-unauthenticated-email-here-is-the-dns-that-fixes-it-233o</guid>
      <description>&lt;p&gt;Since May 5, 2025, Microsoft has been refusing mail that cannot prove where it came from. Outlook.com, Hotmail and Live.com, somewhere around half a billion active mailboxes, now bounce unauthenticated messages from high-volume senders with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;550; 5.7.515 Access denied, sending domain does not meet the required authentication level
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google and Yahoo got there first, back in February 2024. That means all three of the inbox providers that matter now check your DNS before they accept your password resets and receipts. I put the full version of this, with every record and a longer troubleshooting section, on &lt;a href="https://devtoollab.com/blog/email-authentication-developers-guide" rel="noopener noreferrer"&gt;DevToolLab&lt;/a&gt;. Here is the short version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five Records, Two of Them Non-Negotiable
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SPF&lt;/td&gt;
&lt;td&gt;Declares which servers may send as you&lt;/td&gt;
&lt;td&gt;Enforced by all three&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DKIM&lt;/td&gt;
&lt;td&gt;Signs each message so tampering shows&lt;/td&gt;
&lt;td&gt;Enforced by all three&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DMARC&lt;/td&gt;
&lt;td&gt;Binds the two above to your visible From address and sets the penalty&lt;/td&gt;
&lt;td&gt;Enforced by all three&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BIMI&lt;/td&gt;
&lt;td&gt;Puts your logo in the inbox&lt;/td&gt;
&lt;td&gt;Nice to have&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTA-STS&lt;/td&gt;
&lt;td&gt;Demands TLS on inbound delivery&lt;/td&gt;
&lt;td&gt;Nice to have&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Build the first three. Treat the last two as things you add once the first three are boring.&lt;/p&gt;

&lt;h2&gt;
  
  
  SPF: One Line, One Trap
&lt;/h2&gt;

&lt;p&gt;SPF is a TXT record naming the servers permitted to send for your domain. A receiver compares the connecting IP against that list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yourapp.com.  IN  TXT  "v=spf1 include:_spf.resend.com include:sendgrid.net -all"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;End it with &lt;code&gt;-all&lt;/code&gt;, not &lt;code&gt;~all&lt;/code&gt;. The tilde is a soft fail, which politely suggests a receiver might want to consider being suspicious. The hyphen is a hard fail and actually means no.&lt;/p&gt;

&lt;p&gt;Now the trap, because it bites quietly. SPF evaluation is capped at 10 DNS lookups, and each &lt;code&gt;include:&lt;/code&gt; spends one. Add a marketing platform, a help desk, an invoicing tool and a transactional provider and you can cross that line without noticing. Past 10 you get a &lt;code&gt;PermError&lt;/code&gt;, and a &lt;code&gt;PermError&lt;/code&gt; means your genuine mail starts failing authentication with nothing obviously broken. Either flatten the record into literal IP ranges or drop services you no longer send from. Our &lt;a href="https://devtoollab.com/tools/spf-record-checker" rel="noopener noreferrer"&gt;SPF Record Checker&lt;/a&gt; counts the lookups for you, which is faster than reasoning about nested includes by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  DKIM: Signing the Message, Not the Server
&lt;/h2&gt;

&lt;p&gt;SPF vouches for the machine. DKIM vouches for the content. Your provider signs outbound mail with a private key; you publish the public half at a selector subdomain, usually as a CNAME they hand you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resend._domainkey.yourapp.com.  IN  CNAME  resend._domainkey.resend.com.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify by mailing yourself at Gmail and opening Show Original:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication-Results: dkim=pass header.i=@yourapp.com header.s=resend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;dkim=fail&lt;/code&gt; almost always means the key in DNS and the key doing the signing have drifted apart, which is a re-copy from your provider's dashboard rather than a debugging session. Running your own mail server instead? Use 2048-bit RSA. Every major receiver refuses 1024-bit in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  DMARC: The Policy Layer
&lt;/h2&gt;

&lt;p&gt;DMARC does two jobs. It decides what happens when SPF or DKIM fails, and it insists that whichever check passed belongs to the same domain the recipient can actually see in the From line. That second part is what kills spoofing, because a message can pass SPF on an attacker's own domain while displaying yours.&lt;/p&gt;

&lt;p&gt;Resist going straight to enforcement. A &lt;code&gt;p=reject&lt;/code&gt; rolled out blind will silently delete real mail from a service you forgot you owned.&lt;/p&gt;

&lt;p&gt;Month one, watch and learn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_dmarc.yourapp.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@yourapp.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing is blocked. You just start receiving daily XML aggregate reports, which a free parser such as Postmark's DMARC Digests will turn into something a human can read. The job this month is inventory: find every system sending as you and make sure SPF knows about it.&lt;/p&gt;

&lt;p&gt;Month two, start applying pressure to a slice of the traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"v=DMARC1; p=quarantine; pct=10; rua=mailto:dmarc@yourapp.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten percent of failures go to spam. Read the reports, close the gaps, raise &lt;code&gt;pct&lt;/code&gt; toward 100.&lt;/p&gt;

&lt;p&gt;Month three, commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"v=DMARC1; p=reject; rua=mailto:dmarc@yourapp.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failures are discarded and impersonating your domain stops being practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  BIMI: Paying for a Logo
&lt;/h2&gt;

&lt;p&gt;BIMI puts your brand mark beside your messages in Gmail, Yahoo, Apple Mail and Fastmail. The entry fee is DMARC already at quarantine or reject with &lt;code&gt;pct=100&lt;/code&gt;, plus a certificate:&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Verified Mark Certificate&lt;/strong&gt; runs $780 to $1,668 a year and requires a registered trademark, and it is the only route to the blue checkmark in Gmail. A &lt;strong&gt;Common Mark Certificate&lt;/strong&gt; costs around $650 a year, skips the trademark requirement, and Google began honoring them in 2026, though without the checkmark. Your logo has to be SVG conforming to the Tiny PS profile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default._bimi.yourapp.com.  IN  TXT  "v=BIMI1; l=https://yourapp.com/logo.svg; a=https://yourapp.com/vmc.pem"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consumer brand with recognition worth reinforcing? Probably worth it. B2B tool whose users read mail in a client that ignores BIMI anyway? Spend the money on getting to &lt;code&gt;p=reject&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  MTA-STS: HSTS for Mail
&lt;/h2&gt;

&lt;p&gt;MTA-STS instructs sending servers that TLS is mandatory when delivering to you, closing the downgrade attack where someone strips encryption in transit and reads plaintext. It needs a hosted policy file plus a DNS pointer.&lt;/p&gt;

&lt;p&gt;The file lives at &lt;code&gt;https://mta-sts.yourapp.com/.well-known/mta-sts.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: STSv1
mode: testing
mx: mail.yourapp.com
max_age: 86400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Begin in &lt;code&gt;testing&lt;/code&gt;. Move to &lt;code&gt;enforce&lt;/code&gt; after reports show nothing legitimate is being refused.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_mta-sts.yourapp.com.  IN  TXT  "v=STSv1; id=20260601T000000"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bump that &lt;code&gt;id&lt;/code&gt; every time you edit the policy file, otherwise senders keep serving themselves a cached copy. Add TLS-RPT alongside it so failures reach you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_smtp._tls.yourapp.com.  IN  TXT  "v=TLSRPTv1; rua=mailto:tls-reports@yourapp.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Confirming It Actually Works
&lt;/h2&gt;

&lt;p&gt;Mail a Gmail address, open Show Original, and look for three passes on one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spf=pass ... dkim=pass ... dmarc=pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything reading &lt;code&gt;fail&lt;/code&gt; points at exactly which record to revisit. Our &lt;a href="https://devtoollab.com/tools/email-header-analyzer" rel="noopener noreferrer"&gt;Email Header Analyzer&lt;/a&gt; pulls those headers apart and surfaces the pass and fail states along with routing hops and delays, which beats squinting at raw text.&lt;/p&gt;

&lt;p&gt;Then confirm the records are actually live rather than just saved in a control panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig txt yourapp.com +short                      &lt;span class="c"&gt;# SPF&lt;/span&gt;
dig txt _dmarc.yourapp.com +short               &lt;span class="c"&gt;# DMARC&lt;/span&gt;
dig txt resend._domainkey.yourapp.com +short    &lt;span class="c"&gt;# DKIM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Five Failures You Will Probably Hit
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;SPF PermError&lt;/strong&gt; means you crossed 10 lookups. Flatten or prune.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;DKIM mismatch&lt;/strong&gt; means your provider rotated keys and DNS did not follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DMARC failing while SPF passes&lt;/strong&gt; is an alignment problem: the provider is bouncing from something like &lt;code&gt;bounces.yourapp.com&lt;/code&gt; while your From says &lt;code&gt;yourapp.com&lt;/code&gt;. Relax alignment with &lt;code&gt;aspf=r&lt;/code&gt; or line the subdomains up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No BIMI logo in Gmail&lt;/strong&gt; means no VMC or CMC. Yahoo and Apple Mail are more forgiving; Gmail is not.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;MTA-STS 404&lt;/strong&gt; means the policy URL is not reachable. Check the subdomain resolves and the path returns 200.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything In One Block
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SPF
yourapp.com.  IN  TXT  "v=spf1 include:_spf.resend.com -all"

# DKIM
resend._domainkey.yourapp.com.  IN  CNAME  resend._domainkey.resend.com.

# DMARC (begin here, escalate later)
_dmarc.yourapp.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@yourapp.com; adkim=r; aspf=r"

# BIMI (optional)
default._bimi.yourapp.com.  IN  TXT  "v=BIMI1; l=https://yourapp.com/logo.svg; a=https://yourapp.com/vmc.pem"

# MTA-STS
_mta-sts.yourapp.com.  IN  TXT  "v=STSv1; id=20260601T000000"

# TLS-RPT
_smtp._tls.yourapp.com.  IN  TXT  "v=TLSRPTv1; rua=mailto:tls-reports@yourapp.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Order To Do This In
&lt;/h2&gt;

&lt;p&gt;Publish SPF first, since it is a single record and it directly answers the Microsoft requirement. Add DKIM next by following your provider's guide, typically two or three records. Then put DMARC up at &lt;code&gt;p=none&lt;/code&gt; and leave it alone for four weeks while the reports tell you which forgotten system is sending as you. Tighten to quarantine, walk &lt;code&gt;pct&lt;/code&gt; up to 100, then reject, spread over roughly six to eight weeks. MTA-STS goes on once that is stable. BIMI goes last, after reject.&lt;/p&gt;

&lt;p&gt;Total hands-on work is a few hours. The calendar time is almost entirely waiting for DMARC reports to tell you what you did not know you were running. The &lt;a href="https://devtoollab.com/blog/email-authentication-developers-guide" rel="noopener noreferrer"&gt;longer guide&lt;/a&gt; covers the troubleshooting cases in more depth if something refuses to pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/blog/email-authentication-developers-guide" rel="noopener noreferrer"&gt;Original article on DevToolLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmarc.postmarkapp.com/" rel="noopener noreferrer"&gt;Postmark DMARC Digests, a free aggregate report parser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/tools/spf-record-checker" rel="noopener noreferrer"&gt;SPF Record Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/tools/email-header-analyzer" rel="noopener noreferrer"&gt;Email Header Analyzer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>LLM Guard Is Archived. Here Are the Guardrail Tools Still Worth Installing</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Wed, 12 Aug 2026 03:12:27 +0000</pubDate>
      <link>https://dev.to/moksh/llm-guard-is-archived-here-are-the-guardrail-tools-still-worth-installing-1ifc</link>
      <guid>https://dev.to/moksh/llm-guard-is-archived-here-are-the-guardrail-tools-still-worth-installing-1ifc</guid>
      <description>&lt;p&gt;If you go looking for &lt;code&gt;protectai/llm-guard&lt;/code&gt; right now, GitHub greets you with a read-only notice. The repo went into the archive on July 9, 2026, its last commit landed the day before, and the README says outright that neither the code nor the Hugging Face models are being maintained any more. It shipped 15 input scanners and 20 output scanners under MIT, collected 3,200 stars, and it is still sitting near the top of most "best LLM guardrails" roundups you will find today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1c2atodlrd3w3bfq0fdh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1c2atodlrd3w3bfq0fdh.webp" alt="The protectai/llm-guard repository on GitHub showing a banner reading " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Worth checking before it goes into a requirements file. I dug into what replaced it and wrote the full comparison, with every license and price verified against the vendor's own page, over on &lt;a href="https://devtoollab.com/blog/best-llm-guardrails-tools" rel="noopener noreferrer"&gt;DevToolLab&lt;/a&gt;. This is the condensed version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow the Ownership and the Archive Makes Sense
&lt;/h2&gt;

&lt;p&gt;Protect AI maintained LLM Guard, and Palo Alto Networks finished buying Protect AI on July 22, 2025. Look either side of that date and a pattern shows up fast. Check Point picked up Lakera on September 16, 2025, in a deal reported at roughly $300 million. Palo Alto said it intended to buy Portkey on April 30, 2026. OpenAI said it was buying promptfoo on March 9, 2026.&lt;/p&gt;

&lt;p&gt;Inside about a year and a half, nearly every independent vendor in this space turned into a feature of somebody's larger security platform. LLM Guard's capabilities did not vanish, they moved into Palo Alto's commercial AI security module, and the free library stopped getting commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Jobs Wearing One Word
&lt;/h2&gt;

&lt;p&gt;"Guardrails" gets applied to three separate problems, and picking a tool built for the wrong one is where most teams waste a sprint.&lt;/p&gt;

&lt;p&gt;Input rails sit in front of the model and screen what arrives: injection attempts, jailbreaks, off-limits topics, personal data you do not want leaving your network. This is the adversarial surface, because attackers get to choose this text.&lt;/p&gt;

&lt;p&gt;Output rails screen what comes back: toxic content, leaked personal data, claims that do not match your retrieved sources, malformed structure. Different failure mode entirely, since here the model is the thing misbehaving.&lt;/p&gt;

&lt;p&gt;Red teaming is not a filter at all. It is an adversarial test suite you run in CI before shipping. Treating it as a runtime control is how you end up with a clean security report and nothing actually guarding production.&lt;/p&gt;

&lt;p&gt;Expect to need all three. Expect them to be three separate tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Bug Class Stays Open
&lt;/h2&gt;

&lt;p&gt;The 2025 edition of the OWASP Top 10 for LLM Applications lists prompt injection at LLM01, its second consecutive run at the top spot.&lt;/p&gt;

&lt;p&gt;There is a structural reason it does not get patched away. Instructions and data arrive through the same channel with nothing marking which is which, so content can be read as a command. SQL solved this with bound parameters. There is no equivalent primitive for a prompt.&lt;/p&gt;

&lt;p&gt;EchoLeak is the case study worth reading. Tracked as CVE-2025-32711 at CVSS 9.3, found by Aim Security in June 2025, it is documented as the first zero-click prompt injection actually exploited against a shipping LLM product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvnqq4adgyweaowyfvt3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvnqq4adgyweaowyfvt3.webp" alt="The arXiv abstract page for " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The delivery was an email that looked like any other, carrying instructions tucked into an HTML comment or set in white text on white. No click required. When the recipient later asked Microsoft 365 Copilot about something entirely different, retrieval pulled that message into context and the buried text got treated as direction. Getting there meant stacking bypasses: past Microsoft's cross prompt injection classifier, around link redaction using reference-style Markdown, through auto-fetched images, out via a Teams proxy the content security policy already trusted. Microsoft fixed it server-side and saw no exploitation in the wild.&lt;/p&gt;

&lt;p&gt;Note where the payload entered. Not the chat box. A classifier watching user input would never have seen it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint That Beats Any Classifier
&lt;/h2&gt;

&lt;p&gt;Simon Willison named the useful mental model on June 16, 2025: the lethal trifecta. Private data access, untrusted content exposure, external communication. Any two of those together is survivable. All three in one session means whoever controls the untrusted content can read your private data and send it somewhere, without writing a single line of exploit code.&lt;/p&gt;

&lt;p&gt;The reason this beats a detector is that it is deterministic. You can guarantee an architecture; you cannot guarantee a probability. Cut one leg and you have removed the capability rather than lowered the odds, at zero runtime cost. Everything below belongs behind that decision, not instead of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Still Being Maintained
&lt;/h2&gt;

&lt;p&gt;Five options here are genuinely open source and run on hardware you control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NeMo Guardrails&lt;/strong&gt; sits at v0.23.0 as of July 1, 2026, Apache 2.0, around 6,900 stars, commits still landing. Heads up on the URL: &lt;code&gt;NVIDIA/NeMo-Guardrails&lt;/code&gt; now redirects to &lt;code&gt;NVIDIA-NeMo/Guardrails&lt;/code&gt;. It is the most opinionated design of the bunch, splitting enforcement across input, dialog, retrieval, execution and output stages, with policies expressed in a DSL called Colang. Those retrieval rails are the correct place to screen RAG chunks, which is exactly the gap EchoLeak drove through. Costs: Colang is a language you have to learn, and some rail types spend an LLM call per request. If a roundup quotes you a "sub-100ms GPU-accelerated" number for it, be skeptical, since the repo ships no latency benchmarks at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails AI&lt;/strong&gt; reached v0.10.2 on June 4, 2026, Apache 2.0, roughly 7,300 stars. The unit here is a validator, one per risk, which you compose into guards around a call. Good fit for field-level output validation, and easy to introduce one validator at a time. Just read each validator before you deploy it, because several reach out to a model rather than computing locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Presidio&lt;/strong&gt; is at 2.2.364 as of July 22, 2026, MIT, about 10,400 stars, and it only does PII: find it, mask it, anonymize it, across text, images and structured data, entirely offline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1ekktnol4m5oa5l1smj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1ekktnol4m5oa5l1smj.webp" alt="The presidio repository on GitHub under the data-privacy-stack organization, showing 10.4k stars, an MIT license, and a description covering detection, redaction, masking and anonymization of sensitive data across text, images and structured data" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one relocated too, and it went the opposite direction from everything else in this article. &lt;code&gt;microsoft/presidio&lt;/code&gt; now 301s to &lt;code&gt;data-privacy-stack/presidio&lt;/code&gt;, because the project is becoming community-owned under a vendor-neutral org with Microsoft's backing. The license stays MIT and the copyright line now credits Presidio Contributors. The change that will actually break your build: images live at &lt;code&gt;ghcr.io/data-privacy-stack/presidio-*&lt;/code&gt; now, so go grep for hardcoded &lt;code&gt;mcr.microsoft.com&lt;/code&gt; pulls. While the paid end of this market got swallowed by security giants, the best open PII tool escaped one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Llama Prompt Guard 2&lt;/strong&gt; is the classifier you host yourself, labeling prompts benign or malicious across both injection and jailbreak attempts. The 86M build sits on mDeBERTa-base, the 22M build on DeBERTa-xsmall for about 75 percent less latency and compute. Get the licensing right: those base models are MIT, but Meta ships the Prompt Guard weights themselves under the Llama license behind a gated download, which is not the same thing. What I like is the honesty of the model card, which reports .998 AUC on English and 97.5 percent recall at a 1 percent false positive rate, then admits the real-world attack prevention rate is 81.2 percent at 3 percent utility loss. That gap is the most useful number in the document. It also states its limits plainly: 512-token window, no multilingual pretraining in the 22M variant, and an acknowledgment that people will build attacks specifically to defeat it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;promptfoo&lt;/strong&gt; covers red teaming, MIT, about 24,000 stars, commits landing daily. Its red team mode manufactures adversarial prompts across injection, jailbreaks, PII leakage, SSRF, SQL injection, excessive agency and hallucination, then tells you what slipped past. Put it in CI. The OpenAI acquisition means the roadmap is no longer independent, though MIT and a public codebase keep the downside contained.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Managed Options, With Real Numbers
&lt;/h2&gt;

&lt;p&gt;Bedrock Guardrails is refreshingly easy to price because AWS publishes each policy separately, and you only pay for what you switch on. A text unit tops out at 1,000 characters.&lt;/p&gt;

&lt;p&gt;Content filters run $0.15 per 1,000 text units for text and $0.00075 per image. Denied topics are $0.15 per 1,000 units. Sensitive information filters are $0.10 per 1,000 units, or free if you express them as regex. Word filters are free. Contextual grounding checks are $0.10 per 1,000 units. Automated Reasoning checks are $0.17 per 1,000 units per policy.&lt;/p&gt;

&lt;p&gt;Two things jump out. The single cheapest useful control in this whole category is a deny list you write by hand, because word filters and regex PII filters bill nothing. And grounding checks, the direct fix for a RAG pipeline inventing facts, undercut the content filters.&lt;/p&gt;

&lt;p&gt;Do the arithmetic before flipping switches. One million requests a month, an 800-character prompt and a 1,600-character response, works out to three text units per request. Filter both directions and you are billing three million units, so $450. Add denied topics on input only, $150. Add PII filtering on output, $200. You are at $800 a month, which is either budgeted or a surprise depending on whether anyone ran this multiplication.&lt;/p&gt;

&lt;p&gt;Azure prices differently, bundling text moderation, Prompt Shields, groundedness and protected material detection into one rate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wxzypegttorf7m87xvl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wxzypegttorf7m87xvl.webp" alt="The Azure AI Content Safety pricing page for Central US in USD, showing a free web tier of 5,000 text records and 5,000 images per month, and a standard web tier at $0.38 per 1,000 text records and $0.75 per 1,000 images, both covering Text, Prompt Shields, protected material detection and groundedness detection" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The free allowance is 5,000 text records and 5,000 images monthly, and it halts rather than silently billing you. Standard is $0.38 per 1,000 text records and $0.75 per 1,000 images, checked in Central US with USD selected. A record covers up to 1,000 Unicode code points.&lt;/p&gt;

&lt;p&gt;Which is cheaper genuinely depends on your policy count, since Azure's $0.38 already includes what Bedrock charges $0.15 plus $0.10 plus $0.10 for. One or two policies favor Bedrock. All of them can favor Azure. Rates vary by region, so check your own.&lt;/p&gt;

&lt;p&gt;On the commercial side, Lakera Guard remains the best-known dedicated prompt firewall, now inside Check Point following the September 2025 deal, so ask how it is packaged before assuming old pricing survived. Protect AI's Guardian and Recon plus its red teaming layer now live in Palo Alto's Prisma AIRS, which is where LLM Guard's functionality went commercially.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side by Side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Self-hosted&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NeMo Guardrails&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;Input, dialog, retrieval, output&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Active, v0.23.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guardrails AI&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;Input and output validators&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Active, v0.10.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Presidio&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;PII detection and redaction&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Active, 2.2.364&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama Prompt Guard 2&lt;/td&gt;
&lt;td&gt;Llama license, MIT base&lt;/td&gt;
&lt;td&gt;Injection and jailbreak classifier&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;promptfoo&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Red teaming in CI&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Active, OpenAI acquiring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bedrock Guardrails&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;Input and output&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;From $0.10 per 1k text units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Prompt Shields&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;Input and output&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;$0.38 per 1k text records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lakera Guard&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;Runtime prompt firewall&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Check Point, post-acquisition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM Guard&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Input and output scanners&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Archived July 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Defaults Will Corrupt Your Redactions
&lt;/h2&gt;

&lt;p&gt;PII stripping has the least ambiguous spec of any guardrail, so it is the one I actually measured rather than trusted. Everyone's first version is a pile of regular expressions. Here is Presidio 2.2.364 with spaCy 3.8.15 on a synthetic support ticket, straight out of the box:&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;presidio_analyzer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AnalyzerEngine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;presidio_analyzer.nlp_engine&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NlpEngineProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;presidio_anonymizer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AnonymizerEngine&lt;/span&gt;

&lt;span class="n"&gt;TICKET&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;Hi, this is Marcus Delgado from Cleveland. My order never arrived. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You can reach me at marcus.delgado@northgate-supply.com or (216) 555-0142. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I paid with card 4111 1111 1111 1111 and my SSN on file is 401-55-9302. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The request came from 198.51.100.24 if that helps.&lt;/span&gt;&lt;span class="sh"&gt;"&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="nc"&gt;NlpEngineProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nlp_configuration&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;nlp_engine_name&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;spacy&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;models&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;lang_code&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;en&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_name&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;en_core_web_sm&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;analyzer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AnalyzerEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nlp_engine&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="nf"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;supported_languages&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;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;anonymizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AnonymizerEngine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&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="n"&gt;TICKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;anonymizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;anonymize&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="n"&gt;TICKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analyzer_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A regex pass catches the five structured identifiers: email, phone, SSN, card, IP. Presidio catches all five plus &lt;code&gt;PERSON&lt;/code&gt; for "Marcus Delgado" and &lt;code&gt;LOCATION&lt;/code&gt; for "Cleveland", which is the whole reason to run a real engine, because no pattern will ever recognize a name.&lt;/p&gt;

&lt;p&gt;It also returns things you never asked for. &lt;code&gt;marcus.de&lt;/code&gt; came back as a URL at 0.5 confidence, since &lt;code&gt;.de&lt;/code&gt; is a live TLD hiding inside the email address. The digits &lt;code&gt;1111&lt;/code&gt; were classified &lt;code&gt;DATE_TIME&lt;/code&gt; at 0.85. The literal token "SSN" was tagged &lt;code&gt;ORGANIZATION&lt;/code&gt; at 0.85. That last one is not cosmetic, because it wrecks the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hi, this is &amp;lt;PERSON&amp;gt; from &amp;lt;LOCATION&amp;gt;. My order never arrived. You can reach me
at &amp;lt;EMAIL_ADDRESS&amp;gt; or &amp;lt;PHONE_NUMBER&amp;gt;. I paid with card &amp;lt;CREDIT_CARD&amp;gt; and my
&amp;lt;ORGANIZATION&amp;gt; on file is &amp;lt;US_SSN&amp;gt;. The request came from &amp;lt;IP_ADDRESS&amp;gt; if that helps.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"My &lt;code&gt;&amp;lt;ORGANIZATION&amp;gt;&lt;/code&gt; on file" is a bug your customers would read. Two arguments fix it, by naming the entities you actually want and setting a confidence floor:&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="n"&gt;ENTITIES&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;PERSON&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;LOCATION&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;EMAIL_ADDRESS&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;PHONE_NUMBER&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;CREDIT_CARD&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;US_SSN&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;IP_ADDRESS&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;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&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="n"&gt;TICKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ENTITIES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven findings, all correct, clean text. Two caveats: I ran &lt;code&gt;en_core_web_sm&lt;/code&gt; to keep the download small when Presidio really wants &lt;code&gt;en_core_web_lg&lt;/code&gt;, and &lt;code&gt;PHONE_NUMBER&lt;/code&gt; scored exactly 0.4, right on the line, which is your cue to tune that floor on your own data instead of copying mine. The &lt;a href="https://devtoollab.com/blog/best-llm-guardrails-tools" rel="noopener noreferrer"&gt;full write-up&lt;/a&gt; has both runs side by side with every score.&lt;/p&gt;

&lt;p&gt;The transferable lesson: any guardrail left on defaults with no threshold will produce false positives, and in a redaction path a false positive is a shipped bug, not a security win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking One
&lt;/h2&gt;

&lt;p&gt;Conversational app with topic rules, go NeMo Guardrails and use the retrieval rails. Field-level output validation, Guardrails AI, one validator at a time. Hard requirement that personal data never leaves your network, self-hosted Presidio with an explicit entity list. Want your own injection classifier, Llama Prompt Guard 2 at whichever size fits your latency budget, with the license reviewed by whoever reviews licenses. Already on Bedrock, turn on the free word and regex filters today and add paid policies only after measuring. No adversarial tests at all, start with promptfoo in CI, because knowing which attacks land beats a filter you cannot evaluate. And if LLM Guard is already in your requirements file, Guardrails AI plus Presidio covers most of what you were using it for.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Rollout Order That Works
&lt;/h2&gt;

&lt;p&gt;Start by auditing for the lethal trifecta, since removing one leg outperforms every tool listed here and costs nothing at runtime. Then write down every route text takes into your context window, not just the prompt field, remembering that EchoLeak came in through retrieved mail.&lt;/p&gt;

&lt;p&gt;Normalize before inspecting, because payloads hide inside zero-width characters and get base64-wrapped to slip past literal matching. The &lt;a href="https://devtoollab.com/tools/invisible-character-remover" rel="noopener noreferrer"&gt;Invisible Character Remover&lt;/a&gt; strips the hidden ones and the &lt;a href="https://devtoollab.com/tools/base64-encoder-decoder" rel="noopener noreferrer"&gt;Base64 Encoder Decoder&lt;/a&gt; shows you what the model will really see. Test every deny-list pattern against adversarial input in a &lt;a href="https://devtoollab.com/tools/regex-tester" rel="noopener noreferrer"&gt;Regex Tester&lt;/a&gt; before it touches a request path, because a bad pattern either blocks real users or waves everything through.&lt;/p&gt;

&lt;p&gt;After that, add PII redaction with an explicit entity list rather than defaults, get promptfoo running in CI so a system-prompt regression breaks a build, and log every block alongside the input that caused it. A guardrail you cannot audit is a guardrail you cannot tune.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Leaves You
&lt;/h2&gt;

&lt;p&gt;Eighteen months of consolidation: Lakera to Check Point, Protect AI and Portkey to Palo Alto, promptfoo to OpenAI, LLM Guard to the archive. Check a repo's own status page before you trust any comparison article, this one included.&lt;/p&gt;

&lt;p&gt;The open layer is still in good shape. Two Apache 2.0 frameworks under active development, an MIT PII engine that just gained community ownership, and a self-hosted classifier whose own documentation publishes the distance between its benchmark score and its real-world hit rate.&lt;/p&gt;

&lt;p&gt;None of it closes prompt injection, because instructions and data still share a channel. Presidio broke its own output on default settings in the test above, and Meta's numbers put roughly a fifth of genuine attacks through a well-trained classifier. Spend your first hour on architecture and on which leg of the trifecta you can amputate. Treat the rest as depth behind that call.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/blog/best-llm-guardrails-tools" rel="noopener noreferrer"&gt;Original article on DevToolLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/assets/PDF/OWASP-Top-10-for-LLMs-v2025.pdf" rel="noopener noreferrer"&gt;OWASP Top 10 for LLM Applications 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2509.10540" rel="noopener noreferrer"&gt;EchoLeak: The First Real-World Zero-Click Prompt Injection Exploit in a Production LLM System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/" rel="noopener noreferrer"&gt;The lethal trifecta for AI agents, Simon Willison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/NVIDIA-NeMo/Guardrails" rel="noopener noreferrer"&gt;NeMo Guardrails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/guardrails-ai/guardrails" rel="noopener noreferrer"&gt;Guardrails AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/data-privacy-stack/presidio" rel="noopener noreferrer"&gt;Presidio&lt;/a&gt; and its &lt;a href="https://presidio.dataprivacystack.org/project_transition/" rel="noopener noreferrer"&gt;transition to community ownership&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/86M/MODEL_CARD.md" rel="noopener noreferrer"&gt;Llama Prompt Guard 2 model card&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/promptfoo/promptfoo" rel="noopener noreferrer"&gt;promptfoo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/bedrock/pricing/" rel="noopener noreferrer"&gt;Amazon Bedrock pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/pricing/details/content-safety/" rel="noopener noreferrer"&gt;Azure AI Content Safety pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.prnewswire.com/news-releases/palo-alto-networks-completes-acquisition-of-protect-ai-302510757.html" rel="noopener noreferrer"&gt;Palo Alto Networks completes acquisition of Protect AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.checkpoint.com/press-releases/check-point-acquires-lakera-to-deliver-end-to-end-ai-security-for-enterprises/" rel="noopener noreferrer"&gt;Check Point acquires Lakera&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.paloaltonetworks.com/company/press/2026/palo-alto-networks-to-acquire-portkey-to-secure-the-rise-of-ai-agents" rel="noopener noreferrer"&gt;Palo Alto Networks to acquire Portkey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Faker, SDV, MOSTLY AI or NeMo: Picking a Synthetic Data Tool in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:42:34 +0000</pubDate>
      <link>https://dev.to/moksh/faker-sdv-mostly-ai-or-nemo-picking-a-synthetic-data-tool-in-2026-55ne</link>
      <guid>https://dev.to/moksh/faker-sdv-mostly-ai-or-nemo-picking-a-synthetic-data-tool-in-2026-55ne</guid>
      <description>&lt;p&gt;Search for &lt;code&gt;gretel.ai&lt;/code&gt; today and you get redirected to nvidia.com. NVIDIA acquired Gretel in March 2025 for reportedly north of its $320 million valuation and relaunched it as NeMo Data Designer under Apache 2.0. That single redirect captures the last eighteen months in this space pretty well: the venture-backed vendors got bought or repriced, and the open-source tooling got noticeably better.&lt;/p&gt;

&lt;p&gt;The change that actually affects a decision you're making this week is quieter. SDV, the most widely cited open-source synthesizer, left the MIT license behind for the Business Source License. I wrote up the full comparison, with every license and price checked against the vendor's own page in August 2026, on &lt;a href="https://devtoollab.com/blog/best-synthetic-data-generation-tools" rel="noopener noreferrer"&gt;DevToolLab&lt;/a&gt; - this is the condensed version, plus the local test I'd run before trusting any of these tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three different jobs share one name
&lt;/h2&gt;

&lt;p&gt;"Synthetic data" gets used for three distinct problems, and most of the bad tool picks in this space come from solving for one while actually needing another.&lt;/p&gt;

&lt;p&gt;Mock data fabricates rows with no connection to anything real - names, emails, cities. Good for seed scripts and test fixtures. Statistical synthesis trains on your actual table and generates new rows that preserve the distributions and correlations between columns, which matters the moment something downstream learns from the output. De-identification takes real production data and scrubs it so individuals can't be re-identified while keeping row-level structure - a compliance problem, not a data-generation one.&lt;/p&gt;

&lt;p&gt;The gap between the first two is the expensive one, and it's what the test near the end of this post is built to expose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The open-source landscape, quickly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Faker&lt;/strong&gt; is MIT, ubiquitous, and exactly as good as its job description: independent, plausible-looking fields. Seed it and you get byte-identical output across runs, which is genuinely useful for CI fixtures:&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;faker&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Faker&lt;/span&gt;

&lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Faker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en_US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt;&lt;span class="p"&gt;()&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One gotcha worth knowing: the seed locks the entire random stream, not each field separately. Add one &lt;code&gt;fake.*&lt;/code&gt; call anywhere earlier in the script and every value after it shifts. Pin the script alongside the seed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SDV&lt;/strong&gt; (Synthetic Data Vault) is still the deepest open synthesizer - relational tables with foreign keys, sequential data, built-in quality evaluation - but it's Business Source License now, not MIT, the same license family Terraform and Vault moved to. My &lt;a href="https://devtoollab.com/blog/best-infrastructure-as-code-tools" rel="noopener noreferrer"&gt;infrastructure as code piece&lt;/a&gt; ran into the identical question. BUSL allows plenty, but check the terms yourself rather than trusting a roundup that still calls it "MIT licensed" - plenty do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MOSTLY AI's SDK&lt;/strong&gt; is Apache 2.0 and local-first by design: training and generation run on your own machine by default, no cloud account required. If SDV's license is a blocker, this is the first thing to try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NVIDIA NeMo Data Designer&lt;/strong&gt; - what Gretel became - targets generating training data for models rather than cloning a production table. Dependency-aware fields, Python/SQL validators, LLM-as-judge scoring on the output, and a preview mode before you generate at scale. Apache 2.0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3edi3bu784s0jr8d3nto.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3edi3bu784s0jr8d3nto.webp" alt="The mostly-ai/mostlyai repository on GitHub, an Apache 2.0 licensed Synthetic Data SDK that trains and generates locally" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The commercial tier, in one sentence each
&lt;/h2&gt;

&lt;p&gt;Tonic.ai splits into three products worth telling apart: Fabricate (mock generation, the only one with public self-serve pricing - free tier, then $29/month), Structural (de-identification against real databases, quote-based), and Textual (unstructured redaction, billed per word count). The pattern holds across the category - K2view, Syntho, YData are all quote-based enterprise deals. If you need a signed DPA and a live database connector, you're in procurement, not a credit card checkout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fti4mrxbis8f2grqdg21c.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fti4mrxbis8f2grqdg21c.webp" alt="The Tonic.ai pricing page showing Fabricate free and Plus at $29 per month, with Structural and Textual priced by quote" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The test that actually separates these tools
&lt;/h2&gt;

&lt;p&gt;Averages lie here. Generate a real dataset with a deliberate relationship - salary rising with years of experience - then compare it against columns generated independently versus columns run through an actual synthesizer (SDV's GaussianCopulaSynthesizer, in my test):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;real data              corr=+0.988   mean_salary=$119,977   mean_years=20.3
independent columns    corr=-0.061   mean_salary=$123,101   mean_years=20.4
SDV synthetic          corr=+0.880   mean_salary=$118,319   mean_years=21.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every mean is within a few percent across all three. Any "does this look reasonable" check passes all of them. But the correlation between experience and pay goes from +0.988 to essentially zero once the columns are generated independently - the relationship isn't weakened, it's erased. A model trained on that data learns that experience has no bearing on pay. SDV holds the correlation at +0.880, and crucially, zero of its generated rows matched a real row in the source table.&lt;/p&gt;

&lt;p&gt;Run this on your own data, with your own columns, before picking a tool based on a vendor's demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking one
&lt;/h2&gt;

&lt;p&gt;Dev database or test fixtures: Faker, seeded, nothing else needed. Training or evaluating on data you can't touch directly: MOSTLY AI if you need a permissive license, SDV if you want the deeper feature set and BUSL works for you. Generating eval or instruction datasets for LLMs: NeMo Data Designer with preview mode and judge scoring turned on. Real production database plus compliance sign-off: a commercial platform on a quote, budgeted as procurement.&lt;/p&gt;

&lt;p&gt;For quick one-off rows without installing anything, DevToolLab's &lt;a href="https://devtoollab.com/tools/fake-data-generator" rel="noopener noreferrer"&gt;Fake Data Generator&lt;/a&gt; runs in the browser, and the &lt;a href="https://devtoollab.com/tools/sql-insert-generator" rel="noopener noreferrer"&gt;SQL Insert Generator&lt;/a&gt; turns the output into statements you can paste into a seed script.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devtoollab.com/blog/best-synthetic-data-generation-tools" rel="noopener noreferrer"&gt;Original article on DevToolLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://faker.readthedocs.io/en/master/" rel="noopener noreferrer"&gt;Faker documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sdv-dev/SDV" rel="noopener noreferrer"&gt;SDV on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mostly-ai/mostlyai" rel="noopener noreferrer"&gt;MOSTLY AI SDK on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/NVIDIA-NeMo/Data-Designer" rel="noopener noreferrer"&gt;NVIDIA NeMo Data Designer on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tonic.ai/pricing" rel="noopener noreferrer"&gt;Tonic.ai pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>OpenAI, Voyage, Cohere or Open Weights: Picking an Embedding Model in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Sun, 09 Aug 2026 12:23:25 +0000</pubDate>
      <link>https://dev.to/moksh/openai-voyage-cohere-or-open-weights-picking-an-embedding-model-in-2026-245a</link>
      <guid>https://dev.to/moksh/openai-voyage-cohere-or-open-weights-picking-an-embedding-model-in-2026-245a</guid>
      <description>&lt;p&gt;An embedding model does one thing: it turns text into a list of numbers so that similar meaning ends up close together in that number space. Every product in this post is a variation on that idea, and 2026 rearranged the lineup more than any year since embeddings went mainstream. Voyage AI shipped a 4-series after MongoDB's acquisition closed, Google shipped both a multimodal Gemini Embedding 2 and a 308-million-parameter on-device model, and open weights stopped being the budget option and started beating the paid APIs on public benchmarks.&lt;/p&gt;

&lt;p&gt;I wrote a longer version of this comparison on DevToolLab, &lt;a href="https://devtoollab.com/blog/best-embedding-models-apis" rel="noopener noreferrer"&gt;Best Embedding Models and APIs in 2026&lt;/a&gt;, with the full pricing table and every source linked. Here is the short version, including the one experiment worth running yourself before you trust any of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Shapes, Not Ten Products
&lt;/h2&gt;

&lt;p&gt;Every option here is one of two things: a hosted API you call over HTTP and pay per token, or open weights you download and run yourself for free. Three properties separate the options inside each shape: dimension count (which drives your storage and search cost), context window (how much text one call can see), and whether Matryoshka Representation Learning is baked in.&lt;/p&gt;

&lt;p&gt;A Matryoshka-trained model packs the most useful signal into the first dimensions of its output, so truncating a 3,072-dimension vector down to 256 keeps most of the retrieval quality while cutting storage 12x. OpenAI's text-embedding-3 models, Voyage's 4-series, and Qwen3-Embedding all do this natively through a &lt;code&gt;dimensions&lt;/code&gt; parameter. A model that was not trained this way degrades faster when you truncate it, which the experiment near the end of this post shows directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hosted APIs
&lt;/h2&gt;

&lt;p&gt;OpenAI's text-embedding-3-small and text-embedding-3-large have not moved in price since January 2024: $0.02 and $0.13 per million tokens, batch pricing cutting both in half. It remains the safe default: cheapest at the small tier, an 8,191-token context that covers most chunking strategies, and the widest library ecosystem.&lt;/p&gt;

&lt;p&gt;Voyage AI became part of MongoDB in a $220 million deal in February 2025 and kept shipping independently. voyage-4-large is $0.12 per million tokens with a 32,000-token context, four times OpenAI's window, and every model in the 4-series shares the same Matryoshka range (1,024 default, up to 2,048), so the cheaper voyage-4-lite is a price cut rather than a smaller vector.&lt;/p&gt;

&lt;p&gt;Cohere's Embed v4 is the long-context, multimodal specialist: 128,000 tokens of context against OpenAI's 8,191, at $0.12 per million text tokens and $0.47 per million image tokens, handling text, images, and mixed documents like PDFs in one model. Gemini Embedding 2 goes further still, natively embedding text, image, audio, video, and PDF into the same vector space, priced per modality from $0.20 per million text tokens up to $12.00 per million video tokens. Text-only it is the most expensive API here; its case is that nothing else lets you compare a video frame against a text query at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8egwm3x41e16xtkobam8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8egwm3x41e16xtkobam8.webp" alt="The Voyage AI pricing documentation showing the 4-series embedding models each with 200 million free tokens" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Weights Stopped Being the Compromise
&lt;/h2&gt;

&lt;p&gt;Qwen3-Embedding is genuinely Apache 2.0, not the more restrictive license some aggregator sites report, and its model card states it ranked first on the MTEB multilingual leaderboard as of June 2025. It ships in 0.6B, 4B, and 8B sizes with a 32,000-token context and Matryoshka output from 32 to 4,096 dimensions; the 0.6B variant runs on CPU and is still competitive, which makes it the model to try first if you want to self-host.&lt;/p&gt;

&lt;p&gt;BGE-M3, MIT licensed, does something none of the hosted APIs do: dense, sparse, and multi-vector ColBERT-style output from one pass, so hybrid semantic-plus-keyword search does not need two separate models. Most self-hosted production RAG stacks default to it for exactly that reason.&lt;/p&gt;

&lt;p&gt;The model most people miss is Google's EmbeddingGemma, released in September 2025, built on Gemma 3, and aimed at a use case none of the above cover: running entirely offline on a phone or laptop with no API call and no GPU. Google's own numbers: under 200MB of RAM with quantization, embeddings generated in under 22 milliseconds on an EdgeTPU, a 2,048-token context, and Matryoshka output from 768 down to 128. The one thing to get right is the license: it ships under Google's Gemma Terms of Use, not Apache or MIT, so it permits commercial use but is not the drop-in dependency the other three are. Nomic Embed v2 sits next to it as the more permissively licensed alternative at a similar size, trading context length (512 tokens) for Apache 2.0 with no usage restrictions at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa5xrfsohm5z8fz2y2s2s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa5xrfsohm5z8fz2y2s2s.webp" alt="The official Google AI for Developers page for EmbeddingGemma, a 308M parameter embedding model built for on-device use under 200MB of RAM" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Cosine Similarity Actually Measures, Run Locally
&lt;/h2&gt;

&lt;p&gt;Every model above outputs vectors compared the same way. Here is the whole thing, run with &lt;code&gt;sentence-transformers&lt;/code&gt; on a laptop CPU, no API key required:&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;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentence-transformers/all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sentences&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;The cat sat on the mat.&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;A feline rested on the rug.&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;The stock market fell sharply today.&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;emb&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;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normalize_embeddings&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;def&lt;/span&gt; &lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&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;sim(cat/feline)  = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emb&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="n"&gt;emb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&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="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;sim(cat/stock)   = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emb&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="n"&gt;emb&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="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sim(cat/feline)  = 0.5560
sim(cat/stock)   = 0.0747
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two sentences about a cat sitting somewhere score seven times higher on similarity than one about a cat and one about the stock market, despite sharing no words beyond "The". That gap over a plain keyword match is the entire value of everything in this post; a tool like &lt;a href="https://devtoollab.com/tools/word-frequency-counter" rel="noopener noreferrer"&gt;DevToolLab's Word Frequency Counter&lt;/a&gt; would find these two cat sentences almost entirely dissimilar by word overlap, while the embedding correctly reads them as close in meaning.&lt;/p&gt;

&lt;p&gt;Truncating that same 384-dimension output down to 128 and 64 dimensions and re-normalizing shows what an untrained-for-Matryoshka model does under pressure: cat/feline stays clearly higher than cat/stock at every size, but the gap narrows fast, from 0.556 versus 0.075 at full size to 0.601 versus 0.229 at 64 dimensions. A model actually trained for Matryoshka degrades far more gracefully, which is the entire point of training for it rather than truncating an arbitrary model and hoping. The &lt;a href="https://devtoollab.com/blog/best-embedding-models-apis" rel="noopener noreferrer"&gt;full writeup&lt;/a&gt; has the complete truncation table and the reasoning behind every pricing figure in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Pick, Briefly
&lt;/h2&gt;

&lt;p&gt;No strong constraints: text-embedding-3-small. Need more than 8,191 tokens of context: Cohere Embed v4 at 128,000, or Voyage at 32,000 for less money. Corpus includes real images or video: Cohere for text-plus-image, Gemini Embedding 2 if audio or video need to share the same vector space. Self-hosting with the fewest restrictions: Qwen3-Embedding. Hybrid dense-plus-keyword from one model: BGE-M3. Running on-device with no server at all: EmbeddingGemma, with Nomic Embed v2 as the fallback if the Gemma license is a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/best-embedding-models-apis" rel="noopener noreferrer"&gt;Best Embedding Models and APIs in 2026&lt;/a&gt; - the original, with the full comparison table and every price verified&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.openai.com/docs/guides/embeddings" rel="noopener noreferrer"&gt;OpenAI embeddings guide&lt;/a&gt;, &lt;a href="https://docs.voyageai.com/docs/pricing" rel="noopener noreferrer"&gt;Voyage AI pricing&lt;/a&gt;, &lt;a href="https://docs.cohere.com/docs/embed-2" rel="noopener noreferrer"&gt;Cohere Embed v4 docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini API pricing&lt;/a&gt;, &lt;a href="https://ai.google.dev/gemma/docs/embeddinggemma" rel="noopener noreferrer"&gt;EmbeddingGemma overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/Qwen/Qwen3-Embedding-8B" rel="noopener noreferrer"&gt;Qwen3-Embedding-8B model card&lt;/a&gt;, &lt;a href="https://huggingface.co/BAAI/bge-m3" rel="noopener noreferrer"&gt;BGE-M3 model card&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/best-vector-databases" rel="noopener noreferrer"&gt;Best Vector Databases in 2026&lt;/a&gt; - where these embeddings actually get stored and searched&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Terraform Is an IBM Product With a Meter Now. Here Is What the IaC Landscape Looks Like</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Sun, 09 Aug 2026 07:03:17 +0000</pubDate>
      <link>https://dev.to/moksh/terraform-is-an-ibm-product-with-a-meter-now-here-is-what-the-iac-landscape-looks-like-4fof</link>
      <guid>https://dev.to/moksh/terraform-is-an-ibm-product-with-a-meter-now-here-is-what-the-iac-landscape-looks-like-4fof</guid>
      <description>&lt;p&gt;Try opening a state file written by OpenTofu with encryption turned on using Terraform 1.5.7. You get &lt;code&gt;Unsupported state file format&lt;/code&gt;. That single error is a better description of where infrastructure as code sits in 2026 than any licensing debate, because the fork is no longer about which license you prefer. It is about a file one of your tools cannot read.&lt;/p&gt;

&lt;p&gt;Two things happened that most teams only noticed on an invoice. HashiCorp archived the Cloud Development Kit for Terraform on 10 December 2025, read-only, with the recommendation to go back to HCL. Then on 31 March 2026 the legacy HCP Terraform free plan reached end of life and every organization still on it landed on resource metering, in a migration you cannot reverse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlcx0fxw1m1kt7d6tnlx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlcx0fxw1m1kt7d6tnlx.webp" alt="The HashiCorp Developer page for CDK for Terraform, carrying a deprecation announcement stating the Cloud Development Kit for Terraform is deprecated as of December 10, 2025 and no longer supported or maintained" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://devtoollab.com/blog/best-infrastructure-as-code-tools" rel="noopener noreferrer"&gt;longer version of this comparison lives on DevToolLab&lt;/a&gt;, with every price sourced from the vendor's own page and the full ten-tool table. This is the short version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop Treating It as One Choice
&lt;/h2&gt;

&lt;p&gt;Three decisions hide inside "which IaC tool", and they are independent. The engine parses your config and calls cloud APIs. The runner executes it with credentials and leaves an audit trail. The state layer holds the mapping from config to real resource IDs, and decides whether that mapping is encrypted.&lt;/p&gt;

&lt;p&gt;Most cost surprises come from conflating the first and third. You can run OpenTofu as the engine, GitHub Actions as the runner and an encrypted bucket as state, and pay nobody anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform: Still the Default, Still Shipping
&lt;/h2&gt;

&lt;p&gt;IBM closed its $6.4 billion HashiCorp acquisition on 27 February 2025, and Terraform has been under the Business Source License 1.1 since August 2023, with 1.5.7 the last MPL release. Feature work has not stalled. Version 1.14 introduced list resources in &lt;code&gt;.tfquery.hcl&lt;/code&gt; files with a &lt;code&gt;terraform query&lt;/code&gt; command that discovers unmanaged resources and writes import blocks for them. Version 1.15 arrived on 29 April 2026 with dynamic module sources and a &lt;code&gt;deprecated&lt;/code&gt; marker for variables and outputs, and Stacks went generally available as a &lt;code&gt;terraform stacks&lt;/code&gt; subcommand. Current stable is 1.15.8.&lt;/p&gt;

&lt;p&gt;The part worth your attention if you use coding agents is the official Terraform MCP server, generally available since 13 June 2026 and open source. It exposes the registry and your workspaces over Model Context Protocol, which means an agent can read the real inputs of your private modules instead of guessing at resource arguments.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenTofu Went Somewhere Different
&lt;/h2&gt;

&lt;p&gt;OpenTofu forked from Terraform 1.5, kept MPL 2.0, lives under the Linux Foundation and became a CNCF sandbox project on 23 April 2025. Harness, Gruntwork, Spacelift, env0 and Scalr committed nineteen full-time engineers for five years, which is why the pace held up rather than trailing off after the initial news cycle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwhhrvoa4zebjpgkve1bn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwhhrvoa4zebjpgkve1bn.webp" alt="The OpenTofu homepage announcing the 1.12.0 release and describing OpenTofu as open source infrastructure as code under Linux Foundation stewardship, a drop-in replacement for Terraform, with over 3,900 providers and 23,600 modules" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Early variable and locals evaluation shipped in 1.8, roughly twenty months ahead of Terraform's dynamic module sources. Version 1.11 brought ephemeral values that never touch state or plan files and an &lt;code&gt;enabled&lt;/code&gt; meta-argument that retires the &lt;code&gt;count = var.x ? 1 : 0&lt;/code&gt; trick. Version 1.12.0, on 14 May 2026, added dynamic &lt;code&gt;prevent_destroy&lt;/code&gt;, import by resource identity and &lt;code&gt;destroy = false&lt;/code&gt; for removing a resource from state while leaving the real object alone.&lt;/p&gt;

&lt;p&gt;State encryption is the feature with no counterpart. Configure it through an environment variable, so the passphrase stays out of your repository:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TF_ENCRYPTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'key_provider "pbkdf2" "demo" {
  passphrase = "correct-horse-battery-staple-42"
}
method "aes_gcm" "secure" {
  keys = key_provider.pbkdf2.demo
}
state { method = method.aes_gcm.secure }'&lt;/span&gt;
tofu init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; tofu apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applied against a config containing a &lt;code&gt;random_password&lt;/code&gt; resource on OpenTofu 1.12.5, the resulting state file gives up nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ grep -c 'random_password' terraform.tfstate
0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The readable part of the file is a PBKDF2 header at 600,000 iterations. Everything else is ciphertext. Change the passphrase and the next command reports &lt;code&gt;decryption failed for all provided methods&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pulumi Bet on Agents
&lt;/h2&gt;

&lt;p&gt;Pulumi is the choice for teams who want TypeScript, Python, Go or C# instead of HCL, and its 2026 pitch is agentic. Co-founder Joe Duffy said in May that language models now carry out more than 20 percent of deployments on the platform, up from effectively zero twelve months earlier, and expects that past 50 percent within the year. The same release added a Neo CLI, GitHub and Slack apps, scheduled agent tasks that arrive as pull requests, and a &lt;code&gt;pulumi do&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm6t6x34fu7bh3hltilig.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm6t6x34fu7bh3hltilig.webp" alt="The Pulumi Neo product page presenting Neo as an AI infrastructure agent that plans, reviews and executes cloud changes" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Billing is per resource per month: free for one user, $40 a month on Team covering up to 500 resources and then $0.1825 each, $400 a month on Enterprise. Neo tokens are $3 per million. The engine and CLI are Apache 2.0, so self-hosted state without Pulumi Cloud is a legitimate path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crossplane for Platform Teams
&lt;/h2&gt;

&lt;p&gt;Crossplane reframes the problem: cloud resources become Kubernetes objects a controller reconciles continuously, so correcting drift is the default rather than a cron job. It graduated in CNCF on 28 October 2025, and v2 made composite and managed resources namespaced, dropped the claim indirection, and added Operations that run function pipelines for jobs like certificate rotation.&lt;/p&gt;

&lt;p&gt;It earns its keep when other teams consume your platform through Kubernetes APIs. For one VPC and a handful of services, you have just adopted a control plane to operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Actually Holds Your State
&lt;/h2&gt;

&lt;p&gt;This is the decision teams make by accident rather than on purpose. There are really only three options: a bucket you own with encryption turned on, a vendor's hosted state bundled into a platform plan, or a self-hosted backend like OpenTaco's that speaks the same protocol as the commercial ones so nothing downstream has to change if you move.&lt;/p&gt;

&lt;p&gt;Two things are worth knowing before you pick. State holds credentials in plain text unless something encrypts it, which is the entire reason OpenTofu's &lt;code&gt;TF_ENCRYPTION&lt;/code&gt; above is worth setting up on day one rather than after an incident. And encryption only goes one way between engines: once OpenTofu encrypts a state file, Terraform cannot read it, so decrypt before any trip back. If you want the fuller picture on where those credentials belong in the first place, &lt;a href="https://devtoollab.com/blog/best-secrets-management-tools" rel="noopener noreferrer"&gt;DevToolLab's secrets management comparison&lt;/a&gt; covers Vault, OpenBao and the SaaS options this post does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Free Runner Layer Is Good Now
&lt;/h2&gt;

&lt;p&gt;Terragrunt reached 1.0 on 30 March 2026 with a backwards compatibility guarantee across CLI flags, HCL config and serialized output, which matters a lot for something wired into everyone's CI. MIT licensed, around 9,800 stars.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F189mkqflx1lip58dedx4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F189mkqflx1lip58dedx4.webp" alt="The Terragrunt homepage from Gruntwork, describing Terragrunt as a flexible orchestration tool for OpenTofu and Terraform at scale" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Atlantis is still the reference self-hosted pattern, posting plans to the pull request and applying on a comment so credentials never leave your infrastructure. Digger became OpenTaco in November 2025 and now ships a self-hosted state backend with access control, history and rollback behind an HCP Terraform compatible interface. An open source state backend speaking the commercial protocol is the sharpest answer yet to per-resource billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Meters Are Not Comparable
&lt;/h2&gt;

&lt;p&gt;HCP Terraform charges per managed resource per month: $0.10 on Essentials, $0.47 on Standard, $0.99 on Premium, free up to 500 resources with unlimited users and a surprisingly complete feature set including SSO and policy as code. Spacelift covers the most engines and keeps a two-user free tier, but its entry paid plan is now $20,000 a year on annual commitment. Scalr charges per run instead: 50 free monthly, then $0.99 each, with no per-user or per-resource fee. env0, trading as env zero, gives 250 runs and 30 environments free.&lt;/p&gt;

&lt;p&gt;At 1,000 managed resources that works out to $100 a month on Essentials, $470 on Standard, roughly $131 on Pulumi Team, and $99 on Scalr at 100 runs regardless of resource count. Model your own estate before signing anything, because per-resource billing penalizes stable, well-factored infrastructure specifically. The &lt;a href="https://devtoollab.com/blog/best-infrastructure-as-code-tools" rel="noopener noreferrer"&gt;full pricing table on DevToolLab&lt;/a&gt; lays all ten options side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating Takes About Fifteen Minutes
&lt;/h2&gt;

&lt;p&gt;Back up state, install OpenTofu, then run &lt;code&gt;tofu init&lt;/code&gt; in the unchanged directory. No edits are needed, because &lt;code&gt;hashicorp/*&lt;/code&gt; provider addresses resolve through OpenTofu's registry. Run &lt;code&gt;tofu plan&lt;/code&gt; and insist on an empty diff. On a project whose state was written by Terraform 1.5.7, OpenTofu reported &lt;code&gt;found no differences, so no changes are needed&lt;/code&gt;. Enable state encryption before the next apply, swap the binary in CI, and audit any wrapper script that greps output, since the word "Terraform" becomes "OpenTofu". Plan the return trip too: encrypted state has to be decrypted first.&lt;/p&gt;

&lt;p&gt;If you are picking today with nothing to migrate, OpenTofu plus GitHub Actions plus encrypted remote state is the sensible default. It is a superset of the Terraform you would otherwise learn, and there is no license review at the end of it. Two small things that help before you commit anything: the &lt;a href="https://devtoollab.com/tools/subnet-calculator" rel="noopener noreferrer"&gt;Subnet Calculator&lt;/a&gt; for working out CIDR ranges before they get hard-coded into a VPC module, and the &lt;a href="https://devtoollab.com/tools/cron-expression-parser" rel="noopener noreferrer"&gt;Cron Expression Parser&lt;/a&gt; for sanity-checking a drift detection schedule that will run unattended.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/best-infrastructure-as-code-tools" rel="noopener noreferrer"&gt;Best Infrastructure as Code Tools in 2026&lt;/a&gt; - the original, with the full comparison table and verified sources&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/best-secrets-management-tools" rel="noopener noreferrer"&gt;Best Secrets Management Tools in 2026&lt;/a&gt; - Vault, OpenBao and the SaaS options for the credentials your state file holds&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.hashicorp.com/terraform" rel="noopener noreferrer"&gt;Terraform documentation&lt;/a&gt; and the &lt;a href="https://github.com/hashicorp/terraform-mcp-server" rel="noopener noreferrer"&gt;Terraform MCP server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentofu.org/" rel="noopener noreferrer"&gt;OpenTofu&lt;/a&gt;, &lt;a href="https://www.pulumi.com/pricing/" rel="noopener noreferrer"&gt;Pulumi pricing&lt;/a&gt;, &lt;a href="https://www.crossplane.io/" rel="noopener noreferrer"&gt;Crossplane&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://terragrunt.gruntwork.io/" rel="noopener noreferrer"&gt;Terragrunt&lt;/a&gt;, &lt;a href="https://www.runatlantis.io/" rel="noopener noreferrer"&gt;Atlantis&lt;/a&gt;, &lt;a href="https://docs.opentaco.dev/" rel="noopener noreferrer"&gt;OpenTaco&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://scalr.com/pricing" rel="noopener noreferrer"&gt;Scalr pricing&lt;/a&gt;, &lt;a href="https://spacelift.io/pricing" rel="noopener noreferrer"&gt;Spacelift pricing&lt;/a&gt;, &lt;a href="https://www.envzero.com/pricing" rel="noopener noreferrer"&gt;env zero pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/github-actions-best-practices" rel="noopener noreferrer"&gt;GitHub Actions Best Practices&lt;/a&gt; - hardening the runner that applies all of this&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>opensource</category>
      <category>cloud</category>
    </item>
    <item>
      <title>A2A Hit 150 Organizations in a Year. Most Projects Reaching For It Do Not Need It Yet</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Fri, 07 Aug 2026 16:48:34 +0000</pubDate>
      <link>https://dev.to/moksh/a2a-hit-150-organizations-in-a-year-most-projects-reaching-for-it-do-not-need-it-yet-5f23</link>
      <guid>https://dev.to/moksh/a2a-hit-150-organizations-in-a-year-most-projects-reaching-for-it-do-not-need-it-yet-5f23</guid>
      <description>&lt;p&gt;Agent2Agent turned one year old under the Linux Foundation in April 2026 with numbers that are genuinely impressive: more than 150 supporting organizations, up from roughly 50 twelve months earlier, over 22,000 stars on the core repository, and SDKs in Python, JavaScript, Java, Go and .NET. Azure AI Foundry, Amazon Bedrock AgentCore and Google Cloud's Agent Development Kit all ship it.&lt;/p&gt;

&lt;p&gt;None of that tells you whether you should use it. "150 companies signed a support letter" and "engineers pick this over an HTTP call" are separate claims, and the gap between them is where most of the confusion about A2A lives. I wrote a fuller version of this on DevToolLab, &lt;a href="https://devtoollab.com/blog/a2a-protocol-guide" rel="noopener noreferrer"&gt;A2A Protocol Explained&lt;/a&gt;, with the complete working example. Short version below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Concepts, Then You Have It
&lt;/h2&gt;

&lt;p&gt;MCP wires an agent to tools. A2A wires an agent to &lt;strong&gt;other agents&lt;/strong&gt;, possibly on another vendor's stack, in another language, owned by a team you never talk to. The design goal is that agent A never learns how agent B is built, only what B can do and how to hand it work.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;Agent Card&lt;/strong&gt; is a JSON document at &lt;code&gt;/.well-known/agent-card.json&lt;/code&gt; listing skills, input and output modes, and auth requirements. A &lt;strong&gt;Task&lt;/strong&gt; is a unit of work with a real lifecycle: submitted, working, sometimes input-required when it needs more from the caller, then completed, failed or canceled. A &lt;strong&gt;Message&lt;/strong&gt; is one turn in the exchange. An &lt;strong&gt;Artifact&lt;/strong&gt; is the output, built from typed parts that can be text, a file or structured data.&lt;/p&gt;

&lt;p&gt;Transport is JSON-RPC 2.0 over HTTP with Server-Sent Events for streaming and push notifications for tasks that outlive one request. Version 0.3 in August 2025 added gRPC as an alternate transport and JWS-signed Agent Cards, which matters more than it first sounds: without signing, an Agent Card is an unauthenticated JSON file at a predictable URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Is Not Competing With MCP
&lt;/h2&gt;

&lt;p&gt;The framing that survives contact with real systems: MCP is the tool-integration layer, A2A is the agent-collaboration layer. MCP answers "give my agent a function to call." A2A answers "hand this task to an agent I do not control and get a result back." Non-trivial multi-agent systems need both.&lt;/p&gt;

&lt;p&gt;The differences that matter in practice are the unit of work and who owns the other side. MCP moves a tool call; A2A moves a stateful task that can span turns. MCP servers are usually yours; the agent behind an Agent Card usually is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Genuinely Need It
&lt;/h2&gt;

&lt;p&gt;The criticism doing the rounds this year is fair. Plenty of A2A demos show three agents accomplishing what three function calls would. Standing up an Agent Card, a task store and JSON-RPC plumbing for one in-process call is pure overhead.&lt;/p&gt;

&lt;p&gt;It earns its keep when the other agent is actually outside your control, when the interaction is a real task rather than a function call (multiple turns, mid-flight requests for more input, long enough that you want streaming instead of a blocking response), or when you need to discover capabilities at runtime instead of hardcoding them. Skip it when a direct API call, a shared MCP server or in-process orchestration already works. And read "150 organizations" as an interoperability signal, not as proof of production load. The number that would prove that is how many teams keep it after their first authentication failure and compliance review, which is far smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Working Agent, Start to Finish
&lt;/h2&gt;

&lt;p&gt;The reference SDK is &lt;code&gt;a2a-sdk&lt;/code&gt;, compatible with protocol versions 1.0 and 0.3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"a2a-sdk[http-server]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Describe the capability, then publish it in a card:&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;a2a.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AgentCard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AgentCapabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AgentSkill&lt;/span&gt;

&lt;span class="n"&gt;skill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentSkill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summarize_ticket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize Support Ticket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reads a support ticket thread and returns a one-paragraph summary.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;input_modes&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;text/plain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;output_modes&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;text/plain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;tags&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;support&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;summarization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;examples&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;Summarize ticket #4021&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;agent_card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ticket Summarizer Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarizes support tickets on request.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:9999&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default_input_modes&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;text/plain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;default_output_modes&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;text/plain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;AgentCapabilities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;streaming&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;skills&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The executor is where the work happens. Note the status update before the slow part, which is what gives a caller something to stream:&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;a2a.server.agent_execution&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AgentExecutor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequestContext&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;a2a.server.events&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EventQueue&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;a2a.server.tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TaskUpdater&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;a2a.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TaskState&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;a2a.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;new_task_from_user_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_text_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_text_part&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TicketSummarizerExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AgentExecutor&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;execute&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;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventQueue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_task&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;new_task_from_user_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;updater&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TaskUpdater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context_id&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;updater&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TaskState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;working&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;new_text_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reading ticket thread...&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;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# your own logic or LLM call
&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;updater&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_artifact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;new_text_part&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="n"&gt;summary&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;updater&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TaskState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completed&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;cancel&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;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventQueue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&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;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This agent doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t support cancellation yet.&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;Then serve it:&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;a2a.server.apps&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;A2AStarletteApplication&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;a2a.server.request_handlers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultRequestHandler&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;a2a.server.tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InMemoryTaskStore&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uvicorn&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultRequestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent_executor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;TicketSummarizerExecutor&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;task_store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;InMemoryTaskStore&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&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;A2AStarletteApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_card&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http_handler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;uvicorn&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="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before pointing a client at it, confirm the card is actually being served:&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;-s&lt;/span&gt; http://localhost:9999/.well-known/agent-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A malformed Agent Card is a common reason a client silently discovers no skills, so it is worth running that response through a &lt;a href="https://devtoollab.com/tools/json-schema-validator" rel="noopener noreferrer"&gt;JSON Schema validator&lt;/a&gt; while you are iterating. The &lt;a href="https://devtoollab.com/tools/curl-command-generator" rel="noopener noreferrer"&gt;cURL command generator&lt;/a&gt; is also handy for the task-submission call, since hand-typing a JSON-RPC envelope every time you change one parameter gets old fast. &lt;a href="https://devtoollab.com/blog/a2a-protocol-guide" rel="noopener noreferrer"&gt;The original post&lt;/a&gt; walks through each piece in more detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpx7hsj5kukzdek37n257.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpx7hsj5kukzdek37n257.webp" alt="A2A Protocol guide banner covering the Agent2Agent protocol, its adoption numbers and how it compares to MCP" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Security Gap Nobody Demos
&lt;/h2&gt;

&lt;p&gt;Every serious writeup lands on the same hole: an Agent Card says what an agent can do, not who owns it or what it should be trusted with. Four things worth doing on day one rather than retrofitting.&lt;/p&gt;

&lt;p&gt;Verify signed cards, because JWS signing in v0.3 exists precisely so you can confirm a card was not swapped in transit, and HTTPS alone does not give you that. Scope what a remote agent may request instead of trusting every skill its card advertises, the same way you would scope an OAuth grant. Decode the tokens moving between agents rather than assuming an &lt;code&gt;aud&lt;/code&gt; or &lt;code&gt;sub&lt;/code&gt; claim means what you expect. And never forward a caller's token to a third-party agent unmodified: if agent A hands agent B its own credential, B now acts with A's authority on systems it was never meant to reach. That is the confused-deputy problem, and it does not stop being one because both parties are agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Short Answer
&lt;/h2&gt;

&lt;p&gt;A2A is not hype in the sense of not working. The spec is real, the SDKs work, the adoption is genuine. It is hype in the narrower sense that most projects reaching for it in 2026 do not need it yet. Use MCP to give your agent tools. Reach for A2A once you have a second agent you do not control that must accept a task, work across several turns and hand back a result. If your case is "agent A calls agent B once and gets a string," that is a function call wearing a protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/a2a-protocol-guide" rel="noopener noreferrer"&gt;A2A Protocol Explained: 150+ Companies Backed It in a Year&lt;/a&gt; - the original, with the full example and every source&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.linuxfoundation.org/press/a2a-protocol-surpasses-150-organizations-lands-in-major-cloud-platforms-and-sees-enterprise-production-use-in-first-year" rel="noopener noreferrer"&gt;A2A protocol surpasses 150 organizations&lt;/a&gt; - Linux Foundation, first-year numbers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/blog/products/ai-machine-learning/agent2agent-protocol-is-getting-an-upgrade" rel="noopener noreferrer"&gt;Agent2Agent protocol is getting an upgrade&lt;/a&gt; - the v0.3 gRPC and signed-card release&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/best-mcp-servers" rel="noopener noreferrer"&gt;Best MCP Servers&lt;/a&gt; and &lt;a href="https://devtoollab.com/blog/build-mcp-server-python" rel="noopener noreferrer"&gt;Build an MCP Server in Python&lt;/a&gt; - the tool layer underneath&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/mcp-server-authentication-oauth-guide" rel="noopener noreferrer"&gt;MCP Server Authentication with OAuth 2.1&lt;/a&gt; - the confused-deputy problem in detail&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Qdrant Raised $50M, Pinecone Explored a Sale: Picking a Vector Database in 2026</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Thu, 06 Aug 2026 19:44:32 +0000</pubDate>
      <link>https://dev.to/moksh/qdrant-raised-50m-pinecone-explored-a-sale-picking-a-vector-database-in-2026-2klj</link>
      <guid>https://dev.to/moksh/qdrant-raised-50m-pinecone-explored-a-sale-picking-a-vector-database-in-2026-2klj</guid>
      <description>&lt;p&gt;The two most useful facts about vector databases in 2026 are both business facts. Qdrant closed a $50M Series B in March 2026, taking total funding to about $87.8M. Pinecone, the company that turned "vector database" into a product category, reportedly hired bankers to explore a sale and replaced its founder-CEO with a hire from Google.&lt;/p&gt;

&lt;p&gt;Meanwhile AWS, Google Cloud, Databricks, Snowflake and MongoDB all added vector search to databases they already sell, and Postgres does it with a free extension. The pure-play vendors are squeezed from both directions, so they are competing on latency, memory cost and hybrid search rather than on storing vectors at all. If you are building retrieval-augmented generation or agent memory, that competition is working in your favor, and the reflexive 2023 answer of "just use Pinecone" deserves rechecking.&lt;/p&gt;

&lt;p&gt;I published a fuller version of this comparison on DevToolLab, &lt;a href="https://devtoollab.com/blog/best-vector-databases" rel="noopener noreferrer"&gt;Best Vector Databases in 2026&lt;/a&gt;, with the full pricing table and every source linked. Here is the compressed version.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Search Problem in One Paragraph
&lt;/h2&gt;

&lt;p&gt;An embedding model turns text, images or audio into a list of floats. Similar content lands close together in that space, so answering a question means embedding the question and finding the nearest stored vectors. Doing that exactly means scoring every vector you have, which is accurate and slow, so these systems use approximate nearest neighbor indexes, usually HNSW, and give up a sliver of recall for a large speedup. Everything that distinguishes one product from another sits around that: which indexes exist, how metadata filtering interacts with the index, how aggressively vectors can be quantized to cut RAM, and who runs the servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Shapes, Not Five Products
&lt;/h2&gt;

&lt;p&gt;The market splits three ways, and that narrows the decision faster than any feature table.&lt;/p&gt;

&lt;p&gt;Pinecone is &lt;strong&gt;managed and proprietary&lt;/strong&gt;: no infrastructure to touch, no self-hosting escape hatch, usage-based bills. Weaviate, Qdrant and Milvus are &lt;strong&gt;open source with a managed cloud&lt;/strong&gt;, which is where most of the current engineering effort is going. pgvector is &lt;strong&gt;a feature of a database you already operate&lt;/strong&gt;, with vectors sitting beside your relational rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pinecone
&lt;/h2&gt;

&lt;p&gt;Fastest path from nothing to a working retrieval endpoint. Serverless separates storage from compute so idle indexes are cheap, namespaces handle multi-tenancy, and sparse-dense hybrid search is supported. Nothing to tune, which is the point.&lt;/p&gt;

&lt;p&gt;The costs are strategic rather than technical. You cannot self-host, the index internals are undisclosed so you cannot swap algorithms or chase recall yourself, and the vendor is publicly in motion. The Starter tier is free up to 2GB with 1M read units and 2M write units a month. Builder is a flat $20/month; Standard carries a $50/month minimum and Enterprise $500/month, both pay-as-you-go above that. On Standard, usage lands around $16 to $18 per million read units, $4 to $4.50 per million write units and $0.33/GB/month of storage. Forecast your read volume before you commit, because read-heavy RAG is exactly where a usage meter surprises people.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weaviate
&lt;/h2&gt;

&lt;p&gt;Go, BSD-3-Clause, managed cloud and bring-your-own-cloud. Its real differentiator is hybrid search: dense similarity and BM25 keyword scoring fused natively, which beats pure vector search whenever exact tokens matter, and they matter more than people expect for SKUs, surnames and error strings.&lt;/p&gt;

&lt;p&gt;Release 1.37.0 in April 2026 added a built-in MCP server in preview, so coding agents can query Weaviate over the &lt;a href="https://devtoollab.com/blog/best-mcp-servers" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt;, plus MMR diversity search, query profiling and incremental backups. Built-in vectorizer and reranker modules mean it can call your embedding model instead of making you embed client-side. The tradeoffs: the module system is configuration surface you have to learn, and single-node latency generally trails Qdrant. Self-hosting is free, the cloud free tier covers 100,000 objects, Flex starts at $45/month billed per million vector dimensions from $0.00465, and Premium starts at $400/month prepaid. That resource model replaced a $25/month serverless tier retired in October 2025, which annoyed a lot of small projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qdrant
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwjbyhzo4ziptxxjmq3t9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwjbyhzo4ziptxxjmq3t9.webp" alt="Qdrant, the Rust vector database built for low latency and predictable cost" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rust and Apache 2.0, and the one I reach for when latency and a forecastable bill both matter. No garbage collector pauses, SIMD distance math, over 250 million package downloads and 29,000-plus GitHub stars, with Canva, HubSpot and Bosch in production.&lt;/p&gt;

&lt;p&gt;Version 1.18 in May 2026 shipped TurboQuant, a quantization method derived from Google Research that claims roughly scalar-quantization recall at about half the memory, plus per-collection memory monitoring and named vectors you can add or drop without recreating the collection. That sits on top of scalar, product and binary quantization and filterable HNSW, so tight metadata filters do not destroy recall.&lt;/p&gt;

&lt;p&gt;Cloud pricing is per provisioned resource (vCPU, RAM, disk) rather than per query, which makes cost flat no matter how much traffic you send. Fewer AI modules than Weaviate, so bring your own embeddings, and the enterprise feature set is younger than the incumbents. Qdrant also publishes its own benchmark suite showing itself ahead on QPS and latency; the methodology is open source, but read any single-vendor benchmark as directional. The free managed cluster is 1GB with no card and no expiry, which is genuinely enough to prototype on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Milvus
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2nk635l8tiqn7bw1mrj7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2nk635l8tiqn7bw1mrj7.webp" alt="Milvus, the distributed vector database for billion-scale and GPU-accelerated search" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apache 2.0, built by Zilliz for billion-scale work, compute separated from storage. It supports the widest index range here by a distance: HNSW, IVF variants, DiskANN and GPU indexes including NVIDIA CAGRA.&lt;/p&gt;

&lt;p&gt;Milvus 2.6 went open source in June 2025 and reached general availability on Zilliz Cloud on January 20, 2026, themed entirely on cost at scale. Zilliz claims RaBitQ 1-bit quantization cuts memory up to 72%, that tiered storage cuts storage cost substantially, and that its BM25 full-text search beats Elasticsearch by several times. Those are vendor numbers.&lt;/p&gt;

&lt;p&gt;The honest cost is operational. Self-hosting means etcd, an object store, and several coordinator and worker components, which is why most teams that pick Milvus end up on Zilliz Cloud anyway. For a few million vectors it is straightforwardly overkill. Self-hosting is free, Zilliz Cloud has a 5GB free tier, serverless starts at $0 and bills around $4 per million vCUs, and dedicated clusters run from roughly $126/GB/month. Storage dropped to $0.04/GB/month in January 2026, down 87% from $0.30.&lt;/p&gt;

&lt;h2&gt;
  
  
  pgvector
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwce32h41vnnviyyyypg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwce32h41vnnviyyyypg.webp" alt="pgvector, the Postgres extension that adds vector similarity search" width="799" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not a database. An extension under the PostgreSQL License that adds a &lt;code&gt;vector&lt;/code&gt; column type and similarity search to Postgres, and for a large share of applications it is the correct answer that gets skipped for being boring. Embeddings live next to your relational data, inside the same transactions and joins, with no new system to operate. Every managed Postgres supports it.&lt;/p&gt;

&lt;p&gt;0.8.0 added iterative index scans, fixing the old over-filtering problem where a strict &lt;code&gt;WHERE&lt;/code&gt; plus vector search returned too few rows. Note that 0.8.2, released February 26, 2026, was a security fix for a buffer overflow in parallel HNSW index builds, tracked as CVE-2026-3172, so self-managed Postgres should be upgraded. It will not reach billions of vectors as gracefully as Milvus or Qdrant, and high recall at high QPS lags the purpose-built engines. Hybrid search is assembly work with Postgres full-text search rather than a feature. pgvectorscale from Timescale adds a StreamingDiskANN index if you need to close part of that gap.&lt;/p&gt;

&lt;p&gt;The rule I would apply: stay on pgvector until you can prove you have outgrown one large Postgres node. Plenty of teams never do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five Questions, In Order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Already on Postgres with fewer than a few million vectors? &lt;strong&gt;pgvector.&lt;/strong&gt; Do not add infrastructure yet.&lt;/li&gt;
&lt;li&gt;Want zero ops and accept usage billing plus lock-in? &lt;strong&gt;Pinecone.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Is keyword-plus-vector hybrid search core, or are you multi-tenant SaaS? &lt;strong&gt;Weaviate.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Need the lowest latency and a flat bill? &lt;strong&gt;Qdrant.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Hundreds of millions of vectors, or GPU search? &lt;strong&gt;Milvus&lt;/strong&gt;, most likely on Zilliz Cloud.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prototyping on pgvector or a free managed tier and migrating later is a legitimate plan, because embeddings are portable. Migration is mostly re-upserting vectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Database Is Actually Doing
&lt;/h2&gt;

&lt;p&gt;Before an index makes it fast, retrieval is one similarity calculation. This runs with NumPy alone:&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;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cosine_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.34&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="c1"&gt;# real embeddings are 768-3072 dims
&lt;/span&gt;&lt;span class="n"&gt;docs&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;doc-a&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="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doc-b&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="mf"&gt;0.88&lt;/span&gt;&lt;span class="p"&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="mf"&gt;0.42&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;doc_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;cosine_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;reverse&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;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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;cosine_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doc-a: 0.9995
doc-b: 0.2964
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;doc-a&lt;/code&gt; wins because it points nearly the same direction as the query. On Postgres the same idea is plain SQL, where &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt; is cosine distance and smaller means closer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;        &lt;span class="n"&gt;bigserial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;hnsw&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector_cosine_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0.11, 0.92, 0.34, ...]'&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quantization, hybrid fusion, distributed indexing and multi-tenancy all exist to keep that query working at a scale where one &lt;code&gt;ORDER BY&lt;/code&gt; collapses. Two things worth having open while you build: the &lt;a href="https://devtoollab.com/tools/cosine-similarity-calculator" rel="noopener noreferrer"&gt;Cosine Similarity Calculator&lt;/a&gt; for checking your distance math by hand, and the &lt;a href="https://devtoollab.com/tools/docker-compose-generator" rel="noopener noreferrer"&gt;Docker Compose Generator&lt;/a&gt; for standing up self-hosted Qdrant, Weaviate or Milvus locally before you pay anyone.&lt;/p&gt;

&lt;p&gt;There is no winner here, only clean best-fit answers. Pick for the scale you have today, prove retrieval quality on your real corpus, and move when you hit a concrete limit rather than a hypothetical one.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/best-vector-databases" rel="noopener noreferrer"&gt;Best Vector Databases in 2026: Tested and Ranked&lt;/a&gt; - the original, with the full comparison table and every source&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://qdrant.tech/blog/series-b-announcement/" rel="noopener noreferrer"&gt;Qdrant Series B announcement&lt;/a&gt; and &lt;a href="https://qdrant.tech/blog/qdrant-1.18.x/" rel="noopener noreferrer"&gt;Qdrant 1.18&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pinecone.io/pricing/" rel="noopener noreferrer"&gt;Pinecone pricing&lt;/a&gt;, &lt;a href="https://weaviate.io/pricing" rel="noopener noreferrer"&gt;Weaviate pricing&lt;/a&gt;, &lt;a href="https://zilliz.com/pricing" rel="noopener noreferrer"&gt;Zilliz pricing&lt;/a&gt;, &lt;a href="https://qdrant.tech/pricing/" rel="noopener noreferrer"&gt;Qdrant pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.weaviate.io/weaviate/release-notes" rel="noopener noreferrer"&gt;Weaviate release notes&lt;/a&gt; and &lt;a href="https://milvus.io/blog/introduce-milvus-2-6-built-for-scale-designed-to-reduce-costs.md" rel="noopener noreferrer"&gt;Milvus 2.6&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/about/news/pgvector-082-released-3245" rel="noopener noreferrer"&gt;pgvector 0.8.2 security release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/best-mcp-servers" rel="noopener noreferrer"&gt;Best MCP Servers&lt;/a&gt; - what Weaviate's new MCP server sits alongside&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>OpenAI Is Closing Fine-Tuning in January 2027. Here Is Where to Train Instead</title>
      <dc:creator>Moksh Gupta</dc:creator>
      <pubDate>Wed, 05 Aug 2026 16:58:26 +0000</pubDate>
      <link>https://dev.to/moksh/openai-is-closing-fine-tuning-in-january-2027-here-is-where-to-train-instead-20mk</link>
      <guid>https://dev.to/moksh/openai-is-closing-fine-tuning-in-january-2027-here-is-where-to-train-instead-20mk</guid>
      <description>&lt;p&gt;Go and read OpenAI's deprecations page. Not a blog post about it, the page itself, dated 7 May 2026. If your organisation has never run a fine-tuning job, you cannot start one. Since 2 July 2026, new jobs need inference on a fine-tuned model within the previous 60 days. On 6 January 2027 that door shuts for everybody, and fine-tuned gpt-3.5-turbo, gpt-4, gpt-4.1-nano, babbage-002 and davinci-002 go dark on 23 October 2026.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83qf4syci8ivnu0lp7zj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83qf4syci8ivnu0lp7zj.webp" alt="OpenAI's developer documentation Deprecations page, listing deprecated features and their recommended replacements, including the model deprecation notice periods that govern the fine-tuning wind-down" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I put together &lt;a href="https://devtoollab.com/blog/best-ai-fine-tuning-platforms" rel="noopener noreferrer"&gt;the full comparison on DevToolLab&lt;/a&gt; with every price sourced from the provider's own page. This is the compressed version, because the headline deserves to travel: the most visible fine-tuning API in the industry is being retired while every alternative got cheaper and better in the same twelve months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behaviour, Not Facts
&lt;/h2&gt;

&lt;p&gt;Before shopping for a platform, be sure you need one. Fine-tuning changes how a model behaves. It does not install knowledge. "The model has never heard of our internal billing API" is a retrieval problem. "The model understands the task but keeps returning prose when I asked for JSON, or picks the wrong label out of six" is a fine-tuning problem, and prompt engineering will keep almost-fixing it forever.&lt;/p&gt;

&lt;p&gt;One filter decides it: could you sit down and write 200 to 1,000 examples of the output you want? If the answer is no, stop here. Nothing on this list rescues a dataset you cannot describe.&lt;/p&gt;

&lt;p&gt;Four methods, briefly. SFT learns from input and ideal-output pairs, and covers most real needs. DPO learns from pairs where one answer beats another, which suits taste-driven work like tone or summary quality. RFT, in practice GRPO, learns against a grader that scores attempts, so it fits problems you can measure but cannot hand-write, such as code that must pass a test suite. Distillation copies a big model's outputs into a small one purely to cut serving cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Everyone Reaches for LoRA
&lt;/h2&gt;

&lt;p&gt;LoRA freezes the base weights and trains a small adapter beside them. The ratio is the whole story, and it is easy to see for yourself. This ran on my laptop against &lt;code&gt;peft&lt;/code&gt; 0.20.0:&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;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutoModelForCausalLM&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;peft&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LoraConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_peft_model&lt;/span&gt;

&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModelForCausalLM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HuggingFaceTB/SmolLM2-135M&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="nf"&gt;get_peft_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LoraConfig&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="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lora_alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAUSAL_LM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;target_modules&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;q_proj&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;k_proj&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;v_proj&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;o_proj&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;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print_trainable_parameters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trainable params: 1,843,200 || all params: 136,358,208 || trainable%: 1.3517
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under one and a half percent of the weights carry the training. At fp16 that adapter is 3.5 MiB against a 260 MiB model, which is why a single GPU can serve dozens of task-specific variants off one base.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Clouds Are Not All Retreating
&lt;/h2&gt;

&lt;p&gt;OpenAI's own model optimization guide now warns that its fine-tuning material is moving to legacy documentation, which tells you the same thing the deprecation table does. What still runs for existing customers: SFT and DPO on gpt-4.1, mini and nano, vision tuning on gpt-4o-2024-08-06, RFT only on o4-mini-2025-04-16. Training is $25 per million tokens on gpt-4.1, $5 on mini, $1.50 on nano, and RFT bills $100 per hour of core training. No GPT-5 model was ever fine-tunable, and the migration notes send you to untuned gpt-5.4-mini and gpt-5.5.&lt;/p&gt;

&lt;p&gt;Microsoft went the other way with the same model family. Foundry runs RFT on o4-mini with Global Training across 13 regions at lower per-token rates than standard, the trade being data residency. GPT-4.1, mini and nano serve as graders, and Microsoft's own advice is to grade with nano first. If you need managed OpenAI-family tuning that is not on a shutdown schedule, this is the shortest move.&lt;/p&gt;

&lt;p&gt;Google bills Vertex tuning as dataset tokens multiplied by epochs, then charges above base rates to serve the tuned endpoint, which is the part that surprises people at invoice time. Check which snapshot is actually tunable before you plan around it, since Gemini 3.x tuning has been arriving through preview and allowlist rather than open self-serve.&lt;/p&gt;

&lt;p&gt;AWS made the most interesting bet by treating open weights as the main event. Bedrock added RFT for Qwen3-32B and gpt-oss-20b on 17 February 2026, and the tuned model answers immediately through OpenAI-compatible Responses and Chat Completions endpoints. SageMaker AI followed on 25 March 2026 with serverless SFT, DPO and RFT across twelve more open models including gpt-oss-120b and Qwen3 14B, in N. Virginia, Oregon, Tokyo and Ireland. Anthropic is the gap: no fine-tuning in the public Claude API at all, only Bedrock customisation on whichever Claude models it exposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Specialists Compete on Price
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqlzkargmdgaqixwxdizi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqlzkargmdgaqixwxdizi.webp" alt="Together AI fine-tuning documentation overview, showing the two supported approaches: LoRA, which trains adapter weights on a frozen base model and is the default, and full fine-tuning, which updates every weight" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Together AI is the number to benchmark everyone else against: $0.48 per million training tokens for LoRA up to 16B, $1.50 from 17B to 69B, $2.90 from 70B to 100B, with full fine-tuning and DPO also available. Fireworks AI opens at a near-identical $0.50 up to 16B but climbs to $3.00 by 80B and $6.00 by 300B, so the model size you actually intend to train decides which is cheaper, not the logo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9wnk39jvg3mrhx7fjdmt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9wnk39jvg3mrhx7fjdmt.webp" alt="Thinking Machines Lab's Tinker page, headlined " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tinker from Thinking Machines Lab is the one I would reach for if I wanted control without babysitting GPUs. It meters per million tokens instead of per GPU-hour, Qwen3-8B moved from $0.40 to $0.44 on 17 July 2026, so a 50 million token run lands near $22 plus $0.10 per GB-month of checkpoints. It only does LoRA at rank 32, and it exports a plain PEFT adapter, so leaving costs you nothing.&lt;/p&gt;

&lt;p&gt;Predibase deserves a caveat more than a recommendation. GRPO runs $10 per million tokens to 16B and $20 from there to 32B, with generous free serverless inference, but it has belonged to Rubrik since June 2025 and &lt;code&gt;predibase.com&lt;/code&gt; no longer serves its own site as of August 2026. It redirects to Rubrik Agent Cloud. Confirm the product is still sold on its own before you build a roadmap on it. &lt;a href="https://devtoollab.com/blog/best-ai-fine-tuning-platforms" rel="noopener noreferrer"&gt;The original post&lt;/a&gt; has the full pricing table for all of these side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Or Skip the Bill Entirely
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqafk4zs9nsza9dc45qwn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqafk4zs9nsza9dc45qwn.webp" alt="The Unsloth homepage, " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With one GPU, or a rented hour of one, the free tooling is the fastest path in the category. Unsloth 2026.8.3 is the throughput specialist: GRPO roughly 1.3x faster, mixture-of-experts training 3 to 5x faster, and up to 80 percent less VRAM on reinforcement learning, which is precisely what puts GRPO within reach of a consumer card. Axolotl 0.18.0 keeps every run in a YAML file, so a training change arrives as a reviewable diff instead of a notebook nobody can reproduce. LLaMA-Factory 0.9.5 claims the widest model surface at 100-plus and ships a UI. Underneath all of them sit TRL 1.9.2 and PEFT 0.20.0, which now expose SFT, DPO and GRPO trainers directly if you would rather own the loop.&lt;/p&gt;

&lt;p&gt;What you inherit is the GPU, the CUDA versions and the evaluation harness. A sane split: rent a hosted platform to find out whether the idea works at all, then move in-house once the same job runs every week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the Dataset Before the Meter Starts
&lt;/h2&gt;

&lt;p&gt;Training is billed per token, so a broken JSONL file costs money before it costs you an experiment. This is short enough to paste into any repo, and the output below is a real run against a deliberately damaged six-row file:&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;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;

&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;msgs&lt;/span&gt; &lt;span class="o"&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;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&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="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no assistant turn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;line &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: unusable - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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="n"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&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="n"&gt;m&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;content&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="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msgs&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; unusable, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; dataset tokens&lt;/span&gt;&lt;span class="sh"&gt;"&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;3 epochs = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; training tokens = $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.48&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at $0.48/1M&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;line 5: unusable - no assistant turn
line 6: unusable - 'messages'
6 rows, 2 unusable, 53 dataset tokens
3 epochs = 159 training tokens = $0.00 at $0.48/1M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the rate and the token count becomes a quote. Take something realistic: 5,000 examples averaging 400 tokens, three epochs, so 6 million training tokens. That is $2.88 on Together's LoRA tier, $2.64 on Tinker, $30 on gpt-4.1-mini, and $150 on full gpt-4.1. Small open models are not where the money goes. Serving them carelessly is.&lt;/p&gt;

&lt;p&gt;Two housekeeping steps worth doing before upload, because training data ends up inside checkpoints you may keep for months: scrub it with the &lt;a href="https://devtoollab.com/tools/pii-redactor" rel="noopener noreferrer"&gt;PII Redactor&lt;/a&gt;, and drop repeats with &lt;a href="https://devtoollab.com/tools/remove-duplicate-lines" rel="noopener noreferrer"&gt;Remove Duplicate Lines&lt;/a&gt;, since near-identical rows both inflate the bill and teach the model to over-weight whatever you pasted twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Step Teams Skip
&lt;/h2&gt;

&lt;p&gt;Reserve 10 to 20 percent of your examples as an evaluation set the training run never sees. Then run one job on default hyperparameters and compare the result against the untuned base model with a good prompt. That single comparison is the only evidence that matters, and it is the one people leave out because it sometimes says the prompt was fine.&lt;/p&gt;

&lt;p&gt;If the tuned model wins, ship it and watch cost per request. If it does not, the answer is nearly always more or better examples, not a different vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Actually Do
&lt;/h2&gt;

&lt;p&gt;Still on OpenAI with tuning in the roadmap: treat 6 January 2027 as immovable and pick a destination now. Foundry disrupts least, Together or Tinker on an open model costs least and outlives the decision.&lt;/p&gt;

&lt;p&gt;Chasing the lowest price on something small: Together at $0.48, with Fireworks as your second quote. Below 16B the gap is pennies, so choose on which base models are offered.&lt;/p&gt;

&lt;p&gt;Able to score outputs but not write them: that is the RFT case, so Bedrock or SageMaker on AWS, or GRPO in TRL and Unsloth if you want it in-house.&lt;/p&gt;

&lt;p&gt;Not certain fine-tuning is even the answer: it probably is not, yet. Better prompt, then retrieval, then distil the working prompt into a smaller model.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/best-ai-fine-tuning-platforms" rel="noopener noreferrer"&gt;Best AI Fine-Tuning Platforms in 2026: Hosted and Open Source Compared&lt;/a&gt; - the original, with the full pricing table and every source&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.openai.com/api/docs/deprecations" rel="noopener noreferrer"&gt;OpenAI deprecations&lt;/a&gt; and the &lt;a href="https://developers.openai.com/api/docs/guides/model-optimization" rel="noopener noreferrer"&gt;model optimization guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/fine-tuning" rel="noopener noreferrer"&gt;Microsoft Foundry fine-tuning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/bedrock/" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt; and &lt;a href="https://aws.amazon.com/sagemaker/ai/" rel="noopener noreferrer"&gt;SageMaker AI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.together.ai" rel="noopener noreferrer"&gt;Together AI&lt;/a&gt;, &lt;a href="https://fireworks.ai" rel="noopener noreferrer"&gt;Fireworks AI&lt;/a&gt;, &lt;a href="https://thinkingmachines.ai/tinker/" rel="noopener noreferrer"&gt;Tinker&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://unsloth.ai" rel="noopener noreferrer"&gt;Unsloth&lt;/a&gt;, &lt;a href="https://github.com/axolotl-ai-cloud/axolotl" rel="noopener noreferrer"&gt;Axolotl&lt;/a&gt;, &lt;a href="https://github.com/hiyouga/LLaMA-Factory" rel="noopener noreferrer"&gt;LLaMA-Factory&lt;/a&gt;, &lt;a href="https://github.com/huggingface/trl" rel="noopener noreferrer"&gt;TRL&lt;/a&gt;, &lt;a href="https://github.com/huggingface/peft" rel="noopener noreferrer"&gt;PEFT&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devtoollab.com/blog/llm-evals-guide" rel="noopener noreferrer"&gt;LLM Evals Guide&lt;/a&gt; - how to run the comparison that decides whether tuning helped&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
