Streamlit
Interface web en Python pur. Le meilleur rapport temps/effet pour une démo client interne.
Le modèle d'exécution, à comprendre en premier
Le script entier est réexécuté de haut en bas à chaque interaction. Pas de callbacks, pas de composants. C'est ce qui rend Streamlit simple et ce qui provoque toutes ses surprises.
Deux conséquences : tout état doit vivre dans st.session_state, et tout calcul coûteux
doit être mis en cache.
pip install streamlit
streamlit run app.py
You can now view your Streamlit app in your browser.
Local URL: http://localhost:8501
Network URL: http://192.168.1.24:8501
Afficher
import streamlit as st
st.title("Test d'accessibilité produit")
st.header("Résultats")
st.subheader("Par site")
st.write("Accepte à peu près tout : texte, DataFrame, figure, dict")
st.markdown("**gras**, `code`, [lien](https://example.com)")
st.code("requests.post(url, json=payload)", language="python")
st.json({"findable": True, "clicks": 3})
st.dataframe(df) # interactif, triable
st.table(df.head()) # statique
st.metric("Clics moyens", 3.2, delta=-0.4)
Saisir
site = st.text_input("Site", value="versace.com")
produit = st.text_area("Produit recherché")
budget = st.slider("Budget d'étapes", 10, 200, 60)
n = st.number_input("Répétitions", min_value=1, max_value=10, value=3)
mode = st.selectbox("Mode", ["fast", "default"])
tags = st.multiselect("Tests", ["navigation", "panier", "checkout"])
strict = st.checkbox("Mode strict")
fichier = st.file_uploader("CSV", type=["csv"])
if st.button("Lancer"):
...
Chaque widget renvoie sa valeur courante. Le if st.button(...) n'est vrai que sur le
rerun déclenché par le clic.
Mise en page
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Sessions", 12)
with st.sidebar:
api_key = st.text_input("Clé API", type="password")
tab1, tab2 = st.tabs(["Résultats", "Logs"])
with tab1:
st.dataframe(df)
with st.expander("Détails techniques"):
st.code(trace)
with st.container():
st.write("bloc regroupé")
État
if "runs" not in st.session_state:
st.session_state.runs = []
if st.button("Ajouter"):
st.session_state.runs.append({"site": site})
st.write(f"{len(st.session_state.runs)} exécutions")
Sans session_state, la liste serait recréée vide à chaque interaction.
Cache
@st.cache_data # pour des données : DataFrame, JSON, réponses API
def charger(path: str):
return pd.read_csv(path)
@st.cache_resource # pour des objets vivants : client, modèle, connexion
def get_client():
return SomeClient(api_key=os.environ["API_KEY"])
La distinction compte : cache_data sérialise et renvoie une copie, cache_resource
renvoie le même objet partagé entre sessions. Un client HTTP dans cache_data casse.
Retour visuel pendant un traitement long
with st.spinner("Session en cours…"):
result = run_agent(site, produit)
bar = st.progress(0)
for i, test in enumerate(tests):
run(test)
bar.progress((i + 1) / len(tests))
st.success("Terminé")
st.warning("2 tests non concluants")
st.error("Clé API invalide")
with st.status("Navigation…", expanded=True) as s:
st.write("Ouverture du site")
st.write("Recherche du produit")
s.update(label="Terminé", state="complete")
Streaming d'un LLM
def tokens():
for chunk in client.stream(prompt):
yield chunk.text
st.write_stream(tokens)
Pour un chat complet, st.chat_message("user" | "assistant") et st.chat_input().
Secrets et configuration
.streamlit/secrets.toml :
API_KEY = "sk-..."
key = st.secrets["API_KEY"]
.streamlit/config.toml pour le thème et le port :
[server]
port = 8501
[theme]
base = "light"
Limites à connaître avant de s'engager
Pas de routing multi-pages fin (juste un dossier pages/), peu de contrôle sur le CSS, et
le modèle de rerun devient pénible dès qu'on veut une vraie interactivité. Au-delà de la
démo, passer à Next.js avec une API FastAPI derrière.
Déploiement : Streamlit Community Cloud gratuit depuis un repo GitHub, ou un conteneur Docker n'importe où. Pas sur Vercel — Vercel ne fait pas tourner de processus Python long.