A beginner‑friendly mini‑LLM chat service using Flask and SQLite
Welcome to another official UVF IT guide! In this quick‑fire tutorial we’ll build a tiny ChatGPT‑style web app inspired by Karpathy’s nanochat, but stripped down to the absolute essentials so you can have a working clone in under an hour.
We’ll use Python, Flask for the API, and SQLite for persisting conversation history – all free‑tier, no external AI APIs required. By the end you’ll have a local server you can query with curl or a browser and see chat responses stored on disk.
typing features used in the snippets.Create a virtual environment and install Flask; set up the folder structure.
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install Flask
mkdir -p nanochat
cd nanochat
cat > app.py <<'PY'
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/')
def health():
return jsonify({'status':'ok'})
if __name__ == '__main__':
app.run(debug=True)
PYA single table stores each message with role (user/assistant) and timestamp.
app.py
import sqlite3, os
from datetime import datetime
from flask import Flask, request, jsonify
app = Flask(__name__)
DB_PATH = os.path.join(os.path.dirname(__file__), 'chat.db')
# ---------------------------------------------------------------------
# Database helpers – create table if missing and simple insert/select
# ---------------------------------------------------------------------
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
with get_db() as db:
db.execute('''
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role TEXT NOT NULL,
content TEXT NOT NULL,
ts TEXT NOT NULL
)
''')
db.commit()
init_db()
@app.route('/chat', methods=['POST'])
def chat():
data = request.get_json()
user_msg = data.get('message', '')
# store user message
with get_db() as db:
db.execute('INSERT INTO messages (role, content, ts) VALUES (?,?,?)',
('user', user_msg, datetime.utcnow().isoformat()))
db.commit()
# placeholder assistant reply
reply = 'I am a minimal clone; replace this with real LLM output.'
with get_db() as db:
db.execute('INSERT INTO messages (role, content, ts) VALUES (?,?,?)',
('assistant', reply, datetime.utcnow().isoformat()))
db.commit()
return jsonify({'reply': reply})
@app.route('/')
def health():
return jsonify({'status':'ok'})
if __name__ == '__main__':
app.run(debug=True)In this step we expose a Flask route that accepts a JSON payload containing the user's message. The endpoint stores the message in the SQLite database we created earlier and immediately returns a simple canned reply so you can see the whole request‑response loop in action. Keeping the reply static makes debugging easy while we later swap it for a real model. The code also returns a 200 status and a JSON body that the front‑end can consume.
app.py
import json
from flask import Flask, request, jsonify
import sqlite3
app = Flask(__name__)
DB_PATH = 'chat.db'
def init_db():
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT, content TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)''')
conn.commit()
conn.close()
init_db()
@app.route('/chat', methods=['POST'])
def chat():
data = request.get_json(force=True)
user_msg = data.get('message', '')
# Persist user message
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute('INSERT INTO messages (role, content) VALUES (?, ?)', ('user', user_msg))
conn.commit()
conn.close()
# Dummy assistant reply
reply = "I am a placeholder assistant. Your message was: " + user_msg
# Persist assistant reply
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute('INSERT INTO messages (role, content) VALUES (?, ?)', ('assistant', reply))
conn.commit()
conn.close()
return jsonify({'reply': reply})
if __name__ == '__main__':
app.run(debug=True)Now that the endpoint is wired up, start the Flask development server. Flask will listen on http://127.0.0.1:5000 by default, and you can interact with the /chat route using curl, Postman, or a browser extension that sends JSON. Watching the console output will confirm that the request hits the server and that the SQLite inserts succeed.
export FLASK_APP=app.py
export FLASK_ENV=development
flask runAfter sending a request, open the SQLite file to make sure both the user message and the dummy reply are stored. You can also serve a very small HTML page that posts to /chat, giving you a quick UI without leaving the browser. This phase shows the full stack: front‑end → API → database → response.
sqlite3 chat.db "SELECT id, role, content FROM messages ORDER BY id;"| Check | Command / Why it matters |
|---|---|
| Server starts without errors | flask run → should show *Running on http://127.0.0.1:5000/* |
| POST request returns a reply | curl -X POST -H "Content-Type: application/json" -d '{"message":"hello"}' http://127.0.0.1:5000/chat → JSON with 'reply' field |
| Message persisted in SQLite | sqlite3 chat.db "SELECT role,content FROM messages WHERE role='user';" → should show 'hello' |
You now have a functional local chat service. To move closer to the real nanochat, replace the placeholder reply with a call to a small open‑source model (e.g., GPT‑NeoX via HuggingFace) or expose the endpoint to the internet with ngrok for remote testing.