𝟯 𝗟𝗮𝗻𝗴𝗚𝗿𝗮𝗽𝗵 𝗥𝗲𝘄𝗿𝗶𝘁𝗲𝘀: 𝗪𝗵𝗮𝘁 𝗣𝗿𝗼𝗱𝘂𝗰𝘁𝗶𝗼𝗻 𝗖𝗵𝗲𝗰𝗸𝗽𝗼𝗶𝗻𝘁𝗶𝗻𝗴 𝗧𝗮𝘂𝗴𝗵𝘁 𝗠𝗲
Trong suốt ba tuần, agent onboarding của chúng tôi đã làm mất mọi công việc mà lẽ ra nó phải lưu lại. Nhật ký (logs) không hiển thị gì cả. Không lỗi. Không cảnh báo. Bộ checkpointer báo cáo thành công, nhưng trạng thái (state) lại biến mất khi tiếp tục (resume).
Tôi đã viết lại cùng một pipeline LangGraph ba lần trước khi hiểu ra sự thật. Framework đã làm chính xác những gì tôi yêu cầu. Chỉ là tôi đã đưa ra những chỉ dẫn sai lầm.
Nếu bạn xây dựng các agent có trạng thái (stateful agents) cho môi trường production, đừng mặc định rằng các thiết lập mặc định sẽ cứu bạn. Dưới đây là những gì đã hỏng và cách tôi khắc phục chúng.
𝗧𝗵𝗲 𝗙𝗶𝗿𝘀𝘁 𝗕𝗿𝗲𝗮𝗸: 𝗦𝗶𝗹𝗲𝗻𝘁 𝗦𝘁𝗮𝘁𝗲 𝗟𝗼𝘀𝘀
Agent của tôi xử lý một luồng onboarding gồm năm bước. Tôi đã sử dụng Postgres để lưu tiến trình để người dùng có thể tiếp tục sau đó. Nhưng mọi lần resume đều bắt đầu lại từ bước một.
Nguyên nhân nằm ở state schema của tôi. Trong LangGraph, mỗi node trả về một bản cập nhật để hợp nhất (merge) vào state. Nếu bạn không chỉ định cách hợp nhất, mặc định sẽ là ghi đè (overwrite).
Tôi cứ ngỡ danh sách tin nhắn của mình sẽ được nối thêm (append). Thay vào đó, mỗi node mới lại thay thế toàn bộ lịch sử chỉ bằng một tin nhắn duy nhất. Bộ checkpoint đã lưu trữ dữ liệu sai một cách hoàn hảo.
Cách khắc phục: Sử dụng các trường Annotated với các reducer rõ ràng.
• Sử dụng toán tử add cho các danh sách tin nhắn.
• Sử dụng hàm merge tùy chỉnh cho các dictionary.
• Chỉ sử dụng ghi đè mặc định cho các giá trị đơn lẻ như "step".
𝗧𝗵𝗲 𝗦𝗲𝗰𝗼𝗻𝗱 𝗕𝗿𝗲𝗮𝗸: 𝗗𝗲𝘀𝗲𝗿𝗶𝗮𝗹𝗶𝘇𝗮𝘁𝗶𝗼𝗻 𝗮𝗻𝗱 𝗖𝗼𝗻𝗰𝘂𝗿𝗿𝗲𝗻𝗰𝘆
Lần viết lại thứ hai đối mặt với hai vấn đề mới:
- Corrupt rows: Tôi đã lưu trữ các đối tượng tùy chỉnh (custom objects) trong state. Bộ serializer không thể xử lý chúng. Điều này tạo ra các hàng dữ liệu tuy có tồn tại nhưng không thể sử dụng được.
- Duplicate keys: WhatsApp sẽ thử lại (retry) các webhook nếu bạn không phản hồi nhanh. Nếu hai tin nhắn đến cùng một lúc, hai lượt chạy graph sẽ cố gắng ghi vào cùng một thread. Điều này gây ra xung đột cơ sở dữ liệu (database collisions).
Cách khắc phục: • Loại bỏ các đối tượng tùy chỉnh. Chỉ sử dụng các dict thuần túy và các kiểu dữ liệu tiêu chuẩn của LangChain. • Xử lý webhooks bên ngoài graph. Sử dụng một hàng đợi (queue) và một khóa idempotency để loại bỏ các bản trùng lặp. • Thêm khóa cơ sở dữ liệu (database lock). Đảm bảo tại một thời điểm chỉ có một lượt chạy diễn ra trên mỗi thread.
𝗧𝗵𝗲 𝗧𝗵𝗶𝗿𝗱 𝗥𝗲𝘄𝗿𝗶𝘁𝗲: 𝗧𝗵𝗲 𝗦𝘁𝗮𝗯𝗹𝗲 𝗣𝗮𝘁𝘁𝗲𝗿𝗻
Phiên bản cuối cùng tập trung vào ba nguyên tắc:
• Đồ thị nhỏ: Tôi đã chia một đồ thị khổng lồ thành ba đồ thị con nhỏ hơn. Điều này giúp giảm phạm vi ảnh hưởng của các lỗi. • Checkpoint rõ ràng: Tôi đã ngừng việc tạo checkpoint sau mỗi node. Tôi chỉ tạo checkpoint tại các điểm tiếp tục (resume points) có ý nghĩa. Việc này giúp giảm 60% các thao tác ghi vào cơ sở dữ liệu. • Các node có tính idempotent: Điều này cực kỳ quan trọng. Mọi node phải cho ra cùng một kết quả nếu nó được chạy hai lần. Nếu một node nhận thấy một tác vụ đã được hoàn thành trong state, nó nên trả về kết quả ngay lập tức. Điều này giúp ngăn chặn việc phải trả phí hai lần cho các lượt gọi model đắt đỏ.
𝗟𝗲𝘀𝘀𝗼𝗻𝘀 𝗳𝗼𝗿 𝘆𝗼𝘂:
- Đọc kỹ ngữ nghĩa của reducer trước khi viết code.
- Không lưu trữ các đối tượng tùy chỉnh (custom objects) trong state.
- Chuyển việc kiểm soát tính đồng thời (concurrency control) ra bên ngoài đồ thị.
- Đảm bảo mọi node đều có tính idempotent.
Framework không hề lỗi. Chính những giả định của tôi mới là vấn đề.
Cộng đồng học tập tùy chọn: https://t.me/GyaanSetuAi