LangGraph Trong Thực Tế: Những Điều Tài Liệu Không Nói Cho Bạn
Tháng 10 năm 2025, tôi đảm nhận vai trò Tech Lead cho Sales Agent của Parcel Perform — một nền tảng tự động hóa bán hàng AI được xây dựng với LangChain + LangGraph + LangSmith trên AWS Bedrock, chạy Claude Sonnet 4.6. Sau tám tháng vận hành, khi Agentic AI không còn là demo mà đã trở thành hạ tầng kinh doanh thực sự, tôi muốn chia sẻ những gì tôi thực sự học được: các thất bại, cạm bẫy, và quyết định kiến trúc tạo nên sự khác biệt giữa hệ thống production ổn định và một prototype viết tốt.
Tại Sao Chọn LangGraph Thay Vì LangChain Thuần Hay Custom Orchestration
Câu hỏi đầu tiên tôi phải trả lời là: chúng tôi có thực sự cần LangGraph không? Hoàn toàn có thể kết nối các LangChain chains thô lại với nhau, hoặc tự xây dựng một state machine. Chúng tôi chọn LangGraph vì ba lý do cụ thể.
Thứ nhất là conditional routing. Một sales agent cần logic phân nhánh — nếu prospect đã có trong CRM, bỏ qua bước enrichment; nếu độ tin cậy email thấp, chuyển sang review thủ công. Graph edges của LangGraph giúp diễn đạt điều này gọn gàng, không có if-else rối rắm trong application code.
Thứ hai là checkpointing. Checkpointer tích hợp sẵn của LangGraph (chúng tôi dùng Postgres backend) cho phép agents bị gián đoạn, kiểm tra và tiếp tục — miễn phí. Rất quan trọng khi một workflow kéo dài qua nhiều API call và bạn không muốn chạy lại các bước tốn kém.
Thứ ba là tích hợp LangSmith. Bạn có full trace visualization ngay từ đầu. Với một production agent chạm vào Claude Sonnet, Apollo và các internal API của chúng tôi, khả năng replay một run cụ thể và xem chính xác node nào thất bại là điều bắt buộc.
Thiết Kế Graph State: Những Quyết Định Thực Sự Quan Trọng
Quyết định kiến trúc quan trọng nhất trong LangGraph là schema cho state. Làm sai và bạn sẽ phải refactor toàn bộ graph.
State của chúng tôi chứa: dữ liệu prospect, kết quả enrichment, danh sách tool calls đã thực hiện, action queue, và conversation history. Lỗi ban đầu chúng tôi mắc phải là đưa quá nhiều dữ liệu thô vào state. Mỗi checkpoint serialize toàn bộ state — state phình to đồng nghĩa checkpoint chậm và storage tốn kém.
Giải pháp: chỉ lưu reference (prospect ID, thread ID) trong state, không lưu toàn bộ object. Fetch dữ liệu khi vào node. State là control structure, không phải data cache.
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
prospect_id: str
thread_id: str
messages: Annotated[list, add_messages]
action_queue: list[str]
last_tool_result: dict | None
human_review_required: bool
graph = StateGraph(AgentState)
graph.add_node("enrich", enrich_prospect)
graph.add_node("draft_email", draft_email_node)
graph.add_node("review_gate", human_review_node)
graph.add_conditional_edges("enrich", route_after_enrich, {
"draft": "draft_email",
"review": "review_gate",
"end": END,
})AWS Bedrock + Guardrails
Chúng tôi chạy Claude Sonnet 4.6 qua AWS Bedrock, xác thực bằng IRSA (IAM Roles for Service Accounts) trong Kubernetes — không có static credentials, role assumption theo từng pod. Module Terraform cho phần này mất khoảng một ngày để hoàn chỉnh nhưng đem lại lợi ích lớn về bảo mật.
Guardrails là yêu cầu bắt buộc từ ngày đầu: content filter, denied topics (agent không được thảo luận về pricing của đối thủ), và PII redaction ở output. Điểm cần lưu ý: Guardrails thêm ~200-400ms latency mỗi call. Hãy thiết kế timeout budget cho phù hợp.
LangSmith: Observability Không Phải Tùy Chọn
Chúng tôi track mỗi agent run trong LangSmith với metadata có cấu trúc: prospect ID, campaign ID, model version, chi phí mỗi run. Sau ba tháng traffic production, dữ liệu này là thứ chúng tôi dùng để tune hệ thống — không phải trực giác.
Ví dụ cụ thể: chúng tôi phát hiện một tool cụ thể (Apollo enrichment) có tỷ lệ timeout 15% vào giờ cao điểm. Không có LangSmith traces, chúng tôi chỉ thấy "agent failed" trong log. Với LangSmith, chúng tôi thấy chính xác node nào, API call nào, và payload chính xác. Sửa xong trong một buổi chiều.
Các Failure Mode Thực Tế Tôi Gặp Trong Production
1. Tool Call Loops
Agent rơi vào vòng lặp: gọi tool A → nhận kết quả mơ hồ → gọi tool A lại → kết quả như cũ → lặp lại. LangGraph không tự dừng điều này. Giải pháp: thêm field tool_call_count vào state và thêm guard max-iterations tại router node.
2. State Blowup Trên Long Threads
Conversation history trong state tăng không giới hạn theo mặc định với add_messages. Với sales agent có 20+ turns, điều này làm bùng nổ context window và tốn tiền. Giải pháp: triển khai bước trim_messages giữ N messages cuối cùng cộng với summary nén của context trước đó.
3. Bedrock Rate Limits Khi Chạy Campaign
Chạy email generation cho 5.000 prospects trong một campaign window đã đụng TPM limit của Bedrock mạnh. Chúng tôi thêm exponential backoff với jitter ở node level và batch workflow với semaphore. Gợi ý: bật Bedrock Provisioned Throughput nếu bạn chạy campaign ở quy mô lớn.
Khi Nào KHÔNG Nên Dùng LangGraph
LangGraph quá phức tạp cho các chain đơn giản, tuyến tính. Nếu workflow của bạn là: gọi LLM → parse output → trả kết quả, hãy dùng LangChain thuần hoặc gọi thẳng Bedrock API. Giá trị của LangGraph chỉ thể hiện khi bạn có phân nhánh, vòng lặp, human-in-the-loop gate, hoặc workflow dài cần state persistence.
Kết Luận
LangGraph trong production là con thú khác hoàn toàn so với LangGraph trong tutorial. Những lợi ích là thật — conditional routing gọn gàng, checkpointing, LangSmith observability — nhưng các cạm bẫy (state bloat, tool loops, rate limits) sẽ tìm đến bạn nhanh chóng nếu bạn không thiết kế cho chúng từ đầu. Xây dựng observability từ ngày một, giữ state tinh gọn, và đặt ra failure boundaries rõ ràng. Mọi thứ còn lại bạn có thể iterate sau.