A realistic, agentic support-operations console for a fictional e-commerce store ("Northwind Goods"). It is a support-ticket inbox plus a standalone conversational copilot, backed by a real SQLite database seeded with customers, orders, products, tickets, and help-center articles.
The app was built as a test subject for LLM/agent observability tooling: it deliberately exercises many distinct AI code paths — streaming chat, multi-step tool-calling loops, structured output, and embeddings-based RAG — plus real DB reads/writes and an intentionally flaky tool that produces errors.
It ships with no observability/instrumentation SDK of any kind — all AI calls are funnelled through a few centralized modules so instrumentation can be added cleanly later.
- Next.js 16 (App Router) + React 19 + TypeScript + Tailwind CSS v4
- Vercel AI SDK v7 (
ai,@ai-sdk/react) with the OpenRouter provider — chat, tools, andgenerateObject - OpenAI client → OpenRouter for embeddings (a second, distinct LLM path)
- SQLite + Drizzle ORM (
better-sqlite3)
pnpm install
cp .env.example .env.local # then add your OpenRouter API key
pnpm db:push # create the SQLite schema
pnpm db:seed # seed data (+ embed help articles if key is set)
pnpm dev # http://localhost:3000If you seed before setting OPENROUTER_API_KEY, the relational data is still
created and the app runs; run pnpm db:embed afterward to build the help-center
embeddings that power semantic search.
| Variable | Default | Purpose |
|---|---|---|
OPENROUTER_API_KEY |
– | Required for all AI features |
OPENROUTER_MODEL |
openai/gpt-4o-mini |
Chat/agent model (needs tool-calling + JSON) |
OPENROUTER_EMBEDDING_MODEL |
openai/text-embedding-3-small |
RAG embeddings |
DATABASE_URL |
file:./northwind.db |
SQLite file |
CHAOS_FAILURE_RATE |
0.25 |
Probability the checkInventory tool fails |
Reads: lookupCustomer, getCustomerOrders, getOrder, searchProducts,
checkInventory (flaky), searchHelpArticles (RAG).
Writes: issueRefund, updateTicketStatus, postTicketReply, escalateTicket,
createTicket.
- Inbox (
/inbox) — ticket list · thread · customer context, with a ticket-scoped copilot and two structured-output actions (Summarize thread, Draft reply). - Copilot Chat (
/chat) — a standalone agent with all tools and persisted conversation history.
- Open a ticket → Summarize thread, then Draft reply (structured output).
- In the ticket copilot: "Look up this customer and list their recent orders" (multi-tool read loop).
- "Issue a refund for their most recent order" (a write tool that mutates the DB).
- "What's our return policy for opened items?" (embeddings RAG).
- Ask "Is SKU ELE-SPEAKER in stock?" a few times — the warehouse tool fails intermittently by design.
- In
/chat: "Find the customer for ticket #2, check if the item they mention is in stock, and draft an apology" (a longer tool chain).
app/ # routes: /inbox, /chat, and /api/*
api/chat/ # streaming agent endpoint (the main tool loop)
api/tickets/[id]/ # summarize + draft-reply (generateObject) and reads
components/ # UI (InboxClient, ChatClient, CopilotPanel, ToolCallCard…)
lib/ai/ # provider, embeddings, rag, tools, agent ← the AI core
lib/queries.ts # DB read helpers
db/ # schema, client, seed + embed scripts
pnpm dev · pnpm build · pnpm start · pnpm db:push · pnpm db:seed ·
pnpm db:embed