<?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: James Gurung</title>
    <description>The latest articles on DEV Community by James Gurung (@james_gurung_f0757b082535).</description>
    <link>https://dev.to/james_gurung_f0757b082535</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%2F4030353%2F052216bd-72b4-4832-91a2-c627073f844e.png</url>
      <title>DEV Community: James Gurung</title>
      <link>https://dev.to/james_gurung_f0757b082535</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/james_gurung_f0757b082535"/>
    <language>en</language>
    <item>
      <title>How I Used SigNoz to Prove My AI Incident-Triage Fallback Actually Worked</title>
      <dc:creator>James Gurung</dc:creator>
      <pubDate>Sat, 18 Jul 2026 11:38:46 +0000</pubDate>
      <link>https://dev.to/james_gurung_f0757b082535/how-i-used-signoz-to-prove-my-ai-incident-triage-fallback-actually-worked-p3e</link>
      <guid>https://dev.to/james_gurung_f0757b082535/how-i-used-signoz-to-prove-my-ai-incident-triage-fallback-actually-worked-p3e</guid>
      <description>&lt;p&gt;&lt;strong&gt;A FastAPI service, OpenTelemetry, self-hosted SigNoz, and one uncomfortable truth: HTTP 200 can lie.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My API returned HTTP 200. But the LLM it depended on had just failed.&lt;/p&gt;

&lt;p&gt;No error. No exception. The user got a response — just not the AI one. A three-sentence local fallback ran instead, and without telemetry, nobody would ever know the difference.&lt;/p&gt;

&lt;p&gt;This article shows how I built an AI incident triage agent, connected it to self-hosted SigNoz using OpenTelemetry, and used traces, logs, metrics, a dashboard, and an alert to prove exactly when the fallback ran and why.&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%2Fiv2ik9vti37usgf2b1f4.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%2Fiv2ik9vti37usgf2b1f4.png" alt=" " width="799" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;The app receives an error message and triages it into a structured incident report.&lt;br&gt;
Request:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt;&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%2F8ji5ixxp57w8xwtxs744.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%2F8ji5ixxp57w8xwtxs744.png" alt=" " width="599" height="36"&gt;&lt;/a&gt;&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%2Ft5pt2qwfliawwk18c0vh.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%2Ft5pt2qwfliawwk18c0vh.png" alt=" " width="599" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&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%2F0yu2riqzuh4rw6rwvxep.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%2F0yu2riqzuh4rw6rwvxep.png" alt=" " width="599" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three internal steps:&lt;/p&gt;

&lt;p&gt;1.Classify the error with keyword matching&lt;br&gt;
2.Load a matching JSON runbook&lt;br&gt;
3.Call OpenRouter to generate an AI explanation&lt;/p&gt;

&lt;p&gt;If the LLM is unavailable, a local fallback generates a shorter explanation from the runbook. Both paths return the same five-field response — that is the hidden problem. You cannot tell which path ran just by looking at the response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&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%2Fnlbdf9fabc2z37r8cgy0.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%2Fnlbdf9fabc2z37r8cgy0.png" alt=" " width="597" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The service name incident-triage-warmup ties all three signals together in SigNoz. One name, three views of the same application.&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%2Fwcnbtznuj23okroufuoo.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%2Fwcnbtznuj23okroufuoo.png" alt=" " width="798" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Normal Mode — LLM Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start the app:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd blog&lt;br&gt;
set -a; source .env; set +a&lt;br&gt;
export FORCE_LLM_FAILURE=false&lt;br&gt;
.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8001&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health check:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl http://127.0.0.1:8001/health&lt;/code&gt;&lt;br&gt;
{"status":"healthy"}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send a real request:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -i -X POST 'http://127.0.0.1:8001/analyze' \&lt;br&gt;
  -H 'Content-Type: application/json' \&lt;br&gt;
  --data '{"error":"Database timeout while loading customer orders"}'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server log:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;INFO app.llm LLM request started&lt;br&gt;
INFO httpx HTTP Request: POST https://openrouter.ai/api/v1/chat/completions "HTTP/1.1 200 OK"&lt;br&gt;
INFO app.llm LLM explanation generated&lt;br&gt;
INFO app.main Analysis completed&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response&lt;/strong&gt;: HTTP/1.1 200 OK with a 702-character AI explanation. The log line confirms the real provider was called and responded.&lt;/p&gt;

&lt;p&gt;Generate 22 mixed requests:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AGENT_URL=http://127.0.0.1:8001 \&lt;br&gt;
REQUEST_COUNT=22 \&lt;br&gt;
bash blog/scripts/generate_normal_traffic.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each LLM call took several seconds, so 22 requests took a few minutes. All 22 returned HTTP 200. Zero fallback activations in the log.&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%2Fl9tqpcimg1hh6hfv8ogd.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%2Fl9tqpcimg1hh6hfv8ogd.png" alt=" " width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Mistakes Worth Knowing
&lt;/h2&gt;

&lt;p&gt;These are the real problems I hit. I am including them because they only show up when you actually run the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 1 :- The fallback script does not enable fallback mode.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;generate_fallback_traffic.sh is a curl loop. It sends HTTP requests and labels the output as "fallback scenario." It cannot reach inside a running Uvicorn process and change an environment variable. I ran it, saw 22 successes, and assumed I had proven fallback. I had proven nothing. The server was still calling OpenRouter normally.&lt;/p&gt;

&lt;p&gt;Fix: stop the server with Ctrl+C, set FORCE_LLM_FAILURE=true, restart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 2 :- Pasted a curl command without proper line breaks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I got HTTP/1.1 422 Unprocessable Entity, then -H: command not found, then -d: command not found. FastAPI rejected the request because the body was missing. The shell then tried to run -H and -d as commands.&lt;/p&gt;

&lt;p&gt;The correct version with proper escaping:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -i -X POST 'http://127.0.0.1:8001/analyze' \&lt;br&gt;
  -H 'Content-Type: application/json' \&lt;br&gt;
  --data '{"error":"Database timeout while loading customer orders"}'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 3 :- Used List View to understand one request.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I saw llm.request and fallback.generate in the same List View and assumed they belonged together. They came from different requests at different timestamps. List View searches individual spans. Trace View shows the parent-child structure of one complete request. That distinction matters when you are trying to prove causality.&lt;/p&gt;

&lt;p&gt;Testing Fallback Mode LLM Fails, API Survives&lt;br&gt;
Stop the server. Restart with the failure flag:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd blog&lt;br&gt;
set -a; source .env; set +a&lt;br&gt;
export FORCE_LLM_FAILURE=true&lt;br&gt;
.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8001&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send the same request:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -i -X POST 'http://127.0.0.1:8001/analyze' \&lt;br&gt;
  -H 'Content-Type: application/json' \&lt;br&gt;
  --data '{"error":"Database timeout while loading customer orders"}'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Response: HTTP/1.1 200 OK&lt;/p&gt;

&lt;p&gt;The ai_explanation was a short local sentence — not the LLM response. Server log:&lt;/p&gt;

&lt;p&gt;WARNING app.llm LLM unavailable; local fallback activated&lt;br&gt;
INFO app.main Analysis completed&lt;br&gt;
No openrouter.ai line appeared. The provider was never called.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generate 22 fallback requests:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AGENT_URL=&lt;a href="http://127.0.0.1:8001" rel="noopener noreferrer"&gt;http://127.0.0.1:8001&lt;/a&gt; \&lt;br&gt;
REQUEST_COUNT=22 \&lt;br&gt;
bash blog/scripts/generate_fallback_traffic.sh&lt;br&gt;
Output:&lt;/p&gt;

&lt;p&gt;Requests attempted : 22&lt;br&gt;
Requests successful: 22&lt;br&gt;
Requests failed    : 0&lt;br&gt;
Scenario           : fallback (FORCE_LLM_FAILURE=true on server)&lt;br&gt;
Log confirmed: 24 fallback activations, 0 provider calls.&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%2Fws3mdxncwrh93uq0hcoh.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%2Fws3mdxncwrh93uq0hcoh.png" alt=" " width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Favourite Feature: Distributed Tracing
&lt;/h2&gt;

&lt;p&gt;A span is one named operation. incident.classify is a span. llm.request is a span. A trace connects all the spans for one request, in order, with parent-child relationships.&lt;/p&gt;

&lt;p&gt;What I needed to see — and what Screenshot 1 shows — is this structure:&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%2Fsp4j7lx0ktmvluq1vpw1.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%2Fsp4j7lx0ktmvluq1vpw1.png" alt=" " width="402" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;llm.request failed. The application caught it, ran fallback.generate, and the complete HTTP request returned successfully. The parent span is green because the overall request completed without an unhandled exception.&lt;/p&gt;

&lt;p&gt;That is graceful degradation made visible. Without tracing, you see one HTTP 200. With tracing, you see the exact decision tree that produced it — every step, every status, every duration.&lt;/p&gt;

&lt;p&gt;This is why distributed tracing is my favourite SigNoz feature. It transforms a black box into a visible sequence of named operations. When something goes wrong inside a successful response, tracing is the only signal that shows it.&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%2Fwfvsrn1yfk1h2owx7vbv.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%2Fwfvsrn1yfk1h2owx7vbv.png" alt=" " width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Logs with Trace IDs
&lt;/h2&gt;

&lt;p&gt;The warning log tells me fallback happened. The trace tells me what happened around it. The trace_id field connects them.&lt;/p&gt;

&lt;p&gt;Searching SigNoz Log Explorer for LLM unavailable; local fallback activated returns warning entries with service.name = incident-triage-warmup and a trace_id field. One click on that ID opens the exact trace for that request.&lt;/p&gt;

&lt;p&gt;Without trace IDs, correlating a warning to its request means manually matching timestamps across two tools. With them, it is one click.&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%2F92ibdsjta9ksz3el1dxc.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%2F92ibdsjta9ksz3el1dxc.png" alt=" " width="799" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Metrics: How Often Did This Happen?
&lt;/h2&gt;

&lt;p&gt;Traces answer one request. Logs answer one event. Metrics answer the whole window.&lt;/p&gt;

&lt;p&gt;The key counter is triage_fallback_total — it increments exactly once per fallback request. After 22 fallback requests, it shows a clear spike. After 22 normal requests, it stays flat. That difference is the measurement that proves the experiment worked at scale, not just for one manual curl.&lt;/p&gt;

&lt;p&gt;Other instruments I created:&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%2Fq8loky5eu5pus0z5g5k6.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%2Fq8loky5eu5pus0z5g5k6.png" alt=" " width="653" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One practical note: metrics export every 15 seconds. If you check SigNoz immediately after sending requests, data may not appear yet. Wait one export interval before concluding the metric is missing.&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%2F26gsa4b57zyxsige7590.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%2F26gsa4b57zyxsige7590.png" alt=" " width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard: One View for Everything
&lt;/h2&gt;

&lt;p&gt;I created a dashboard named AI Triage Reliability with five panels.&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%2Fvgtpl5wwqy0offi6256f.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%2Fvgtpl5wwqy0offi6256f.png" alt=" " width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The P95 duration panel tells the most interesting story. In normal mode, every request waits several seconds for the OpenRouter API. In fallback mode, the pipeline completes in milliseconds because no external call is made. That difference shows up as two completely separate bands on the same chart — without reading a single log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alert: Don't Let Fallback Hide an Outage
&lt;/h2&gt;

&lt;p&gt;Fallback protects the user. But it must not silently hide a broken LLM provider. The engineering team needs to know when the AI layer is unhealthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alert configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name:&lt;/strong&gt; AI Incident Agent Using Local Fallback&lt;br&gt;
&lt;strong&gt;Metric:&lt;/strong&gt; triage_fallback_total&lt;br&gt;
&lt;strong&gt;Condition:&lt;/strong&gt; greater than 0&lt;br&gt;
&lt;strong&gt;Evaluation window:&lt;/strong&gt; 5 minutes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger it:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AGENT_URL=http://127.0.0.1:8001 \&lt;br&gt;
REQUEST_COUNT=10 \&lt;br&gt;
bash blog/scripts/generate_fallback_traffic.sh&lt;br&gt;
Wait one evaluation window, then check the Alerts page.&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;HTTP 200 does not mean every internal step succeeded&lt;/strong&gt;. It means the HTTP layer succeeded. The AI layer can fail silently behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A fallback script cannot change a running server's environment&lt;/strong&gt;. You must stop and restart with the new variable set. This cost me real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List View finds spans. Trace View explains a request.&lt;/strong&gt; They are different tools for different questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A parent span can succeed while a child span fails.&lt;/strong&gt; That is graceful degradation — not a telemetry bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logs with trace IDs are worth far more than logs without them.&lt;/strong&gt; One field turns a search into a one-click jump to the exact request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metrics show trends. Traces show causes.&lt;/strong&gt; After the fallback ran 24 times, metrics told me the scale. Traces told me why each one happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An alert on triage_fallback_total &amp;gt; 0&lt;/strong&gt; turns silent degradation into a visible incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A reliable AI application needs more than fallback code. It needs telemetry that proves when the fallback was used, why it was used, and whether the user remained protected.&lt;/p&gt;

&lt;p&gt;Every piece of that story was visible in SigNoz. The trace showed the failed LLM call, the successful fallback, and the successful HTTP response together. The log confirmed the warning with a trace ID attached. The metric showed the count growing across 22 requests. The alert fired when the threshold crossed.&lt;/p&gt;

&lt;p&gt;Distributed tracing was my favourite SigNoz feature. It transformed "the API returned 200" into "here is exactly what happened inside that request, in order, with a status for every step." That is the difference between assuming the system worked and proving it.&lt;/p&gt;

&lt;p&gt;Code:- &lt;a href="https://github.com/jmass-ggg/signoz-silent-llm-fallback.git" rel="noopener noreferrer"&gt;https://github.com/jmass-ggg/signoz-silent-llm-fallback.git&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
