Bỏ qua tới nội dung chính
Quay lại tin tức

Kiến trúc được sử dụng để xây dựng Speakroom, một ứng dụng luyện nói ngôn ngữ AI

Medium Towards AI· Kamen Zhekov· 4/6/2026general

Tôi gần đây đã xây dựng và phát hành Speakroom, một ứng dụng luyện tập ngôn ngữ, nơi người dùng có thể chọn một ngôn ngữ, chọn một chủ đề, trò chuyện về chủ đề đó với AI và nhận phản hồi về cuộc hội thoại của mình theo thời gian thực. Phần hấp dẫn rõ ràng là tương tác bằng giọng nói. Người dùng nói, hệ thống phản hồi, phản hồi hiển thị, thật tuyệt vời. Với tư cách là một nhà phát triển, đây cũng là phần được dự đoán là khó nhất, nhưng trên thực tế, phần lớn công việc được thực hiện khá tốt bởi mô hình AI đang được sử dụng. Phần thú vị hơn đối với tôi là kiến trúc xung quanh nó, bởi vì

Speakroom, một ứng dụng luyện tập ngôn ngữ, đã được phát triển và phát hành gần đây. Ứng dụng này cho phép người dùng chọn ngôn ngữ, chủ đề, trò chuyện với AI và nhận phản hồi về cuộc hội thoại theo thời gian thực. Phần tương tác bằng giọng nói là điểm nổi bật của ứng dụng. Người dùng nói, hệ thống phản hồi và đưa ra phản hồi, tạo ra trải nghiệm tuyệt vời. Với tư cách là nhà phát triển, đây cũng là phần được dự đoán là khó nhất, nhưng trên thực tế, phần lớn công việc được thực hiện bởi mô hình AI đang được sử dụng. Phần thú vị hơn đối với tôi là kiến trúc xung quanh nó, bởi vì đó là nơi ứng dụng có thể duy trì sự dễ hiểu hoặc dần trở nên phức tạp. Speakroom sử dụng kiến trúc Python / React mà tôi thực sự yêu thích: FastAPI cho phần phụ trợ (backend), React và Vite cho phần giao diện người dùng (frontend), Postgres cho trạng thái bền vững và một tiến trình worker cho các tác vụ nền. Cấu trúc này không tự nhiên mà có. Tôi đã xây dựng một mẫu khởi đầu nhỏ xoay quanh chính những vấn đề này, và tính năng ví dụ của nó là xử lý tài liệu chứ không phải luyện nói. Speakroom tái sử dụng cấu trúc và các điểm nối của mẫu khởi đầu, không phải tính năng của nó, đó là mục đích của mẫu. Không có gì lạ lẫm, không có gì ấn tượng trên một slide hội nghị, nhưng đây là một cấu trúc rất tốt nếu bạn muốn xây dựng một sản phẩm thực sự mà không biến mọi tính năng thành một dự án nghiên cứu nhỏ. Điều tôi thích nhất là nó làm rõ ràng nơi đặt mã. Ứng dụng có một cấu trúc thời gian chạy rõ ràng. Cấu trúc thời gian chạy khá đơn giản: * Giao diện người dùng React * API FastAPI * Cơ sở dữ liệu Postgres * Một tiến trình worker * Mã tích hợp nhà cung cấp đằng sau các điểm nối của phần phụ trợ Điều này nghe có vẻ cơ bản, nhưng cơ bản thường là điều bạn muốn. Giao diện người dùng chịu trách nhiệm về hành vi của trình duyệt, API chịu trách nhiệm xử lý và điều phối yêu cầu, cơ sở dữ liệu chịu trách nhiệm về trạng thái sản phẩm bền vững và tiến trình worker chịu trách nhiệm về các tác vụ nền không nên chặn một yêu cầu. Phần quan trọng là các ranh giới này định hình sản phẩm cũng như việc triển khai: giao diện người dùng giao tiếp với API ứng dụng, API ứng dụng giao tiếp với các dịch vụ miền, các worker mở các phiên riêng và làm việc từ các ID, và các bộ điều hợp nhà cung cấp nằm sau mã phụ trợ thay vì rò rỉ vào trình duyệt. Cấu trúc đó đã giúp ích rất nhiều cho Speakroom vì ứng dụng có nhiều thứ xung quanh hơn là một phiên thoại: tài khoản, tín dụng, thanh toán, xem xét phiên, xóa, ranh giới quyền riêng tư, kiểm tra thời gian chạy và một loạt các hành vi người dùng bình thường xung quanh phần nổi bật. Một bản demo có thể bỏ qua hầu hết những điều đó, nhưng một sản phẩm cuối cùng phải xử lý chúng. Nhân tiện, nếu bạn quan tâm đến việc dùng thử, hãy xem liên kết bên dưới. Speakroom - Luyện nói AI bằng 24 ngôn ngữ. Các tuyến đường (routes) nên giữ nguyên tính đơn giản. Một trong những cách nhanh nhất để phá hỏng một phần phụ trợ là để các bộ xử lý tuyến đường trở thành nơi mọi thứ diễn ra, bởi vì chúng gần với yêu cầu và dễ chỉnh sửa. Kiến trúc này chống lại điều đó một cách khá trực tiếp: các tuyến đường phân tích đầu vào, gọi dịch vụ phù hợp và trả về phản hồi, trong khi logic thực tế nằm trong các mô-đun miền cục bộ theo tính năng. Tôi thích sự phân chia này vì nó giữ cho bề mặt API công khai dễ đọc mà không biến phần phụ trợ thành một khung làm việc chung. Bạn có thể mở một tệp tuyến đường và hiểu tài nguyên mà nó hiển thị, sau đó mở dịch vụ tính năng và xem logic nghiệp vụ thực tế. Đối với Speakroom, điều này quan trọng vì nhiều luồng là đa bước ngay cả khi hành động của người dùng trông đơn giản. Bắt đầu một cái gì đó, tính phí một cái gì đó, lưu một cái gì đó, xem xét một cái gì đó, xóa một cái gì đó và phục hồi từ. thất bại đều là những trách nhiệm khác nhau, và nếu tất cả những điều đó kết thúc trong các bộ xử lý định tuyến (route handler), bạn vẫn có thể triển khai, nhưng mỗi thay đổi trong tương lai sẽ trở nên tồi tệ hơn một chút. Tôi ít quan tâm đến sự thuần túy hơn là khả năng quay lại sau này, có hoặc không có một tác nhân (agent), và biết được thay đổi tiếp theo thuộc về đâu. Trạng thái sản phẩm nên thuộc về ứng dụng Ví dụ minh họa của người khởi tạo là một luồng tài liệu, không liên quan gì đến việc trò chuyện với một gia sư AI. Hình dạng đó là điểm mấu chốt, không phải tính năng. Ban đầu, giao diện người dùng (frontend) của nó tải lên tài liệu, phần phụ trợ (backend) lưu trữ các bản ghi thuộc sở hữu của ứng dụng, bộ xử lý (worker) xử lý chúng, và giao diện người dùng thăm dò một tài nguyên công việc cấp sản phẩm cho đến khi công việc hoàn thành. Tài nguyên công việc cấp sản phẩm đó là phần tôi quan tâm. Hàng đợi là cơ sở hạ tầng phân phối, trong khi bản ghi thuộc sở hữu của ứng dụng là hợp đồng sản phẩm. Trình duyệt không nên phụ thuộc vào các bảng hàng đợi hoặc các chi tiết nội bộ của bộ xử lý, bởi vì đó là các chi tiết triển khai và chúng sẽ thay đổi vì những lý do mà người dùng không quan tâm. Điều đó phù hợp một cách độc đáo với các sản phẩm AI. Luôn có một số trạng thái nội bộ hấp dẫn mà bạn có thể làm rò rỉ: trạng thái hàng đợi, trạng thái nhà cung cấp, trạng thái thử lại, trạng thái bộ xử lý một phần, có thể là một số hình dạng phản hồi ngẫu nhiên của nhà cung cấp có vẻ hữu ích trong 5 phút. Một khi giao diện người dùng phụ thuộc vào điều đó, việc triển khai đã trở thành một phần của sản phẩm, điều mà tôi cố gắng tránh, bởi vì trình duyệt nên biết những gì người dùng cần biết và phần phụ trợ có thể giữ các chi tiết kỹ thuật cho riêng mình. Lời nhắc (Prompts) là mã sản phẩm Một phần khác của kiến trúc mà tôi thích là sự phân chia tích hợp LLM: tính năng sở hữu lời nhắc và hình dạng đầu ra mong muốn, trong khi bộ điều hợp nhà cung cấp (provider adapter) sở hữu việc truyền tải, các tùy chọn nhà cung cấp và ánh xạ lỗi. Điều đó nghe có vẻ hiển nhiên, nhưng tôi đã thấy điều này trở nên lộn xộn rất nhanh. Bạn bắt đầu với một tệp provider.py, sau đó đột nhiên tệp đó chứa lời nhắc, các quy tắc sản phẩm, phân tích phản hồi, hành vi thử lại và đủ kiến thức miền để thay đổi sản phẩm có nghĩa là chỉnh sửa tích hợp nhà cung cấp. Không, cảm ơn. Đối với Speakroom, bộ điều hợp nhà cung cấp không nên biết một buổi luyện nói tốt có nghĩa là gì. Nó nên biết cách giao tiếp với nhà cung cấp và cách ánh xạ lỗi của nhà cung cấp thành thứ mà ứng dụng có thể xử lý, trong khi tính năng sở hữu hành vi sản phẩm. Điều đó cũng làm cho việc lập trình tác nhân (agentic coding) an toàn hơn. "Thay đổi hành vi sản phẩm" và "thay đổi cách chúng ta gọi nhà cung cấp" là những nhiệm vụ rất khác nhau, và tôi không muốn người bạn tác nhân của mình nhầm lẫn chúng vì cơ sở mã đã nhầm lẫn chúng trước. Mã giao diện người dùng (Frontend code) nằm gần tính năng

Nguồn tin: Medium Towards AI — Tác giả: Kamen Zhekov. Bản dịch tiếng Việt do AI thực hiện, có thể có sai sót.