Dùng AI

Làm giao diện chatbot web bằng Streamlit

Streamlit cho bạn làm giao diện web chỉ bằng Python, không cần HTML hay JavaScript. Bài này dựng một chatbot trợ giảng Python gồm hai tệp: một tệp lo giao diện (ô nhập, khung tin nhắn, nhớ hội thoại, chữ hiện dần), một tệp lo gọi mô hình Claude và cắt bớt lịch sử cũ. Cách tách này giúp thử phần gọi mô hình mà không cần mở trình duyệt, và đổi mô hình mà không đụng vào giao diện.

Ý chính
  • Streamlit có sẵn các thành phần cho chat: ô nhập st.chat_input, khung tin nhắn st.chat_message, và st.write_stream để hiện chữ dần dần.
  • Streamlit chạy lại cả tệp mỗi lần người dùng tương tác, nên lịch sử hội thoại phải lưu trong st.session_state.
  • Tách phần gọi mô hình ra tệp riêng: thử được bằng client giả, đổi mô hình không cần sửa giao diện.
  • Cắt bớt lịch sử cũ trước khi gửi, để chi phí không tăng mãi theo độ dài hội thoại; giữ khóa API ngoài code.

Bài này dựa trên hướng dẫn làm ứng dụng chat của Streamlit, trang tham chiếu st.write_stream, trang quản lý bí mật của Streamlit, và trang truyền dần (streaming) trong tài liệu của Anthropic, đọc ngày 11/11/2026. Nếu chưa gọi API mô hình bao giờ, đọc trước bài gọi API AI bằng Python.

Streamlit chạy thế nào

Một ứng dụng Streamlit là một tệp Python bình thường. Mỗi lệnh như st.title hay st.markdown vẽ một thành phần lên trang. Điểm khác với chương trình thường: mỗi khi người dùng gõ một câu hay bấm một nút, Streamlit chạy lại tệp từ đầu tới cuối. Biến thường vì vậy bị đặt lại mỗi lần. Thứ cần nhớ qua các lần chạy, như lịch sử hội thoại, phải để trong st.session_state, một chỗ lưu riêng cho mỗi phiên trình duyệt.

Hướng dẫn chính thức của Streamlit dùng đúng mẫu này: lịch sử là một danh sách các tin nhắn, mỗi tin có vai trò và nội dung; mỗi lần chạy, vẽ lại toàn bộ lịch sử rồi xử lý câu hỏi mới nếu có. Định dạng tin nhắn này trùng với định dạng Messages API cần, nên có thể gửi thẳng lịch sử cho mô hình.

Chatbot web: hai tệp Giao diện tách khỏi phần gọi mô hình app.py: nhận câu hỏist.chat_inputô nhập ở cuối trang app.py: nhớ hội thoạist.session_stategiữ qua mỗi lần chạy lại tro_ly.py: gọi mô hìnhmessages.streamcắt bớt lịch sử cũ app.py: hiện dầnst.write_streamchữ hiện ngay khi có Tách phần gọi mô hình để thử được không cần giao diện
Chatbot web: hai tệp app.py: nhận câu hỏist.chat_inputô nhập ở cuối trang app.py: nhớ hội thoạist.session_stategiữ qua mỗi lần chạy lại tro_ly.py: gọi mô hìnhmessages.streamcắt bớt lịch sử cũ app.py: hiện dầnst.write_streamchữ hiện ngay khi có Tách phần gọi mô hình để thử được không cần giao diện
Hình 1. Chatbot web gồm hai tệp. app.py nhận câu hỏi bằng st.chat_input (ô nhập ở cuối trang), nhớ hội thoại bằng st.session_state (giữ qua mỗi lần chạy lại), hiện chữ dần bằng st.write_stream. tro_ly.py gọi mô hình bằng messages.stream và cắt bớt lịch sử cũ. Tách phần gọi mô hình để thử được không cần giao diện.

Hai tệp của chatbot

Tệp tro_ly.py không biết gì về giao diện. Hàm cat_lich_su giữ tối đa mười tin gần nhất và bảo đảm tin đầu tiên gửi đi là của người dùng, vì API yêu cầu hội thoại bắt đầu bằng người dùng. Hàm tra_loi_tung_phan gọi Claude ở chế độ truyền dần và trả về từng đoạn chữ ngay khi mô hình sinh ra; lỗi từ thư viện anthropic được đổi thành một lỗi riêng có lời nhắn dễ hiểu. Khi luồng kết thúc, hàm xem lý do dừng: câu trả lời bị cắt vì chạm max_tokens, bị từ chối hay không có chữ cũng thành lỗi riêng đó, để giao diện báo rõ và không lưu câu dở dang vào lịch sử; max_tokens để rộng vì phần suy nghĩ của mô hình cũng tính vào đó. Hàm tao_client đọc khóa từ biến môi trường và báo rõ nếu chưa có.

Tệp app.py là giao diện. Nó vẽ tiêu đề và một dòng nhắc rằng câu trả lời do AI tạo có thể sai, vẽ lại lịch sử, và khi có câu hỏi mới thì thêm vào lịch sử, gọi tra_loi_tung_phan, đưa kết quả cho st.write_stream để hiện dần, rồi lưu câu trả lời đầy đủ vào lịch sử.

"""Giao diện chatbot bằng Streamlit. Chạy: streamlit run app.py
Cài: python -m pip install streamlit anthropic ; đặt biến môi trường ANTHROPIC_API_KEY."""
import streamlit as st

import tro_ly

st.title("Trợ giảng Python")
st.caption("Câu trả lời do AI tạo, có thể sai. Đừng nhập thông tin cá nhân.")

if "lich_su" not in st.session_state:
    st.session_state.lich_su = []

for tin in st.session_state.lich_su:
    with st.chat_message(tin["role"]):
        st.markdown(tin["content"])

if cau_hoi := st.chat_input("Hỏi về Python..."):
    st.session_state.lich_su.append({"role": "user", "content": cau_hoi})
    with st.chat_message("user"):
        st.markdown(cau_hoi)
    try:
        client = tro_ly.tao_client()
    except RuntimeError as e:
        st.error(str(e))
        st.stop()
    with st.chat_message("assistant"):
        try:
            tra_loi = st.write_stream(tro_ly.tra_loi_tung_phan(client, tro_ly.cat_lich_su(st.session_state.lich_su)))
        except tro_ly.LoiAPI as e:
            st.session_state.lich_su.pop()  # bỏ câu hỏi chưa được trả lời để lịch sử luôn xen kẽ người dùng / trợ lý
            st.error(str(e))
            st.stop()
    st.session_state.lich_su.append({"role": "assistant", "content": tra_loi})

Chạy bằng lệnh streamlit run app.py trong thư mục chứa hai tệp, sau khi cài streamlit và anthropic và đặt biến môi trường khóa API. Trình duyệt sẽ mở trang chat ở địa chỉ máy của bạn.

Dựng từng bước

Nếu tự gõ lại từ đầu, nên đi từng bước nhỏ và chạy thử sau mỗi bước, thay vì viết cả hai tệp một lần rồi mới chạy.

Bước một, làm một trang chỉ có tiêu đề và ô nhập, mỗi lần gõ thì in lại đúng câu vừa gõ. Bước này giúp bạn quen với việc Streamlit chạy lại cả tệp: thêm một dòng in ra thời gian hiện tại ở đầu tệp, gõ vài câu, bạn sẽ thấy thời gian đổi sau mỗi lần gửi.

Bước hai, thêm lịch sử. Lần đầu thử lưu vào một danh sách thường, gõ hai câu, thấy câu đầu biến mất. Chuyển sang lưu trong bộ nhớ phiên, gõ lại, thấy cả hai câu còn. Đây là bài học quan trọng nhất của Streamlit.

Bước ba, thay câu trả lời giả bằng lời gọi mô hình thật nhưng chưa truyền dần: chờ xong rồi hiện. Đo xem mất bao lâu với một câu hỏi dài.

Bước bốn, chuyển sang truyền dần và so cảm giác khi dùng. Bước năm, tách phần gọi mô hình ra tệp riêng và viết bộ thử với client giả. Sau năm bước, bạn có đúng hai tệp như trong bài và hiểu vì sao từng dòng có mặt ở đó.

Đưa lên mạng cho người khác dùng

Chạy trên máy là đủ để học và demo. Khi muốn gửi đường dẫn cho bạn bè hay nhà tuyển dụng xem, có vài lựa chọn, trong đó có dịch vụ của chính Streamlit lấy mã trực tiếp từ kho GitHub. Trước khi đưa lên, rà ba điều. Khóa nằm trong phần cấu hình bí mật của dịch vụ, không nằm trong kho. Có hạn mức chi tiêu ở phía tài khoản API, vì ai có đường dẫn cũng gọi được mô hình bằng tiền của bạn. Và trang có dòng nhắc rõ đây là ứng dụng thử, câu trả lời do AI tạo. Với portfolio, kèm theo README mô tả kiến trúc hai tệp và cách chạy thử, như hướng dẫn trong bài portfolio AI trên GitHub.

Vì sao hiện chữ dần

Một câu trả lời vài trăm chữ có thể mất vài giây. Nếu chờ xong mới hiện, người dùng nhìn màn hình trống và tưởng ứng dụng treo. Truyền dần cho chữ hiện ngay khi mô hình sinh ra. Phía Claude, thư viện có hàm messages.stream với thuộc tính text_stream trả từng đoạn chữ; phía Streamlit, st.write_stream nhận một nguồn sinh như vậy, hiện từng đoạn và trả về toàn bộ chuỗi khi xong. Hai thứ nối với nhau bằng một hàm generator chỉ vài dòng.

Tài liệu của Anthropic còn lưu ý lỗi có thể xảy ra giữa chừng khi đang truyền dần. Trong code, lỗi như vậy làm hàm generator ném lỗi riêng; giao diện bắt lỗi, bỏ câu hỏi chưa được trả lời ra khỏi lịch sử để lịch sử luôn xen kẽ người dùng và trợ lý, rồi hiện thông báo.

Giữ chi phí và khóa an toàn

Mỗi câu hỏi gửi kèm cả lịch sử, nên hội thoại càng dài càng tốn. Hàm cắt lịch sử giữ mười tin gần nhất là cách đơn giản nhất. Cách tốt hơn cho hội thoại dài là tóm tắt phần cũ, hoặc dùng prompt caching cho phần đầu cố định.

Khóa API không bao giờ viết trong code. Chạy trên máy, đặt biến môi trường. Khi đưa ứng dụng lên dịch vụ của Streamlit, tài liệu hướng dẫn dùng tệp bí mật trong thư mục .streamlit và đọc bằng st.secrets; tệp đó phải nằm trong danh sách bỏ qua của Git. Xem thêm bài giữ khóa API an toàn. Nếu cho người khác dùng ứng dụng, nên đặt hạn mức chi tiêu trong trang quản lý tài khoản API và giới hạn số câu hỏi mỗi phiên.

Thử mà không mở trình duyệt

Vì phần gọi mô hình nằm riêng, bạn thử nó bằng một client giả trả về vài đoạn chữ cố định, không tốn tiền, không cần mạng. Streamlit còn có công cụ thử giao diện trong mô-đun kiểm thử của nó: chạy app.py, điền câu hỏi vào ô nhập, rồi kiểm tra lịch sử và các tin nhắn hiện ra. Bộ thử đi kèm bài dùng cả hai cách: giả lập hai lượt hỏi đáp, giả lập lỗi API, và trường hợp chưa có khóa.

Mở rộng

Từ khung này có thể làm tiếp nhiều dự án portfolio. Thêm ô tải tệp để chatbot trả lời dựa trên giáo trình, theo cách trong bài RAG là gì. Thêm thanh bên để chọn mô hình hoặc độ dài câu trả lời. Thêm nút xóa hội thoại bằng cách đặt lại st.session_state. Mỗi thay đổi chỉ chạm một trong hai tệp.

Cẩn thận
  • Viết khóa API thẳng trong app.py rồi đưa lên GitHub.
  • Lưu lịch sử trong biến thường: mỗi lần chạy lại là mất.
  • Gửi toàn bộ lịch sử không giới hạn, chi phí tăng theo từng câu.
  • Không xử lý lỗi giữa chừng, để lại câu hỏi treo trong lịch sử.
  • Cho người lạ dùng mà không có hạn mức chi tiêu.

Câu hỏi thường gặp

Streamlit có miễn phí không?

Thư viện là mã nguồn mở. Dịch vụ đưa ứng dụng lên mạng của Streamlit có gói miễn phí với giới hạn; xem trang của họ để biết điều khoản hiện tại.

Dùng mô hình chạy trên máy thay Claude được không?

Được. Chỉ cần viết lại hàm trong tro_ly.py để gọi mô hình trên máy, giao diện giữ nguyên.

Nhiều người dùng cùng lúc thì sao?

Mỗi phiên trình duyệt có st.session_state riêng, nên hội thoại không lẫn nhau. Với nhiều người dùng thật, cần tính tới giới hạn tốc độ của API.

Có cần học HTML, CSS không?

Không, với ứng dụng như trong bài. Khi cần giao diện riêng hơn, Streamlit cho tùy biến giới hạn; ứng dụng phức tạp nên dùng khung web đầy đủ.

Học tiếp

Nếu bạn muốn học Python và AI bài bản cùng giảng viên, tham khảo khóa học AI của Big-O Coding.

Muốn học bài bản cùng giảng viên?

Khóa AI – Introduction to Machine Learning: nền tảng Máy học từ thuật toán cơ bản đến Neural Networks và LLM, học bằng Python. Học thử buổi 1 miễn phí.

Xem Khóa AI

Nguồn tham khảo

Chia sẻ: Facebook LinkedIn
Nhắn Zalo tư vấnZalo Gọi 0937 401 483