Bốn bước của một lượt luyện nói AI, và sáu chỗ tôi đã làm sai
Tôi làm một app học từ vựng tiếng Anh cho người Việt, một mình. Phần "AI nghe – sửa phát âm – trả lời bằng giọng" là chỗ tôi học được nhiều nhất, và cũng là chỗ đốt tiền nhất. Bài này dành cho anh em định làm tính năng tương tự: tôi mô tả đúng kiến trúc đang chạy, số nào đo thật thì nói số, chưa đo thì ghi rõ là chưa đo.
1. Một lượt hội thoại là bốn bước, không phải một lời gọi API
Sơ đồ thực tế đang chạy:
thu âm (client)
-> upload file tạm, nhận về một đường dẫn nội bộ
-> gọi model nhận dạng giọng nói (bất đồng bộ: submit -> poll -> download kết quả)
-> gửi lịch sử + câu mới cho LLM, nhận văn bản trả lời
-> đưa văn bản sang TTS, stream về máy người dùng
Điều bất ngờ nhất lúc mới làm: model nhận dạng giọng không nhận dữ liệu nhúng trực tiếp trong request, chỉ nhận một URL. Nên bước upload không phải tối ưu, nó là bắt buộc. Hệ quả là tôi phải chịu một liên kết lưu tạm có tuổi thọ (trong cấu hình đang chạy là 48 giờ), và mọi xử lý phải khép lại trong phiên — không có chuyện "để đó mai tính".
Bước nhận dạng cũng là async. Không có kiểu POST xong nhận kết quả luôn. Tôi submit, rồi hỏi lại cho tới khi có file kết quả. Anh em nào quen viết request–response đồng bộ sẽ thấy đoạn code poll này hơi khó chịu, nhưng nó đổi lại việc không phải giữ một kết nối mở hàng chục giây.
2. Lịch sử hội thoại: trần 20 tin -> 10 tin
Vì mỗi lượt gửi lại toàn bộ đoạn chat, độ trễ tăng dần theo số lượt. Đây là bug không bao giờ báo lỗi, chỉ khiến người dùng cảm thấy "càng nói càng chậm".
// trước đây: 20
private static final int MAX_HISTORY_MESSAGES = 10;
Tôi hạ xuống 10 tin cho phần luyện nói tự do, và 16 tin cho phần hội thoại theo tình huống — vì một màn tình huống thường diễn 4 tới 6 lượt là hết chuyện, giữ thêm lịch sử chỉ để model lặp lại ý cũ. Cùng chỗ đó tôi giới hạn max_tokens ở mức 1000 (luyện nói) và 600 (tình huống), đủ cho một câu trả lời hội thoại và không đủ để model "diễn văn".
3. Ba tham số nhỏ gây ra hành vi khó hiểu
language_hints chỉ đọc phần tử đầu tiên. Tôi từng gửi một danh sách hai ngôn ngữ và tin rằng hệ thống sẽ tự cân bằng giữa chúng. Không. Phần tử thứ hai bị bỏ qua hoàn toàn. Muốn xử lý hai ngôn ngữ thì phải tự chọn một rồi gửi, tức là logic suy đoán nằm phía mình, không nằm ở model.
enable_thinking: false. Tắt phần suy luận ẩn để rút ngắn thời gian chờ. Với tác vụ hội thoại ngắn thì đúng, nhưng đây là đánh đổi: câu trả lời ít "chín" hơn so với để bật.
temperature: 0.8. Cao hơn mức an toàn sách vở, vì hội thoại học tập cần đa dạng câu, không cần ổn định tuyệt đối.
4. Lời dặn "đọc chậm và rõ" phải trả bằng thời gian chờ
Phần TTS tôi thêm instruction dạng Please speak slowly and clearly.... Kết quả đo được: cùng một nội dung, lượng âm thanh phát ra dài hơn khoảng 15 tới 19 phần trăm. Tức là người dùng chờ lâu hơn và hoá đơn tốn hơn.
Tôi vẫn giữ, vì tỉ lệ người nghe nhận đúng tăng rõ. Nhưng điểm muốn nói là: một dòng instruction tưởng như vô hại lại là một quyết định chi phí. Nếu anh em bật loại lời dặn này, hãy đo thời lượng audio, đừng chỉ nghe thử một câu rồi thấy "ổn".
5. Model chấm điểm phát âm: cái bẫy ảo giác dễ mắc nhất
Đây là lỗi tôi mắc và đáng kể nhất. Có một model cho điểm rất cao kể cả với file ghi âm im lặng. Nó không chấm phát âm, nó đang đoán là có điểm. Nếu anh em chọn model bằng cách nhìn "điểm trung bình có hợp lý không", gần như chắc chắn sẽ chọn nhầm con này, vì kết quả của nó trông rất đẹp mắt.
Bây giờ model dự phòng của tôi là một bản khác, và tiêu chí chọn đã đổi: test bằng input rác trước, input tốt sau. File im lặng, file tiếng ồn, file nói lệch câu — nếu model vẫn cho điểm cao thì loại.
6. Rate limit là phanh chi phí, đừng gọi nó là "chính sách dùng hợp lý"
Số đang chạy, công khai luôn cho đỡ nói tránh:
| Đường | Giới hạn |
|---|---|
| LLM soạn câu trả lời | 3 request / phút / người |
| Nhận dạng giọng | 20 / phút / người, 40 / phút / IP |
| Chuyển văn bản thành tiếng | 30 / phút / người, 60 / phút / IP |
| Chấm phát âm | 6 / phút |
| Chấm phát âm (trần HTTP) | read 90s — thực đo p50 2,3s, chậm nhất cỡ 3,2s |
Hai con số theo IP tồn tại vì một mình con số theo user không đủ: nhiều người dùng chung một NAT (quán net, ký túc xá) sẽ đá lẫn nhau nếu chỉ đếm theo IP. Tôi học điều này theo cách tốn tiền.
Về hạn mức cho người mới, tôi chọn tổng số lượt chứ không phải "số lượt mỗi ngày". Một người học nghiêm túc cần được nói nhiều trong vài hôm đầu để biết mình có hợp hay không; một người dùng dăm ba hôm rồi bỏ thì hạn mức mỗi ngày cũng không giữ họ lại. Đánh đổi là tôi mất khả năng nuôi dài hạn bằng một mức nhỏ đều mỗi ngày. Đây là quyết định kinh doanh đội lốt quyết định kỹ thuật, anh em nên biết trước khi làm giống tôi.
7. Những gì tôi chưa đo
Nói thẳng để bài không thành quảng cáo:
- Chưa tách được chi phí mỗi lượt theo khu vực, vì hoá đơn nhà cung cấp cộng gộp nhiều thứ.
- Chưa load-test ở giờ cao điểm — các số p50 ở trên đo trong điều kiện sử dụng bình thường.
- Chưa đánh giá phần nhận dạng giọng với đường truyền yếu (rất phổ biến khi học trên xe buýt), nên chưa biết tỉ lệ fail thực tế là bao nhiêu.
Nếu anh em định làm tính năng dạng này, tôi khuyên đo ba cái đó trước khi viết bài như bài này.
Bản gốc, có bảng biểu và toàn bộ tham số trình bày đầy đủ hơn: Bốn bước của một lượt luyện nói AI
All rights reserved