The AI Demo Problem
Every week, a client asks us to "add AI to the product." When we ask what problem it solves, the answer is usually: "It's the future" or "competitors are doing it."
This is how you end up with a chatbot that nobody uses, sitting in the corner of your app like an unloved feature.
At NineLab, we have a simple rule: AI is a solution to a specific problem, not a product in itself.
The Framework: Problem → User → Model
Step 1: Find the Friction
Before thinking about models, map your user's highest-friction moments:
These are your AI opportunities.
Step 2: Define the Minimal Viable AI Feature
Don't build a general-purpose assistant. Build a laser-focused tool that does one thing remarkably well.
Bad: "An AI that helps users with anything"
Good: "An AI that writes product descriptions from a SKU and 3 keywords"
Step 3: Choose the Right Model
| Use Case | Model | Why |
|---|
|----------|-------|-----|
| Text generation | GPT-4o | Best quality/cost balance |
| Code generation | Claude 3.5 Sonnet | Excellent reasoning |
| Fast responses | GPT-4o-mini | 10x cheaper, 3x faster |
| Image analysis | GPT-4o Vision | Best multimodal |
| Embeddings/search | text-embedding-3-small | Cost-effective semantic search |
Implementation: Streaming Responses in Next.js
Users hate waiting for AI responses. Stream them:
// app/api/ai/route.ts
import OpenAI from 'openai'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
export async function POST(req: Request) {
const { prompt } = await req.json()
const stream = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
stream: true,
max_tokens: 500,
})
const encoder = new TextEncoder()
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content || ''
controller.enqueue(encoder.encode(text))
}
controller.close()
},
})
return new Response(readable, {
headers: { 'Content-Type': 'text/event-stream' },
})
}
// Client component
'use client'
import { useState } from 'react'
export function AIWriter() {
const [output, setOutput] = useState('')
async function generate(prompt: string) {
setOutput('')
const res = await fetch('/api/ai', {
method: 'POST',
body: JSON.stringify({ prompt }),
headers: { 'Content-Type': 'application/json' },
})
const reader = res.body!.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
setOutput(prev => prev + decoder.decode(value))
}
}
return (
<div>
<button onClick={() => generate('Write a product description for...')}>
Generate
</button>
<p>{output}</p>
</div>
)
}
Cost Control
AI costs can spiral fast. Our standard guardrails:
1. Rate limiting per user: max 10 requests/minute
2. Token limits: set max_tokens aggressively for your use case
3. Cache common responses: use Redis to cache identical prompts
4. Use smaller models first: GPT-4o-mini is 10x cheaper. Escalate to GPT-4o only when needed.
The Morpho Cafe Example
For Morpho Cafe, we built a "Barista Mode" — an AI recommender that suggests the perfect drink based on mood, temperature preference, and taste profile.
Instead of a generic chatbot, it asks 3 specific questions and returns a single confident recommendation with explanation. Users love it because it feels like talking to an expert barista, not querying a database.
That's the difference between a demo and a feature.
