AI Showcase
Enable the AI Showcase add-on to wire Anthropic + OpenAI through the Vercel AI SDK, the useAi seam in the glasses app, and six sensor/AI demo apps that ship as references.
The AI Showcase add-on wires a single Convex action:
api.ai.complete. It wraps the
Vercel AI SDK with Anthropic and OpenAI
providers, runs on the Convex side so your provider key never ships
to the device, and is rate-limited per user.
Six example demo apps in app/src/apps/ all use the same seam
through a useAi() hook. They demonstrate the platform's full
capability surface (GPS / orientation / motion / Neural Band) paired
with AI prompts. Fork the closest one to your product.
Off by default
AI Showcase is one of three opt-in add-ons (see
Add-ons). The base boilerplate ships with no
AI keys required and the glasses launcher empty, until you
enable AI Showcase or add your own apps to the APPS registry
in app/src/App.tsx.
Enable
pnpm run addon enable aiThis single command:
- renames
.disabledfiles forconvex/ai.ts,app/src/lib/useAi.ts,app/src/lib/AiResponse.tsx, plus the six demo apps inapp/src/apps/ - inserts the AI env schema (
AI_PROVIDER,ANTHROPIC_API_KEYorOPENAI_API_KEY, optionalAI_MODEL) inenv.ts - inserts the demo-app imports +
APPSregistry entries inapp/src/App.tsxso all six show up in the launcher
Then pick a provider and push its key to Convex:
cd packages/backend
pnpm exec convex env set AI_PROVIDER anthropic # or `openai`
pnpm exec convex env set ANTHROPIC_API_KEY sk-ant-... # or OPENAI_API_KEY=sk-...
pnpm exec convex dev --oncepnpm run addon disable ai reverses everything (renames files back to
.disabled, removes the demo apps from the launcher). Drop the env
keys with pnpm exec convex env remove ….
What ships in the box
| File | What it does |
|---|---|
packages/backend/convex/ai.ts | The complete action: picks provider, calls generateText, returns text |
packages/backend/convex/rateLimiter.ts | aiPrompt token bucket + consumeAiPrompt mutation |
app/src/lib/useAi.ts | React hook: ask(prompt, opts) returns text + loading + error |
app/src/lib/AiResponse.tsx | Renders the hook's state (placeholder → loading → text or error) |
Why a server-side action
Provider keys must not ship to the device
The action keeps keys on the Convex side; the glasses app calls
useAction(api.ai.complete) and gets text back. The same seam
works from the companion site; both apps share one Convex
deployment.
This also lets Convex enforce rate limits per user (via the
@convex-dev/rate-limiter component) before paying a provider for
the call. See rateLimiter.ts's aiPrompt token bucket.
Configuring the provider
Set in the Convex dashboard (Settings → Environment Variables).
Convex functions run in Convex's cloud, not from a local .env:
AI_PROVIDER=anthropic # or "openai"
# Pick one — the action chooses based on AI_PROVIDER above:
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
# Optional — override the specific model. Defaults to a cheap fast one:
# anthropic → claude-3-5-haiku-latest
# openai → gpt-4o-mini
AI_MODEL=Swap providers by changing AI_PROVIDER. No code changes needed.
Calling the seam
import { useAi } from "../lib/useAi";
import { AiResponse } from "../lib/AiResponse";
function MyApp() {
const ai = useAi();
return (
<>
<button
className="focusable"
onClick={() => ai.ask("Write a haiku about glasses.")}
disabled={ai.loading}
>
{ai.loading ? "Thinking…" : "Generate"}
</button>
<AiResponse ai={ai} placeholder="Press Generate." />
</>
);
}ai.ask takes an optional second argument for system and
maxTokens:
ai.ask(prompt, {
system: "You are a concise navigation assistant.",
maxTokens: 80,
});The same action: just useAction it from a client component:
"use client";
import { useAction } from "convex/react";
import { api } from "@glasskit/backend/convex/_generated/api";
export function Generate() {
const complete = useAction(api.ai.complete);
// …
}Examples in the box
The six example apps each pair a sensor mix with one AI prompt. Copy the pattern:
| App | Sensors | Prompt shape |
|---|---|---|
| AI Wayfinder | GPS + orientation | One-line walking direction to nearest landmark |
| Tour Guide | GPS + orientation | One fascinating fact about your location |
| Running Coach | Motion + timer | Live pacing / form cue |
| Sous-Chef | Neural Band pinch | Elaborate on the current recipe step |
| Presentation Coach | Neural Band pinch | Score pace given words / elapsed time |
| Trail Companion | GPS + motion | Safety + pacing note for the terrain |
Open any of them in app/src/apps/ to see the full call.
Extending: streaming
The shipped action uses generateText (one-shot). For long
responses you'll want streaming. Replace generateText with
streamText and return a stream from an HTTP route on the Convex
side, then consume it on the client with the AI SDK's useChat
hook.
A streaming complete action in convex/ai.ts:
import { streamText } from "ai";
// in an httpAction:
const result = streamText({ model: model(), prompt, system });
return result.toDataStreamResponse();The companion side then uses useChat({ api: "/your-route" }) to
consume the stream. See the
Vercel AI SDK docs.
Extending: structured output
import { generateObject } from "ai";
import { z } from "zod";
const { object } = await generateObject({
model: model(),
schema: z.object({
title: z.string(),
tags: z.array(z.string()).max(5),
}),
prompt,
});Significantly more reliable than asking for JSON in the prompt and parsing it manually: the SDK does retries on schema failures.
Extending: tool use
import { generateText, tool } from "ai";
import { z } from "zod";
const { text } = await generateText({
model: model(),
prompt,
tools: {
getWeather: tool({
description: "Get the current weather for a city",
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, tempC: 22 }),
}),
},
// Allow the model to call tools then synthesize:
maxSteps: 5,
});If you need to call a Convex query/mutation from a tool, do it via
ctx.runQuery / ctx.runMutation inside the tool's execute.
Cost discipline
Defaults are tuned for cheap
- Use the smallest model that works. The defaults
(
claude-3-5-haiku-latest,gpt-4o-mini) are 30–60× cheaper than the flagship models on most tasks. - Cap
maxOutputTokens. The shipped action defaults to 220, so a runaway generation can't bankrupt you. - Rate-limit per user. Already done via
aiPromptinrateLimiter.ts. Adjust the bucket if your app fires more. - Cache prompts. Anthropic supports prompt caching; OpenAI has prompt caching on certain endpoints. Useful when system prompts are stable and long.
Provider-specific features
If you need a provider-specific feature the AI SDK hasn't surfaced
yet (Anthropic extended thinking, OpenAI o1 reasoning budgets,
etc.), drop down to the native SDK. Both @ai-sdk/anthropic and
@ai-sdk/openai are already installed; you can also
pnpm add @anthropic-ai/sdk (or openai) directly and call them
from a Convex action exactly the same way.