Built withNext.js 16, LangGraph and PostgreSQL (pgvector)
AI support that answers from your docs, and hands the chat to a person when it can't.
An AI customer support platform built on Next.js 16 App Router, LangGraph state graphs, PostgreSQL with pgvector for document search, and Fastify 5 WebSockets. Human handoff is enforced on the server.
Input: "How do I configure vector cosine similarity search?"
Classification: needsRetrieval = true | handoffRequested = false
Querying PostgreSQL 16 + vector table "KnowledgeDocument"
Model: Hugging Face (Qwen2.5-Instruct / OpenRouter)
Tokens: 412 prompt / 148 completion · Streaming via Socket.IO room session_9f82
Personal data check passed · Safety rules passed · Answer matches the retrieved documents
See the LangGraph steps for one question
Pick a scenario and replay it: intent routing, document search, answer generation and the server-side guardrails.
Step-by-step simulator
Scripted demo, sample dataPick a scenario to see which LangGraph step handles it.
How does the platform handle vector cosine similarity search and embedding storage?
needsRetrieval: true | handoffRequested: false
Hugging Face / OpenRouter (qwen2.5-0.5b-instruct) · 284 tokens
Personal data: None found · Flag: NONE
1 - (embedding <=> $1)) with a configurable similarity threshold (default >0.65) to inject ground truth into prompt templates.How the platform is built
Pick a part of the system to see how it works.
A graph of fixed steps for every answer
The platform uses a LangGraph StateGraph to move each question through explicit steps: intent classification, document retrieval, answer generation, and guardrails.
- Typed state graph with immutable context snapshots
- Quick path for conversational greetings
- Fallback to another provider when one fails
- Execution telemetry logged per node
Technical details
- Pipeline steps
- 4 core nodes
- Orchestration
- LangGraph
System layers
From the chat window, through the Fastify gateway, to the LangGraph steps and PostgreSQL. Select a layer to see its details.
LangGraph Intent Classifier Node
Intent triage and handoff detection
What it does
Detects plain greetings (skipping document search to save compute) and flags explicit requests for a person before any search runs.
Technology
Simplified source excerpt
// packages/agent/src/workflow/nodes.ts
export function classifyIntent(query: string): RouteResult {
const isPureGreeting = PURE_GREETING_RE.test(query.trim());
const handoffRequested = HANDOFF_RE.test(query.trim());
return { needsRetrieval: !isPureGreeting, handoffRequested };
}Follow a message through the system
Step through five scenarios: a document question, a handoff to a person, a blocked prompt injection, a greeting, and a provider outage.
Customer Client Dispatch
User dispatches query payload over WebSocket connection (/chat namespace).
Full RAG pipeline: intent triage ➔ memory lookup ➔ cosine search ➔ grounded answer ➔ guardrail check.
Simplified code excerpts
Shortened versions of the LangGraph workflow, Prisma schema, Fastify server and Docker Compose setup. The full source is in the GitHub repository.
import { ChatMessage, RetrievedDocument } from '../providers/types';
export function classifyIntent(query: string): RouteResult {
const trimmed = query.trim();
const isPureGreeting = PURE_GREETING_RE.test(trimmed);
const needsRetrieval = !isPureGreeting;
const handoffRequested = HANDOFF_RE.test(trimmed);
return { needsRetrieval, handoffRequested };
}
export function buildSystemPrompt(
template: string,
systemName: string,
docs: RetrievedDocument[],
memoryContext = '',
): string {
let system = template.replace(/\{SYSTEM_NAME\}/g, systemName);
if (docs.length > 0) {
const context = docs
.map((d) => `${d.title}\n${d.content}`)
.join('\n---\n')
.slice(0, 12000);
system += `\n\n[CONTEXT]\n${context}\n[/CONTEXT]`;
}
return system;
}
export function sanitizeAnswer(answer: string): string {
const cleaned = answer.trim();
for (const pattern of BANNED_PATTERNS) {
if (pattern.test(cleaned)) return GUARDRAIL_FALLBACK;
}
return cleaned.slice(0, 8000);
} Run it on your own machine
Docker Compose, a database push, and the dev server: three commands to a running instance.
- # 1. Start PostgreSQL and Redis
docker compose up -d - # 2. Push the database schema and seed test data
bun run db:push && bun run db:seed - # 3. Start the Fastify API (:4000) and Next.js web app (:3000)
bun run dev
Role-based accessJWT + bcrypt
- AdministratorRole: ADMIN
Manages LLM providers, knowledge base documents, guardrails and audit logs.
- Support agentRole: AGENT
Works the live queue, takes over conversations from the AI, and resolves them.
- CustomerRole: USER
Chats with the AI over Socket.IO and is handed to a person when needed.