~/wiki

Cheatsheets — vue longue

retour à la liste

Toutes les pages concaténées sur un seul document, pour un Ctrl-F direct.

Interfaces de démo pour modèles ML. Contrairement à streamlit, le modèle est événementiel : on déclare des composants et on branche des fonctions dessus, sans réexécution complète du script.

uv add gradio
python app.py          # sert sur http://127.0.0.1:7860

Le plus court chemin

import gradio as gr

def check(site: str, produit: str) -> str:
    return f"{produit} trouvé sur {site} en 3 clics"

gr.Interface(
    fn=check,
    inputs=["text", "text"],
    outputs="text",
    title="Findability checker",
).launch()
* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set `share=True` in `launch()`.

share=True ouvre un tunnel public valable 72 h — pratique pour montrer une démo à un client sans rien déployer.

Blocks, pour tout le reste

Interface couvre le cas « une fonction, des entrées, des sorties ». Dès qu'il faut de la mise en page ou plusieurs interactions, passer à Blocks.

with gr.Blocks(title="Findability") as demo:
    gr.Markdown("## Test d'accessibilité produit")

    with gr.Row():
        site = gr.Textbox(label="Site", value="versace.com")
        produit = gr.Textbox(label="Produit")

    with gr.Row():
        budget = gr.Slider(10, 200, value=60, step=10, label="Budget d'étapes")
        mode = gr.Radio(["fast", "default"], value="default", label="Mode")

    run = gr.Button("Lancer", variant="primary")
    out = gr.JSON(label="Résultat")
    gallery = gr.Gallery(label="Captures", columns=3)

    run.click(fn=check, inputs=[site, produit, budget, mode], outputs=[out, gallery])

demo.launch()

inputs et outputs sont des listes de composants, et la fonction doit renvoyer autant de valeurs qu'il y a de sorties, dans le même ordre.

Composants courants

Composant Usage
gr.Textbox(lines=5) texte court ou long
gr.Number, gr.Slider valeurs numériques
gr.Dropdown(choices=[...], multiselect=True) listes
gr.Checkbox, gr.Radio booléens et choix exclusifs
gr.Image(type="pil") image en entrée ou sortie
gr.File, gr.Audio, gr.Video fichiers
gr.Dataframe tableau éditable
gr.JSON, gr.Label sorties structurées
gr.Plot figure matplotlib ou plotly

Événements

run.click(fn, inputs=..., outputs=...)
site.change(fn, inputs=site, outputs=out)
site.submit(fn, ...)          # touche Entrée
demo.load(fn, ...)            # au chargement de la page

Enchaîner des étapes en gardant l'interface réactive :

run.click(prepare, [site], [status]).then(execute, [site], [out])

Streaming

def stream(prompt):
    partial = ""
    for chunk in llm.stream(prompt):
        partial += chunk
        yield partial            # un yield = une mise à jour de l'UI

gr.Interface(fn=stream, inputs="text", outputs="text").launch()

Un générateur suffit : chaque yield pousse une nouvelle valeur au composant de sortie.

Chat

def respond(message, history):
    return f"Reçu : {message}"

gr.ChatInterface(respond, type="messages").launch()

history arrive en liste de dicts {"role", "content"} avec type="messages". C'est le format aligné sur les API LLM, à préférer à l'ancien format en tuples.

État par session

with gr.Blocks() as demo:
    state = gr.State([])          # propre à chaque visiteur

    def add(item, current):
        current = current + [item]
        return current, current

    box = gr.Textbox()
    out = gr.JSON()
    box.submit(add, [box, state], [state, out])

gr.State n'est jamais rendu, il transporte des données Python entre événements.

Files d'attente et progression

def long_task(x, progress=gr.Progress()):
    for i in progress.tqdm(range(10), desc="Sessions"):
        run(i)
    return "ok"

demo.queue(max_size=20).launch()

queue() est nécessaire dès que plusieurs personnes utilisent la démo en même temps.

Déployer sur Hugging Face Spaces

Un repo avec app.py, requirements.txt, et un README.md à en-tête :

---
title: Findability Checker
sdk: gradio
sdk_version: "5.0.0"
app_file: app.py
---

Les secrets se règlent dans les paramètres du Space et se lisent par os.environ.

Gradio ou Streamlit

Gradio pour exposer un modèle ou une fonction, surtout avec image, audio ou chat, et pour publier sur Spaces. Streamlit pour un tableau de bord de données avec des filtres. Les deux sont des outils de démo : au-delà, une vraie application.

See also

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.

See also