# Con agent thứ ba mới là chỗ hỏng: chia việc cho máy theo chức năng, đừng chia theo bước
Con bot đầu tiên của mình chạy rất ngon. Nó đọc nguồn, tự lọc, tự quyết cái gì đáng giữ, tự ghi ra kết quả. Mình ngồi xem log và nghĩ: xong rồi, giờ chỉ cần nhân bản nó lên cho các việc khác.
Nên mình thêm con thứ hai. Rồi con thứ ba.
Và đó là tuần hệ của mình tệ đi một cách rất khó giải thích. Không có cái gì sập. Không có lỗi đỏ nào. Chỉ là: cùng một việc đôi khi được làm hai lần, kết quả hai lần lệch nhau, chi phí gấp ba, và khi mình hỏi "cái này do con nào quyết?" thì không có câu trả lời.
Mình mất khá lâu mới nhận ra mình không gặp bài toán model. Mình gặp bài toán tổ chức.
Một con làm hết thì tốt, vì sao? Vì nó không phải chia việc cho ai
Nghịch lý đầu tiên: con bot một mình làm tốt chính vì nó không có đồng nghiệp.
Nó mang trong ngữ cảnh mọi thứ về công việc đó — nguồn nào đáng tin, khách nào khó, lần trước sai ở đâu. Không có ai để bàn giao, nên không có chỗ nào rơi ngữ cảnh. Mọi quyết định nằm trong một cái đầu.
Nhưng cái đầu đó có trần. Ngữ cảnh là một ngân sách, và nó không mở rộng theo số việc mình giao. Khi mình nhồi thêm vai — vừa đọc, vừa nghĩ, vừa viết, vừa tự soát bài của mình — có hai thứ xảy ra, và cả hai đều không báo lỗi:
Một, mỗi vai bị pha loãng. Một agent làm năm vai thì không có vai nào đủ sắc. Nó đọc ở mức đủ để viết, nghĩ ở mức đủ để không bị bắt lỗi, soát ở mức đủ để thấy mình ổn.
Hai, vai "soát" luôn thua vai "làm". Đây là chỗ mình thấy rõ nhất. Con bot tự kiểm bài mình vừa viết sẽ có xu hướng xác nhận. Không phải vì nó gian, mà vì trong cùng một ngữ cảnh, nó vừa là người làm vừa là người chấm — và cái đầu nào cũng thấy lý do cho việc mình vừa làm.
Nên nhu cầu tách vai là thật. Vấn đề là tách kiểu gì.
Chia theo bước: sai ở đúng chỗ ai cũng tưởng là đúng
Cách tự nhiên nhất để chia việc là nhìn vào quy trình rồi cắt khúc: bước 1 đọc, bước 2 phân tích, bước 3 viết, bước 4 kiểm, bước 5 gửi. Mỗi bước một con. Nghe gọn như dây chuyền.
Mình đã làm đúng như thế và nó là sai lầm đắt nhất.
Vì mỗi lần cắt là một lần bàn giao, và mỗi lần bàn giao là hai lần mất mát:
- ▹Ngữ cảnh bị nén lại thành một đoạn văn. Con sau không biết vì sao con trước loại ba nguồn kia.
- ▹Và tệ hơn: mỗi lần bàn giao là một cơ hội để đứa trước báo xong trong khi chưa xong. Nó không nói dối. Nó chỉ gói phần nó làm được thành một câu nghe như đã hoàn tất. Đứa sau nhận câu đó và không có cách nào kiểm.
Mình có một tuần mà bốn bước đều "xong", log xanh hết, và bài ra vẫn thiếu đúng cái phần quan trọng nhất — vì ở bước hai, một nguồn bị bỏ vì lý do hợp lý, và lý do đó không được chuyển tiếp.
Quy trình càng nhiều bước, hệ càng giống một trò chơi điện thoại bị truyền miệng.
Chia theo chức năng: mỗi con chỉ có một loại đầu ra
Cái làm hệ mình ổn lại là đổi trục chia. Không chia theo thời gian (bước nào trước bước nào sau), mà chia theo loại đầu ra mỗi con được phép sinh ra.
Bốn vai, và điều quan trọng không phải tên vai mà là kiểu dữ liệu họ trả về:
- ▹Vai đọc — chỉ trả về dữ liệu có cấu trúc. Không được viết câu văn. Không được nhận xét.
- ▹Vai nghĩ — chỉ trả về kế hoạch: làm gì, theo thứ tự nào, cần gì. Không được tự làm.
- ▹Vai làm — chỉ trả về trạng thái: đã đổi cái gì, tới đâu, còn gì. Không được tự đánh giá là tốt.
- ▹Vai soát — chỉ có quyền nói "không". Không được viết lại. Nếu nó được viết lại, nó thành người làm, và mình mất người soát.
Khác biệt lớn nhất không nằm ở việc chia nhỏ công việc — số vai gần như y hệt. Nó nằm ở chỗ: mỗi đầu ra có một tiêu chí kiểm riêng, và tiêu chí đó không phải "nghe có vẻ đúng".
Dữ liệu có cấu trúc thì kiểm được bằng schema. Kế hoạch thì kiểm được bằng "có bước nào thiếu đầu vào không". Trạng thái thì kiểm được bằng đối chiếu với thế giới thật. Chỉ có câu văn là không kiểm được — và đó chính là thứ mình đã dùng để bàn giao suốt mấy tháng.
"LLM là não, không phải mắt tai" — câu này sửa được nhiều thứ
Trong tuần mình đi tìm cách làm hệ nhiều vai cho gọn, có một nguyên tắc mình đọc được và áp dụng ngay, vì nó giải thích đúng cái mình đang thấy: đừng để model vừa đọc vừa nghĩ.
Nghe hiển nhiên, nhưng mặc định của hầu hết hệ thống là đưa nguyên liệu thô vào model rồi bảo nó "hiểu và làm". Nghĩa là mình giao cho nó cả việc cảm nhận (đọc, bóc tách, nhận ra cái gì là cái gì) lẫn việc suy luận.
Vấn đề: khi khâu cảm nhận sai, khâu suy luận sẽ viết lại cái sai đó thành văn xuôi tự tin. Không có ngoại lệ. Và mình không có lỗi nào để bắt, vì ở đầu ra chỉ có một đoạn văn trôi chảy.
Nên tầng đọc phải là tầng cứng: bóc tách cấu trúc bằng code, chuẩn hoá, đánh dấu chỗ không chắc. Model chỉ nhận phần đã cấu trúc, và nhận kèm danh sách "chỗ này tao không đọc được". Một dòng "không rõ" ở tầng đọc rẻ hơn một đoạn văn tự tin ở tầng nghĩ, rất nhiều lần.
Minh hoạ ở mức khái niệm (pseudo-code, không phải nguồn triển khai):
# tầng cảm nhận: không gọi model
raw -> parse cấu trúc
-> chỗ nào không parse được: gắn nhãn "unknown" + giữ con trỏ về gốc
# tầng suy luận: chỉ nhận dữ liệu đã cấu trúc
neu(get_structured(), unknowns) -> plan hoặc yêu cầu đọc lại đúng chỗ "unknown"Chú ý dòng cuối: nó không được phép tự đoán chỗ "unknown". Nó phải yêu cầu đọc lại — và nếu đọc lại vẫn không ra, việc dừng ở đó, có người biết.
Bàn giao phải là hợp đồng dữ liệu, không phải hội thoại
Đây là thay đổi mình thấy đáng tiền nhất, và cũng là thứ ít người nói tới khi bàn về multi-agent.
Khi hai agent nói chuyện với nhau bằng tin nhắn, mình có một cuộc hội thoại đẹp và không có gì ràng buộc. Không ai định nghĩa "xong" nghĩa là gì. Không ai có quyền từ chối. Không ai biết ngân sách còn bao nhiêu.
Nên mình đổi mọi điểm bàn giao thành một gói dữ liệu có trường bắt buộc. Không có gói thì không có bàn giao.
envelope:
owner: # con nào chịu trách nhiệm việc này, DUY NHẤT một con
scope: # được làm gì, và rõ ràng: không được làm gì
input_ref: # con trỏ tới nguồn gốc, không phải bản sao
done_when: # điều kiện kiểm được bằng máy, không phải "chất lượng tốt"
budget: # số lần thử + chi phí tối đa
on_unknown: # gặp chỗ không chắc thì làm gì (mặc định: dừng)
escalate_to: # ai nhận khi vượt ngân sách hoặc gặp chỗ không có đường lùi
may_refuse: # được quyền nói "không làm" — và nói vì saoBa trường làm thay đổi nhiều nhất với mình:
`done_when` — phải kiểm được bằng máy. Nếu mình viết "bài viết tốt" thì mình đã tạo ra một điểm bàn giao không kiểm được, và con sau sẽ nhận một thứ không ai xác nhận.
`input_ref` — con trỏ, không phải bản sao. Bản sao thì mỗi lần truyền lại mất một lớp sự thật, và đến con thứ tư thì không ai biết số liệu gốc ở đâu. Con trỏ thì con nào cũng quay về được cùng một chỗ.
`may_refuse` — cái này nghe lạ nhưng quan trọng nhất. Một con agent không có quyền từ chối sẽ luôn chọn làm tiếp, kể cả khi nó đang đoán. Quyền từ chối là cách rẻ nhất để biến "đoán" thành "báo".
Sau khi thêm ba trường đó, số lần việc bị làm hai lần gần như về không — không phải vì agent giỏi hơn, mà vì chỉ có một `owner`. Tranh chấp biến mất ở đúng chỗ nó sinh ra: chỗ không ai được giao.
Ai chịu trách nhiệm: câu hỏi không có trong tài liệu kỹ thuật nào
Chỗ mình vẫn phải giữ ở phía người không phải là "duyệt từng bước". Mình không duyệt từng bước — làm thế thì tự động hoá để làm gì.
Chỗ mình giữ là những điểm không có đường lùi: gửi ra ngoài, tiêu tiền, xoá dữ liệu, cam kết với khách. Ở đó, một quyết định phải có đúng một chủ, và chủ đó là người.
Còn lại, nguyên tắc mình dùng để phân xử khi hai con cãi nhau (và chúng có cãi): con nào chạm được vào thế giới thật thì con đó thua. Con chỉ đọc và nghĩ thì được quyền sai. Con ghi ra ngoài thì không. Nên mọi quyết định "ghi" phải qua một cổng hẹp, và cổng hẹp thì dễ kiểm.
Ba câu mình tự hỏi mỗi lần định thêm một con agent nữa:
- 1.Việc này có loại đầu ra khác với các con đang có, hay chỉ là cùng một loại đầu ra chia làm hai?
- 2.Điểm bàn giao mới có
done_whenkiểm được bằng máy chưa, hay lại là một câu văn? - 3.Nếu con này im lặng cả tuần, mình có biết không — và biết bằng cái gì?
Câu thứ ba là câu mình hay quên nhất, và cũng là câu đắt nhất.
Vì sao doanh nghiệp vừa là chỗ dễ bán — và dễ bán sai
Mình để ý dạo này có khá nhiều case study công khai về việc dựng hệ multi-agent cho doanh nghiệp vừa, kiểu vài chục đến vài trăm người. Đọc thì thấy đúng cái khe mình đang nhìn:
Doanh nghiệp vừa đã chán công cụ phổ thông nhưng chưa đủ tiền và chưa đủ đội để dựng một nền tảng riêng. Họ có ngân sách thật, có quy trình thật, và có một người đau đầu thật. Đó là điều kiện tốt hơn nhiều so với startup chỉ có ý tưởng.
Nhưng cũng chính vì thế mà dễ bán sai. Cái dễ bán nhất là câu: "để agent tự lo hết". Câu đó bán được một lần, rồi hợp đồng chết ở lần gia hạn đầu tiên — vì khách không đo được cái gì đã xảy ra.
Cách mình thấy bền hơn là bán một chức năng, một chỉ số. Không bán "hệ agent toàn diện". Bán "khâu này, mỗi tuần ra được cái này, đo bằng con số này". Rồi mở rộng theo chức năng, không mở rộng theo bước.
Và đây là chỗ nối vào tiền, theo cách ít ai tính: chi phí thật của multi-agent không phải token. Token là phần rẻ nhất. Chi phí thật là số điểm bàn giao mình phải định nghĩa. Mỗi điểm bàn giao là một hợp đồng phải viết, một done_when phải kiểm được, một chỗ phải có người nhận khi vượt ngân sách.
Nên khi tính giá một hệ nhiều agent, mình tính theo số điểm bàn giao, không theo số agent. Hai agent mà bàn giao mười lần thì đắt hơn năm agent bàn giao hai lần.
Kết: câu hỏi đúng không phải "thêm con nào"
Sau tuần đó mình không thêm con agent nào nữa. Mình ngồi viết lại các điểm bàn giao, và hoá ra hệ của mình có bốn chỗ mà hai con cùng nghĩ mình là chủ. Không chỗ nào báo lỗi. Chỉ là mỗi chỗ làm một kiểu.
Điều mình rút ra, và cũng là điều mình hay nói với người đang định nhân bản con bot đầu tiên: một con agent giỏi là chuyện của model. Nhiều con agent chạy được là chuyện của tổ chức. Và tổ chức thì không tự mọc ra từ việc thêm người — nó mọc ra từ việc định nghĩa ai giao cái gì cho ai, thế nào là xong, và ai được phép nói không.
Nếu anh em đang có một con bot chạy ngon và đang nghĩ tới chuyện nhân bản nó — thử một việc trước: mở log ra và tìm xem có việc nào đã được làm hai lần trong tuần qua không. Nếu có, thì vấn đề chưa bao giờ là thiếu agent. Nó là thiếu chủ.
Anh em đang chia vai cho agent theo bước hay theo chức năng? Mình tò mò nhất là chỗ nào anh em thấy khó định nghĩa done_when nhất — vì đó thường là chỗ đang ngốn thời gian mà không ai gọi tên.