BestYou / app.py
mat.marra
Chat: cache_examples=False (il precalcolo esempi chiama respond() senza OAuth e crasha lo startup)
e348a27
Raw
History Blame Contribute Delete
5.85 kB
import gradio as gr
from huggingface_hub import InferenceClient
from embed import embed_texts, preload_model_async
from measure import estimate_measurements
# Scalda il modello embeddings in background: la prima chiamata del gestionale non paga il cold start.
preload_model_async()
# Modello: 20B open-weights via HF Inference (gira sul token OAuth dell'utente loggato).
MODEL_ID = "openai/gpt-oss-20b"
# Persona e regole dell'assistente. Baked-in: non è un campo modificabile dall'utente,
# così la chat si comporta sempre come l'assistente di BestYou e non come un chatbot generico.
BESTYOU_SYSTEM = """Sei l'assistente AI di BestYou, l'app di coaching di Simone Bestagno.
Aiuti gli atleti seguiti dal coach su allenamento in palestra, tecnica degli esercizi,
alimentazione sportiva, recupero, sonno e motivazione.
STILE
- Parli SEMPRE in italiano e dai del tu.
- Tono da coach: chiaro, diretto, concreto e motivante, mai supponente.
- Lunghezza adattiva: risposte brevi per domande semplici; entra nel dettaglio (anche con
elenchi puntati) solo quando la domanda lo richiede davvero. Niente muri di testo inutili.
- Vai al sodo: consigli pratici e applicabili, non teoria generica.
COSA PUOI FARE
- Spiegare come eseguire un esercizio, quali muscoli lavora, errori comuni e alternative.
- Dare consigli generali su serie/ripetizioni, recupero, progressione, tecnica, riscaldamento.
- Consigli generali di alimentazione sportiva, idratazione, timing dei pasti, integratori comuni
(proteine, creatina, ecc.) a livello informativo.
- Motivare, aiutare con costanza, gestione della fatica e degli imprevisti.
LIMITI (importante)
- NON sei un medico: non fai diagnosi né dai terapie. In caso di dolore, infortunio, patologie,
gravidanza o farmaci, invita a fermarsi e a rivolgersi al medico e a sentire il coach Simone.
- La scheda di allenamento e la dieta le imposta il COACH. Tu le spieghi e dai consigli generali,
ma NON crei né modifichi la scheda o la dieta del cliente e non inventi numeri specifici del suo
piano che non conosci. Se serve una modifica del piano, invita a scrivere al coach.
- Se non sai qualcosa, dillo con onestà invece di inventare.
- Resta sul tema fitness/benessere: se la domanda è fuori contesto, riporta gentilmente al tema.
"""
def respond(
message,
history: list[dict[str, str]],
hf_token: gr.OAuthToken,
):
"""
Genera la risposta dell'assistente BestYou in streaming.
Docs Inference API: https://huggingface.co/docs/huggingface_hub/guides/inference
"""
client = InferenceClient(token=hf_token.token, model=MODEL_ID)
messages = [{"role": "system", "content": BESTYOU_SYSTEM}]
messages.extend(history)
messages.append({"role": "user", "content": message})
response = ""
for chunk in client.chat_completion(
messages,
max_tokens=1024,
stream=True,
temperature=0.6,
top_p=0.95,
):
choices = chunk.choices
token = ""
if len(choices) and choices[0].delta.content:
token = choices[0].delta.content
response += token
yield response
chatbot = gr.ChatInterface(
respond,
title="Assistente BestYou",
description="Il tuo coach virtuale per allenamento, alimentazione e recupero. "
"Accedi con Hugging Face (pulsante nella barra laterale) per iniziare.",
examples=[
"Come si esegue bene lo stacco rumeno?",
"Che differenza c'è tra serie a cedimento e a buffer?",
"Cosa mangio prima di allenarmi la mattina presto?",
"Non ho voglia di allenarmi oggi, come mi motivo?",
],
# Senza questo Gradio prova a PRECALCOLARE gli esempi all'avvio chiamando respond(), che
# richiede il login OAuth dell'utente -> lo Space crasherebbe in startup.
cache_examples=False,
)
# Stima misure corporee (vita/petto/fianchi) da una foto check + altezza nota: usata dal gestionale
# BestYou (FitRework-KMP) nella schermata "Check". Pipeline CPU-only: segmentazione (rembg) + pose
# keypoints (mediapipe) + calibrazione con l'altezza del cliente. Vedi measure.py per i dettagli e i
# limiti (è una STIMA approssimativa, non sostituisce il metro).
measure_interface = gr.Interface(
fn=estimate_measurements,
inputs=[
gr.Image(type="pil", label="Foto check (frontale, figura intera)"),
gr.Number(label="Altezza cliente (cm)", value=170, minimum=100, maximum=230),
gr.Textbox(label="Token", type="password", value=""),
],
outputs=gr.JSON(label="Stima misure (approssimativa)"),
title="Stima misure da foto",
description=(
"Stima APPROSSIMATIVA di vita/petto/fianchi da una foto frontale a figura intera + altezza "
"nota del cliente. Da usare come valore di PARTENZA da confermare/correggere a mano, non "
"come sostituto del metro da sarta."
),
)
# Embeddings per il matching semantico del gestionale (esercizi -> catalogo). Endpoint API
# (/gradio_api/call/embed_texts); il tab serve solo per prova manuale.
embed_interface = gr.Interface(
fn=embed_texts,
inputs=[
gr.Textbox(label='Testi (array JSON, es. ["lat machine", "panca piana"])'),
gr.Textbox(label="Token", type="password", value=""),
],
outputs=gr.JSON(label="Vettori normalizzati"),
title="Embeddings testi (uso interno)",
description="Vettori semantici per il matching esercizi del gestionale BestYou. "
"Similarità coseno = prodotto scalare (vettori normalizzati).",
)
with gr.Blocks() as demo:
with gr.Sidebar():
gr.LoginButton()
with gr.Tabs():
with gr.Tab("Chat"):
chatbot.render()
with gr.Tab("Misure da foto"):
measure_interface.render()
with gr.Tab("Embeddings"):
embed_interface.render()
if __name__ == "__main__":
demo.launch()