Streamlit turns Python scripts into data apps. pip install streamlit. streamlit run app.py. import streamlit as st. st.title("My App"), st.write("Hello!"), st.markdown("**Bold**"). Data: st.dataframe(df) renders interactive table, st.metric("MRR", "$12k", "+8%"). Charts: st.line_chart(df), st.plotly_chart(fig), st.altair_chart(chart). Inputs: x = st.slider("Value", 0, 100, 50), name = st.text_input("Name"), option = st.selectbox("Model", ["llama3","gpt4"]), file = st.file_uploader("Upload CSV"). Sidebar: with st.sidebar: st.selectbox(...). Session state: st.session_state.messages persists across reruns — if "messages" not in st.session_state: st.session_state.messages = []. Cache: @st.cache_data memoizes functions returning data (DataFrames, lists), @st.cache_resource caches singleton objects (models, DB connections). Chat: for msg in st.session_state.messages: st.chat_message(msg["role"]).write(msg["content"]). if prompt := st.chat_input(): .... Streaming: with st.chat_message("assistant"): st.write_stream(llm.stream(prompt)). Columns: col1, col2 = st.columns(2), with col1: st.image(img). Tabs: tab1, tab2 = st.tabs(["Train","Eval"]). Form: with st.form("settings"): lr = st.number_input("LR"); submitted = st.form_submit_button("Run"). Multi-page: pages/1_Overview.py, pages/2_Predictions.py convention. st.navigation([Page("app.py","Home"), Page("pages/predict.py","Predict")]). Deploy: push to GitHub → Streamlit Community Cloud at share.streamlit.io. Claude Code generates Streamlit dashboards for ML monitoring, model evaluation, data exploration, and LLM chatbots.
CLAUDE.md for Streamlit
## Streamlit Stack
- Version: streamlit >= 1.37
- Reruns on every widget interaction — design for idempotency
- State: st.session_state["key"] persists across reruns; init with if "key" not in st.session_state
- Cache: @st.cache_data (DataFrames, pure fns) / @st.cache_resource (models, connections)
- Chat: st.chat_input() + st.chat_message(role) + st.write_stream(generator)
- Layout: st.columns(N), st.tabs([...]), st.sidebar, st.expander
- Multi-page: pages/ directory or st.navigation([st.Page(...)])
Streamlit ML Dashboard
# app/streamlit_app.py — ML model monitoring and prediction dashboard
from __future__ import annotations
import io
import time
from typing import Generator
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st
# ── Page config ───────────────────────────────────────────────────────────────
st.set_page_config(
page_title="Claude Code ML Dashboard",
page_icon="🤖",
layout="wide",
initial_sidebar_state="expanded",
)
# ── Cached resources ──────────────────────────────────────────────────────────
@st.cache_resource
def load_model():
"""Load ML model once and cache for all sessions."""
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import make_classification
import time
X, y = make_classification(n_samples=2000, n_features=10, random_state=42)
model = GradientBoostingClassifier(n_estimators=100, random_state=42)
model.fit(X[:1600], y[:1600])
return model, X[1600:], y[1600:]
@st.cache_data
def load_metrics_history() -> pd.DataFrame:
"""Simulated model metrics over time — replace with real DB/file loads."""
dates = pd.date_range("2026-01-01", periods=30, freq="D")
rng = np.random.default_rng(42)
return pd.DataFrame({
"date": dates,
"train_auc": 0.88 + rng.normal(0, 0.01, 30).cumsum() * 0.01,
"val_auc": 0.85 + rng.normal(0, 0.015, 30).cumsum() * 0.01,
"data_drift": rng.uniform(0, 0.3, 30),
"prediction_vol": rng.integers(800, 1200, 30),
})
# ── Helper: streaming LLM response ───────────────────────────────────────────
def stream_llm_response(prompt: str) -> Generator[str, None, None]:
"""Simulated streaming LLM — replace with real model."""
response = f"Analysis of '{prompt[:30]}...': Based on the data, I recommend focusing on features with highest SHAP values. The model shows stable performance with no significant drift detected."
for word in response.split():
time.sleep(0.04)
yield word + " "
# ── Sidebar ────────────────────────────────────────────────────────────────────
with st.sidebar:
st.title("ML Dashboard")
st.markdown("---")
page = st.radio(
"Navigation",
["Overview", "Predictions", "Data Explorer", "Chatbot"],
label_visibility="collapsed",
)
st.markdown("---")
st.markdown("**Model:** GradientBoosting v2.3")
st.markdown("**Status:** 🟢 Healthy")
threshold = st.slider("Decision Threshold", 0.1, 0.9, 0.5, 0.05)
show_proba = st.checkbox("Show probabilities", value=True)
# ── Pages ─────────────────────────────────────────────────────────────────────
if page == "Overview":
st.title("Model Overview")
# KPI metrics row
col1, col2, col3, col4 = st.columns(4)
col1.metric("Val AUC", "0.874", "+0.003")
col2.metric("Daily Volume", "1,024", "+12%")
col3.metric("Data Drift", "0.12", "-0.03", delta_color="inverse")
col4.metric("Latency P99", "42ms", "-5ms")
# Performance chart
df_metrics = load_metrics_history()
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_metrics["date"], y=df_metrics["train_auc"],
name="Train AUC", line=dict(color="royalblue")))
fig.add_trace(go.Scatter(x=df_metrics["date"], y=df_metrics["val_auc"],
name="Val AUC", line=dict(color="tomato")))
fig.add_hline(y=0.80, line_dash="dash", line_color="gray", annotation_text="SLA")
fig.update_layout(title="AUC Over Time", height=350, margin=dict(t=40, b=20))
st.plotly_chart(fig, use_container_width=True)
# Data drift
with st.expander("Data Drift Details"):
fig2 = px.bar(df_metrics, x="date", y="data_drift",
color="data_drift", color_continuous_scale="RdYlGn_r")
fig2.add_hline(y=0.2, line_dash="dash", annotation_text="Alert threshold")
st.plotly_chart(fig2, use_container_width=True)
elif page == "Predictions":
st.title("Run Predictions")
model, X_test, y_test = load_model()
tab1, tab2 = st.tabs(["Single Prediction", "Batch Upload"])
with tab1:
st.markdown("Enter feature values for a single prediction.")
with st.form("prediction_form"):
cols = st.columns(5)
features = []
for i in range(10):
with cols[i % 5]:
val = st.number_input(f"Feature {i+1}", value=0.0, format="%.3f")
features.append(val)
submitted = st.form_submit_button("Predict", type="primary")
if submitted:
x = np.array(features).reshape(1, -1)
proba = model.predict_proba(x)[0, 1]
pred = int(proba >= threshold)
col_a, col_b = st.columns(2)
col_a.metric("Prediction", "Positive ✅" if pred else "Negative ❌")
if show_proba:
col_b.metric("Probability", f"{proba:.3f}")
# Gauge chart
fig = go.Figure(go.Indicator(
mode="gauge+number",
value=proba,
domain={"x": [0, 1], "y": [0, 1]},
gauge={
"axis": {"range": [0, 1]},
"bar": {"color": "tomato" if proba >= threshold else "steelblue"},
"steps": [{"range": [0, threshold], "color": "lightblue"},
{"range": [threshold, 1], "color": "lightsalmon"}],
"threshold": {"value": threshold, "line": {"color": "black", "width": 3}},
},
))
fig.update_layout(height=200, margin=dict(t=20, b=10))
st.plotly_chart(fig, use_container_width=True)
with tab2:
uploaded = st.file_uploader("Upload CSV (no header, 10 features)", type="csv")
if uploaded:
df_upload = pd.read_csv(uploaded, header=None)
st.write(f"Loaded {len(df_upload)} rows")
with st.spinner("Running predictions..."):
time.sleep(0.5)
probas = model.predict_proba(df_upload.values)[:, 1]
preds = (probas >= threshold).astype(int)
df_out = df_upload.copy()
df_out["probability"] = probas.round(4)
df_out["prediction"] = preds
st.dataframe(df_out, use_container_width=True)
csv = io.StringIO()
df_out.to_csv(csv, index=False)
st.download_button("Download Predictions", csv.getvalue(),
"predictions.csv", "text/csv")
elif page == "Data Explorer":
st.title("Data Explorer")
model, X_test, y_test = load_model()
df_test = pd.DataFrame(X_test, columns=[f"f{i}" for i in range(10)])
df_test["label"] = y_test
df_test["score"] = model.predict_proba(X_test)[:, 1]
col1, col2 = st.columns([1, 3])
with col1:
feat_x = st.selectbox("X axis", df_test.columns[:-2], index=0)
feat_y = st.selectbox("Y axis", df_test.columns[:-2], index=1)
color = st.selectbox("Color", ["label", "score"])
with col2:
fig = px.scatter(df_test, x=feat_x, y=feat_y, color=color,
opacity=0.6, height=400)
st.plotly_chart(fig, use_container_width=True)
with st.expander("Raw Data"):
st.dataframe(df_test.head(100), use_container_width=True)
elif page == "Chatbot":
st.title("Model Analysis Chatbot")
st.caption("Ask questions about model performance or get recommendations.")
# Initialize message history in session state
if "messages" not in st.session_state:
st.session_state.messages = []
# Display full chat history
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.write(message["content"])
# Chat input
if prompt := st.chat_input("Ask about the model..."):
# Add user message
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.write(prompt)
# Stream assistant response
with st.chat_message("assistant"):
response = st.write_stream(stream_llm_response(prompt))
st.session_state.messages.append({"role": "assistant", "content": response})
For the Gradio alternative when building ML demos specifically for HuggingFace Spaces with image/audio/video I/O and a purpose-built ChatInterface that requires less boilerplate than Streamlit’s chat primitives — Gradio’s component model is designed for single-turn input-output demos while Streamlit’s st.session_state persistence and multi-page routing make it better for multi-step workflows like model comparison dashboards, data exploration, and analytics applications that users spend time navigating. For the Dash/Plotly alternative when building production BI dashboards with multi-page apps, REST API backends, and enterprise authentication in an organization that already uses Plotly — Dash is the more production-grade choice for pure dashboards while Streamlit’s simpler mental model and faster iteration cycle make it the default for data scientists building ML demos and self-service analytics tools. The Claude Skills 360 bundle includes Streamlit skill sets covering ML dashboards, cache patterns, chatbot UIs with streaming, multi-page apps, form inputs, and Plotly chart integration. Start with the free tier to try data app generation.