Giảm chi phí là một chuyện; làm hệ thống cảm thấy nhanh là chuyện khác — và đôi khi hai mục tiêu này đánh nhau. Bài này về ba công cụ điều phối độ trễ: streaming (cảm nhận), song song hóa (độ trễ thực), và batching (thông lượng & chi phí).
Phân biệt độ trễ thực vs cảm nhận
- Độ trễ thực: tổng thời gian tới khi hoàn tất.
- Độ trễ cảm nhận: thời gian tới khi người dùng thấy tiến triển.
1) Streaming
Bật streaming cho mọi phản hồi hướng người dùng. Đo TTFT riêng (Bài 2). Ghép với routing: dùng tầng nhỏ trả lời tức thì phần dễ, tầng lớn xử nền phần khó.
2) Song song hóa (parallel)
Agent thường gọi tuần tự vì lười, dù các lệnh gọi độc lập. Nếu bước không phụ thuộc nhau, chạy song song:
import asyncio
async def gather_context(q):
docs, profile, orders = await asyncio.gather(
rag_search(q), # độc lập
get_user_profile(q), # độc lập
get_recent_orders(q), # độc lập
)
return docs, profile, orders
Ba lệnh mỗi lệnh 500ms: tuần tự 1.500ms, song song ~500ms. Với agent multi-tool, đây là mức cắt độ trễ lớn nhất mà không đổi một token nào.
3) Batching
Gom nhiều đơn vị công việc vào ít lệnh gọi hơn:
- Micro-batching trực tuyến: gom yêu cầu tương tự trong cửa sổ 50–100ms rồi gửi chung (đánh đổi độ trễ lấy thông lượng — hợp cho embedding, phân loại hàng loạt).
- Batch API (ngoại tuyến): nhiều nhà cung cấp có chế độ batch giảm giá mạnh (thường ~50%) cho việc không cần real-time (làm giàu dữ liệu ban đêm, gán nhãn, backfill).
- Gộp trong một prompt: phân loại 20 mục trong một lệnh gọi thay vì 20 lệnh — tiết kiệm cả overhead lẫn token system lặp lại.
# Gộp: một lệnh gọi phân loại nhiều mục
prompt = "Phân loại từng dòng, trả JSON [{id, label}]:\n" + \
"\n".join(f"{i}. {t}" for i, t in items)
Khung quyết định điều phối
Cần real-time cho người dùng?
├─ CÓ → streaming + song song hóa (đừng batch chờ)
└─ KHÔNG (job nền) → Batch API (giảm giá) / micro-batch
Các lệnh gọi có độc lập không?
├─ CÓ → asyncio.gather / song song
└─ KHÔNG → giữ tuần tự, tối ưu từng bước
Các bước triển khai
- Vẽ đồ thị phụ thuộc các bước trong một phiên; đánh dấu bước độc lập.
- Song song hóa các nhánh độc lập bằng async/gather.
- Bật streaming cho đầu ra người dùng, đo TTFT.
- Chuyển job không-real-time sang Batch API để hưởng giảm giá.
- Gộp các phân loại/embedding rời rạc vào một lệnh gọi khi có thể.
- Đặt trần đồng thời (concurrency limit) để tránh vượt rate limit gây lỗi hàng loạt.
Sai lầm thường gặp
- Batch cho luồng real-time → thêm độ trễ chờ gom, người dùng phải đợi.
- Song song hóa bước phụ thuộc nhau → dùng dữ liệu chưa sẵn sàng, sinh kết quả sai.
- Không đặt trần đồng thời → bùng rate limit, hàng loạt request 429, tệ hơn ban đầu.
- Nhầm streaming là giảm chi phí: streaming cải thiện cảm nhận, không giảm token.
- Gộp quá nhiều mục vào một prompt → vượt cửa sổ context hoặc model bỏ sót mục cuối; chia lô vừa phải.
Ví dụ kết hợp ba công cụ
Hãy tưởng tượng một lượt chat: người dùng gửi câu hỏi cần tra 3 nguồn rồi soạn trả lời. Bản tối ưu điều phối như sau: (1) ngay khi nhận yêu cầu, chạy song song ba lệnh tra nguồn (~500ms thay vì 1.500ms tuần tự); (2) khi soạn trả lời, stream token ra màn hình để người dùng thấy chữ đầu sau ~400ms; (3) phần gán nhãn/embedding cho log phân tích thì đẩy sang micro-batch nền, không chặn luồng chính. Ba công cụ này bổ trợ nhau: song song cắt độ trễ thực, streaming cắt độ trễ cảm nhận, batching dời việc nền ra khỏi đường tới hạn.
Đừng quên đặt trần đồng thời (ví dụ tối đa 20 lệnh song song). Khi traffic tăng vọt, không có trần này bạn sẽ vượt rate limit của nhà cung cấp và nhận hàng loạt lỗi 429 — biến một tối ưu độ trễ thành một sự cố diện rộng.