Bí ẩn tác vụ dài trong A2A: Vì sao việc kéo dài hơn hai phút cứ thất bại?
Hermes message platform integration part 38: Three causes and fixes for A2A long-task timeout issues.
Hai Hermes trên hai máy tính khác nhau “gọi điện” cho nhau giao việc, tác vụ nào kéo dài quá hai phút là thất bại — chỉnh cấu hình kiểu gì cũng vô dụng. Đây không phải chuyện tâm linh, mà là ba cái hố nhỏ chồng lên nhau.
Câu đố: Lời nguyền hai phút
Hãy tưởng tượng bạn nhờ đồng nghiệp A xử lý một tập tài liệu, đã thỏa thuận là anh ấy xử lý xong sẽ gọi điện báo kết quả. Vậy mà cứ mỗi cuộc gọi kéo dài quá hai phút, phía bạn lại “rắc” một cái cúp máy, rồi hiện “cuộc gọi thất bại”. Bạn thử đổi điện thoại, đổi đường dây, thậm chí đổi cả văn phòng, vấn đề vẫn y nguyên.
Đây chính là chuyện người dùng Hermes gặp phải. Hai máy mỗi máy chạy một Hermes, kết nối qua plugin A2A (giao thức truyền thông mở giữa các tác tử). Máy A giao việc cho máy B, chỉ cần tác vụ kéo dài quá khoảng 2 phút là thất bại chắc chắn. Cộng đồng gọi vui là “long reply làm hỏng theo dõi tiến độ”. Bực mình hơn là, phía máy B thực ra đã làm xong việc, nhưng máy A mãi không nhận được kết quả.
Nguyên nhân một: Người gọi thiếu kiên nhẫn
Cái hố đầu tiên, thuần túy là vấn đề “nóng vội”.
Hermes client có một thiết lập timeout mặc định — 120 giây. Nghĩa là sao? Là người gọi tự đặt cho mình một cái đồng hồ báo thức: 120 giây mà đối phương chưa trả lời, tôi cúp máy. Còn phía server thì sao? Nó dành cho tác tử một cửa sổ phản hồi 300 giây, tức 5 phút.
Bạn thấy đấy, người gọi 2 phút đã cúp, người nghe cứ tưởng bạn kiên nhẫn được 5 phút. Kết quả là: tác vụ còn đang chạy, cuộc gọi đã đứt trước. Mà cái 120 giây này là hard-code trong code, người dùng bình thường muốn sửa cũng chẳng tìm được chỗ mà sửa.
Nguyên nhân hai: Đường dây cũ không có nút “timeout”
Cái hố thứ hai, là vấn đề tồn đọng từ xưa.
Hermes cung cấp hai cách gọi A2A: một là “tổng đài chuyển tiếp” chính quy (gọi qua peer đã cấu hình), hai là “đường dây cũ gọi thẳng” (gọi trực tiếp bằng URL gốc). Đường dây cũ là di sản từ phiên bản đầu, bên trong nó cũng hard-code timeout 120 giây, và không đọc file cấu hình.
Nghĩa là, dù bạn có học được cách đổi thời gian timeout, đường dây cũ chẳng thèm nghe bạn. Giống như bạn lắp pin mới cho chiếc điện thoại bàn cũ kỹ, nhưng nó vốn chẳng có núm chỉnh âm lượng.
Nguyên nhân ba: Đến giờ là vứt thành quả đi
Cái hố thứ ba mới ác nhất: phía server đến giờ là “dọn sân”.
Giả sử cuối cùng bạn cũng tăng được timeout của client lên, vượt qua được hai cái hố đầu. Kết quả là phía server lại giở trò: cửa sổ phản hồi 300 giây vừa hết, nó lập tức đánh dấu tác vụ là “thất bại”, mặc kệ tác tử vẫn đang cắm đầu làm việc. Tệ hơn nữa, trạng thái “thất bại” này có tính bám dính — như keo dính, gỡ không ra. Đợi đến khi tác tử thực sự làm xong, mang kết quả về, thì phát hiện chẳng còn ai đợi mình, kết quả bị vứt thẳng.
Cứ như anh shipper đến giờ là tan ca, mặc kệ gói hàng của bạn đã giao chưa, hệ thống cứ hiện “giao thất bại” trước. Đến hôm sau bạn nhận được gói hàng, thông tin vận đơn mãi nằm ở cột “thất bại”, sửa không được.
Bước phá án một: Chỉnh đồng hồ chậm lại
Hiểu rõ ba cái hố, việc sửa chữa trở nên dễ dàng.
Bản sửa đầu tiên rất trực tiếp: đổi timeout mặc định của client từ 120 giây thành 330 giây. 330 giây > 300 giây của server, như vậy người gọi sẽ kiên nhẫn hơn người nghe, tác vụ có thể sống sót qua cửa sổ phản hồi của server. Đồng thời, a2a_call bổ sung tham số ghi đè timeout theo từng cuộc gọi — bạn có thể đặt timeout riêng cho từng tác vụ, không cần sửa toàn cục. Đường dây cũ cuối cùng cũng được “hiện đại hóa”, giờ nó đọc cấu hình, kế thừa xác thực và thiết lập timeout.
Bước phá án hai: Tác vụ “tách rời” thay vì “thất bại”
Bản sửa thứ hai thông minh hơn. Phía server đến giờ không còn đánh dấu tác vụ là thất bại nữa, mà là “tách rời”. Nghĩa là sao? Giống như xếp hàng chờ tổng đài chăm sóc khách hàng, bạn đợi lâu quá không muốn đợi nữa, có thể cúp máy, nhưng phiếu yêu cầu của bạn vẫn còn trong hệ thống, xử lý xong sẽ nhắn tin báo cho bạn.
Cụ thể: khi cửa sổ phản hồi hết hạn, bên gọi nhận được một trạng thái không phải trạng thái cuối “đang xử lý”, kèm theo hướng dẫn. Tác vụ giữ nguyên trạng thái WORKING, hệ thống sắp xếp một “người chờ có giới hạn” tiếp tục theo dõi. Đợi đến khi tác tử thực sự làm xong, kết quả thật được ghi lại. Sau đó dù truy vấn qua tasks/get hay tasks/resubscribe, người quan sát đều thấy được kết quả cuối cùng. Kết quả đến muộn, không còn bị vứt bỏ nữa.
Bước phá án ba: Những cái hố sửa được luôn thể
Sửa xong vụ chính, còn moi ra thêm vài cái hố của nhà hàng xóm.
Streaming reply bị cắt cụt: Trước đây streaming reply có thể chỉ trả về đoạn cuối cùng cho bên gọi. Ví dụ một chuỗi event_id dài, bị cắt chỉ còn đoạn thứ hai, bên gọi nhận được thông tin khiếm khuyết. Sau khi sửa, phản hồi tích lũy được giữ nguyên vẹn, 152 test case đều pass.
Giả hoàn thành: Khi tác tử cạn kiệt ngân sách lặp, trước đây nó trả về một bản tóm tắt “đã hoàn thành”, nhưng bên đối tác không phân biệt được “công việc bị cắt ngang” và “công việc hoàn thành thành công”, có thể chấp nhận kết quả một phần như kết quả đầy đủ. Giờ đây tác vụ bị cắt ngang sẽ báo rõ ràng TASK_STATE_FAILED, không còn giả vờ thành công nữa.
Bộ khởi động ngoài: Người dùng không phải root giờ có thể cấu hình bộ khởi động tác tử ngoài, chạy subprocess hoặc RPC worker theo phiên bản, hỗ trợ đầu ra có giới hạn, thu gom cây tiến trình, hủy bỏ, kết thúc đúng một lần. Giải quyết ba vấn đề cũ: khả năng hiển thị hoàn thành, căn chỉnh timeout, và tính liên tục đa vòng.
Điều đó có nghĩa gì với bạn
Giờ đây, giữa hai Hermes giao việc cho nhau, tác vụ kéo dài hơn hai phút không còn “thất bại chắc chắn” nữa. Thời gian timeout có thể chỉnh, đường dây cũ cũng chịu nghe lời. Quan trọng hơn, dù server có đợi không nổi, tác vụ cũng không bị “kết án tử” — nó vẫn giữ trạng thái đang xử lý, làm xong vẫn ghi nhận kết quả bình thường, bạn có thể tra cứu bất cứ lúc nào.
Tác vụ dài cuối cùng cũng giống như chạy marathon, có người bấm giờ, có người chạy cùng, có người ghi thành tích. Không còn như trước, chạy được nửa đường bị trọng tài thổi còi, thành tích bị hủy bỏ.
📖 Tài liệu chính thức
この記事は Hermes Agent のTài liệu chính thứcに基づいています:Tài liệu chính thức › user-guide/messaging/a2a