Bài 7 — Streaming: phản hồi thời gian thực
Agent gọi nhiều tool có thể mất nhiều giây tới vài chục giây. Nếu người dùng nhìn màn hình trắng, họ nghĩ hỏng. Streaming cho phép hiển thị text ngay khi model sinh ra và báo tiến trình "đang gọi tool...", giảm cảm giác chờ và cho phép hủy sớm.
Streaming với Anthropic
with client.messages.stream(
model="claude-sonnet-4-5", max_tokens=1024,
messages=[{"role": "user", "content": "Tóm tắt đơn ORD-1234"}],
tools=tools,
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True) # in dần từng mẩu
final = stream.get_final_message() # lấy message hoàn chỉnh
text_stream cho bạn từng đoạn text; get_final_message() trả message đầy đủ (gồm cả tool_use) để bạn tiếp tục vòng lặp. Trong loop agent, bạn stream phần text và khi stop_reason == tool_use thì chạy tool như bình thường.
Streaming với OpenAI Agents SDK
import asyncio
from agents import Agent, Runner
async def main():
result = Runner.run_streamed(agent, "Thời tiết Hà Nội?")
async for event in result.stream_events():
if event.type == "raw_response_event":
# từng delta text từ model
...
elif event.type == "run_item_stream_event":
# sự kiện cấp cao: tool được gọi, output tool, handoff...
print("[sự kiện]", event.name)
asyncio.run(main())
SDK phát hai tầng sự kiện: raw_response_event (delta token thô) và run_item_stream_event (sự kiện có ý nghĩa: gọi tool, nhận kết quả, handoff). Dùng tầng cao để cập nhật UI "Đang tra cứu đơn hàng...", tầng thấp để in text dần.
Vì sao sự kiện tool quan trọng hơn bạn nghĩ
Nếu chỉ stream token text, người dùng thấy chữ chạy mượt rồi... khựng vài giây khi agent gọi tool, rồi lại chạy tiếp. Khoảng khựng đó không có lời giải thích khiến họ nghĩ hệ thống treo. Đây chính là lúc run_item_stream_event cứu bạn: khi nhận sự kiện tool được gọi, hiển thị ngay "Đang tra cứu đơn hàng..."; khi nhận kết quả tool, đổi thành "Đã có dữ liệu, đang soạn câu trả lời...". Người dùng luôn biết agent đang làm gì. Với agent gọi nhiều tool tuần tự (tra đơn → tính hoàn tiền → tạo phiếu), chuỗi trạng thái này biến một khoảng chờ mờ mịt thành một tiến trình minh bạch — khác biệt lớn về trải nghiệm.
Các bước tích hợp streaming vào UI
- Chọn tầng sự kiện phù hợp: text delta để in chữ; sự kiện cấp cao để hiện trạng thái.
- Hiển thị chỉ báo tool: khi có tool call, hiện "Đang xử lý: <tên tool>" thay vì im lặng.
- Ghép text delta an toàn: nối vào buffer, đừng giả định mỗi delta là một câu trọn.
- Xử lý hủy: cho người dùng nút Dừng; đóng stream để không tốn token thừa.
- Luôn lấy message cuối (
get_final_message/ kết quả run) để có bản đầy đủ cho lịch sử và log. - Bọc try/finally để đảm bảo stream được đóng kể cả khi lỗi.
Checklist trải nghiệm streaming tốt
- [ ] Chữ hiện dần trong <1s sau khi gửi.
- [ ] Có trạng thái rõ khi agent đang gọi tool (không để màn hình đứng im).
- [ ] Cho phép hủy giữa chừng và giải phóng tài nguyên.
- [ ] Vẫn lưu được message cuối đầy đủ để audit/log.
- [ ] Guardrail output vẫn chạy trên bản cuối, không phải trên từng delta.
Streaming không hợp mọi ngữ cảnh
Streaming đáng giá khi có con người đang nhìn màn hình chờ. Với tác vụ nền (một cron job phân loại email, một agent chạy hàng loạt), streaming chỉ thêm phức tạp mà không ai hưởng lợi — hãy dùng chế độ chờ kết quả cuối cho gọn. Ngoài ra, guardrail output (Bài 6) phải chạy trên bản trả lời cuối hoàn chỉnh, không phải trên từng delta: một mẩu delta có thể vô hại nhưng ghép lại mới lộ thông tin nhạy cảm. Vì vậy dù stream để hiển thị, bạn vẫn giữ lại message cuối và cho guardrail duyệt nó trước khi coi là an toàn.
Sai lầm thường gặp
- Chỉ stream token mà bỏ sự kiện tool → người dùng thấy khựng lúc gọi tool mà không hiểu vì sao.
- Chạy output guardrail trên từng delta → sai; guardrail phải chạy trên bản cuối hoàn chỉnh.
- Quên
get_final_message→ mất tool_use, loop tiếp theo hỏng. - Không đóng stream khi lỗi/hủy → rò kết nối, tốn token.
- Giả định delta = câu hoàn chỉnh → hiển thị bị vỡ; luôn nối buffer.
- Stream trong tác vụ nền không có UI → vô ích, chỉ thêm phức tạp; chỉ stream khi có người xem.