<?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: Abdulsalam Abdulsalam</title>
    <description>The latest articles on DEV Community by Abdulsalam Abdulsalam (@seek3r).</description>
    <link>https://dev.to/seek3r</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%2F4157226%2Fb39adcad-5bea-4418-99b8-0088e3aa119a.png</url>
      <title>DEV Community: Abdulsalam Abdulsalam</title>
      <link>https://dev.to/seek3r</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seek3r"/>
    <language>en</language>
    <item>
      <title>Break It, Then Ask Why: Exposing a Hidden Internal Dev Console With One Flipped Flag</title>
      <dc:creator>Abdulsalam Abdulsalam</dc:creator>
      <pubDate>Fri, 02 Oct 2026 14:07:31 +0000</pubDate>
      <link>https://dev.to/seek3r/break-it-then-ask-why-exposing-a-hidden-internal-dev-console-with-one-flipped-flag-5blk</link>
      <guid>https://dev.to/seek3r/break-it-then-ask-why-exposing-a-hidden-internal-dev-console-with-one-flipped-flag-5blk</guid>
      <description>&lt;p&gt;Honestly, this one was kind of an accident.&lt;/p&gt;

&lt;p&gt;I'd been on this travel site for a couple of hours (the big booking one) and had basically nothing to show for it. Ran through the usual stuff on the endpoints I could find and none of it went anywhere. When I get to that point I tend to stop looking for a specific bug and just start breaking things to see how the app reacts, and that's how I ended up here.&lt;/p&gt;

&lt;p&gt;Quick note, I'm redacting the program, the hostnames and the real service names. It got fixed, so I'd rather keep it anonymous.&lt;/p&gt;

&lt;p&gt;One thing I always keep half an eye on is the booleans in responses. They're everywhere once you look, &lt;code&gt;debug:false&lt;/code&gt;, &lt;code&gt;isInternal:false&lt;/code&gt;, feature flags, all that. And it kind of nags at me, because the server already made the decision, so why is it bothering to send it to me? And if it's in my browser then it's mine to mess with.&lt;/p&gt;

&lt;p&gt;So I tested that in the laziest way possible. Opened Burp, set up a Match and Replace on the response body, swap &lt;code&gt;false&lt;/code&gt; for &lt;code&gt;true&lt;/code&gt;. Not one specific flag, just all of them at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;Response body&lt;/span&gt;
&lt;span class="na"&gt;Match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;Replace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a sledgehammer, I know. But I wasn't going for precision, I just wanted to see what the page would do if none of its "no" answers made it through to the browser. Turned the rule on, refreshed, and waited for something to fall over.&lt;/p&gt;

&lt;p&gt;Nothing fell over. Instead a little developer panel slid up from the bottom of the page. Took me a second to even clock what I was looking at.&lt;/p&gt;

&lt;p&gt;So then the real question was why that worked at all.&lt;/p&gt;

&lt;p&gt;When I dug into it, the console's visibility was tied to a few validation flags that in production were set to &lt;code&gt;false&lt;/code&gt;. Fine so far. The problem is where that check was happening. The server was sending the flags down and letting the frontend decide whether to render the tool. So the one thing keeping an internal console hidden was a &lt;code&gt;false&lt;/code&gt; sitting in a response I was already holding in Burp. Flip it before the JavaScript reads it and the whole thing just renders. I didn't really bypass anything, the code was already shipped to everyone's browser, that flag was just the thing standing in front of it.&lt;/p&gt;

&lt;p&gt;It's the same shape as a disabled button you can re-enable in devtools, or an &lt;code&gt;isPremium&lt;/code&gt; check that only lives in JavaScript. If the browser gets to decide, it was never actually decided.&lt;/p&gt;

&lt;p&gt;And the panel wasn't some empty stub either. It had a row of tabs, and one of them, "Service Overrides," listed internal services by name, each with an editable host field and a port next to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Service         Host           Port
ad-delivery     add override   ····/http
ad-selection    add override   ····/http
ab-testing      add override   ····/https
app-config      add override   ····/https
...and a dozen or so more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Names generalised, ports stripped. On the live site these were the real ones.)&lt;/p&gt;

&lt;p&gt;So a public homepage was basically handing out a map of the backend. Internal service names, ports, protocols, and little override hooks for repointing a service somewhere else. There were a few other tabs too, observability, GraphQL, audits, the kind of thing that's clearly only ever meant to be seen by engineers.&lt;/p&gt;

&lt;p&gt;By itself it's information disclosure, and that's how it got triaged, P4, accepted, small payout. Nothing popped directly. But for recon this stuff is gold. Normally you're guessing at how the backend is wired together, and here it was just written out for me, ports and all. Those host-override fields especially had me wanting to go spend a long afternoon looking for SSRF.&lt;/p&gt;

&lt;p&gt;What I actually take away from it is less about the bug and more about how it turned up. I wasn't being smart, I was just bored of the checklist and started breaking things, and then when something unexpected happened I didn't wave it off. That last bit is the part people skip. The app doing something weird is usually it telling you where its assumptions are thin. Most of the time you undo it and carry on. Every now and then you sit with it and it turns into a report.&lt;/p&gt;

&lt;p&gt;And if you're on the other side of this and fixing it, the short version is don't ship debug tooling to production, and if you absolutely have to, enforce it on the server where the user can't get at the switch.&lt;/p&gt;

</description>
      <category>security</category>
      <category>bugbounty</category>
      <category>webdev</category>
      <category>pentesting</category>
    </item>
    <item>
      <title>Stop Parsing LLM Output: I Built a Routing Pipeline on a Decision-Only Model</title>
      <dc:creator>Abdulsalam Abdulsalam</dc:creator>
      <pubDate>Fri, 02 Oct 2026 12:07:15 +0000</pubDate>
      <link>https://dev.to/seek3r/stop-parsing-llm-output-i-built-a-routing-pipeline-on-a-decision-only-model-m7e</link>
      <guid>https://dev.to/seek3r/stop-parsing-llm-output-i-built-a-routing-pipeline-on-a-decision-only-model-m7e</guid>
      <description>&lt;p&gt;Every LLM tutorial ends the same way. You get back a blob of text, and now you have to parse it. You beg the model for JSON. It hands you JSON wrapped in an apology. You write a regex. The regex breaks on the next prompt. You add a retry. The retry costs you another second and another fraction of a cent.&lt;/p&gt;

&lt;p&gt;So I built a small app on a model that &lt;strong&gt;cannot write a sentence&lt;/strong&gt;, and that turned out to be the entire point.&lt;/p&gt;

&lt;p&gt;Here is what I learned building a support-ticket router on a decision-only LLM.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem: your code wants a decision, the model gives you an essay
&lt;/h2&gt;

&lt;p&gt;Most "AI feature" work is not generation. It is classification and routing. Which team should this ticket go to? How urgent is it? Is this a security incident? Should a human see it?&lt;/p&gt;

&lt;p&gt;Those are decisions your code needs to branch on. A &lt;code&gt;switch&lt;/code&gt;, an &lt;code&gt;if&lt;/code&gt;, a priority sort. But a chat model answers them in prose:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This looks like a billing issue, and it seems fairly urgent given the tone, so I'd route it to..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you are parsing. You prompt for JSON, you validate the shape, you handle the time it returns &lt;code&gt;"Billing"&lt;/code&gt; instead of &lt;code&gt;"billing"&lt;/code&gt;, you retry when it wraps the object in a code fence. You have built a brittle translation layer between a text generator and a program that just wanted one value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the decision-only model
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://typesafe.ai" rel="noopener noreferrer"&gt;Jev&lt;/a&gt; (TypeSafe's "System One" model) skips the prose entirely. You give it some text and a set of typed questions, and it answers in types.&lt;/p&gt;

&lt;p&gt;The name is not an accident. In Daniel Kahneman's &lt;em&gt;Thinking, Fast and Slow&lt;/em&gt;, &lt;strong&gt;System 1&lt;/strong&gt; is the fast, automatic, intuitive mind: the part that reads a short message and just &lt;em&gt;knows&lt;/em&gt;, with no deliberate reasoning. &lt;strong&gt;System 2&lt;/strong&gt; is the slow, effortful mind you use for long division or weighing a hard choice. Most models we reach for behave like System 2: they deliberate, they explain, they take their time. Jev is deliberately System 1: a snap judgment, returned as a type, in a few hundred milliseconds. For triage you do not want a System 2 essay agonising over a ticket. You want a fast, confident reflex, and then your own code decides what to do with it.&lt;/p&gt;

&lt;p&gt;There are three primitives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;choice&lt;/code&gt;&lt;/strong&gt; picks one labelled option. You get the chosen option, a probability for every option, and a confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;score&lt;/code&gt;&lt;/strong&gt; rates against an ordered rubric. You get a number that can land &lt;em&gt;between&lt;/em&gt; the levels, weighted by the probabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;noul&lt;/code&gt;&lt;/strong&gt; is a single yes/no probability from 0 to 1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One request, in the shape your code already thinks in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/systemone&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I was charged twice this morning and support has not replied."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jev-latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"questions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"choice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"criteria"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"technical"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"abuse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"urgency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"criteria"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Can wait"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This week"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Today"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Drop everything"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"needs_human"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does this need a human agent?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the answer comes back already typed, no parsing required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"choice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"probabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"technical"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"urgency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"legend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Can wait"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This week"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Today"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Drop everything"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"needs_human"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;urgency: 2.28&lt;/code&gt;. That is not a bucket. It is the probability-weighted average across the rubric, sitting between "Today" and "Drop everything". You get the model's uncertainty as a number, for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built: a two-stage triage pipeline
&lt;/h2&gt;

&lt;p&gt;A single classification is a nice demo. A pipeline is a real app. So I wired two stages where &lt;strong&gt;the first decision chooses the second&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ticket
  -&amp;gt; Stage 1: triage   (route + urgency + needs_human)
  -&amp;gt; code reads route
  -&amp;gt; Stage 2: team-specific questions  (billing? technical? account? abuse?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A billing ticket gets billing questions. A suspected account takeover gets security questions. The model does the understanding; my code does the branching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: three decisions in one call
&lt;/h2&gt;

&lt;p&gt;The whole triage is one request. In my testing it ran in about &lt;strong&gt;0.9 seconds&lt;/strong&gt; on roughly &lt;strong&gt;500 input tokens&lt;/strong&gt;, which at Jev's pricing is a few thousandths of a cent.&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;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/decide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stage1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;questions&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;answers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No prompt engineering to force a format. No &lt;code&gt;JSON.parse&lt;/code&gt; wrapped in a try/catch. The response is the shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  The move that matters: the decision is the control flow
&lt;/h2&gt;

&lt;p&gt;Here is the part that made the whole design click. With a chat model, I would be extracting &lt;code&gt;"billing"&lt;/code&gt; out of a sentence. With a decision model, the answer is already a value I can use as a map key:&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;route&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// "billing"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stage2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;    &lt;span class="c1"&gt;// just an object lookup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. The model's output drops straight into an object lookup and becomes the next step of the program. The "AI" and the control flow are the same line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: different questions per route
&lt;/h2&gt;

&lt;p&gt;Each route points at its own question set, declared as plain data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"branches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"questions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subtype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"choice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"criteria"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"double_charge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"refund_request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"churn_risk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Is this customer at risk of cancelling?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"auto_refundable"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Can this be auto-refunded without a manager?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"questions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"issue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"choice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"criteria"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"cannot_login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"password_reset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"security_sensitive"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Could this be an account takeover?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"verify_identity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Require extra identity verification first?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a whole new route is a data change, not a code change. The engine stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part the docs do not warn you about: you still need a backend
&lt;/h2&gt;

&lt;p&gt;I wanted this to run as a static page. It cannot, for two reasons I only found by trying:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The API blocks browser origins.&lt;/strong&gt; I sent a preflight from a GitHub Pages origin and got &lt;code&gt;Disallowed CORS origin&lt;/code&gt;. I tried localhost. I tried the vendor's own app origin. All rejected. This API is server-to-server by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It needs a secret key&lt;/strong&gt;, which you can never ship inside a public page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the fix is a tiny backend. Mine is about 40 lines of Python standard library, no dependencies:&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;call_jev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&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;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JEV_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&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;Bearer &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# key lives here, never in the browser
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&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;application/json&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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&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;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser only ever talks to &lt;code&gt;127.0.0.1&lt;/code&gt;. The backend holds the key and makes the real call. No CORS problem, because the browser never touches the vendor. This is the boring, correct way to use any keyed API, and it is worth saying out loud because "just call it from the frontend" is the first thing most tutorials do wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results: what it actually does
&lt;/h2&gt;

&lt;p&gt;I ran four realistic tickets through the full two-stage pipeline. Every one routed correctly, and the second stage caught the thing that actually matters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ticket&lt;/th&gt;
&lt;th&gt;Stage 1 route&lt;/th&gt;
&lt;th&gt;Key stage-2 signals&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Charged twice, disputing with my bank"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;billing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;double_charge&lt;/code&gt; 1.00, &lt;code&gt;churn_risk&lt;/code&gt; 0.98, &lt;code&gt;auto_refundable&lt;/code&gt; 0.43 (correctly unsure, it is disputed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Checkout API returning 503, losing sales"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;technical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;severity 3.0, &lt;code&gt;is_regression&lt;/code&gt; 0.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Password and email changed, not me, locked out"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;account&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;security_sensitive&lt;/code&gt; 0.97, &lt;code&gt;verify_identity&lt;/code&gt; 0.92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"A user said he knows where I live"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;abuse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;credible_threat&lt;/code&gt; 0.81, escalation 2.95&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two live calls per ticket, roughly 1.7 seconds and about 1,000 input tokens total, well under a hundredth of a cent. The &lt;code&gt;auto_refundable&lt;/code&gt; 0.43 is my favourite output: the model is telling me it is genuinely uncertain, so route it to a human instead of auto-approving. That is a signal a parsed text label would have thrown away.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a decision model is the wrong tool
&lt;/h2&gt;

&lt;p&gt;This is not a chat model, and pretending otherwise will burn you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need generated text&lt;/strong&gt; (a reply, a summary, code). Wrong tool. Use a generative model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your categories are open-ended&lt;/strong&gt; and you cannot enumerate them. &lt;code&gt;choice&lt;/code&gt; needs options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want one freeform answer.&lt;/strong&gt; Overkill, just prompt a normal model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It shines when the model's answer becomes an &lt;code&gt;if&lt;/code&gt; or a &lt;code&gt;switch&lt;/code&gt;: routing, triage, moderation, scoring, gating, prioritisation. Anything where you were about to parse a label out of prose.&lt;/p&gt;

&lt;p&gt;The real pattern is to use both minds. Let a fast System 1 model like Jev make the snap, typed decisions, and reserve a slow System 2 model (a large reasoning or generative model) for the work that genuinely needs deliberation, like writing the actual reply to the customer. Cheap reflex up front, expensive thought only where it earns its keep. Kahneman's whole point was that a healthy mind knows which system to hand a problem to; the same is true of a system design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you reach for one? A quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Does your code branch on the model's answer?&lt;/li&gt;
&lt;li&gt;Can you enumerate the options, or define a rubric?&lt;/li&gt;
&lt;li&gt;Do you want probabilities and confidence, not just a bare label?&lt;/li&gt;
&lt;li&gt;Are you tired of coaxing and parsing JSON out of a text model?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that is mostly "yes", a decision model will make the feature smaller and more reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Chat models answer in prose you have to parse. A decision model answers in types you can branch on.&lt;/li&gt;
&lt;li&gt;Jev is a "System 1" model in Kahneman's &lt;em&gt;Thinking, Fast and Slow&lt;/em&gt; sense: fast intuition, not slow deliberation. Pair it with a System 2 model for the heavy thinking.&lt;/li&gt;
&lt;li&gt;I built a two-stage support router: one call triages, the result picks the next set of questions, and the model's output becomes an object lookup.&lt;/li&gt;
&lt;li&gt;The API blocks browsers and needs a key, so it runs behind a 40-line zero-dependency backend that keeps the key server-side.&lt;/li&gt;
&lt;li&gt;Four realistic tickets routed correctly, in about 1.7 seconds and a hundredth of a cent each, with uncertainty surfaced as numbers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code and the full pipeline are on GitHub: &lt;a href="https://github.com/abdulsalam-create/jev-prism" rel="noopener noreferrer"&gt;github.com/abdulsalam-create/jev-prism&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
