Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions websockets/fastapi-ai-chat/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# FastAPI AI Chat with WebSocket

A real-time AI chat application using **Next.js** (frontend), **FastAPI** (backend), **WebSocket** for streaming, and the [Python AI SDK](https://github.com/vercel-labs/ai-python) for LLM integration. Deployed on Vercel using [experimental services](https://vercel.com/docs/functions/experimental-services).
A real-time AI chat application using **Next.js** (frontend), **FastAPI** (backend), **WebSocket** for streaming, and the [Python AI SDK](https://github.com/vercel-labs/ai-python) for LLM integration. Deployed on Vercel using [Services](https://vercel.com/docs/services).

## How It Works

- The **frontend** is a Next.js single-page app with a chat UI that connects to the backend via WebSocket.
- The **backend** is a FastAPI server that accepts WebSocket connections, streams LLM responses using the Python AI SDK, and sends text deltas back to the client in real time.
- On Vercel, the frontend and backend run as separate services routed by path prefix (`/` and `/svc/api`).
- Python ASGI apps handle WebSocket upgrades natively on Vercel — no `experimental_upgradeWebSocket` workaround needed.
- On Vercel, the frontend and backend run as separate services routed by path prefix (`/` and `/api`).

## How to Use

Expand Down
12 changes: 7 additions & 5 deletions websockets/fastapi-ai-chat/backend/main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import os

import fastapi

import ai
import ai, fastapi

app = fastapi.FastAPI()

Expand All @@ -15,13 +13,15 @@
"system": ai.system_message,
}

api = fastapi.APIRouter(prefix="/api")


@app.get("/")
@api.get("/")
async def health():
return {"status": "ok"}


@app.websocket("/ws")
@api.websocket("/ws")
async def websocket_endpoint(websocket: fastapi.WebSocket):
await websocket.accept()
try:
Expand All @@ -47,3 +47,5 @@ async def websocket_endpoint(websocket: fastapi.WebSocket):
)
except fastapi.WebSocketDisconnect:
pass

app.include_router(api)
4 changes: 1 addition & 3 deletions websockets/fastapi-ai-chat/frontend/app/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import { useState, useEffect, useRef } from "react";

const BACKEND = process.env.NEXT_PUBLIC_BACKEND_URL || "/svc/api";

export default function Chat() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
Expand All @@ -25,7 +23,7 @@ export default function Chat() {
if (cancelled) return;

const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}${BACKEND}/ws`;
const wsUrl = `${protocol}//${window.location.host}/api/ws`;

setStatus("connecting");
const ws = new WebSocket(wsUrl);
Expand Down
28 changes: 21 additions & 7 deletions websockets/fastapi-ai-chat/vercel.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
{
"experimentalServices": {
"rewrites": [
{
"source": "/api/:path*",
"destination": {
"type": "service",
"service": "backend"
}
},
{
"source": "/(.*)",
"destination": {
"type": "service",
"service": "frontend"
}
}
],
"services": {
"frontend": {
"framework": "nextjs",
"entrypoint": "frontend",
"routePrefix": "/"
"root": "frontend",
"framework": "nextjs"
},
"backend": {
"framework": "fastapi",
"entrypoint": "backend/main.py",
"routePrefix": "/svc/api"
"root": "backend",
"framework": "fastapi"
}
}
}