<?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: Tatiana Radchenko 🇩🇰</title>
    <description>The latest articles on DEV Community by Tatiana Radchenko 🇩🇰 (@taniacoder).</description>
    <link>https://dev.to/taniacoder</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%2F3999372%2F7af2116b-b834-4900-9ae3-ee2e2d294004.jpg</url>
      <title>DEV Community: Tatiana Radchenko 🇩🇰</title>
      <link>https://dev.to/taniacoder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/taniacoder"/>
    <language>en</language>
    <item>
      <title>Did the model get worse, or is it just you? How to tell when an LLM API silently changes</title>
      <dc:creator>Tatiana Radchenko 🇩🇰</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:30:34 +0000</pubDate>
      <link>https://dev.to/taniacoder/did-the-model-get-worse-or-is-it-just-you-how-to-tell-when-an-llm-api-silently-changes-1lia</link>
      <guid>https://dev.to/taniacoder/did-the-model-get-worse-or-is-it-just-you-how-to-tell-when-an-llm-api-silently-changes-1lia</guid>
      <description>&lt;p&gt;You ship a feature on top of &lt;code&gt;gpt-x&lt;/code&gt; or &lt;code&gt;claude-y&lt;/code&gt;. It works. Three weeks later your users say the outputs feel worse — vaguer, sloppier, failing on the same prompts that used to pass. You didn't change your code. Did the model change, or are you imagining it?&lt;/p&gt;

&lt;p&gt;This question eats an astonishing number of engineering hours, and it almost always dissolves into a vibes argument: half the thread says "it's clearly worse", the other half says "you have no evidence, it's confirmation bias." Both sides are right, because nobody has instrumented the one thing that would settle it. Here is how to settle it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your gut is useless here (and so is a single comparison)
&lt;/h2&gt;

&lt;p&gt;Two things move at once when a hosted model "feels worse":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The provider's side&lt;/strong&gt; — a new checkpoint, a routing change, an inference optimization (batching, speculative decoding, quantization), or an infrastructure bug. You are not told when these happen. Often the provider doesn't announce it, and sometimes it isn't even a model change — it's an infra regression that only &lt;em&gt;looks&lt;/em&gt; like the model got dumber.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your side&lt;/strong&gt; — a prompt tweak, a temperature change, a new library version, a different context length, or just which prompts you happened to hit today.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From a single account, on a handful of anecdotal prompts, these two are &lt;strong&gt;indistinguishable&lt;/strong&gt;. That's the trap. You need two things to escape it: an &lt;em&gt;objective, dated signal&lt;/em&gt;, and a way to tell "it changed for me" from "it changed for everyone."&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Fix a canary suite
&lt;/h2&gt;

&lt;p&gt;Pick a small, frozen set of prompts — 20 to 100 — that exercise the capabilities you actually depend on. Keep it small and cheap; this is a smoke detector, not a benchmark. Freeze it: same prompts, same parameters (&lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;top_p&lt;/code&gt;, &lt;code&gt;max_tokens&lt;/code&gt;), same model string, forever. The moment you change the suite you lose your baseline.&lt;/p&gt;

&lt;p&gt;For each canary, define a &lt;strong&gt;cheap scalar&lt;/strong&gt; you can compute automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON/schema validity rate (did it return parseable, valid output?)&lt;/li&gt;
&lt;li&gt;Output length (a sudden shift often precedes a quality change)&lt;/li&gt;
&lt;li&gt;A pass/fail against a fixed assertion (does the function it wrote compile?)&lt;/li&gt;
&lt;li&gt;A rubric score from a cheap grader&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You are not trying to measure "quality" in the abstract. You are trying to detect &lt;em&gt;change&lt;/em&gt;. A boring, stable scalar is exactly what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Record a baseline while things are good
&lt;/h2&gt;

&lt;p&gt;Run the suite on a schedule — hourly, daily, whatever your budget allows — and store &lt;code&gt;(timestamp, prompt_id, metric)&lt;/code&gt;. Do this &lt;strong&gt;before&lt;/strong&gt; you suspect anything. The baseline is the whole game: without "what normal looked like last month" you have nothing to compare today against. Estimate the mean &lt;code&gt;mu0&lt;/code&gt; and standard deviation &lt;code&gt;sigma0&lt;/code&gt; of your metric per prompt from a stable window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Run a change-point test, not a threshold
&lt;/h2&gt;

&lt;p&gt;A fixed threshold ("alert if success rate &amp;lt; 90%") is noisy and late. What you want is a &lt;strong&gt;sequential change-point detector&lt;/strong&gt; that accumulates small, consistent shifts and fires with a timestamp. The classic is &lt;strong&gt;CUSUM&lt;/strong&gt; (Page, 1954). It's a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cusum_alarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Two-sided CUSUM on standardized observations.
    k = slack (in std devs), h = alarm threshold.
    Returns the index where a sustained shift is detected, or None.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;s_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s_neg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&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="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mu0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sigma0&lt;/span&gt;          &lt;span class="c1"&gt;# standardize against the baseline
&lt;/span&gt;        &lt;span class="n"&gt;s_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s_pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# catches upward shifts
&lt;/span&gt;        &lt;span class="n"&gt;s_neg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s_neg&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# catches downward shifts
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s_pos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s_neg&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;                     &lt;span class="c1"&gt;# dated change-point
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed it your per-prompt metric over time. Instead of "feels worse", you now get &lt;em&gt;"json_success_rate on prompt #14 stepped down starting Tuesday"&lt;/em&gt; — a claim with a date on it that you can put in a ticket. (&lt;code&gt;h=5.0&lt;/code&gt;, &lt;code&gt;k=0.5&lt;/code&gt; are reasonable starting points for standardized data; tune on your own history. Bayesian Online Change-Point Detection is a fancier alternative if you want posterior probabilities.)&lt;/p&gt;

&lt;p&gt;This alone puts you ahead of ~95% of "the model got worse" threads, which never get past anecdotes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — The part one account can never answer: "is it just me?"
&lt;/h2&gt;

&lt;p&gt;Here's the ceiling you hit: even with a perfect change-point signal, &lt;strong&gt;you still can't tell a provider-side change from your own drift&lt;/strong&gt;, because from one seat they look identical. Your CUSUM fired — but was it the model, or the LlamaIndex bump you did last week, or a flaky region?&lt;/p&gt;

&lt;p&gt;The only way to separate them is &lt;strong&gt;correlation across independent observers&lt;/strong&gt;. If your canary fires on Tuesday, and several &lt;em&gt;other&lt;/em&gt; organizations watching the &lt;em&gt;same&lt;/em&gt; model on the &lt;em&gt;same&lt;/em&gt; metric also fire around Tuesday, that agreement is the signal that it's provider-side — not your harness. One observer has N=1 forever; that's why these threads never converge. This is a genuinely different kind of detection: not "monitor my app" (every observability tool does that), but "did this change for &lt;em&gt;everyone&lt;/em&gt;, privately confirmed across the fleet."&lt;/p&gt;

&lt;p&gt;That cross-observer angle is what &lt;a href="https://github.com/Tania-coder/SEISMOGRAPH" rel="noopener noreferrer"&gt;SEISMOGRAPH&lt;/a&gt; is built around: a lightweight probe runs your canary suite, a change-point detector flags shifts locally, and only privacy-preserving distributional features — never your prompts or outputs — are shared, so a public "model weather" alert only fires when &lt;em&gt;multiple independent observers agree&lt;/em&gt;. A single org's signal stays private.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does this actually work? A worked example
&lt;/h2&gt;

&lt;p&gt;The method isn't hypothetical. Take the September 2025 Anthropic incident: for a stretch, &lt;code&gt;claude-sonnet-4&lt;/code&gt; degraded because of infrastructure bugs — notably a context-window routing error — &lt;strong&gt;not&lt;/strong&gt; a model update (Anthropic said so explicitly in &lt;a href="https://www.anthropic.com/engineering/a-postmortem-of-three-recent-issues" rel="noopener noreferrer"&gt;their postmortem&lt;/a&gt;). Exactly the "is it the model or the infra?" fog this whole article is about.&lt;/p&gt;

&lt;p&gt;Replaying a canary suite over that period with the CUSUM method above: &lt;strong&gt;a seeded backtest flags it 38 days before the postmortem.&lt;/strong&gt; Not a live catch — a reproducible backtest, seeded and open — but it shows the signal was sitting in cheap distributional metrics well before the official acknowledgement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do it yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Probe (open source, Apache-2.0):&lt;/strong&gt; &lt;code&gt;pip install seismograph-probe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live "model weather" dashboard:&lt;/strong&gt; &lt;a href="https://driftdefense.dev?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=evergreen_howto" rel="noopener noreferrer"&gt;https://driftdefense.dev?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=evergreen_howto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code + method:&lt;/strong&gt; &lt;a href="https://github.com/Tania-coder/SEISMOGRAPH" rel="noopener noreferrer"&gt;https://github.com/Tania-coder/SEISMOGRAPH&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need any of that to start — the four steps above work with a cron job, a CSV, and the ten lines of CUSUM. But if you want the "is it just me?" half answered, that needs more than one observer, and that's the part worth federating.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Did GPT / Claude / Gemini actually get worse recently?&lt;/strong&gt;&lt;br&gt;
Sometimes yes, sometimes it's your setup, and often it's a provider-side infra change that isn't a "worse model" at all. The point of this article is that you shouldn't answer from vibes — instrument it and get a dated signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I prove a hosted model changed?&lt;/strong&gt;&lt;br&gt;
You can't &lt;em&gt;prove&lt;/em&gt; it from one account, but you can get strong objective evidence: a frozen canary suite + a baseline + a change-point test gives you a dated shift. Provider-side vs. local drift is only separable with agreement across multiple independent observers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't this just LLM observability / evals?&lt;/strong&gt;&lt;br&gt;
Related but different. Evals score quality at a point in time; observability watches &lt;em&gt;your&lt;/em&gt; traffic. This is about detecting &lt;em&gt;change over time&lt;/em&gt; in a hosted dependency you don't control — and, uniquely, correlating that change across organizations to rule out "it's just me."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What metrics should I track?&lt;/strong&gt;&lt;br&gt;
Start with the cheapest ones that move when quality moves: schema/JSON validity, output length, and a single fixed pass/fail per canary. Add a rubric score later if you want resolution.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>monitoring</category>
      <category>devops</category>
    </item>
    <item>
      <title>Your LLM didn't get worse. It changed — and nobody told you.</title>
      <dc:creator>Tatiana Radchenko 🇩🇰</dc:creator>
      <pubDate>Thu, 02 Jul 2026 11:22:37 +0000</pubDate>
      <link>https://dev.to/taniacoder/your-llm-didnt-get-worse-it-changed-and-nobody-told-you-4ecl</link>
      <guid>https://dev.to/taniacoder/your-llm-didnt-get-worse-it-changed-and-nobody-told-you-4ecl</guid>
      <description>&lt;p&gt;It's 2am. Your JSON parse errors are up 12%. Latency: normal. Uptime: 100%. Your prompt didn't change. Your code didn't change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it you, or did the model silently change underneath you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every team building on third-party LLM APIs eventually hits this moment. And the frustrating part: your monitoring stack can't answer the question. Latency, error rates, uptime — all green. The thing that changed is &lt;em&gt;behavior&lt;/em&gt;, and behavior emits no infrastructure signal.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/Tania-coder/SEISMOGRAPH" rel="noopener noreferrer"&gt;SEISMOGRAPH&lt;/a&gt; — an open-source, privacy-preserving early-warning network for exactly this failure mode. This post covers what silent drift is, how to detect it without leaking a single prompt, and a reproducible backtest where the detector flags a real, publicly documented provider incident &lt;strong&gt;38 days before the official postmortem&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "silent drift" actually is
&lt;/h2&gt;

&lt;p&gt;Provider APIs do not broadcast behavioral changes. An endpoint that returns 200 can still start producing subtly different outputs: degraded JSON fidelity, shifted response-length distributions, changed reasoning patterns.&lt;/p&gt;

&lt;p&gt;This isn't hypothetical. In September 2025, Anthropic published a detailed postmortem describing &lt;strong&gt;three infrastructure bugs&lt;/strong&gt; that silently degraded Claude output quality across August–September 2025. The most long-lived one: a context-window routing error, introduced on August 5, that misrouted a fraction of Claude Sonnet 4 requests to servers configured for the 1M-token context window. It started at ~0.8% of traffic. On August 29 a load-balancer change escalated it to ~16%. The public postmortem landed on September 17.&lt;/p&gt;

&lt;p&gt;Note what this incident was &lt;em&gt;not&lt;/em&gt;: it was not a model update, and not intentional degradation — Anthropic was explicit about that. It was infrastructure. Which is precisely the point: &lt;strong&gt;the provider's own uptime and latency dashboards stayed green the whole time.&lt;/strong&gt; The signal only existed at the semantic layer.&lt;/p&gt;

&lt;p&gt;So the question I wanted to answer in Phase 0 of the project: would a lightweight behavioral canary have caught it, and how early?&lt;/p&gt;

&lt;h2&gt;
  
  
  The detection idea: canaries + change-point statistics
&lt;/h2&gt;

&lt;p&gt;The approach is deliberately boring:&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;canary suite&lt;/strong&gt; — a fixed, content-addressed set of prompts (≤200, temperature 0) — runs on a schedule against the API you depend on. Deterministic prompts against a deterministic-ish endpoint give you a behavioral baseline. Every suite version is immutably hash-addressed, so a baseline can never silently mutate either.&lt;/p&gt;

&lt;p&gt;Each response is reduced to &lt;strong&gt;distributional features&lt;/strong&gt;: JSON parse success rate, output length, result counts. Not the text — the shape of the behavior.&lt;/p&gt;

&lt;p&gt;Those features feed a &lt;strong&gt;Page-CUSUM change-point detector&lt;/strong&gt; per &lt;code&gt;(model_tuple, metric_name)&lt;/code&gt; stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S+(n) = max(0, S+(n-1) + z(n) - k)    # upward shifts
S-(n) = max(0, S-(n-1) - z(n) - k)    # downward shifts
Alert when S+ or S- &amp;gt; h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CUSUM accumulates small standardized deviations from a learned baseline (mu0, sigma0 from the first 30 observations). That's what makes it good at this job: a 0.8% fault doesn't produce a visible spike on any single day, but it produces a &lt;em&gt;persistent small negative bias&lt;/em&gt; — and CUSUM integrates exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The privacy boundary (the part that makes federation possible)
&lt;/h2&gt;

&lt;p&gt;One org's canary is a smoke detector. A &lt;em&gt;network&lt;/em&gt; of canaries across independent orgs is an early-warning system — if orgs are willing to participate. They're only willing if participation leaks nothing.&lt;/p&gt;

&lt;p&gt;So the probe enforces a hard perimeter. &lt;strong&gt;Raw prompts and outputs never leave your infrastructure.&lt;/strong&gt; What gets transmitted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SHA-256 hash of each response (not the response)&lt;/li&gt;
&lt;li&gt;DP-noised aggregates: &lt;code&gt;json_success_rate&lt;/code&gt;, &lt;code&gt;avg_output_length&lt;/code&gt; (Laplace mechanism, ε = 2.0 per flush)&lt;/li&gt;
&lt;li&gt;the content-addressed canary suite version hash&lt;/li&gt;
&lt;li&gt;a pseudonymous Ed25519 public key — batches are signed, org identity is not disclosed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the alerting side has a matching rule: &lt;strong&gt;a single organization's signal is never promoted to a public alert.&lt;/strong&gt; A quorum scorer requires ≥ 2 independent orgs to agree on the same &lt;code&gt;(model, metric)&lt;/code&gt; drift before anything goes public. That one gate filters probe bugs, network hiccups, and Sybil injection in a single move — and it means your private fleet data stays private fleet data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proof: a reproducible backtest
&lt;/h2&gt;

&lt;p&gt;Here's the Phase 0 experiment (seeded, fully reproducible — &lt;code&gt;SEED=42&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;Reconstruct the Aug–Sep 2025 incident timeline from the public postmortem as synthetic daily probe traffic:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Dates&lt;/th&gt;
&lt;th&gt;json_success_rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;Jul 1 – Aug 4&lt;/td&gt;
&lt;td&gt;0.990&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phase 1 (0.8% misrouting)&lt;/td&gt;
&lt;td&gt;Aug 5 – Aug 28&lt;/td&gt;
&lt;td&gt;0.982&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phase 2 (16% misrouting)&lt;/td&gt;
&lt;td&gt;Aug 29 – Sep 17&lt;/td&gt;
&lt;td&gt;0.840&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Feed it through the exact CUSUM detector from the codebase (&lt;code&gt;h=5.0, k=0.5&lt;/code&gt;). Watch when it fires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;Date&lt;/span&gt;        &lt;span class="k"&gt;Phase&lt;/span&gt;          &lt;span class="k"&gt;rate&lt;/span&gt;    &lt;span class="k"&gt;S&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;      &lt;span class="k"&gt;note&lt;/span&gt;
&lt;span class="ld"&gt;2025-08-03&lt;/span&gt;  &lt;span class="k"&gt;Stable&lt;/span&gt;         &lt;span class="mf"&gt;0.9902&lt;/span&gt;  &lt;span class="mf"&gt;0.000&lt;/span&gt;
&lt;span class="ld"&gt;2025-08-04&lt;/span&gt;  &lt;span class="k"&gt;Stable&lt;/span&gt;         &lt;span class="mf"&gt;0.9881&lt;/span&gt;  &lt;span class="mf"&gt;0.000&lt;/span&gt;
&lt;span class="ld"&gt;2025-08-05&lt;/span&gt;  &lt;span class="k"&gt;Phase&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="mf"&gt;0.9855&lt;/span&gt;  &lt;span class="mf"&gt;0.598&lt;/span&gt;   &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="k"&gt;bug&lt;/span&gt; &lt;span class="k"&gt;introduced&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="ld"&gt;2025-08-06&lt;/span&gt;  &lt;span class="k"&gt;Phase&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="mf"&gt;0.9857&lt;/span&gt;  &lt;span class="mf"&gt;1.142&lt;/span&gt;
&lt;span class="ld"&gt;2025-08-07&lt;/span&gt;  &lt;span class="k"&gt;Phase&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="mf"&gt;0.9786&lt;/span&gt;  &lt;span class="mf"&gt;3.309&lt;/span&gt;
&lt;span class="ld"&gt;2025-08-08&lt;/span&gt;  &lt;span class="k"&gt;Phase&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="mf"&gt;0.9877&lt;/span&gt;  &lt;span class="mf"&gt;3.396&lt;/span&gt;
&lt;span class="ld"&gt;2025-08-09&lt;/span&gt;  &lt;span class="k"&gt;Phase&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="mf"&gt;0.9816&lt;/span&gt;  &lt;span class="mf"&gt;4.889&lt;/span&gt;
&lt;span class="ld"&gt;2025-08-10&lt;/span&gt;  &lt;span class="k"&gt;Phase&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="mf"&gt;0.9777&lt;/span&gt;  &lt;span class="mf"&gt;7.278&lt;/span&gt;   &lt;span class="err"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;FIRST&lt;/span&gt; &lt;span class="k"&gt;ALERT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First alert: &lt;strong&gt;August 10&lt;/strong&gt; — five days into the subtle 0.8% window.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;19 days before&lt;/strong&gt; the escalation made the problem visible to users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;38 days before&lt;/strong&gt; the official postmortem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can re-run this yourself: &lt;code&gt;python scripts/anthropic_backtest.py&lt;/code&gt; in the repo regenerates the full report and asserts the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this does and doesn't prove
&lt;/h3&gt;

&lt;p&gt;I want to be precise here, because this is where drift-detection claims usually get slippery:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;This is a backtest on synthetic data&lt;/strong&gt;, not a live catch. The timeline and magnitudes come from the public postmortem; real probe noise may differ, and real lead time could be shorter or longer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's a single simulated observer.&lt;/strong&gt; A real public alert requires the ≥2-org quorum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The simulation omits live DP noise&lt;/strong&gt;, which adds variance and may delay an alert by a few days.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What it &lt;em&gt;does&lt;/em&gt; prove: a metric as cheap as "did the JSON parse?" — measured consistently, at temperature 0, against a fixed suite — carries enough signal to surface a 0.8%-of-traffic infrastructure fault weeks before it becomes user-visible. Conventional monitoring is structurally blind to this class of failure. A $0.10/day canary is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture in one pass
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your infra                          public
┌─────────────────────────┐
│ probe SDK               │
│  canary suite (≤200,    │
│  temp 0, hash-addressed)│
│  → features + DP noise  │
│  → Ed25519 signature    │
└──────────┬──────────────┘
           ▼
   ingestion gateway (FastAPI)
   schema validation, signature check
           ▼
   CUSUM per (model, metric)  →  local alert (private)
           ▼
   quorum scorer (≥2 orgs)    →  public drift alert
           ▼
   "model weather" dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stack: Python probe SDK (OpenTelemetry GenAI semantic conventions), FastAPI gateway, SQLAlchemy storage, CUSUM + Bayesian online change-point detection, 127 tests, ruff-clean CI. Apache-2.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The public &lt;strong&gt;"model weather" dashboard&lt;/strong&gt; (no login) shows live drift status for four production models:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://seismograph-weather.onrender.com/dashboard" rel="noopener noreferrer"&gt;seismograph-weather.onrender.com/dashboard&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(free host — first load may take ~30s)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Run the probe against your own endpoint:&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;seismograph-probe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or clone the whole stack — gateway, dashboard, the federated quorum demo (&lt;code&gt;scripts/demo_simulation.py&lt;/code&gt; shows two orgs independently confirming a drift and the public dashboard flipping only when quorum is met):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Tania-coder/SEISMOGRAPH" rel="noopener noreferrer"&gt;github.com/Tania-coder/SEISMOGRAPH&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've been burned by a silent model change — I'd like to hear the story. That failure mode is exactly what this network exists to catch, and every additional independent observer makes the signal stronger.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;SEISMOGRAPH is open source (Apache-2.0), archived at &lt;a href="https://doi.org/10.5281/zenodo.21045517" rel="noopener noreferrer"&gt;DOI 10.5281/zenodo.21045517&lt;/a&gt;. Built by &lt;a href="https://github.com/Tania-coder" rel="noopener noreferrer"&gt;Tatiana Radchenko&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>observability</category>
    </item>
    <item>
      <title>SEISMOGRAPH now has a live public dashboard</title>
      <dc:creator>Tatiana Radchenko 🇩🇰</dc:creator>
      <pubDate>Wed, 24 Jun 2026 12:04:48 +0000</pubDate>
      <link>https://dev.to/taniacoder/seismograph-now-has-a-live-public-dashboard-nhj</link>
      <guid>https://dev.to/taniacoder/seismograph-now-has-a-live-public-dashboard-nhj</guid>
      <description>&lt;p&gt;When I wrote about detecting silent LLM provider drift, the dashboard was localhost-only — you had to take my word for it. Now it's live and you can touch it:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://seismograph-weather.onrender.com/dashboard" rel="noopener noreferrer"&gt;https://seismograph-weather.onrender.com/dashboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It shows real-time "model weather" for 4 production models (GPT-4o, GPT-4o-mini, Claude 3.5 Sonnet, Claude 3 Haiku) — no login. The same engine flagged the Claude 3.5 Sonnet degradation 38 days before the official postmortem on a reproducible backtest.&lt;/p&gt;

&lt;p&gt;Open-source, Apache-2.0, 107 tests.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install seismograph-probe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Tania-coder/SEISMOGRAPH" rel="noopener noreferrer"&gt;https://github.com/Tania-coder/SEISMOGRAPH&lt;/a&gt; — building in public, feedback welcome.&lt;br&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%2Fl327p4l00scdk8ag9fue.png" 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%2Fl327p4l00scdk8ag9fue.png" alt=" " width="799" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>I've been building SEISMOGRAPH for 3 weeks. Here's what shipped today</title>
      <dc:creator>Tatiana Radchenko 🇩🇰</dc:creator>
      <pubDate>Tue, 23 Jun 2026 20:42:47 +0000</pubDate>
      <link>https://dev.to/taniacoder/ive-been-building-seismograph-for-3-weeks-heres-what-shipped-today-401h</link>
      <guid>https://dev.to/taniacoder/ive-been-building-seismograph-for-3-weeks-heres-what-shipped-today-401h</guid>
      <description>&lt;p&gt;tl;dr: pip install seismograph-probe — a Python probe that detects silent LLM API drift using CUSUM change-point detection, with privacy-preserving signal aggregation. 103 tests passing. Dashboard live. Open source.&lt;/p&gt;

&lt;p&gt;Three weeks ago I asked a question I couldn't answer:&lt;/p&gt;

&lt;p&gt;"Did GPT-4 just change underneath me, or is it my prompt?"&lt;/p&gt;

&lt;p&gt;No latency spike. No downtime. Just subtly different outputs from the same prompts, same parameters, same everything. I spent days debugging something that wasn't my fault.&lt;/p&gt;

&lt;p&gt;So I built a detector.&lt;/p&gt;

&lt;p&gt;Today I'm shipping it publicly.&lt;/p&gt;

&lt;p&gt;What's actually working right now&lt;br&gt;
This isn't a concept post. Here's what's live:&lt;br&gt;
The probe SDK — on PyPI today&lt;br&gt;
pip install seismograph-probe&lt;/p&gt;

&lt;p&gt;from probe.sdk import ProbeSDK&lt;/p&gt;

&lt;p&gt;sdk = ProbeSDK(provider="openai", model="gpt-4-turbo")&lt;/p&gt;

&lt;p&gt;result = sdk.run_canary_suite()&lt;/p&gt;

&lt;p&gt;print(result.drift_score)  # 0.0 stable → 1.0 significant shift&lt;/p&gt;

&lt;p&gt;The probe runs ≤200 canary prompts at temperature=0 daily. These are semantically stable tasks — deterministic questions, structured reasoning, format-adherence checks. The goal is a reliable behavioral baseline, not a capability benchmark.&lt;/p&gt;

&lt;p&gt;Privacy boundary: raw prompts and model outputs never leave your machine. The probe extracts SHA-256 feature hashes, distributional stats, and DP-noised aggregates. That's all that transmits.&lt;br&gt;
CUSUM change-point detection — running&lt;br&gt;
The correlation engine uses CUSUM (Cumulative Sum) — a sequential statistical test that's sensitive to gradual drift, not just threshold crossings.&lt;/p&gt;

&lt;p&gt;When I backtest against a known LLM behavioral shift event (Aug–Sep 2025):&lt;/p&gt;

&lt;p&gt;Day 0:   CUSUM statistic: 0.12  (stable baseline)&lt;/p&gt;

&lt;p&gt;Day 11:  First elevation detected&lt;/p&gt;

&lt;p&gt;Day 19:  Alert threshold crossed  ← SEISMOGRAPH fires&lt;/p&gt;

&lt;p&gt;Day 57:  Public postmortem published&lt;/p&gt;

&lt;p&gt;38-day lead time. That's the number I keep coming back to.&lt;br&gt;
Ingestion gateway — deployed&lt;br&gt;
FastAPI gateway with:&lt;/p&gt;

&lt;p&gt;Ed25519-signed batch verification (unsigned batches rejected atomically)&lt;br&gt;
Pydantic v2 schema validation&lt;br&gt;
SQLAlchemy ORM + SQLite (ClickHouse migration planned for Phase 2)&lt;br&gt;
Bearer token auth on audit export endpoint&lt;br&gt;
Public dashboard — live at localhost, hosted version coming&lt;br&gt;
Dark-mode model weather dashboard. Polls /v1/weather every 60 seconds. Shows per-model drift status across your fleet.&lt;/p&gt;

&lt;p&gt;GET /v1/weather&lt;/p&gt;

&lt;p&gt;→ [{ "model": "gpt-4-turbo", "status": "STABLE", ... },&lt;/p&gt;

&lt;p&gt;{ "model": "claude-3-5-sonnet", "status": "STABLE", ... }]&lt;br&gt;
Test suite — 103/103 passing&lt;br&gt;
Not "it works on my machine." 103 tests across probe SDK, storage layer, gateway, CUSUM detector, privacy boundary, and auth. Zero ruff violations across 22 Python files.&lt;br&gt;
Provider ToS compliance — checked&lt;br&gt;
Before adding any provider to the canary suite, I verify it doesn't violate their Terms of Service. Done for: OpenAI ✅, Anthropic ✅, Google Gemini ✅, Mistral ✅, Cohere ✅. Documented in docs/PROVIDER_TOS_CHECKS.md.&lt;/p&gt;

&lt;p&gt;What's NOT done yet (being honest)&lt;br&gt;
No hosted gateway yet. The gateway runs locally. Public ingestion endpoint is Phase 1.&lt;br&gt;
No Bayesian online detector yet. CUSUM is running. BayesianOnlineDetector.update() is deferred — it's on the backlog.&lt;br&gt;
No federation yet. Right now it's single-org. The cross-observer agreement scoring that makes it genuinely valuable is Phase 2.&lt;br&gt;
No cloud dashboard. localhost:8000 only for now.&lt;/p&gt;

&lt;p&gt;This is Phase 0: I'm proving the detection logic works before scaling it.&lt;/p&gt;

&lt;p&gt;The architecture in one diagram&lt;br&gt;
Your app&lt;/p&gt;

&lt;p&gt;│  (gen_ai.* OTel spans)&lt;/p&gt;

&lt;p&gt;▼&lt;/p&gt;

&lt;p&gt;ProbeSDK&lt;/p&gt;

&lt;p&gt;│  SHA-256 hashes + DP-noised stats only&lt;/p&gt;

&lt;p&gt;│  Ed25519-signed batch&lt;/p&gt;

&lt;p&gt;▼&lt;/p&gt;

&lt;p&gt;Ingestion Gateway (FastAPI)&lt;/p&gt;

&lt;p&gt;│  signature check → schema validation → store&lt;/p&gt;

&lt;p&gt;▼&lt;/p&gt;

&lt;p&gt;SQLite / ClickHouse&lt;/p&gt;

&lt;p&gt;│&lt;/p&gt;

&lt;p&gt;▼&lt;/p&gt;

&lt;p&gt;CUSUM Detector ──► DriftAlert&lt;/p&gt;

&lt;p&gt;│&lt;/p&gt;

&lt;p&gt;▼&lt;/p&gt;

&lt;p&gt;/v1/weather dashboard&lt;/p&gt;

&lt;p&gt;OTel-native throughout. If you're already emitting gen_ai.* spans, the adapter plugs straight in.&lt;/p&gt;

&lt;p&gt;Why this matters (and why it has to be federated)&lt;br&gt;
A single organization's drift signal is almost useless. Your outputs change because your users change. Your prompts change. Your context windows change.&lt;/p&gt;

&lt;p&gt;But if 15 independent organizations running the same canary suite all see correlated semantic drift on the same day — that's a model change. That's the signal you can act on.&lt;/p&gt;

&lt;p&gt;Single-org signal = private fleet data (yours only).&lt;br&gt;
Multi-org correlated signal = public drift alert.&lt;/p&gt;

&lt;p&gt;That's the design. Federation is Phase 2. The local probe is shippable today.&lt;/p&gt;

&lt;p&gt;Try it / follow along&lt;br&gt;
GitHub: github.com/Tania-coder/SEISMOGRAPH&lt;br&gt;
PyPI: pypi.org/project/seismograph-probe&lt;/p&gt;

&lt;p&gt;If you've been burned by a silent model change — I want to hear about it. Open an issue, or find me on Twitter @tatyanti.&lt;/p&gt;

&lt;p&gt;The probe is Apache 2.0. The gateway will be too.&lt;/p&gt;

&lt;p&gt;Tatiana Radchenko · AI Infrastructure · Aarhus, Denmark&lt;br&gt;
Building in public. Phase 0 of 3.&lt;/p&gt;

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