- Chia sẻ phương pháp cụ thể để duy trì các dự án quy mô hàng chục nghìn dòng với tỷ lệ lỗi thấp thông qua quy trình đa tác tử kiến trúc sư - lập trình viên - người phản biện trong phát triển phần mềm dùng LLM
- Tôi quan tâm đến việc tạo ra thứ gì đó hơn là bản thân việc lập trình, và khi LLM đã viết code tốt, tôi có thể tập trung vào việc xây dựng
- So với năng lực viết code, kỹ năng kỹ sư về thiết kế kiến trúc hệ thống và đưa ra lựa chọn đúng đắn giờ đây quan trọng hơn rất nhiều
- Trộn và dùng nhiều mô hình khác nhau để nâng cao chất lượng code review, tận dụng điểm mạnh và điểm yếu của từng mô hình theo từng vai trò
- Công khai toàn bộ một phiên thêm tính năng email thực tế, ghi chép chi tiết quy trình cộng tác giữa con người và LLM do con người dẫn dắt từ quyết định kiến trúc đến QA
Lợi ích của việc xây dựng với LLM
- Tôi từng nghĩ mình thích lập trình, nhưng thực ra tôi thích chính việc tạo ra thứ gì đó, và từ khi LLM giỏi lập trình, tôi liên tục làm ra đủ thứ
- Vào khoảng thời điểm Codex 5.2 ra mắt, và gần đây hơn là Opus 4.6, việc viết phần mềm với tỷ lệ lỗi rất thấp đã trở nên khả thi. Tỷ lệ lỗi thậm chí có thể thấp hơn đáng kể so với khi tự tay code
- Trước đây, sau 2~3 ngày làm việc thì code thường rơi vào trạng thái không thể bảo trì, nhưng hiện tại tôi có thể làm liên tục nhiều tuần và ổn định mở rộng thành hàng chục nghìn dòng code hữu ích
- Kỹ năng kỹ sư không trở nên vô dụng mà chỉ là dịch chuyển: thay vì khả năng viết code cho đúng, điều cốt lõi là khả năng kiến trúc hệ thống đúng đắn và đưa ra phán đoán để nó trở nên dùng được
- Với các dự án dùng công nghệ nền tảng mà tôi hiểu rõ (ví dụ: backend), ngay cả ở quy mô hàng chục nghìn SLoC cũng không có vấn đề gì; nhưng với công nghệ tôi chưa rành (ví dụ: ứng dụng di động), code vẫn trở nên lộn xộn do sự tích lũy của những lựa chọn sai lầm
- Ở giai đoạn đầu của LLM (sau davinci), tôi phải xem lại mọi dòng code; ở các thế hệ sau là mức hàm; còn hiện tại xu hướng là chỉ cần kiểm tra ở mức kiến trúc tổng thể. Sang năm có lẽ ngay cả việc đó cũng không cần nữa
Những dự án được tạo ra theo cách này
- Stavrobot: trợ lý cá nhân LLM tập trung vào bảo mật. Thực hiện quản lý lịch, đánh giá mức độ sẵn sàng, nghiên cứu, tự mở rộng bằng cách viết code, nhắc nhở, tự quản lý việc nhà, v.v. Giá trị cốt lõi không nằm ở một tính năng sát thủ, mà là giải quyết hàng nghìn phiền toái nhỏ
- Middle: một thiết bị mặt dây chuyền nhỏ để ghi âm voice memo, chuyển thành văn bản rồi gửi qua webhook. Điều cốt lõi là luôn có thể mang theo và dùng được gần như không ma sát
- Sleight of Hand: dự án nghệ thuật đồng hồ treo tường luôn chính xác theo phút nhưng tích tắc bất thường theo từng giây. Hỗ trợ nhiều chế độ như nhịp biến thiên 500ms~1500ms, thay đổi tốc độ khó nhận ra rồi dừng ngẫu nhiên, chạy ở tốc độ gấp đôi rồi chờ 30 giây, v.v.
- Pine Town: một canvas multiplayer vô hạn, nơi mỗi người nhận một mảnh đất nhỏ để vẽ, một dự án đồng cỏ tương tác
- Tôi đã tạo tất cả các dự án này bằng LLM, và dù phần lớn code tôi chưa từng đọc trực tiếp, tôi hiểu rõ kiến trúc và cách vận hành bên trong của từng dự án
Công cụ harness
- Tôi đang dùng OpenCode làm harness, và cũng từng có trải nghiệm tốt với Pi
- Hai yêu cầu thiết yếu với harness:
- Có thể dùng nhiều mô hình từ nhiều công ty khác nhau: đa số harness first-party (Claude Code, Codex CLI, Gemini CLI) chỉ hỗ trợ mô hình của chính họ nên không đáp ứng điều này
- Các tác tử tùy biến có thể tự chủ gọi lẫn nhau
Vì sao cần dùng nhiều mô hình
- Có thể coi một mô hình cụ thể như một con người; ngay cả khi reset context, nó vẫn có xu hướng giữ nguyên quan điểm / điểm mạnh / điểm yếu
- Nếu yêu cầu mô hình tự review đoạn code do chính nó viết, nó thường có xu hướng tự đồng tình nên gần như vô nghĩa; nhưng nếu giao một mô hình khác review, chất lượng sẽ cải thiện đáng kể
- Ở thời điểm hiện tại, Codex 5.4 tỉ mỉ và khó tính nên phù hợp để review, Opus 4.6 lại khá khớp với những quyết định mà tôi sẽ tự đưa ra, còn Gemini 3 Flash đôi khi đề xuất lời giải mà các mô hình khác bỏ sót
- Để có kết quả tốt nhất, cần phối hợp tất cả các mô hình
Workflow: kiến trúc sư → lập trình viên → người phản biện
- Workflow gồm kiến trúc sư, lập trình viên và 1~3 người phản biện. Họ được cấu hình thành các tác tử OpenCode (file kỹ năng)
- Có ba lý do để dùng nhiều tác tử:
- Dùng mô hình đắt tiền (Opus) cho lập kế hoạch, mô hình rẻ hơn (Sonnet) cho viết code để tiết kiệm token
- Review bằng các mô hình khác nhau sẽ bắt được những vấn đề khác nhau
- Có thể tách quyền theo vai trò (ví dụ: chỉ đọc so với có quyền ghi)
- Dùng hai tác tử cùng mô hình, cùng quyền hạn thì cũng giống như một người đội hai chiếc mũ khác nhau nên không có nhiều ý nghĩa
- File kỹ năng được tự viết thủ công. Nếu để LLM viết kỹ năng, chẳng khác nào bảo ai đó viết “cách trở thành một kỹ sư xuất sắc”, rồi đưa lại chỉ dẫn ấy và nói “giờ thì hãy trở nên xuất sắc đi”
Vai trò kiến trúc sư
- Kiến trúc sư (hiện là Claude Opus 4.6) là tác tử duy nhất tôi trực tiếp trò chuyện, và phải là mô hình mạnh nhất
- Tôi đưa ra mục tiêu tính năng hoặc sửa lỗi rất cụ thể, rồi trao đổi tới 30 phút cho đến khi chốt được mục tiêu, ràng buộc và trade-off
- Kết quả là một kế hoạch khá thấp tầng đến mức từng file và từng hàm riêng lẻ
- Đây không chỉ là prompting đơn thuần mà là quá trình hình thành kế hoạch với sự trợ giúp của LLM. Khi LLM sai hoặc khác cách tôi muốn làm, tôi chỉnh lại rất nhiều, và đó là phần đóng góp cốt lõi khiến dự án trở thành “của mình”
- Tác tử được cấu hình không bắt đầu triển khai cho đến khi tôi nói rõ từ “approved”. Một số mô hình có xu hướng tự cho rằng đã hiểu và vội vàng lao vào hiện thực hóa
- Sau khi được phê duyệt, kiến trúc sư chia công việc thành các task, ghi chi tiết vào file kế hoạch rồi gọi lập trình viên
Vai trò lập trình viên
- Lập trình viên có thể dùng mô hình yếu hơn nhưng hiệu quả token hơn (Sonnet 4.6)
- Vì kế hoạch đã giảm tối đa khoảng trống cho quyền tự quyết, vai trò này được giới hạn nghiêm ngặt ở việc triển khai các thay đổi theo kế hoạch
- Sau khi triển khai xong, nó sẽ gọi người phản biện
Vai trò người phản biện
- Mỗi người phản biện độc lập xem xét kế hoạch và diff rồi đưa ra phê bình
- Tối thiểu luôn dùng Codex, đôi khi thêm Gemini, còn với dự án quan trọng thì thêm cả Opus
- Phản hồi quay lại cho lập trình viên; nếu giữa các người phản biện có bất đồng, vấn đề sẽ được escalated lên kiến trúc sư
- Opus rất giỏi trong việc chọn phản hồi đúng, và đôi khi cũng bỏ qua những phản hồi quá khắt khe nhưng khả năng gây vấn đề thực tế lại thấp so với công sức triển khai
Cách tiếp cận tổng thể và các failure mode
- Với cách làm này, tôi nắm được mọi lựa chọn từ mức trên hàm trở lên, và dùng tri thức đó cho các công việc tiếp theo
- Khi LLM có điểm mù và bỏ sót một yếu tố nào đó trong codebase, chỉ cần chỉ ra “phải dùng Y”, LLM sẽ nhận ra sự tồn tại của Y và chuyển sang cách làm tốt hơn
- Nếu không quen với công nghệ đó, bạn sẽ không bắt được những quyết định sai của LLM, và rồi các quyết định sai chồng chất lên nhau cho tới khi rơi vào trạng thái không thể cứu vãn
- Mẫu thất bại điển hình là cứ lặp đi lặp lại “code không chạy”, LLM đáp “đã hiểu! tôi sẽ sửa” rồi làm nó tệ hơn nữa
- Vì vậy, ngay cả khi chưa quen với một công nghệ nào đó, tôi vẫn cố gắng hiểu càng nhiều càng tốt ở giai đoạn lập kế hoạch
Phiên thực tế: thêm hỗ trợ email cho Stavrobot
- Công khai toàn văn một phiên làm việc thực tế có chú thích; các lời gọi công cụ và phần dài dòng được lược bớt, nhưng đối thoại và quá trình ra quyết định được giữ nguyên
- Đối thoại ban đầu: đưa ra mục tiêu ở mức cao (“tôi muốn thêm hỗ trợ email cho bot này”) → LLM đọc code và nắm được pattern hiện tại (webhook đầu vào → enqueueMessage → xử lý bằng LLM → phản hồi), rồi nêu ra các câu hỏi thiết kế
- Cách inbound (IMAP polling, webhook, SMTP server), cách outbound, có hai chiều hay không, kiến trúc (container riêng hay in-process), xử lý email HTML, theo dõi thread, file đính kèm, v.v.
- Các quyết định thiết kế: dùng Cloudflare Email Worker webhook cho inbound, SMTP client cho outbound, hội thoại hai chiều hoàn chỉnh, xử lý in-process, chuyển sang Markdown, coi email là độc lập, hỗ trợ file đính kèm
- Đề xuất thiết kế chi tiết của LLM: đưa ra 7 mối quan tâm cùng phương án thiết kế cụ thể như phân tích MIME (dùng mailparser), xác thực webhook (shared secret), sự cần thiết của subject cho outbound, xử lý email chỉ có HTML, danh tính địa chỉ From, xử lý email chuyển tiếp, file đính kèm outbound
- Đề xuất đơn giản hóa payload của Worker thành
{ from, to, raw } và parse phía server
- Sắp xếp cấu trúc config, luồng inbound/outbound, danh sách file cần sửa, và các mục explicit non-goal (YAGNI)
- Tinh chỉnh kế hoạch: con người chỉ ra các chỗ còn thiếu như cập nhật README.md và config.example.toml, bỏ kiểm tra E.164 khỏi trang allowlist email → LLM tích hợp lại
- Chia thành 6 task: config / dependency, allowlist, kiểm tra UI/backend cho allowlist, email inbound, email outbound, README / test
- Cải tiến bổ sung: đề xuất ý tưởng cho phép email inbound hoạt động ngay cả khi không cấu hình SMTP bằng cách biến các trường SMTP thành tùy chọn → được triển khai
- Bug được phát hiện trong QA: email của owner không được đăng ký nên message bị drop → nguyên nhân là
seedOwnerInterlocutor đã bỏ sót kênh email → đã sửa
- Đề xuất cải thiện code: refactor để lặp qua danh sách kênh dùng chung thay vì dùng các khối if hardcode theo từng kênh. Sau khi thảo luận về trường hợp đặc biệt phải chuyển đổi số của Telegram, quyết định chỉ áp dụng vòng lặp cho
seedOwnerInterlocutor, còn getOwnerIdentities thì giữ nguyên vì khác biệt kiểu dữ liệu là bản chất
- Allowlist email với wildcard: bổ sung hỗ trợ wildcard ở cấp domain dạng
*@example.com. Điều này cần cho use case thực tế sử dụng địa chỉ email dùng một lần
- Cân nhắc bảo mật: để ngăn tấn công kiểu
"me@mydomain.com"@evildomain.com, * được chuyển thành [^@]* để không thể vượt qua ranh giới @
- Cũng hỗ trợ wildcard một phần như
myusername+*@gmail.com
- Con người chỉ ra rằng khi dùng regex thì phải escape mọi ký tự khác trong địa chỉ email
- Xác nhận wildcard hoạt động cả ở trường owner lẫn allowlist, dùng chung hàm helper
matchesEmailEntry
- Toàn bộ tính năng được triển khai trong khoảng 1 giờ
Lời kết
- Đây không phải một thiết lập cực kỳ hào nhoáng, nhưng hoạt động rất tốt, và tôi hài lòng với độ tin cậy của quy trình
- Stavrobot đã chạy gần một tháng ở chế độ 24/7 và rất ổn định
9 bình luận
Giống như hơn 20 năm trước, khi trào lưu web editor hay blog sản xuất hàng loạt khiến vô số trang chủ hay bài viết không ai xem xuất hiện ồ ạt, thì trong thời đại AI hiện nay cũng đang có hiện tượng tương tự. Tuy nhiên, việc tạo các ứng dụng tùy chỉnh và chia sẻ quy trình hay thói quen làm việc đó rõ ràng là một tài sản tuyệt vời và rất lớn. Cá nhân tôi cho rằng ở thời điểm này, điều quan trọng không phải là tạo ra ứng dụng hay dịch vụ kiếm tiền bằng AI, mà là dễ dàng làm ra những công cụ tùy chỉnh mình cần để nâng cao năng suất.
Thực ra con người có lẽ cũng vậy. Có lẽ đây cũng là một lý do vì sao con người cần theo đuổi sự đa dạng...
Vì là mô hình ngôn ngữ nên để mô hình đắt tiền đảm nhận việc lập kế hoạch là hợp lý.
> Dùng model đắt tiền (Opus) cho việc lập kế hoạch, model rẻ hơn (Sonnet) cho việc viết code để tiết kiệm token
Cũng có nhiều người làm ngược lại, tức là dùng Sonnet để lên kế hoạch và Opus để triển khai code, nhưng ở đây thì ngược lại nhỉ
Tôi cũng đang để opus và codex tung hứng với nhau ngay từ khâu lập kế hoạch. Việc coding thì giao cho opus, còn review code thì lại giao cho một opus khác và codex. Lúc làm mới thấy là dù là AI hay con người thì hình như đều rất giỏi soi lỗi người khác...
Trong thiết lập mặc định của Oh my opencode, phần lập kế hoạch do opus đảm nhiệm còn phần triển khai thì được thực hiện bởi một model nhẹ hơn.
Xung quanh tôi thì mọi người hay lập kế hoạch/thiết kế bằng Sonnet rồi viết code bằng GLM-5..
Tôi cũng đang dùng Sonnet để lập kế hoạch và Opus để hiện thực hóa mã, nhưng có lẽ cách của tác giả bài viết còn hiệu quả hơn.
Tôi cũng lập kế hoạch bằng Opus, phần review thì dùng Codex high, còn viết mã thực tế thì chạy bằng sonet hoặc codex medium.