<?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: GetAskAI - Free Ask AI</title>
    <description>The latest articles on DEV Community by GetAskAI - Free Ask AI (@getaskai).</description>
    <link>https://dev.to/getaskai</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%2F4034230%2F7fa4ef5f-52f9-4e4b-a594-517d5b0c9b18.png</url>
      <title>DEV Community: GetAskAI - Free Ask AI</title>
      <link>https://dev.to/getaskai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/getaskai"/>
    <language>en</language>
    <item>
      <title>Stop Using a Keyword List to Decide When Your Chatbot Searches the Web</title>
      <dc:creator>GetAskAI - Free Ask AI</dc:creator>
      <pubDate>Sun, 06 Sep 2026 10:10:11 +0000</pubDate>
      <link>https://dev.to/getaskai/stop-using-a-keyword-list-to-decide-when-your-chatbot-searches-the-web-3ccj</link>
      <guid>https://dev.to/getaskai/stop-using-a-keyword-list-to-decide-when-your-chatbot-searches-the-web-3ccj</guid>
      <description>&lt;p&gt;A keyword list is the wrong shape for routing a chatbot to web search. It matches fragments instead of meaning, it breaks the moment you support more than one language, and it fails silently in the expensive direction: the question that needed a lookup and did not get one. Let the model make the call instead, with a short prompt built on an inverted default. Here is the bug that taught me this, and the prompt shape that replaced it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last updated: September 2026.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;The chat had a cheap gate before the expensive part. If the message contained a trigger word, run a web search and answer from the results. Otherwise answer directly. The list held the obvious things: price, weather, today, score, plus the currency words for every language the product supports.&lt;/p&gt;

&lt;p&gt;One of those was the Turkish word for exchange rate: &lt;code&gt;kur&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The German word for "briefly" is &lt;code&gt;kurz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A user wrote &lt;strong&gt;"Sag kurz Hallo"&lt;/strong&gt;, roughly "say a quick hello". The substring match found &lt;code&gt;kur&lt;/code&gt; inside &lt;code&gt;kurz&lt;/code&gt;, the gate fired, and the system paid for a web search in order to answer a greeting.&lt;/p&gt;

&lt;p&gt;That is a funny bug. The unfunny part is what it revealed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the list was never going to work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Substring matching has no word boundaries across languages.&lt;/strong&gt; You can bolt on tokenisation, but you are now maintaining locale-aware word splitting for something that was supposed to be a cheap shortcut.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is always wrong in both directions.&lt;/strong&gt; It fires on greetings and stays silent on "is my residence permit application still processed the old way", where being a year out of date is genuinely harmful. Adding words fixes today's miss and creates tomorrow's false positive. I went three rounds of this before admitting the shape was wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It scales with languages, not with logic.&lt;/strong&gt; Eleven languages meant eleven sets of triggers, and every new language meant another translation pass by someone guessing which words natives actually type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It cannot see intent.&lt;/strong&gt; "Explain inflation" and "what is inflation right now" share their meaningful words. Only one needs a lookup.&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%2Fl43ozkkx18e25jycc1pe.jpg" 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%2Fl43ozkkx18e25jycc1pe.jpg" alt="Keyword gate versus model-routed search: where each one breaks&lt;br&gt;
" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Let the model decide, and give it a shape it can follow. Two mechanically different options depending on your stack:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool calling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expose a &lt;code&gt;search_web&lt;/code&gt; tool; the model either answers directly or calls the tool with a query it wrote itself&lt;/td&gt;
&lt;td&gt;Your model supports function calling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cheap classifier hop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One small call returns either a search query or the literal token &lt;code&gt;NO_SEARCH&lt;/code&gt;, then you branch&lt;/td&gt;
&lt;td&gt;No tool calling, or you want the router on a different model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both share the property that made the difference: the model reads the whole sentence instead of pattern matching on fragments. It also writes a better query than the user's raw message, which turned out to be a larger quality win than the routing decision itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt shape that worked
&lt;/h2&gt;

&lt;p&gt;This is the part I got wrong twice, so it is the part worth copying.&lt;/p&gt;

&lt;p&gt;My first instinct was a detailed, categorised list of what deserves a search: prices, weather, sports, news, regulations, each with examples. The score went &lt;strong&gt;down&lt;/strong&gt;. A small routing model gets lost in a long taxonomy.&lt;/p&gt;

&lt;p&gt;What worked was shorter and inverted. Around two hundred words with three moves:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set the default to search.&lt;/strong&gt; Not "search when needed" but "search unless the question is on this closed list".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the exception list closed and concrete.&lt;/strong&gt; Arithmetic, greetings, translation, grammar, code, creative writing, summarising text the user pasted in, and textbook facts that are the same in every country and every year. Everything else is a search.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give two decision tests instead of categories.&lt;/strong&gt; Does the answer change depending on the user's country or the current year? And what does a wrong answer cost this person: money, health, legal standing, safety, a missed deadline? If either trips, search.&lt;/p&gt;

&lt;p&gt;Then one closing line that matters more than it looks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Search these even if you think you already know the answer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The inversion is the trick. A list of things to search is infinite and you will always be adding to it. A list of things &lt;em&gt;not&lt;/em&gt; to search is small, stable, and a model can hold it in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure it, or you are guessing
&lt;/h2&gt;

&lt;p&gt;Build a fixed evaluation set before you touch the prompt. Mine is thirty questions: fifteen that must search and fifteen that must not, spread across several languages and domains.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Must search&lt;/th&gt;
&lt;th&gt;Must not search&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Family law question with a country in it&lt;/td&gt;
&lt;td&gt;Greeting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rent or tenancy rule&lt;/td&gt;
&lt;td&gt;Arithmetic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visa procedure&lt;/td&gt;
&lt;td&gt;Translation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drug interaction&lt;/td&gt;
&lt;td&gt;Grammar correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live exchange rate&lt;/td&gt;
&lt;td&gt;Code question&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A specific local business&lt;/td&gt;
&lt;td&gt;Write me a poem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product comparison&lt;/td&gt;
&lt;td&gt;Textbook fact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax or fee amount&lt;/td&gt;
&lt;td&gt;Summarise this pasted text&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run the whole set after every prompt change. Two things this catches that spot checks never do: a change that fixes your new case and breaks two old ones, and a router that has quietly become search-happy, which always looks correct if you only inspect the questions that should search.&lt;/p&gt;

&lt;p&gt;Both of my "improvements" scored worse than the version before them. I would not have known without the set.&lt;/p&gt;

&lt;p&gt;For scale, in production this router now sends &lt;strong&gt;18.1 percent of questions&lt;/strong&gt; to a live search over a 60 day window of 5,977 answers. The remaining 82 percent are answered directly, which is the entire economic point of routing rather than searching on everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things I would not do again
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do not put a time budget inside the router.&lt;/strong&gt; I briefly capped the routing stage so it could not slow the reply down. What it actually did was make the system give up early and fall through to the expensive path anyway. Correctness first, then optimise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not let a strong style prompt fight the router.&lt;/strong&gt; The main system prompt tells the model to be direct and expert. That instruction was competing with routing and pushing the model toward answering from memory. If the router shares a prompt with the personality, the personality wins. Give the routing decision its own explicit sentence rather than assuming the tool description covers it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general lesson
&lt;/h2&gt;

&lt;p&gt;The keyword list was not too small. It was the wrong kind of thing. Any rule that pattern matches on fragments of language will fail across languages, and it will fail silently in the direction that costs the most.&lt;/p&gt;

&lt;p&gt;If you are building anything that chooses between a cheap path and an expensive one based on what the user wrote, the model is a better judge than your list, provided you give it a short prompt with an inverted default and a closed set of exceptions.&lt;/p&gt;

&lt;p&gt;And keep the greeting in your test set. That is how I found out.&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%2Fnlvv8ke371z7aced6l48.jpg" 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%2Fnlvv8ke371z7aced6l48.jpg" alt="A thirty question evaluation set: fifteen that must trigger a search and fifteen that must not&lt;br&gt;
" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is from building GetAskAI. GetAskAI is a free, ad-supported AI chat that works without an account. It answers in 11 languages, searches the web and shows numbered sources when a question needs current information, and reads PDFs and images without storing them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>lessonslearned</category>
    </item>
    <item>
      <title>How I built a free, unlimited, no-login AI chat and kept abuse under control</title>
      <dc:creator>GetAskAI - Free Ask AI</dc:creator>
      <pubDate>Fri, 17 Jul 2026 17:52:26 +0000</pubDate>
      <link>https://dev.to/getaskai/how-i-built-a-free-unlimited-no-login-ai-chat-and-kept-abuse-under-control-ci</link>
      <guid>https://dev.to/getaskai/how-i-built-a-free-unlimited-no-login-ai-chat-and-kept-abuse-under-control-ci</guid>
      <description>&lt;p&gt;Every AI chat I tried wanted something before the first question. An email. A verification link. A card, "just to confirm you're human." Or it let me in and started counting: five messages, then a wall.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://getaskai.com" rel="noopener noreferrer"&gt;GetAskAI&lt;/a&gt;: a free AI chat with no login, no app and no message counter. You open it, type, get an answer.&lt;/p&gt;

&lt;p&gt;Then I found out what that actually costs.&lt;/p&gt;

&lt;p&gt;This post is about the part nobody puts on the landing page: when you remove the account, you remove the one thing that makes abuse control easy, and you inherit a bill that scales with strangers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why no login is a technical decision, not a marketing one
&lt;/h2&gt;

&lt;p&gt;The pitch writes itself. No signup, no friction, ask and go.&lt;/p&gt;

&lt;p&gt;The engineering reality is less fun. An account is not just a retention hook. It is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a stable identity to attach usage to&lt;/li&gt;
&lt;li&gt;a natural rate limit boundary&lt;/li&gt;
&lt;li&gt;a cost ceiling per human&lt;/li&gt;
&lt;li&gt;an abuse trail you can ban&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Delete it and every one of those disappears at once. You are now serving inference, the most expensive thing on your bill, to anonymous traffic, with no way to say "this is the same person as five seconds ago."&lt;/p&gt;

&lt;p&gt;That is the whole problem. Everything below is a consequence of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bill is the constraint, not the traffic
&lt;/h2&gt;

&lt;p&gt;Most side projects have a fixed cost and variable traffic. A spike is a bragging right.&lt;/p&gt;

&lt;p&gt;An LLM product inverts that. There is no meaningful fixed cost to amortise. Every request costs real money, and the cost scales linearly with usage. A traffic spike is a cost spike. Going viral is an invoice.&lt;/p&gt;

&lt;p&gt;Which means the honest framing is not "how do I stop bots." It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every request has a price. My job is to make sure a human asking a real question always gets served, and everything else gets served last or not at all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rate limiting stops being a security feature and becomes a business model. That reframing changed every decision after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the obvious answers do not work
&lt;/h2&gt;

&lt;p&gt;I am not going to publish my exact setup, mostly because writing down your abuse rules is how they stop working. But it is worth saying why the first three things everyone reaches for are weaker than they look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IP limits.&lt;/strong&gt; Carrier-grade NAT puts entire mobile networks behind a handful of addresses. A university, an office, a co-working space, all one IP. Set a limit tight enough to stop a script and you have banned a country's mobile users on a bad day. And residential proxy pools rotate through real consumer IPs, so a determined scraper looks like a hundred innocent people. IP is a weak hint, not a boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser fingerprinting.&lt;/strong&gt; Technically effective, and it quietly destroys the product. The whole promise is that I do not build an identity for you. Fingerprinting builds one anyway, just without telling you. It is the same tracking with worse ethics. I decided this was off the table, and I want to be clear that this is a values call, not a technical one. It costs me abuse resistance. I pay it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CAPTCHA on everything.&lt;/strong&gt; Kills the thing that makes the product worth using. The whole point is five seconds from question to answer. A puzzle in front of every message is a signup form with extra steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually worked, in one line
&lt;/h2&gt;

&lt;p&gt;Stop thinking about &lt;em&gt;who&lt;/em&gt; and start thinking about &lt;em&gt;cost&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I do not need to know your name. I need to guarantee that any single visitor, whoever they are, can never cost much, and that the cheap path is the default while the expensive things stay the exception. Once you frame the problem that way, most of the design follows from it, and none of it needs an account.&lt;/p&gt;

&lt;p&gt;The principle underneath all of it: &lt;strong&gt;degrade, do not block.&lt;/strong&gt; Suspected abuse gets slower, not a hard error. A false positive then costs a real person a little latency instead of their answer. When you cannot identify people you &lt;em&gt;will&lt;/em&gt; have false positives, so make them survivable.&lt;/p&gt;

&lt;h2&gt;
  
  
  On live answers and files
&lt;/h2&gt;

&lt;p&gt;Two things people always ask about.&lt;/p&gt;

&lt;p&gt;A model on its own is a frozen snapshot, so anything genuinely recent is a confident guess, which is the worst kind. There is a step that pulls in fresh information when a question needs it, used sparingly rather than on every message, because that is expensive too.&lt;/p&gt;

&lt;p&gt;And files. Images and PDFs work, and they are never written to disk. A file is held only long enough to answer the question, then it is gone. That is not a policy line, it is architecture: a file that is never stored cannot leak, cannot be subpoenaed, and cannot be sold by whoever buys the company later. It also means I cannot debug your failed upload, which is a real cost I accept.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would I do it again
&lt;/h2&gt;

&lt;p&gt;Yes, with less certainty than I had at the start.&lt;/p&gt;

&lt;p&gt;The no-login decision is genuinely good for the user and genuinely bad for the business in every measurable way. No email list. No retention. No cohort analysis. Every visit starts cold. I have no idea if the same person came back yesterday, and by design I never will.&lt;/p&gt;

&lt;p&gt;What I would tell someone about to do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The rate limiting is the product.&lt;/strong&gt; Not a chore you bolt on later. If you cannot serve one human cheaply, you cannot serve a thousand at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Degrade, never block.&lt;/strong&gt; You will have false positives. Make them cost latency, not the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide what you refuse to build before you need it.&lt;/strong&gt; Fingerprinting looks reasonable at 3am during an attack. Decide at noon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ads are a cleaner incentive than a free tier.&lt;/strong&gt; With a paywall I would spend my time tuning where the wall goes. With ads, the tool being good and the tool making money point the same direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs scale with strangers.&lt;/strong&gt; Budget for the spike you want, not the traffic you have.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Not a business yet. A tool that pays for itself, if the ads hold.&lt;/p&gt;




&lt;p&gt;It runs at &lt;a href="https://getaskai.com" rel="noopener noreferrer"&gt;getaskai.com&lt;/a&gt;. If you find a hole in the rate limiting I would genuinely rather hear it from you than find it on the invoice.&lt;/p&gt;

&lt;p&gt;Happy to talk about the tradeoffs in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
