Trong bài học trước, chúng ta đã học cách stream từng chữ (tokens) của AI. Tuy nhiên, trong thực tế, các Tool có thể thực hiện các tác vụ nặng như crawl dữ liệu, xử lý file hoặc gọi API bên thứ ba mất nhiều thời gian. Nếu chỉ stream kết quả cuối cùng, người dùng sẽ không biết Tool đang làm gì. Bài học này sẽ hướng dẫn bạn cách truyền thông tin từ "bên trong" một Tool ra ngoài màn hình theo thời gian thực.
I. Tại sao cần Custom Updates?
Khi một Agent đối mặt với các thách thức về Latency (độ trễ) mà chúng ta đã thảo luận ở Bài 1, việc gửi các bản tin trạng thái giúp cải thiện đáng kể UX:
- Báo cáo tiến độ: "Đang kết nối server...", "Đang tải dữ liệu (50%)...".
- Ghi log nghiệp vụ: Gửi các thông tin phụ mà không làm loãng nội dung trả lời chính của AI.
II. Cơ chế config.writer và ToolRuntime
Mỗi Tool trong LangChain có thể nhận vào một tham số thứ hai là config kiểu ToolRuntime. Đối tượng này chứa hàm .writer(). Bất cứ thứ gì bạn truyền vào hàm này sẽ được đóng gói và gửi về một luồng dữ liệu riêng biệt gọi là custom.
Để nhận được các thông tin này, bạn phải cài đặt streamMode: "custom" khi gọi hàm .stream().
III. Mã nguồn: 10-streaming-custom-updates.ts
Chúng ta sẽ tạo một Tool giả lập việc tìm kiếm dữ liệu qua 3 giai đoạn để quan sát cách trạng thái được gửi về terminal từng giây một.
// apps/langchain/scripts/10-streaming-custom-updates.ts
// pnpm --filter=ai-notes-langchain run tsx scripts/10-streaming-custom-updates.ts
import './env';
import { createGeminiModel, generateImage } from '@workspace/util-langchain';
import { createAgent, tool, type ToolRuntime } from 'langchain';
import { z } from 'zod';
// 1. Define a Tool that reports progress using config.writer
const searchWithProgress = tool(
async ({ query }, config: ToolRuntime) => {
// Stage 1: Send custom update via writer
config.writer?.(`[STATUS]: Connecting to search server for "${query}"...`);
await new Promise((res) => setTimeout(res, 1000));
// Stage 2: Report progress
config.writer?.(
`[STATUS]: Found 15 results, filtering high-quality content...`,
);
await new Promise((res) => setTimeout(res, 1000));
// Stage 3: Summarizing
config.writer?.(`[STATUS]: Synthesizing final data...`);
await new Promise((res) => setTimeout(res, 1000));
return 'Search Result: LangChain v1.0 released with native Multi-agent support.';
},
{
name: 'search_with_progress',
description:
'Search information on the internet and report detailed progress.',
schema: z.object({ query: z.string() }),
},
);
async function main() {
const model = createGeminiModel();
const agent = createAgent({
model,
tools: [searchWithProgress],
});
// Generate visual diagram
await generateImage(
agent.graph,
'images/courses/langchain-course/scripts-10-custom-updates.jpg',
);
console.log('--- Starting request (Watch the [STATUS] lines) ---\n');
// 2. Use streamMode: "custom" to receive data from config.writer
const stream = await agent.stream(
{
messages: [{ role: 'user', content: 'Search for news about LangChain.' }],
},
{ streamMode: 'custom' },
);
// 3. Iterate over the custom data stream
for await (const chunk of stream) {
// Each chunk here is the raw data passed to config.writer()
console.log(chunk);
}
console.log('\n--- Agent task completed ---');
}
main().catch(console.error);IV. Phân tích kỹ thuật
streamMode: "custom": Đây là cài đặt bắt buộc. Nếu không có nó, các lệnh gọiconfig.writerbên trong Tool sẽ chạy ngầm mà không gửi bất kỳ dữ liệu nào về phía người gọi.- Tách biệt luồng (Stream Isolation): Chế độ này tách biệt hoàn toàn giữa nội dung trả lời (tokens/messages) và trạng thái hệ thống (custom data). Ở phía Frontend chuyên nghiệp, bạn có thể hiển thị dữ liệu
customtrong một thanh tiến trình (progress bar) trong khi AI vẫn đang "suy nghĩ" ở luồng chính. - An toàn với
?.: Luôn sử dụngconfig.writer?.(...)vì nếu Tool được gọi qua hàm.invoke()thay vì.stream(), hàmwritersẽ không tồn tại.
Sơ đồ hoạt động:

V. Tổng kết Phần 1 (Cơ bản)
Chúc mừng bạn! Bạn đã hoàn thành 10 bài học cơ bản của khóa học "Master AI Agents with LangChain". Hiện tại, bạn đã nắm vững:
- Thiết lập môi trường chuyên nghiệp với
env.tsvàutil-langchain. - Xây dựng bộ công cụ (Tools) mạnh mẽ có xác thực dữ liệu qua Zod.
- Làm chủ vòng lặp ReAct và kiến trúc createAgent.
- Quản lý bộ nhớ ngắn hạn (Checkpointers) và nén lịch sử trò chuyện (Summarization).
- Xây dựng giao diện thời gian thực với Streaming tokens và Custom updates.
Trong Phần 2 (Nâng cao), chúng ta sẽ đi sâu vào việc can thiệp vào "hệ thần kinh" của Agent bằng Middleware, thiết lập các tầng bảo mật Guardrails và xây dựng hệ thống Đa Agent phối hợp!
👉 Bài tiếp theo: 11. Middleware Hooks (beforeModel & afterModel)