🐍 Ngày 49 Python 365 ngày | Xây dựng demo AI với Gradio
🗓 Ngày 49: Đưa model AI cho người khác dùng trong 5 phút
Streamlit rất tốt cho dashboard dữ liệu. Nhưng khi mục tiêu là "cho người khác thử model AI của tôi", Gradio là công cụ chuyên dụng: nó được thiết kế riêng cho input/output của model, hỗ trợ ảnh, audio, video, webcam, file — và deploy lên Hugging Face Spaces chỉ bằng một cú push.
🎯 Mục tiêu bài học
- Hiểu khi nào dùng Gradio thay vì Streamlit.
- Tạo app bằng
gr.Interfacechỉ với 1 hàm Python. - Dùng
gr.Blocksđể bố cục tùy biến. - Xây chatbot với
gr.ChatInterface. - Xử lý ảnh, audio, file.
- Deploy lên Hugging Face Spaces.
- Chia sẻ app tạm thời bằng
share=True.
🆚 Gradio vs Streamlit
| Tiêu chí | Gradio | Streamlit |
|---|---|---|
| Mục đích chính | Demo model AI | Dashboard dữ liệu |
| Điểm mạnh | Ảnh/audio/video/webcam, chatbot, Hugging Face | Bảng, metric, chart, layout |
| Số dòng code cho demo | ~5 | ~15 |
| Chạy cục bộ | demo.launch() | streamlit run app.py |
| Tốc độ dựng UI | Nhanh hơn cho AI | Nhanh hơn cho bảng biểu |
| Deploy phổ biến nhất | Hugging Face Spaces | Streamlit Cloud |
💡 Chọn nhanh: Kết quả là con số/bảng/biểu đồ → Streamlit. Kết quả là ảnh, âm thanh, văn bản sinh ra từ model → Gradio.
🧰 Cài đặt
python -m venv .venv
.venv\Scripts\activate
pip install gradio
python app.py
Gradio tự mở http://127.0.0.1:7860.
🚀 Bước 1: gr.Interface — app nhỏ nhất
# app.py
import gradio as gr
def chao(ten, muc_do):
return f"Xin chào {ten}! Bạn đang ở mức {muc_do} của chuỗi Python 365 ngày. 🐍"
demo = gr.Interface(
fn=chao,
inputs=[
gr.Textbox(label="Tên của bạn", placeholder="VD: Minh"),
gr.Slider(1, 365, value=49, step=1, label="Ngày thứ"),
],
outputs=gr.Textbox(label="Kết quả"),
title="🐍 Demo Python 365 ngày",
description="Nhập tên và số ngày để nhận lời chào.",
examples=[["Minh", 49], ["Lan", 100]],
)
if __name__ == "__main__":
demo.launch()
Cấu trúc gr.Interface chỉ có 4 phần:
| Thành phần | Vai trò |
|---|---|
fn | Hàm Python xử lý logic |
inputs | Danh sách component đầu vào |
outputs | Danh sách component đầu ra |
examples | Mẫu thử — Gradio tự sinh nút bấm |
Gradio tự suy ra kiểu dữ liệu và tự tạo giao diện. Bạn không viết HTML, không viết route.
🎨 Bước 2: Bảng component thường dùng
| Component | Dùng cho | Ví dụ |
|---|---|---|
gr.Textbox | Văn bản | gr.Textbox(lines=5) |
gr.Number | Số | gr.Number(value=0) |
gr.Slider | Khoảng giá trị | gr.Slider(0, 100) |
gr.Dropdown | Chọn một/nhiều | gr.Dropdown(["A","B"], multiselect=True) |
gr.Radio | Chọn một | gr.Radio(["MUA","BAN"]) |
gr.Checkbox | Đúng/sai | gr.Checkbox(label="Bật") |
gr.Image | Ảnh / webcam | gr.Image(type="numpy") |
gr.Audio | Âm thanh / micro | gr.Audio(type="filepath") |
gr.Video | Video | gr.Video() |
gr.File | Tệp bất kỳ | gr.File(file_count="multiple") |
gr.Dataframe | Bảng dữ liệu | gr.Dataframe(headers=[...]) |
gr.Plot | Biểu đồ | gr.Plot() |
gr.Chatbot | Hội thoại | gr.Chatbot(type="messages") |
🔢 Bước 3: Ví dụ thực tế — máy tính chỉ báo kỹ thuật
import gradio as gr
import pandas as pd
import numpy as np
def tinh_chi_bao(gia_str: str, chu_ky_sma: int) -> tuple[pd.DataFrame, str]:
"""Tính SMA và RSI từ chuỗi giá người dùng nhập."""
try:
gia = [float(x.strip()) for x in gia_str.replace(",", " ").split() if x.strip()]
except ValueError:
return pd.DataFrame(), "❌ Dữ liệu không hợp lệ. Nhập các số cách nhau bởi dấu cách."
if len(gia) < chu_ky_sma:
return pd.DataFrame(), f"❌ Cần ít nhất {chu_ky_sma} giá trị, bạn nhập {len(gia)}."
df = pd.DataFrame({"gia": gia})
df["sma"] = df["gia"].rolling(chu_ky_sma).mean()
delta = df["gia"].diff()
tang = delta.clip(lower=0).rolling(14).mean()
giam = (-delta.clip(upper=0)).rolling(14).mean()
rs = tang / giam.replace(0, np.nan)
df["rsi"] = 100 - (100 / (1 + rs))
nhan_xet = []
if df["rsi"].iloc[-1] > 70:
nhan_xet.append("⚠️ RSI > 70 — vùng quá mua")
elif df["rsi"].iloc[-1] < 30:
nhan_xet.append("💡 RSI < 30 — vùng quá bán")
else:
nhan_xet.append("➖ RSI trong vùng trung tính")
return df.round(2), "\n".join(nhan_xet)
demo = gr.Interface(
fn=tinh_chi_bao,
inputs=[
gr.Textbox(label="Chuỗi giá (cách nhau bởi dấu cách)",
placeholder="100 102 101 105 108 107 110",
lines=3),
gr.Slider(2, 50, value=5, step=1, label="Chu kỳ SMA"),
],
outputs=[
gr.Dataframe(label="Bảng kết quả"),
gr.Textbox(label="Nhận xét"),
],
title="📊 Máy tính chỉ báo kỹ thuật",
description="Nhập chuỗi giá để tính SMA và RSI.",
)
demo.launch()
Điểm quan trọng: hàm fn trả về tuple khớp thứ tự với danh sách outputs.
🧱 Bước 4: gr.Blocks — bố cục tự do
gr.Interface bị giới hạn theo khuôn mẫu. Khi cần nhiều tab, nhiều nút, nhiều luồng xử lý → dùng gr.Blocks.
import gradio as gr
def phan_tich(symbol, so_ngay):
return f"Đang phân tích {symbol} trong {so_ngay} ngày", pd.DataFrame()
with gr.Blocks(title="Phân tích Crypto", theme=gr.themes.Soft()) as demo:
gr.Markdown("# 📈 Công cụ phân tích Crypto")
gr.Markdown("Chọn cặp và số ngày, bấm **Phân tích**.")
with gr.Row():
with gr.Column(scale=1):
symbol = gr.Dropdown(
["BTCUSDT", "ETHUSDT", "SOLUSDT"], value="BTCUSDT", label="Cặp giao dịch"
)
so_ngay = gr.Slider(7, 365, value=90, step=7, label="Số ngày")
nut = gr.Button("🔍 Phân tích", variant="primary")
nut_xoa = gr.Button("🗑 Xóa")
with gr.Column(scale=3):
with gr.Tabs():
with gr.Tab("Kết quả"):
ket_qua = gr.Textbox(label="Trạng thái", lines=2)
bang = gr.Dataframe(label="Dữ liệu")
with gr.Tab("Hướng dẫn"):
gr.Markdown("""
1. Chọn cặp giao dịch
2. Kéo thanh chọn số ngày
3. Bấm **Phân tích**
""")
# Gắn sự kiện — đây là điểm khác biệt cốt lõi so với gr.Interface
nut.click(phan_tich, inputs=[symbol, so_ngay], outputs=[ket_qua, bang])
nut_xoa.click(lambda: ("", None), outputs=[ket_qua, bang])
demo.launch()
Trong gr.Interface | Trong gr.Blocks |
|---|---|
| Không khai báo sự kiện | .click(), .change(), .submit() |
| Một luồng duy nhất | Nhiều sự kiện, nhiều nút |
| Không bố cục tùy biến | Row, Column, Tabs, Accordion |
💬 Bước 5: Chatbot với gr.ChatInterface
import gradio as gr
def tra_loi(message, history):
"""Chatbot rule-based đơn giản (thay bằng LLM API nếu muốn)."""
cau_hoi = message.lower()
if "python" in cau_hoi:
return "Python là ngôn ngữ lập trình bậc cao, dùng nhiều trong AI và phân tích dữ liệu."
if "sma" in cau_hoi or "trung bình" in cau_hoi:
return "SMA là đường trung bình động đơn giản — trung bình cộng của N giá gần nhất."
if "cài" in cau_hoi or "install" in cau_hoi:
return "Cài thư viện: `pip install ten-thu-vien`"
return f"Tôi chưa có câu trả lời cho: “{message}”. Bạn hỏi về Python, SMA hoặc cách cài thư viện nhé!"
gr.ChatInterface(
fn=tra_loi,
title="🐍 Trợ lý Python 365 ngày",
description="Hỏi về Python, chỉ báo kỹ thuật hoặc cách cài thư viện.",
examples=["Python là gì?", "SMA là gì?", "Cách cài thư viện?"],
type="messages",
).launch()
💡 Mở rộng lên LLM thật: chỉ cần thay thân hàm
tra_loibằng lời gọi API (OpenAI, Gemini, mô hình local). Toàn bộ giao diện giữ nguyên.
🖼️ Bước 6: Xử lý ảnh
import gradio as gr
from PIL import Image, ImageFilter
def lam_mo_anh(anh: Image.Image, ban_kinh: float):
if anh is None:
return None, "Chưa có ảnh"
ket_qua = anh.filter(ImageFilter.GaussianBlur(radius=ban_kinh))
return ket_qua, f"Đã làm mờ với bán kính {ban_kinh}"
demo = gr.Interface(
fn=lam_mo_anh,
inputs=[
gr.Image(type="pil", label="Ảnh đầu vào", sources=["upload", "webcam"]),
gr.Slider(0, 20, value=5, step=0.5, label="Bán kính làm mờ"),
],
outputs=[gr.Image(label="Ảnh kết quả"), gr.Textbox(label="Trạng thái")],
live=True, # tự cập nhật khi kéo slider — không cần bấm nút
title="🖼️ Làm mờ ảnh",
)
demo.launch()
live=True là điểm rất mạnh của Gradio: kéo slider là ảnh cập nhật ngay.
🌐 Bước 7: Chia sẻ và deploy
Chia sẻ tạm thời (72 giờ):
demo.launch(share=True)
# → https://xxxxx.gradio.live
Deploy lên Hugging Face Spaces (miễn phí, vĩnh viễn):
- Tạo tài khoản huggingface.co.
- New Space → chọn SDK Gradio → Public.
- Upload
app.py+requirements.txt. - Space tự build và chạy. URL:
https://huggingface.co/spaces/<user>/<ten>.
requirements.txt:
gradio==5.*
pandas
plotly
Checklist:
-
demo.launch()chỉ ở trongif __name__ == "__main__"hoặc gọi trực tiếp ở cuối file - Không hard-code API key — dùng Hugging Face Secrets
- Đã liệt kê đủ thư viện trong
requirements.txt - Xử lý lỗi trong
fnđể app không crash
🧪 Bài tập thực hành
- Viết app tính MACD từ chuỗi giá người dùng nhập.
- Thêm
gr.Plotđể vẽ biểu đồ giá và SMA bằng Plotly. - Tạo app cho phép tải file CSV lên và trả về thống kê mô tả.
- Xây
gr.ChatInterfacetrả lời về 5 khái niệm Python bạn đã học. - Dùng
gr.Blockstạo app 2 tab: tab 1 phân tích, tab 2 hướng dẫn. - Deploy một app lên Hugging Face Spaces và lấy link.
⚠️ Lỗi thường gặp
| Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
Number of outputs must match | Số phần tử trả về ≠ số outputs | Trả về tuple đúng số lượng |
AttributeError: 'str' object has no attribute ... | Sai type= của component | gr.Image(type="pil") trả PIL, type="numpy" trả array |
| App chạy chậm | Xử lý nặng trong fn | Thêm @lru_cache hoặc xử lý vector hóa |
share=True không hoạt động | Mạng chặn | Kiểm tra firewall; dùng GRADIO_SERVER_NAME |
| Port 7860 bị chiếm | App khác đang chạy | demo.launch(server_port=7861) |
| Space build lỗi | Thiếu thư viện | Cập nhật requirements.txt |
📝 Ghi chú
- Gradio tự sinh API — mọi app đều có endpoint REST; xem tab "Use via API" ở chân trang.
queue()giúp xử lý request nặng tuần tự, tránh quá tải.- Không dùng Gradio cho dashboard bảng biểu phức tạp — Streamlit phù hợp hơn.
- Gradio là lựa chọn số 1 khi công khai model lên Hugging Face.
- Với LLM,
gr.ChatInterface+type="messages"là chuẩn hiện tại.
🎯 Tóm tắt ngày 49
✅ Hiểu khi nào chọn Gradio thay vì Streamlit
✅ Tạo app bằng gr.Interface chỉ với 4 thành phần
✅ Bố cục tự do và gắn sự kiện bằng gr.Blocks
✅ Xây chatbot với gr.ChatInterface
✅ Xử lý ảnh/audio/video/file
✅ Deploy lên Hugging Face Spaces và chia sẻ link tạm