<?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: Cuepreem</title>
    <description>The latest articles on DEV Community by Cuepreem (@cuepreem).</description>
    <link>https://dev.to/cuepreem</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%2F4185799%2F4af3d7bf-4dbf-457d-9550-2458b8419108.png</url>
      <title>DEV Community: Cuepreem</title>
      <link>https://dev.to/cuepreem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cuepreem"/>
    <language>en</language>
    <item>
      <title>Telling a cheap model to "think a little" made it slower and sometimes silent</title>
      <dc:creator>Cuepreem</dc:creator>
      <pubDate>Thu, 15 Oct 2026 03:49:54 +0000</pubDate>
      <link>https://dev.to/cuepreem/telling-a-cheap-model-to-think-a-little-made-it-slower-and-sometimes-silent-474j</link>
      <guid>https://dev.to/cuepreem/telling-a-cheap-model-to-think-a-little-made-it-slower-and-sometimes-silent-474j</guid>
      <description>&lt;p&gt;I told a model to think less, and it started thinking.&lt;/p&gt;

&lt;p&gt;That's the whole bug. But how I got there is worth writing down, because anyone putting one endpoint in front of several model families will probably hit some version of it.&lt;/p&gt;

&lt;p&gt;The setup: I build a desktop app that can talk to a model three ways. A local model through Ollama, the user's own API key, or cloud credits that go through a small gateway I run (a Cloudflare Worker) in front of OpenRouter. In that last mode the app doesn't know which real model it's using. It asks for a virtual label like &lt;code&gt;cuepreem-cloud-fast&lt;/code&gt;, and the gateway decides what that means today. So anything model-specific has to happen in the gateway, because it's the only place that knows the real model name.&lt;/p&gt;

&lt;p&gt;One model-specific thing it does is cap reasoning. Some models think before they answer by default, and that thinking comes out of the same &lt;code&gt;max_tokens&lt;/code&gt; budget as the answer. Give one of them a tight budget and it can spend all of it thinking and hand back an empty string, with the tokens still billed. So the gateway checks the model name and, for models that reason, adds a cap. Trimmed down, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildUpstreamBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spec&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;body&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="c1"&gt;// The client only sends a virtual label, so only the gateway can decide this.&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nf"&gt;isReasoningModel&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="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;effort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&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="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;isReasoningModel&lt;/code&gt; was a regex over model names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before (trimmed)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sr"&gt;/gpt-oss|deepseek-r1|qwq|magistral|gemini-2&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;5/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Gemini 2.5 models I had in mind think by default, so &lt;code&gt;gemini-2\.5&lt;/code&gt; looked right.&lt;/p&gt;

&lt;p&gt;Then I moved the fast lane to Gemini 2.5 Flash-Lite. It's cheap, it's quick, and it doesn't think unless you ask it to. Which is exactly what was happening. &lt;code&gt;google/gemini-2.5-flash-lite&lt;/code&gt; contains &lt;code&gt;gemini-2.5&lt;/code&gt;, so the gateway sent it &lt;code&gt;reasoning: { effort: "low" }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the part I had wrong in my head. I'd been treating &lt;code&gt;effort&lt;/code&gt; as a dial that only turns down: "low" means "less than you'd normally do". For a model that thinks by default, that's roughly true. For a model that doesn't, there's nothing to turn down. Any effort value is a request to start. "Low" meant "some", and some is more than none.&lt;/p&gt;

&lt;p&gt;To confirm it, I made a small test call with &lt;code&gt;max_tokens&lt;/code&gt; set to 16. The response reported 12 reasoning tokens and an empty reply. Most of the budget went on thinking, and the user-visible answer was blank.&lt;/p&gt;

&lt;p&gt;With a roomier budget the answer can still fit after the thinking, so the failure is quieter: the reply just arrives later than it should, from the model you picked because it was fast. With a tight budget, which is what you set when you want a short answer quickly, the thinking can take all of it.&lt;/p&gt;

&lt;p&gt;That's what made it slippery. Nothing errored, and the upstream call came back 200. The symptoms were "slower than it should be" and, sometimes, nothing at all.&lt;/p&gt;

&lt;p&gt;The fix was one negative lookahead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// after (trimmed)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sr"&gt;/gpt-oss|deepseek-r1|qwq|magistral|gemini-2&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;5&lt;/span&gt;&lt;span class="se"&gt;(?!&lt;/span&gt;&lt;span class="sr"&gt;-flash-lite&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;plus a test that pins it, next to one that makes sure a non-reasoning model doesn't get the field either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;never sends an effort to Gemini 2.5 Flash-Lite — it would switch thinking ON&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildUpstreamBody&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google/gemini-2.5-flash-lite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeUndefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;leaves a non-reasoning model alone&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildUpstreamBody&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta-llama/llama-3.3-70b-instruct&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeUndefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Separately, the gateway no longer takes a 200 at its word. If the visible reply is empty once any &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; block is stripped, that's a failed call, not a delivered answer.&lt;/p&gt;

&lt;p&gt;The lookahead is the smallest fix, not the best one, and I know it. The real lesson is that I was asking the wrong question. It isn't "does this model support reasoning?" Flash-Lite does support it, and that's the problem. The question is "does this model think when nobody tells it anything?" Model families don't answer that consistently. A &lt;code&gt;-lite&lt;/code&gt; or &lt;code&gt;-mini&lt;/code&gt; variant can have the opposite default to its siblings, and a family-name regex will quietly sweep it in the day you switch to it.&lt;/p&gt;

&lt;p&gt;The better rule is to key the cap on exact model IDs you've checked, not on names that look like ones you've checked. And the cheap test that would have caught this in one call: before a model goes into a latency-sensitive lane, call it with a tiny &lt;code&gt;max_tokens&lt;/code&gt;, read the reasoning-token count, and assert the visible reply isn't empty.&lt;/p&gt;

&lt;p&gt;I ran into this building CuePreem, an interview-prep desktop app (disclosure: it's mine, and the cloud gateway is the part this post is about): &lt;a href="https://cuepreem.com/byok-interview-copilot?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=build-in-public" rel="noopener noreferrer"&gt;https://cuepreem.com/byok-interview-copilot?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=build-in-public&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run more than one model family behind one endpoint, how are you tracking which ones think by default? A hand-kept regex is all I've got, and after this I don't trust it.&lt;/p&gt;

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