DEV Community

Cover image for How I Rebuilt Educando.app's Core: Tavily + GPT-5.4 nano Search Pipeline
Lucas Santos Rodrigues
Lucas Santos Rodrigues

Posted on

How I Rebuilt Educando.app's Core: Tavily + GPT-5.4 nano Search Pipeline

After a security incident forced Educando.app offline, I took the opportunity to rethink the entire product. The original version was a Gemini-powered activity generator for Brazilian teachers. The new version is an intelligent content repository with a semantic search pipeline. This post is about the technical decisions behind that rebuild.

Why the Generator Had to Go

The original architecture was simple: user submits a prompt → Next.js API route → Gemini Flash → response streamed to the UI. It worked, but it had two structural problems.

First, a compromised API key triggered unauthorized generation at scale. The key was client-accessible during an early dev iteration and never fully locked down server-side. Lesson: even keys that "should" be server-only need to be audited regularly — especially when Vercel environment variable scoping can be misconfigured silently.

Second, the value was never in real-time generation. Teachers don't want to prompt an LLM. They want to find a good activity fast, trust it, and use it. A repository with good search solves that better than a generator.

The New Search Pipeline

The core is a three-layer pipeline:

User query → Tavily search → GPT-5.4 nano evaluation → ranked JSON → UI
Enter fullscreen mode Exit fullscreen mode

Layer 1: Tavily

Tavily handles the actual search. It returns structured results with titles, descriptions, and URLs. The query goes in raw — no preprocessing — and Tavily does the semantic heavy lifting.

Layer 2: GPT-5.4 nano evaluation

Each result batch gets passed to GPT-5.4 nano with a structured prompt asking it to score and rank the results as JSON. The model evaluates relevance, content quality, and match to the Brazilian curriculum (BNCC codes). The prompt enforces strict JSON output — no preamble, no markdown fences — so the response can be parsed directly.

const evaluationPrompt = `
You are evaluating educational activity search results for Brazilian teachers.
Score each result from 0-10 based on: relevance, pedagogical quality, BNCC alignment.
Return ONLY a JSON array: [{ "index": 0, "score": 8, "reason": "..." }, ...]
Results: ${JSON.stringify(searchResults)}
Query: "${userQuery}"
`
Enter fullscreen mode Exit fullscreen mode

GPT-5.4 nano is the right model here — it's fast, cheap, and the task is classification/ranking, not generation. No need for a larger model.

Layer 3: Supabase cache

Results are cached in Supabase keyed by a normalized query hash. Cache hit = skip Tavily + GPT entirely. This keeps costs predictable and latency low for repeated queries.

const queryHash = createHash('md5').update(normalizedQuery).digest('hex')
const cached = await supabase
  .from('search_cache')
  .select('results')
  .eq('query_hash', queryHash)
  .single()

if (cached.data) return cached.data.results
Enter fullscreen mode Exit fullscreen mode

A separate search_telemetry table logs every query and click for future ranking improvements.

Content Schema

The old schema was flat — just the generated text blob. The new schema is structured:

create table activities (
  id uuid primary key default gen_random_uuid(),
  title text not null,
  theme text not null,
  short_description text,
  long_description text,
  bncc_codes text[],
  type text,
  image_url text,
  created_at timestamptz default now()
);
Enter fullscreen mode Exit fullscreen mode

BNCC codes are the Brazilian national curriculum identifiers (e.g. EF05LP01). Having them as a queryable array field lets the search layer filter by curriculum alignment without relying purely on semantic matching.

Image Processing with Replicate + Qwen

Activity thumbnails go through a qwen/qwen-image-2-pro pipeline on Replicate. The model handles resizing, quality normalization, and description extraction — the output feeds back into the activity metadata for better search indexing.

Abuse Prevention

The original version had FingerprintJS + IP hashing. It was complex and brittle — VPN, browser switch, new tab, and the identity broke.

The new version uses Cloudflare Turnstile at the edge combined with server-side rate limiting per IP. Turnstile runs before the request hits the Next.js API route, so abuse is stopped at the CDN level. Simpler, more reliable, and no client-side fingerprinting library to maintain.

No auth in this version. The Saved Activities feature is scoped to v2, which will include Supabase Auth + Google SSO. There was no reason to ship an auth system without a paywall to motivate it.

Stack

  • Next.js + TypeScript — unchanged
  • Supabase — database, cache, telemetry
  • Tavily — search
  • GPT-5.4 nano — result evaluation and ranking
  • Replicate (Qwen) — image processing
  • Cloudflare Turnstile — abuse prevention
  • Vercel — deployment

What's Next

The premium tier — download/print behind a paywall, Supabase Auth, Google SSO — is designed but not yet shipped. The repository needs enough content before a paywall makes sense.

The security incident was painful. Rebuilding was the right call.

Top comments (0)