A beginner-friendly clone of an AI pipeline using Flask and SQLite
Welcome to another official UVF IT build guide! In this tutorial we’ll craft a tiny AI engineering pipeline inspired by the massive open‑source curriculum you referenced. The goal is a runnable Flask service that stores prompts, runs a placeholder "model" (just reverses text), and returns results—all with free, local tools.
You’ll finish in ~45 minutes, end up with a local web UI, and have a solid foundation to expand into real vector stores, LLM calls, or orchestration later.
pip. The guide uses only the standard library plus Flask and SQLite – no external AI APIs.python -m venv venv then venv\Scripts\activate; on macOS/Linux use source venv/bin/activate.First we create an isolated Python environment so our dependencies don’t clash with other projects. Then we install Flask, the only web framework we need, and lay out a simple folder hierarchy that will hold the app code, the database schema, and static assets.
The folder layout mirrors typical Flask projects: a top‑level app package for Python modules, a templates folder for HTML UI, and a pipeline folder for the SQLite schema and future utilities. This scaffold gives us a clean starting point for incremental development.
python -m venv venv
# Activate the environment
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
pip install --upgrade pip
pip install Flask
# Create folder structure
mkdir -p app/templates pipeline
touch app/__init__.py app/routes.py pipeline/schema.sqlOur pipeline needs to persist the original prompt and the model’s output. SQLite is perfect for a lightweight, file‑based store and requires no external server. We’ll create a single table called interactions with columns for an auto‑incrementing ID, the prompt text, the result text, and a timestamp.
The schema lives in pipeline/schema.sql so it can be reapplied easily during development or testing. Later phases will load this file and execute it against the SQLite database file pipeline/data.db.
pipeline/schema.sql
CREATE TABLE IF NOT EXISTS interactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prompt TEXT NOT NULL,
result TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);In this step we create a tiny “model” that pretends to be an AI engine. The function simply reverses whatever text you give it – that’s enough to prove the pipeline works end‑to‑end. Keeping the logic isolated in its own file makes it easy to swap for a real LLM later without touching the Flask code.
pipeline/model.py
import sqlite3
import os
DB_PATH = os.path.join(os.path.dirname(__file__), 'pipeline.db')
def _init_db():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute('''CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
prompt TEXT NOT NULL,
response TEXT NOT NULL,
ts DATETIME DEFAULT CURRENT_TIMESTAMP
)''')
conn.commit()
conn.close()
# Ensure the DB exists before any calls
_init_db()
def run_model(prompt: str) -> str:
"""Placeholder model – returns the reversed prompt string.
Also stores the prompt/response pair in SQLite for later inspection.
"""
response = prompt[::-1]
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute('INSERT INTO logs (prompt, response) VALUES (?, ?)', (prompt, response))
conn.commit()
conn.close()
return responseNow we wire a tiny Flask server to expose three endpoints: the home page, a JSON API to submit a prompt, and a view that lists all past interactions. The UI is a single HTML template that posts a prompt and shows the growing history table. All routes use the run_model function from the previous phase, so the reversed text appears instantly.
We keep the Flask app minimal – no blueprints, no extensions – to stay beginner‑friendly. The SQLite file lives next to the model, and we read it directly when rendering the history list.
pipeline/app.py
from flask import Flask, request, jsonify, render_template_string, redirect, url_for
from model import run_model
import sqlite3, os
app = Flask(__name__)
DB_PATH = os.path.join(os.path.dirname(__file__), 'pipeline.db')
HTML = """
<!doctype html>
<title>Simple AI Pipeline</title>
<h1>Enter a prompt</h1>
<form action="/submit" method="post">
<input name="prompt" placeholder="type something" required>
<button type="submit">Run</button>
</form>
<h2>History</h2>
<table border=1>
<tr><th>Prompt</th><th>Response</th></tr>
{% for row in logs %}
<tr><td>{{row[0]}}</td><td>{{row[1]}}</td></tr>
{% endfor %}
</table>
"""
def get_logs():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute('SELECT prompt, response FROM logs ORDER BY id DESC')
rows = cur.fetchall()
conn.close()
return rows
@app.route('/')
def index():
return render_template_string(HTML, logs=get_logs())
@app.route('/submit', methods=['POST'])
def submit():
prompt = request.form.get('prompt', '')
if prompt:
run_model(prompt)
return redirect(url_for('index'))
@app.route('/api/run', methods=['POST'])
def api_run():
data = request.get_json(silent=True) or {}
prompt = data.get('prompt', '')
response = run_model(prompt) if prompt else ''
return jsonify({'prompt': prompt, 'response': response})
if __name__ == '__main__':
app.run(debug=True)With the code in place, start the Flask server using the provided Bash command. The app will listen on http://127.0.0.1:5000/ – open that URL in a browser, type a phrase, and watch the reversed result appear in the history table. Finally, you can peek into the SQLite database to confirm the prompt/response pair was persisted.
# From the repository root, install Flask if needed and launch the app
python -m pip install flask --quiet
python pipeline/app.py| Check | Command / Why it matters |
|---|---|
| Server starts without error | You see "Running on http://127.0.0.1:5000/" in the terminal |
| Submitting a prompt shows reversed text in the list | Enter "hello" in the browser UI; the page displays "olleh" under the prompt |
| SQLite file contains a row | Run sqlite3 pipeline/pipeline.db "SELECT prompt,response FROM logs;" and see the stored values |
You now have a functional skeleton. Swap run_model for an actual LLM (OpenAI, HuggingFace, etc.), add vector‑store persistence, or expand the UI. Happy hacking!