Thiết kế API bất đồng bộ để agent không báo thành công quá sớm
Agent gọi điểm cuối chuyển mã video, nhận 202 Accepted cùng ID công việc, rồi báo rằng quá trình đã hoàn tất và chuyển sang bước đọc một tệp chưa tồn tại. Đây là lỗi điển hình khi agent hiểu mọi phản hồi 2xx là thành công cuối cùng.
Hoạt động chạy dài tách hợp đồng API thành hai phần: bắt đầu và hoàn tất. Nếu không thiết kế rõ ràng, agent có thể:
- tuyên bố thành công ngay sau
202; - thăm dò liên tục, làm tốn token và lượt suy luận;
- bị treo trong một lượt hội thoại quá lâu;
- quên
job_idsau khi ngữ cảnh bị nén.
Bài viết này tập trung vào đường dẫn “thành công chậm”; với lỗi API tổng quát, xem hướng dẫn về khôi phục lỗi agent.
Vì sao agent xử lý bất đồng bộ sai?
Agent đọc 2xx là đã xong
202 Accepted chỉ có nghĩa là máy chủ đã nhận yêu cầu để xử lý, không đảm bảo công việc đã hoàn thành. Đặc tả ngữ nghĩa HTTP cũng nêu rõ điều đó.
Vì vậy, đừng buộc agent phải suy luận chỉ từ mã trạng thái.
Thăm dò trong vòng lặp suy luận rất tốn kém
Thăm dò mỗi 2 giây cho một tác vụ 4 phút tạo ra 120 lần kiểm tra. Mỗi lần có thể tiêu tốn một lượt mô hình và thêm ngữ cảnh hội thoại. Xem thêm: giữ phản hồi công cụ ra khỏi cửa sổ ngữ cảnh.
job_id dễ bị mất
Nếu công cụ trả về ID công việc nhưng agent phải tự nhớ nó trong một cuộc hội thoại dài, ID có thể bị nén hoặc bị quên trước khi công việc hoàn thành.
Thiết kế phản hồi không thể bị đọc sai
Phần thân phản hồi cần nói rõ ba điều:
- Công việc chưa hoàn tất.
- Công cụ hoặc URL cần dùng tiếp theo.
- Khoảng thời gian tối thiểu trước khi kiểm tra lại.
{
"status": "processing",
"job_id": "job_7f21c",
"message": "Quá trình chuyển mã đã BẮT ĐẦU và CHƯA hoàn tất. Đừng báo cáo thành công. Kiểm tra trạng thái bằng getJobStatus(job_id) sau ít nhất 30 giây.",
"poll_after_seconds": 30,
"estimated_duration_seconds": 240,
"status_url": "/v1/jobs/job_7f21c"
}
Các từ như “chưa hoàn tất”, tên công cụ kế tiếp và thời gian chờ tối thiểu hiệu quả hơn việc để mô hình tự diễn giải 202.
Bạn có thể áp dụng hình dạng tài nguyên của AIP-151 của Google về các hoạt động chạy dài: một đối tượng vận hành nhất quán với các trường done, error và response.
Phản hồi kiểm tra trạng thái nên đơn giản:
{
"job_id": "job_7f21c",
"status": "processing",
"done": false,
"progress_percent": 45,
"elapsed_seconds": 108,
"poll_after_seconds": 45,
"message": "Vẫn đang xử lý. Không chuyển sang bước tiếp theo."
}
Khi thành công, trả kết quả nhỏ trực tiếp để tránh thêm một lệnh gọi API:
{
"job_id": "job_7f21c",
"status": "succeeded",
"done": true,
"result": {
"output_url": "https://cdn.example.com/out/7f21c.mp4",
"duration_seconds": 372
}
}
Thăm dò bên ngoài mô hình, không phải bên trong mô hình
Với tác vụ kéo dài vài giây đến vài phút, hãy đặt vòng lặp chờ trong trình bao bọc công cụ:
import time
def start_and_await_transcode(client, source_url, max_wait=600):
job = client.post("/v1/transcode", json={"source_url": source_url}).json()
job_id = job["job_id"]
delay = job.get("poll_after_seconds", 5)
waited = 0
while waited < max_wait:
time.sleep(delay)
waited += delay
status = client.get(f"/v1/jobs/{job_id}").json()
if status.get("done"):
if status["status"] == "succeeded":
return {"status": "succeeded", "result": status["result"]}
return {"status": "failed", "error": status.get("error")}
delay = min(int(delay * 1.5), 60)
return {
"status": "timed_out",
"job_id": job_id,
"message": f"Still running after {max_wait}s. Job {job_id} continues in the background.",
}
Với agent, đây chỉ là một lệnh gọi công cụ: gọi, chờ, nhận kết quả cuối cùng. Không có 120 lượt thăm dò trong ngữ cảnh, không mất job_id, và không có vòng lặp suy luận lãng phí.
Áp dụng hai quy tắc:
- Luôn có
max_wait. - Khi hết thời gian, luôn trả về
job_id.
Phân biệt rõ ba kết quả:
succeededfailedtimed_out
Dùng backoff theo cấp số nhân, giới hạn tối đa khoảng 60 giây, và cân nhắc jitter theo hướng dẫn của Amazon về thời gian chờ, thử lại và lùi thời gian với jitter.
Với công việc kéo dài hàng giờ, không nên giữ lượt hội thoại mở. Hãy dùng hai công cụ:
-
startJob()để khởi tạo. -
getJobStatus(job_id)để kiểm tra sau.
Đồng thời lưu bền vững job_id, tác vụ liên quan và thời điểm bắt đầu ở ngoài ngữ cảnh hội thoại.
Khi nào nên dùng webhook?
| Tình huống | Lựa chọn phù hợp |
|---|---|
| Công việc mất vài giây đến vài phút | Thăm dò |
| Agent cần kết quả để tiếp tục ngay | Thăm dò trong trình bao bọc |
| Công việc kéo dài hàng giờ | Webhook hoặc lịch kiểm tra |
| Có nhiều công việc chạy đồng thời | Webhook |
| Không thể có điểm cuối công khai | Thăm dò hoặc SSE |
| Người dùng đang theo dõi tiến độ trực tiếp | SSE |
Xem so sánh chi tiết giữa webhook và thăm dò.
Webhook hiệu quả nhưng yêu cầu:
- điểm cuối công khai;
- xác minh chữ ký;
- xử lý retry;
- cơ chế đánh thức agent khi callback đến.
Tham khảo hướng dẫn về thiết kế webhook đáng tin cậy và xác minh chữ ký webhook.
Nếu client có thể giữ kết nối, truyền phát phản hồi API với SSE là lựa chọn trung gian tốt: có ngữ nghĩa đẩy mà không cần webhook công khai.
Dù dùng polling, webhook hay SSE, xử lý hoàn tất phải idempotent. Một tín hiệu thành công đến hai lần không được chạy bước tiếp theo hai lần. Xem thêm về tính bất biến cho các agent AI.
Kiểm tra cả đường dẫn chậm
Stub cục bộ hoàn thành trong 200 ms không phản ánh công việc thật mất 4 phút. Hãy lưu các kịch bản sau trong CI:
-
Công việc chậm: trả
processingvài lần rồisucceeded. Kiểm tra polling, backoff và kết quả cuối. -
Thất bại muộn: trả
processingba lần, sau đófailedvới nội dung lỗi. -
Hết thời gian: luôn trả
processingđến khi vượtmax_wait; công cụ phải trảtimed_outvà giữ nguyênjob_id. - Hoàn thành trùng lặp: gửi thành công hai lần qua webhook retry hoặc polling cạnh tranh; bước tiếp theo chỉ được chạy một lần.
Bạn có thể dùng Apidog để tạo mock thay đổi theo số lượt gọi hoặc tham số điều khiển, giúp kiểm thử luôn lặp lại được. Xem thêm hướng dẫn về kiểm thử hợp đồng API.
Xử lý kết quả một phần
Một công việc có thể hoàn thành nhưng vẫn có bản ghi thất bại. Đừng ép nó vào mô hình nhị phân thành công/thất bại.
{
"job_id": "job_a11f",
"status": "completed_with_errors",
"done": true,
"summary": {
"processed": 20000,
"succeeded": 19860,
"failed": 140
},
"errors_url": "/v1/jobs/job_a11f/errors?limit=50",
"message": "Nhập dữ liệu hoàn tất. 140 hàng bị lỗi và không được ghi. Hãy xem xét lỗi trước khi báo cáo thành công."
}
Đặt số liệu tổng hợp trực tiếp trong phản hồi để agent có thể quyết định ngay. Đặt danh sách lỗi sau URL phân trang hoặc có giới hạn để không làm đầy ngữ cảnh.
Ba ví dụ thực tế
Tạo báo cáo
Agent tài chính yêu cầu xuất dữ liệu quý, mất 90 giây. Nếu chỉ nhận job_id, agent có thể gửi liên kết tải xuống quá sớm. Với trình bao bọc chặn, agent chỉ nhận URL thật khi báo cáo đã sẵn sàng.
Nhập dữ liệu hàng loạt
Agent vận hành nhập 20.000 bản ghi trong 8 phút và thất bại một phần ở hàng 14.000. done: true không đủ để gọi đây là thành công; hãy trả completed_with_errors, số lượng thành công/thất bại và URL lỗi.
Huấn luyện mô hình hoặc xây dựng CI
Tác vụ kéo dài 40 phút không phù hợp với trình bao bọc chặn. Hãy bắt đầu công việc, lưu job_id, kết thúc lượt hiện tại và dùng callback hoặc lịch kiểm tra để chạy bước sau. Xem cách duy trì trạng thái giữa các lần chạy trong hướng dẫn về bàn giao và truyền ngữ cảnh giữa nhiều agent.
Đừng để công việc bị đình trệ vô chủ
timed_out là kết quả đúng nếu tác vụ vẫn chạy, nhưng chỉ hữu ích khi có người hoặc hệ thống nhận trách nhiệm theo dõi nó.
Hãy đưa trạng thái đó vào hàng đợi vận hành, hệ thống cảnh báo hoặc nền tảng thực thi. Ví dụ, Sharkly hiển thị lần chạy bị chặn trong Nhiệm vụ cùng trạng thái và kết quả. Điểm quan trọng không phải công cụ cụ thể: “vẫn đang chạy, kiểm tra sau” phải có người chịu trách nhiệm.
Danh sách kiểm tra
- Mỗi điểm cuối chậm trả về
job_id,status_urlvà thông báo rõ rằng công việc chưa hoàn thành. - Phản hồi trạng thái có
doneboolean. - Polling nằm trong trình bao bọc công cụ, có exponential backoff và giới hạn cứng.
- Timeout luôn trả về
job_id. -
succeeded,failedvàtimed_outlà ba kết quả khác nhau. - Công việc dài hơn vài phút được lưu bên ngoài hội thoại.
- Đường dẫn hoàn tất idempotent, bất kể nhận tín hiệu qua polling hay callback.
- CI kiểm tra tác vụ chậm, thất bại muộn, timeout và hoàn thành trùng lặp.
Câu hỏi thường gặp
API nên trả 202 hay 200 khi khởi động tác vụ bất đồng bộ?
Dùng 202 Accepted. Tuy nhiên, đừng chỉ dựa vào nó cho agent: phản hồi cũng phải nói rõ công việc chưa hoàn thành.
Trình bao bọc nên chờ bao lâu?
Đặt giới hạn cao hơn một chút so với trường hợp xấu nhất thực tế, thường từ 2 đến 10 phút. Nếu lâu hơn, chuyển sang mô hình bắt đầu rồi kiểm tra sau.
Khoảng thăm dò nên là bao lâu?
Bắt đầu bằng poll_after_seconds từ máy chủ nếu có. Sau đó nhân khoảng chờ với khoảng 1.5, giới hạn ở khoảng 60 giây. Thăm dò cố định mỗi giây dễ lãng phí yêu cầu và gây giới hạn tỷ lệ; xem hướng dẫn về vượt quá giới hạn tỷ lệ.
Agent có thể làm việc khác trong khi chờ không?
Có, nếu bộ điều phối hỗ trợ lệnh gọi công cụ đồng thời. Nếu không, trình bao bọc chặn đơn giản thường ít lỗi hơn một scheduler tự xây.
Làm sao ngăn agent báo thành công sớm?
Nói rõ trong phản hồi rằng công việc chưa hoàn thành, cung cấp done: false, và chỉ trả kết quả ở công cụ hoàn tất hoặc phản hồi trạng thái thành công.
Webhook có dùng được với agent chạy trên máy tính xách tay không?
Không trực tiếp vì máy tính xách tay thường không có điểm cuối công khai. Dùng tunnel khi phát triển, như trong hướng dẫn kiểm thử API localhost bằng dịch vụ webhook, hoặc tiếp tục dùng polling.
Thiết kế phản hồi rõ ràng và đặt phần chờ trong trình bao bọc công cụ sẽ biến tác vụ chạy dài thành hợp đồng mà agent xử lý tốt nhất: gọi công cụ, chờ, nhận kết quả. Tải xuống Apidog để xây dựng mock cho các tác vụ chậm và đưa các kịch bản này vào kiểm thử.


Top comments (0)