Nhật ký
Dự án này được làm và triển khai thế nào, từng bước. Có cả những chỗ làm sai rồi sửa và những khó khăn khi chạy thật trên Raspberry Pi 5, vì đó mới là phần học được nhiều nhất.
Bản 1: mấy bot nhỏ
Bắt đầu từ một ý đơn giản
kênh chat đỡ buồn
Tách khỏi bot nhạc
Mấy bot này sinh ra trong repo của bot nhạc musiDISCORD, vì ý tưởng nảy ra ở đó: server có bot nhạc vẫn trống trải giữa các bài. Vài ngày sau chúng được tách ra dự án riêng vì không dùng chung gì lúc chạy, ai chỉ cần chat vui không phải tải cả bot nhạc, và hai repo cập nhật hoặc lùi bản độc lập. Trên Raspberry Pi chúng chỉ là hai thư mục, hai container.
Phép lịch sự đi trước tiếng cười
Một kênh duy nhất, giờ im lặng, giới hạn mỗi ngày, và bot tự lùi khi có người đang nói chuyện. Bot không đọc nội dung tin nhắn: chỉ cần biết có người vừa viết là đủ.
Bot luôn là bot
Giữ nhãn BOT, không giả làm người, không nói mình ăn, ngủ hay đi chơi. Làm cho server có không khí thì được, còn lừa người ta rằng đó là người thì không.
Bản 2: biết chơi
Hỏi, đố, bình chọn
và biết ai đang chơi
Câu hỏi trong ngày, poll, đố vui
Câu hỏi vào giờ cố định, poll thật của Discord, đố vui có nút bấm đáp án và bảng xếp hạng tuần. Mỗi server có thể thêm câu hỏi, chuyện cười, fun fact riêng bằng lệnh.
Đo xem có ích không
/companions statscho biết bao nhiêu cuộc trò chuyện thật sự khiến người ta nói theo. Nếu bot chỉ nói một mình thì cũng nên biết.Giờ là phần khó, không phải câu đùa
Bản test đầu dùng khoảng thời gian quá ngắn nên test punchline đạt vì lý do sai. Chuyển sang đẩy đồng hồ từng bước nhỏ rồi kiểm tra ai nói lúc nào (và ai không nói) trung thực hơn nhiều. Một lỗi khác: tin nhắn của người dùng đúng mili giây cuộc trò chuyện bắt đầu không được tính là có người tham gia, sửa bằng so sánh
>=.
Bản 3: ngồi cùng mọi người
Có mặt 24/7
dù ai cũng đã thoát
Voice là vòng lặp đối chiếu
Phản ứng với từng sự kiện ngắt kết nối thì rối (Discord chuyển bot giữa các máy chủ voice, đá ra, khởi động lại). Thay vào đó mỗi 15 giây bot so sánh điều nên đúng (phòng đã lưu, số bot) với điều đang đúng rồi sửa, chờ lâu dần sau mỗi lần vào hỏng. Khởi động lại container thì phòng tự được khôi phục, mình đã thử trên Pi.
Nhiều phòng, bot giữ chỗ
Mỗi phòng nhớ bot của mình: phòng mới hoặc phòng lớn thêm chỉ lấy bot đang rảnh, không lấy của phòng khác. Lúc đầu mình chia lại từ đầu mỗi khi đổi phòng và bot bị kéo ra khỏi phòng cũ, người dùng phàn nàn đúng.
Phòng "+ New Channel"
Kênh tạo phòng riêng khiến mỗi bot vào là tạo một phòng mới và bot cứ vào lại kênh gốc mãi. Giờ bot học phòng được tạo ra, ở chung trong đó và không quay lại kênh gốc.
Tiện ích quanh phòng
Chào khi ai đó vào phòng, Pomodoro, bảng giờ voice, lời chào người mới vào server (đọc loại tin nhắn hệ thống thay vì xin quyền đặc biệt), nhắc việc và đếm ngược sự kiện. Mỗi thứ chứa ID người dùng đều có lệnh xóa riêng và một dòng trong bảng "Lưu gì".
Bản 4: để yên cũng chạy
Bot không ai trông
vẫn phải đáng tin
Mỗi vòng không được đè lên chính nó
Với nhiều server và việc vào voice chậm, một vòng có thể dài hơn 15 giây rồi vòng thứ hai chạy chồng lên, đưa một bot vào hai lần. Giờ mỗi vòng bỏ qua khi vòng trước chưa xong.
Học từ cách dùng, có kiểm soát
Tinh chỉnh thích nghi nhân trọng số mặc định với mức độ gây trò chuyện của từng loại, giới hạn từ một nửa đến gấp đôi, kéo về trung bình khi dữ liệu còn ít và chỉ bật sau 20 cuộc trò chuyện. Giải thích được trong một câu nên báo cáo được trong
/companions stats.Đo thay vì đoán
Mô phỏng 300 server trong 8 giờ: vòng chậm nhất khoảng 30 ms, bộ nhớ tăng vài chục MB, mọi server vẫn trong giới hạn hằng ngày.
Báo động và sao lưu
Webhook tùy chọn báo khi bot mất đăng nhập hoặc không vào lại được voice, và mỗi ngày dữ liệu được sao lưu, giữ 7 bản. Xuất và xóa dữ liệu theo server, tự dọn một ngày sau khi hết bot trong server.
Bí mật trong test
Một test cần chuỗi trông như token đã bị GitHub chặn khi push. Giờ chuỗi đó được ghép từ nhiều mảnh, không bỏ qua cảnh báo.
Bản 4.x: ba mươi tính cách
Từ chín lên ba mươi
và mỗi bot một giọng
Từ 10 lên 30 bot
21 tính cách mới, mỗi cái có lời thoại và 25 ghi chú vui dưới tên cho mỗi ngôn ngữ. Đăng nhập từng nhóm năm bot và bộ nhớ đệm nhẹ hơn để 30 bot vẫn nhỏ: trên Raspberry Pi 5 chỉ dùng khoảng 5% CPU và 113 MB RAM với 23 bot đang chạy.
furyZ và Chamy
Hai bot chơi thân với nhau: furyZ là game thủ Valorant, Chamy là chị em thân thiết của cô ấy, với ghi chú theo cặp bạn thân.
Hàng nghìn lời thoại
Khoảng 1.000 câu mỗi ngôn ngữ cho mỗi mục đích: chào người mới, chào vào phòng voice, tạm biệt, nhắc việc, và 500 câu cho mỗi bước của phiên tập trung. Sau đó mỗi tính cách nói bằng giọng riêng của mình, 20 câu cho mỗi mục đích.
1.259 câu đố vui mỗi ngôn ngữ
Khoảng 960 câu mới ở 20 chủ đề, mỗi câu 4 đáp án. Từng câu được một người đọc thứ hai kiểm tra, loại 20 câu bị nghi ngờ, rồi mình tự đọc ngẫu nhiên 70 câu.
Lỗi nhỏ nhưng đáng nhớ
Một lời tự giới thiệu dài hơn giới hạn 100 ký tự của Discord làm bản triển khai bị crash khi đăng ký lệnh. Kiểm tra mô tả lệnh giờ nằm trong CI và chạy trước mỗi lần deploy.
Trang web
Một trang tĩnh
có chút điện ảnh
Từ trang tĩnh đến trải nghiệm
Đám quả cầu theo con trỏ, tiêu đề chữ chạy, cảnh kể chuyện ghim khi cuộn và một trò đố vui chơi được ngay trên trang. Tất cả là HTML, CSS và JS thuần, tôn trọng chế độ giảm chuyển động, có hai ngôn ngữ và được kiểm tra bằng Chrome thật ở nhiều cỡ màn hình.
Triển khai trên Raspberry Pi 5
Chạy thật trên một cái Pi
cạnh bot nhạc
Hai dự án, một cái Pi
Pi 5 của mình (aarch64, 8 GB RAM) chạy cả bot nhạc musiDISCORD lẫn companions. Hai dự án không dùng chung gì lúc chạy: mỗi bên một thư mục (
~/musiDISCORDvà~/companionsDISCORD), một compose project, một.env, một thư mụcdata/và container riêng. Companions không cần Lavalink hay cơ sở dữ liệu nên chỉ là một tiến trình Node.js nhỏ, không mở cổng nào ra ngoài. Bot nhạc Quynh Anh là dự án và token khác hẳn, nên không lệnh nào của companions chạm tới nó.Cài bằng một lệnh, tự cập nhật mỗi đêm
scripts/install-pi.shcài mọi thứ (có--dry-runđể xem trước,--timerđể bật cập nhật hằng ngày). Cập nhật chạy bằng một systemd timer: lấy commit mới, chỉ chấp nhận fast-forward, build lại, chờ 45 giây xem container có đứng vững không rồi mới giữ bản mới, nếu không thì quay về bản cũ. Một khóaflockngăn hai lần cập nhật chạy chồng nhau, và script từ chối chạy nếu thư mục có thay đổi chưa commit.Container không có healthcheck, nên "khỏe" phải tự định nghĩa
Mình không có endpoint nào để hỏi "bot còn sống không". Nên với script cập nhật, "khỏe" nghĩa là: đang chạy và không bị khởi động lại sau một khoảng chờ ngắn. Chưa hoàn hảo, nhưng bắt được lỗi hay gặp nhất là bản mới crash ngay khi khởi động.
Thư mục
data/thuộc về rootContainer ghi vào
data/bằng root, nên từ máy chủ không sửa trực tiếp được cấu hình (cần sudo, mà sudo trên Pi hỏi mật khẩu). Mình sửa bằng cách chạy một đoạn Node nhỏ ngay trong môi trường của container:docker compose run --rm --no-deps --entrypoint node. Quyền khớp, không cần sudo, và không ai phải gõ mật khẩu vào dòng lệnh.Token là bí mật, kể cả lúc triển khai
.envbị gitignore, token không bao giờ được in ra hay đưa lên website. Trên Pi mình chỉ thay đúng một dòng bằng cách chép một file tạm qua SSH (đăng nhập bằng khóa, không dùng mật khẩu), và khi cần kiểm tra thì chỉ in tên biến chứ không in giá trị. Cách này tránh để lộ token vào lịch sử lệnh hay log.Mạng chưa sẵn sàng ngay lúc container vừa chạy
Lúc container vừa lên, DNS đôi khi chưa hoạt động, nên lần đăng ký lệnh
/companionsđầu tiên có thể hỏng. Giờ việc đăng ký được thử lại với thời gian chờ tăng dần thay vì bỏ cuộc.Khởi động lại là chuyện thường
Mỗi lần triển khai, 23 bot đăng nhập từng nhóm năm con rồi vào lại phòng voice từng con một. Thỉnh thoảng cổng voice của Discord trả lỗi 522 cho một hai bot trong chốc lát. Không ai phải can thiệp: vòng lặp voice thử lại với thời gian chờ tăng dần, và log vẫn gọn.
Đo trên Pi thật thay vì đoán
Với 23 bot đang chạy, companions dùng khoảng 113 MB và khoảng 5% CPU của Pi 5, còn Pi vẫn thừa khoảng 5,6 GB RAM trống khi cả hai dự án đều chạy. Con số này đến từ việc chạy thật, và nó cho mình biết có thể thêm bot mà không sợ quá tải.
Bản 4.4 đến 4.7: phần thêm sau cùng
Camera giả, AI tùy chọn
và gọi bot đi khắp nơi
Camera chỉ là biển hiệu
Bot không thể gửi video thật, nên
/companions voice camerachỉ hiện dòng "Bật camera" dưới tên bot đang ngồi trong phòng voice và một bot báo trong chat, kèm câu đùa nói thẳng đó chỉ là biển hiệu. Bài học: nói rõ tính năng không làm gì, thay vì để người ta tưởng nó thật.AI tùy chọn bằng Gemini, tắt mặc định
Có hai phần: bot trả lời khi được nhắc tên hoặc reply (
/companions ai replies), và mỗi ngày một câu đố có đáp án ẩn, câu hỏi hoặc câu "bạn chọn gì" (/companions ai daily). Discord chỉ đưa nội dung tin nhắn cho bot khi tin đó nhắc tên bot, nên không cần xin intent đặc quyền: bot chỉ đọc đúng tin nhắn đang nói với nó. Phần câu đố không gửi dữ liệu thành viên nào.Lời hứa "không đọc tin nhắn" phải đổi thành sự thật mới
Trước đây README, trang quyền riêng tư và trang cài đặt đều nói bot không đọc tin nhắn. Khi thêm AI, câu đó chỉ còn đúng ở trạng thái mặc định. Mình viết lại tất cả (cả hai ngôn ngữ) để nói thẳng: mặc định không đọc, chỉ khi bật AI thì đúng một tin nhắn nhắc tên bot được gửi cho Gemini của Google, không lưu gì.
Model đầu tiên đã bị đóng
Gọi thử lần đầu trả 404. Mình đọc phần thân lỗi và hỏi API danh sách model:
gemini-2.5-flashvẫn nằm trong danh sách nhưng đã không còn cho tài khoản mới. Chuyển sanggemini-3.5-flashlà chạy. Bài học: đọc thông báo lỗi của API thay vì đoán, và đừng chép tên model từ ví dụ cũ.Model mới "nghĩ" bằng chính token trả lời
Với model mới, một phần giới hạn token đầu ra bị dùng cho việc suy nghĩ, nên có lúc câu trả lời rỗng hoặc bị cắt giữa chừng. Mình tăng giới hạn token, và nếu Google báo câu bị cắt thì bỏ đi, bot nói một câu dự phòng hài hước thay vì đăng nửa câu.
Hạn mức miễn phí thấp hơn mình nghĩ
Chỉ vài lần thử liên tiếp đã bị báo 429. Nên có sẵn các giới hạn: mỗi người cách 20 giây, tối đa 5 lượt mỗi phút cho cả hệ thống, 60 lượt mỗi ngày cho mỗi server, và nghỉ một phút sau khi bị báo quá hạn mức. Hết lượt trong ngày thì bot nói đúng một câu rồi im.
Giữ key và an toàn nội dung
Key chỉ nằm trong
.env, gửi bằng header chứ không nằm trong địa chỉ, không bao giờ vào log, và có test kiểm tra điều đó. Câu trả lời được làm sạch (bỏ@, link, tiêu đề markdown), prompt bắt bot luôn nhận mình là bot, và tin nhắn của người dùng được coi là dữ liệu chứ không phải chỉ thị để bot không bị "dụ" đổi luật./assemblevà/random/assemblegọi bot về phòng voice bạn đang ngồi,/randomrải bot vào các phòng ngẫu nhiên. Cả hai thay danh sách phòng đã lưu và cho bot không được chọn rời phòng cũ. Mặc định chỉ người có quyền Move Members dùng được, và mỗi server cách nhau một phút. Mình cố ý không tự chạy vòng vào phòng bên trong lệnh mà để vòng lặp chung làm, vì bài học ở D-04: hai vòng chạy chồng lên nhau sẽ đưa một bot vào hai lần.
Những gì mình học được
Chín điều
mang sang dự án sau
Sửa cho đúng thay vì phản ứng
Với việc ngồi trong voice, cách bền nhất là một vòng lặp so sánh "nên thế nào" với "đang thế nào" rồi sửa, không phải lắng nghe từng sự kiện.
Thời gian phải đẩy từng bước
Test với đồng hồ giả, kiểm tra ai nói khi nào (và ai không nói) trung thực hơn nhiều so với kiểm tra trạng thái cuối.
Nội dung cũng là dữ liệu, và dữ liệu phải kiểm
Hàng nghìn câu thoại và câu đố đều qua script (placeholder, độ dài, trùng lặp, dấu tiếng Việt) và một người đọc thứ hai trước khi vào repo.
Bí mật chỉ đi một chiều
Token và key chỉ nằm trong
.env, không in ra, không để trong địa chỉ, không để trong log, và có test cho những chỗ có thể rò.Đọc lỗi của API
Cả lỗi 404 của model lẫn lỗi 429 đều tự nói nguyên nhân trong thân phản hồi. Đọc nó nhanh hơn đoán.
Nói thẳng tính năng là gì, và không là gì
Camera chỉ là biển hiệu, AI gửi một tin nhắn cho Google. Câu chữ trung thực quan trọng ngang với code.
Giới hạn cho mọi thứ tốn tiền hoặc gây ồn
Khoảng cách giữa hai câu trả lời, trần mỗi ngày, trần mỗi phút, nghỉ sau khi bị chặn. Không giới hạn thì sớm muộn sẽ có lúc tự làm phiền chính mình.
Đo trên phần cứng thật
113 MB và 5% CPU trên Pi 5 đến từ việc chạy thật, không phải từ phỏng đoán.
Tách dự án cho gọn
Bot nhạc và companions không chia sẻ gì khi chạy, nên cập nhật hay quay lại bản cũ ở bên này không bao giờ ảnh hưởng bên kia.
Còn bỏ ngỏ
Nói thật
những gì chưa làm
Chưa theo dõi lâu trên server thật
Mọi thứ đã chạy trên Pi với bot thật và phòng voice thật, nhưng ghi chú dưới tên, lời tạm biệt, bản tóm tắt tuần, chuỗi đố vui, các gói theo mùa,
/assemble,/randomvà việc bot trả lời qua Gemini chủ yếu mới được thử bằng bản giả, cộng với một lần gọi Gemini thật. Vài ngày trên server thật sẽ cho biết nhịp độ và nội dung cần chỉnh gì.Hạn mức Gemini miễn phí khá thấp
Giới hạn theo phút và theo ngày của gói miễn phí có thể thấp hơn các con số mình đặt sẵn. Cần xem hạn mức thật của tài khoản và chỉnh
AI_DAILY_LIMITnếu cần.Chỉ có tiếng Anh và tiếng Việt
Thêm ngôn ngữ nghĩa là thêm cả ngân hàng nội dung và một test độ khớp.
Bot không phát trực tiếp
Bot không thể stream như người dùng; làm vậy cần tài khoản người dùng tự động, điều Discord cấm. Thay vào đó mỗi bot có ghi chú vui dưới tên, và camera chỉ là biển hiệu.
Dự án đã khép lại
Bản 4.7
hoàn chỉnh
Dự án dừng ở bản 4.7: 30 tính cách (23 bot đang chạy), 96 test đạt, tài liệu và website đã cập nhật, chạy trên một Raspberry Pi 5 cạnh bot nhạc. Mã nguồn mở, ai cũng có thể chạy bản của mình. Mình dừng ở đây để chuyển sang việc khác.