AI·9 min read

Add your first AI feature

Your first AI feature is not a chatbot. It's a small, boring, well-bounded task inside your existing app that a language model happens to be very good at — and the difference between something that ships and something that embarrasses you in production is almost entirely in the plumbing around the model, not the model itself. This is the playbook for wiring one in without shooting yourself in the foot.

Pick a feature where being wrong is cheap

The single biggest mistake first-timers make is putting AI on the critical path of something irreversible — auto-sending emails, auto-charging cards, auto-deleting data. Models are probabilistic; they will be confidently wrong some percentage of the time, and your job is to choose a feature where that percentage doesn't hurt. The sweet spot for a first feature is a 'draft, don't do' task: summarize this support thread, suggest three tags for this note, draft a reply the user can edit before sending, extract the line items from this pasted invoice. In every one of those, a human is the last step and a wrong answer costs one click to fix. Ship the assistive version first, earn trust with real usage data, and only then talk about automating the human out of the loop.

The API key never touches the browser

This is non-negotiable and it's the thing vibe coders get wrong most often because the happy-path tutorial calls the model straight from client code. If your Anthropic or OpenAI key ships in a frontend bundle, it will be scraped and someone will run up a five-figure bill on your account within days. The model call goes through your own backend — a Next.js route handler, a Supabase Edge Function, an Express endpoint — and the key lives in a server-side environment variable that is never bundled. Your frontend calls your endpoint; your endpoint calls the model. This also gives you the one place where you'll later add auth checks, rate limiting, logging, and cost caps, so you want it to exist from day one anyway.

typescript
// app/api/summarize/route.ts — server only, key stays here
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic(); // reads ANTHROPIC_API_KEY from env

export async function POST(req: Request) {
  const { thread } = await req.json();
  const msg = await client.messages.create({
    model: 'claude-sonnet-4-5',
    max_tokens: 400,
    messages: [{ role: 'user', content: `Summarize this support thread in 3 bullets:\n\n${thread}` }],
  });
  return Response.json({ summary: msg.content });
}

Treat the prompt like source code, not a magic incantation

The prompt is the highest-leverage code you'll write for this feature, so stop pasting it as a giant inline string and start treating it like a real function. Give it a system prompt that states the model's role and hard constraints, keep the user's actual data clearly separated from your instructions, and put the whole thing in a versioned file so you can diff it when behavior changes. The specific craft move that pays off immediately: tell the model what to do when it can't do the task. 'If the invoice text is unreadable, return an empty items array and set needs_review to true' prevents the model from hallucinating plausible-looking garbage to satisfy you. Vague prompts don't fail loudly — they fail by inventing, which is far worse.

Make it return data, not prose

If your feature does anything other than show raw text to a human, you want structured output — JSON with a schema you control — not a paragraph you have to parse with regex. Define the shape with Zod, hand it to the model, and validate what comes back before you trust it. The Vercel AI SDK's generateObject and the Anthropic/OpenAI structured-output modes make the model return schema-conforming JSON directly, which eliminates an entire category of 'the model wrote a friendly intro sentence before the JSON' bugs. Validation on the way out is the important part: the schema is a contract, and a model that returns the wrong shape should be a caught error you retry, not a runtime crash three functions later.

typescript
import { generateObject } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';

const { object } = await generateObject({
  model: anthropic('claude-sonnet-4-5'),
  schema: z.object({
    tags: z.array(z.string()).max(3),
    needsReview: z.boolean(),
  }),
  prompt: `Suggest up to 3 tags for this note:\n\n${note}`,
});
// object.tags is typed and validated — no parsing, no guessing

Stream it, because latency is a UX problem you can't hide

A model generating 400 tokens can take several seconds, and a spinner that sits there for four seconds feels broken even when it's working perfectly. Streaming — rendering tokens as they arrive — turns dead waiting time into visible progress and is the single biggest perceived-speed win available to you, for basically free with streamText or the SDK's stream helpers. The caveat that trips people up: only stream when the user is meant to read the output as it forms, like a chat reply or a draft. If the output is structured JSON that feeds other logic, don't stream a half-built object into your UI — wait for the complete, validated result. Stream prose to humans; buffer data for machines.

The model will fail, so build for it on day one

Rate limits, timeouts, 529 overload errors, and the occasional malformed response are not edge cases — they are Tuesday. Wrap every model call in a timeout and a bounded retry with exponential backoff, and decide explicitly what the user sees when all retries fail: a graceful 'couldn't generate this, try again' beats a spinning loader that never resolves. Two failures that specifically bite AI features: the model returning valid-JSON-but-wrong-content (handle with your schema validation plus a sanity check), and the request that succeeds but takes 30 seconds and blows past your serverless function's timeout. Set your function timeout deliberately, cap max_tokens so responses can't run away, and never let a hung model call hold a user's request open indefinitely.

Know your token math before the bill teaches it to you

You pay per token, input and output priced separately, and the number that surprises people is input: if you stuff an entire 40-page document or a long chat history into every call, you're paying for all of it every single time. Before you ship, do the arithmetic — expected tokens per call times price times calls per day — and put a hard ceiling somewhere: a per-user daily cap, a max_tokens limit, and ideally a cheaper model for the easy cases. Reach for the smaller, faster model (Claude Haiku, GPT's mini tier) first and only escalate to the frontier model when a real eval shows the small one isn't good enough; teams routinely burn money running their most expensive model on a task a cheap one nails. Prompt caching is the other lever — if you send the same big system prompt or document on every call, caching it cuts that repeated input cost dramatically.

You don't 'know it works' until you have an eval

Vibe-checking three inputs by hand and shipping is how AI features regress silently the moment you tweak a prompt or bump a model version. You don't need a fancy framework to start: collect 15-20 real example inputs with the output you'd consider correct, write a script that runs them through your feature, and eyeball or automatically grade the results. Now a prompt change is a measurable before/after instead of a vibe, and 'the new model broke tagging' becomes a number instead of a support ticket. This is the practice that separates a demo from a feature — the demo works once on stage, the feature works on the inputs you haven't seen yet, and the only way to have any confidence about those is to have measured the ones you have.

Takeaways

  • Ship assistive first: draft-don't-do features where a wrong answer costs one click, not real damage.
  • The API key lives on your server, always — a key in the browser bundle gets scraped and drained.
  • Make the model return schema-validated JSON, not prose, whenever anything downstream consumes the output.
  • Stream prose to humans for perceived speed; buffer structured data until it's complete and validated.
  • Retries, timeouts, and token/cost caps are launch requirements, not polish — failures are routine.
  • Build a 15-20 example eval set so prompt and model changes become measurable instead of vibes.