cd /news/natural-language-processing/i-upgraded-my-ai-concierge-by-turnin… · home topics natural-language-processing article
[ARTICLE · art-43195] src=dev.to ↗ pub= topic=natural-language-processing verified=true sentiment=· neutral

I upgraded my AI concierge by turning it to a plain search box

A developer replaced the AI concierge on Beaches of Greece with a plain search box after the NLP stack produced confident nonsense. The original system used wink-nlp, compromise, and sentiment analysis but failed on substring matches, place lookups, and sentiment scoring. The new search uses whole-word matching and explicit phrase-to-filter mappings, resulting in faster, smaller, and more predictable results.

read3 min views1 publishedJun 29, 2026

I wanted Beaches of Greece to be an AI concierge. You would describe your perfect beach day in plain language, and it would just understand you.

Under the hood that meant a proper NLP stack: wink-nlp

for entities, compromise for parsing, a sentiment analyzer, fuzzy matching for place names. It felt smart.

It mostly produced confident nonsense. So I deleted it and shipped a plain search box, and search got faster, smaller, and genuinely better. Here is what went wrong.

The failures were not subtle:

It scanned keywords as substrings, so "shade" matched inside "shaded", and "bar" (as in beach bar) matched inside "sandbar".

Place lookup did places.find(p => query.includes(p))

, so "ios" inside "agios nikolaos" resolved to the island of Ios. Wrong island, zero results.

"near heraklion" returned nothing. It made Heraklion (a city) a location filter, but beaches are filed under their island, Crete, so the filter could never match.

Sentiment analysis scored "I hate crowds but love calm water" as mixed polarity. A fun trick that never changed which beaches to show.

Every time, the stack performed the look of understanding, then did something a human never would. And when it was wrong, I had to debug a model's guess.

They list wants: calm, sandy, shade, parking, near somewhere. The vocabulary is small and knowable, so I matched against it literally, on whole-word boundaries:

function hasPhrase(q: string, phrase: string): boolean {
  // matches "bar" but not "sandbar"
  return new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(phrase)}(?:[^a-z0-9]|$)`).test(q);
}

That single boundary check erased the whole "bar inside sandbar" class of bug. The rest of the vocabulary is just data: lists of phrases mapped to filter values.

Boring matching also forces decisions the NLP layer used to hide. "quiet" now means few people, not flat water. "no parking" is checked before "parking" so it wins outright. Each one is a product call sitting in plain sight instead of inside a model.

Place names get the same treatment: whole-word, longest match, and every name resolves to an area that beaches actually live in, so "heraklion" maps to Crete instead of nuking the results. The one bit of fuzziness I kept is a tight typo fallback. And because each town carries a coordinate, "near heraklion" can now rank by real distance, which the AI version never managed.

Gained: predictable and debuggable (a wrong result is a phrase to fix, not a model to retrain), faster, a smaller bundle, and honest. It does what a human would do with those words, no theater.

Gave up: novel phrasing. Someone on Reddit instantly tried a mixed Greek-and-English query and it caught only half. Fair trade for this domain.

"Add AI" is not free. It costs latency, bundle size, and worst of all legibility: when the clever thing is wrong, you cannot tell why. For a bounded problem with a knowable vocabulary, a search box that makes no claim to intelligence can be the smarter build.

It is live at beachesofgreece.com. Throw a weird query at it and tell me what breaks.

── more in #natural-language-processing 4 stories · sorted by recency
── more on @beaches of greece 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/i-upgraded-my-ai-con…] indexed:0 read:3min 2026-06-29 ·