Bài này ghép mọi thứ đã học thành một agent hoàn chỉnh: nhận yêu cầu, gọi tool tra cứu, tự đánh giá và lặp lại (cycle), rồi chờ người duyệt trước khi xuất bản (HITL) — tất cả có checkpoint để bền vững. Đây là kiến trúc bạn có thể tái dùng cho nhiều bài toán thật.
Đặc tả agent
Bài toán: viết bản tóm tắt một chủ đề, có kiểm chứng bằng công cụ tìm kiếm, tự sửa cho đến khi đủ chất lượng, rồi chờ người duyệt trước khi kết thúc.
Các node:
plan: phân tích yêu cầu, quyết định cần tra cứu gì.search: gọi tool tìm kiếm, đưa kết quả vào state.write: soạn bản tóm tắt từ ngữ cảnh đã có.critique: tự chấm điểm bản tóm tắt.human_review: chờ người duyệt trước khi kết thúc.
plan → search → write → critique; nếu điểm thấp thì quay lại search; nếu đạt thì tới human_review → END.Thiết kế State
from typing import Annotated, TypedDict
from operator import add
class State(TypedDict):
topic: str
findings: Annotated[list[str], add] # cộng dồn qua các vòng
draft: str
score: int
attempts: int
approved: bool
findings dùng reducer add để tích lũy dữ liệu qua nhiều vòng tra cứu; attempts chặn vòng lặp; approved lưu quyết định của người dùng.
Các node then chốt
def search(state) -> dict:
try:
hits = tool_search(state["topic"]) # gọi tool thật
except Exception:
hits = [] # lỗi có kiểm soát
return {"findings": hits}
def critique(state) -> dict:
score = score_draft(state["draft"]) # LLM chấm 0-10
return {"score": score, "attempts": state["attempts"] + 1}
def route_after_critique(state) -> str:
if state["score"] >= 8 or state["attempts"] >= 3:
return "review"
return "revise"
Lưu ý search bọc try/except để một lần API lỗi không giết cả graph, và critique luôn tăng attempts để cycle có ngưỡng dừng.
Lắp graph
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
b = StateGraph(State)
for name, fn in [("plan", plan), ("search", search),
("write", write), ("critique", critique),
("human_review", human_review)]:
b.add_node(name, fn)
b.add_edge(START, "plan")
b.add_edge("plan", "search")
b.add_edge("search", "write")
b.add_edge("write", "critique")
b.add_conditional_edges("critique", route_after_critique,
{"review": "human_review", "revise": "search"})
b.add_edge("human_review", END)
app = b.compile(checkpointer=MemorySaver(),
interrupt_before=["human_review"])
Cách ghép: cycle nằm ở critique → search, HITL ở interrupt_before=["human_review"], và checkpoint giữ toàn bộ tiến trình để có thể dừng/khôi phục.
Các bước chạy dự án
- Khởi tạo
configvớithread_idriêng cho từng yêu cầu. app.invoke({"topic": ..., "attempts": 0}, config)— chạy đến khi dừng ởhuman_review.- Xem
app.get_state(config)để đọc bản nháp và điểm số hiện tại. - Nếu cần sửa tay:
app.update_state(config, {"draft": ...}). - Resume:
app.invoke(None, config)để hoàn tất và kết thúc.
Checklist nghiệm thu agent
- [ ] Cycle có ngưỡng dừng kép (điểm hoặc số lần thử).
- [ ]
findingstích lũy đúng, không bị ghi đè giữa các vòng. - [ ] Dừng đúng trước
human_review, không xuất bản khi chưa duyệt. - [ ] Đổi
thread_idcho các yêu cầu khác nhau, không lẫn dữ liệu. - [ ] Tool có xử lý lỗi (timeout, rỗng) để node không chết.
Sai lầm thường gặp
- Gọi tool trong node mà không bắt lỗi: một lần API lỗi làm cả graph chết. Bọc try/except, trả findings rỗng có kiểm soát.
- Quên khởi tạo
attempts:state["attempts"] + 1sẽ lỗi KeyError. Đặt giá trị đầu khi invoke. - Để cycle không giới hạn khi tool luôn trả kết quả kém, đốt token vô ích. Ngưỡng
attemptslà bắt buộc. - Đặt
interrupt_beforesai node khiến agent xuất bản trước khi người kịp duyệt. - Trộn logic routing vào node
critique: giữ quyết định trong hàm route riêng để dễ đọc và test.