Xây dựng một chatbot đơn giản thì dễ, nhưng xây dựng một hệ thống sẵn sàng cho thực tế — như một Agent hỗ trợ khách hàng — thì rất khó. Bí quyết không nằm ở việc viết prompt tốt hơn, mà nằm ở Kiến trúc tốt hơn. Bài học này dạy bạn "LangGraph Mindset" để chuyển đổi các quy trình phức tạp vào mã nguồn.
I. Quy trình thiết kế 5 bước
Khi đối mặt với một nhiệm vụ tự động hóa mới, hãy làm theo khung tư duy này:
- Vẽ sơ đồ luồng (Map the Workflow): Vẽ quy trình ra giấy. Các bước rời rạc là gì? (Ví dụ: Đọc -> Phân loại -> Nghiên cứu -> Soạn thảo).
- Phân loại các Node: Bước này là Quyết định của LLM, một Lời gọi API, hay chờ Người dùng nhập liệu?
- Thiết kế State: Thông tin nào cần "sống sót" qua các Node? Lưu ý: Luôn ưu tiên lưu trữ dữ liệu thô (JSON), tránh lưu trữ các chuỗi prompt đã format.
- Định nghĩa chiến lược lỗi: Làm thế nào để xử lý khi LLM trả về kết quả sai hoặc API bị lỗi?
- Kết nối bằng Command: Sử dụng đối tượng
Commandđể các Node tự quản lý logic điều hướng của chính chúng (đây là phong cách thiết kế hiện đại và linh hoạt nhất).
II. Triết lý về State: Raw Data vs. Formatted Data
Một sai lầm phổ biến là lưu "câu trả lời cuối cùng" dưới dạng text vào State.
- Cách tiếp cận cũ: Lưu chuỗi
"Người dùng đang gặp lỗi thanh toán." - LangGraph Mindset: Lưu đối tượng
{ intent: "billing", status: "unpaid", priority: "high" }.
Bằng cách lưu dữ liệu thô, các Node khác nhau (ví dụ: Node gửi Email, Node tạo Ticket Jira) có thể sử dụng cùng một thông tin nhưng format theo cách khác nhau tùy vào mục đích.
III. Mã nguồn: 06-design-mindset.ts
Ví dụ dưới đây trình bày quy trình tư duy cho một Hệ thống điều phối hỗ trợ khách hàng. Chúng ta sử dụng đối tượng Command để điều hướng nội bộ thay vì dùng addConditionalEdges ở bên ngoài.
// path: 06-design-mindset.ts
import { StateGraph, START, END, Command } from '@langchain/langgraph';
import { generateImage } from '@workspace/util-langchain';
import z from 'zod';
// 1. DESIGN STATE: Structured raw data
const EmailAgentState = z.object({
emailContent: z.string(),
classification: z
.object({
intent: z.enum(['billing', 'technical', 'general']),
urgency: z.enum(['low', 'high']),
})
.optional(),
resolution: z.string().optional(),
});
type State = z.infer<typeof EmailAgentState>;
// 2. DEFINE NODES: Each node has one responsibility
async function analyzeEmail(state: State) {
console.log('--- NODE: Analyzing Email Intent ---');
// Simulate LLM structured output
const mockAnalysis = {
intent: state.emailContent.includes('money') ? 'billing' : 'general',
urgency: 'high' as const,
};
const nextStep =
mockAnalysis.intent === 'billing' ? 'billing_node' : 'general_node';
// 'Command' handles both State update and Navigation (goto)
return new Command({
update: { classification: mockAnalysis },
goto: nextStep,
});
}
async function handleBilling(state: State) {
console.log('--- NODE: Handling Billing Issue ---');
return { resolution: 'Processed refund request based on policy.' };
}
async function handleGeneral(state: State) {
console.log('--- NODE: Handling General Inquiry ---');
return { resolution: 'Sent FAQ regarding office hours.' };
}
// 3. CONNECT GRAPH
export const thinkingGraph = new StateGraph(EmailAgentState)
.addNode('analyze', analyzeEmail, {
ends: ['billing_node', 'general_node'],
})
.addNode('billing_node', handleBilling)
.addNode('general_node', handleGeneral)
.addEdge(START, 'analyze')
// Note: No addConditionalEdges needed here because
// 'analyzeEmail' uses Command({ goto }) to route itself!
.addEdge('billing_node', END)
.addEdge('general_node', END)
.compile();
// 4. EXECUTION
async function run() {
await generateImage(
thinkingGraph,
'graph-ignore/scripts-06-design-mindset.jpg',
);
const input = {
emailContent: 'I was charged twice for my subscription, where is my money?',
};
console.log('Starting Customer Support Dispatcher...');
const result = await thinkingGraph.invoke(input);
console.log('\n=== AGENT WORKFLOW COMPLETE ===');
console.log(`Intent: ${result.classification?.intent}`);
console.log(`Resolution: ${result.resolution}`);
}
run();IV. Các điểm mấu chốt rút ra từ Code
- Điều hướng nội bộ (Internal Routing): Node
analyzeEmailtự quyết định bước tiếp theo thông qua thuộc tínhgotocủaCommand. Điều này giúp cấu trúc Graph sạch sẽ hơn vì logic "quyết định" nằm ngay cạnh logic "suy luận". - State thuần khiết: Chúng ta lưu toàn bộ đối tượng
classification. Nếu sau này bạn thêm một Node "Ghi log thống kê", nó có thể đọc trườngurgencyđể phân loại báo cáo mà không cần phân tích lại văn bản. - Tính Module: Mỗi Node hoàn toàn độc lập. Bạn có thể thay thế Node
handleBillingbằng một Subgraph phức tạp hơn mà không làm ảnh hưởng đến Nodeanalyze.
Sơ đồ hoạt động:

V. Tổng kết
"Thinking in LangGraph" nghĩa là thoát khỏi tư duy code tuyến tính. Đó là việc thiết kế một môi trường có trạng thái, nơi các Node hoạt động như những chuyên gia độc lập, cùng cập nhật một bộ nhớ chung và quyết định bước đi tiếp theo dựa trên dữ liệu thực tế.
Chúc mừng bạn đã hoàn thành Phần 1: Khởi đầu! Bạn đã nắm vững các khối xây dựng cốt lõi. Trong Phần 2, chúng ta sẽ khám phá các Advanced Capabilities để biến những Graph này thành những Agent thực thụ có trí nhớ dài hạn và khả năng tự sửa lỗi.
👉 Bài tiếp theo: 7. Workflow Patterns I - Chaining & Parallelization