Khi xây dựng các hệ tác nhân AI phức tạp trong thực tế, các framework cũ (như LangChain Chains) bộc lộ hạn chế lớn: chúng chỉ hỗ trợ các luồng xử lý tuyến tính một chiều đơn giản. Nếu Agent cần thực hiện các vòng lặp phi tuyến, tự kiểm tra mã nguồn, rẽ nhánh điều kiện dựa theo kết quả chạy thử, hoặc tạm ngắt để chờ sự phê duyệt của con người, chúng ta cần một kiến trúc đồ thị trạng thái có hướng.
Giải pháp toàn diện là LangGraph — thư viện chuyên dụng để xây dựng các AI Agent dưới dạng đồ thị có trạng thái (Stateful Multi-Agent Graphs). Bài học này sẽ giúp bạn hiểu sâu sắc nguyên lý hoạt động của cấu trúc Node - Edge, cơ chế quản lý trạng thái chung (State Management), kỹ thuật ngắt tương tác con người (Human-in-the-loop), và tự tay lập trình chu trình Agent tự viết code - tự kiểm thử - tự sửa lỗi tuần hoàn.
18.1 Sự ra đời của Graph-based Agent
Một luồng công việc AI thực tế thường không đi theo đường thẳng. Ví dụ, trong quy trình viết code: Mô hình viết code ➔ Biên dịch thử ➔ Nếu lỗi, quay lại sửa code kèm thông báo lỗi ➔ Nếu thành công, chuyển tiếp đi lưu trữ. Đây là một đồ thị có chứa vòng lặp (Cyclic Graph).
LangGraph mô hình hóa Agent dưới dạng một Đồ thị (Graph):
- Nodes (Nút): Đại diện cho các bước xử lý, là các hàm Python nhận vào trạng thái hiện tại, thực hiện tác vụ (gọi LLM, chạy code) và trả về thông tin cập nhật.
- Edges (Cạnh): Xác định hướng đi tiếp theo giữa các nút. Có hai loại cạnh: Cạnh cố định (chuyển tiếp thẳng từ nút A sang B) và Cạnh điều kiện (Conditional Edges - rẽ nhánh dựa theo kết quả logic của nút vừa chạy).
18.2 Quản lý Trạng thái chung (State Management)
Trái tim của LangGraph là đối tượng State (Trạng thái). State là một cấu trúc dữ liệu chung (Pydantic Model hoặc TypedDict) được truyền đi xuyên suốt qua toàn bộ các nút của đồ thị.
Đặc tính của quản lý trạng thái trong LangGraph:
- Đồng bộ trạng thái: Mỗi nút khi chạy xong sẽ trả về các trường dữ liệu cập nhật. Hệ thống tự động ghi đè hoặc cộng dồn (Reducer) các trường này vào State chung.
-
Reducer Function: Cho phép định nghĩa cách cập nhật dữ liệu đặc thù. Ví dụ, đối với
lịch sử trò chuyện chat, ta dùng reducer
operator.addđể tự động nối đuôi (append) các tin nhắn mới vào danh sách cũ thay vì ghi đè xóa sạch lịch sử.
operator.add (đã kiểm chứng)["Xin chào"]. Một nút vừa chạy xong và trả
về tin nhắn mới ["Tôi cần giúp đỡ"]. Chạy thử bằng Python (đã kiểm chứng):
-
Với Reducer
operator.add: kết quả là["Xin chào", "Tôi cần giúp đỡ"]— lịch sử cũ được giữ nguyên, tin nhắn mới được nối đuôi vào. -
Nếu KHÔNG dùng Reducer (ghi đè mặc định): kết quả chỉ còn
["Tôi cần giúp đỡ"]— toàn bộ lịch sử trò chuyện trước đó bị xóa sạch ngay lập tức.
18.3 Cơ chế Con người phê duyệt (Human-in-the-loop)
Trong môi trường sản xuất của doanh nghiệp, việc để AI tự trị hoàn toàn 100% đưa ra các hành động quan trọng (như gửi email trực tiếp cho khách hàng, thanh toán hóa đơn tài chính, hoặc lưu trực tiếp file code lên server chạy thật) là cực kỳ nguy hiểm.
Cơ chế Human-in-the-loop (Con người phê duyệt) cho phép ta đặt một điểm ngắt (Interrupt) ngay trước khi đồ thị chuyển tới các nút nhạy cảm. Đồ thị sẽ tự động đóng băng trạng thái hiện tại vào đĩa cứng (State Persistence). Hệ thống tạm dừng, gửi thông báo cho người quản trị. Sau khi con người nhấn "Phê duyệt" hoặc chỉnh sửa trực tiếp nội dung trong State, đồ thị mới được kích hoạt chạy tiếp tục từ điểm ngắt.
18.4 Dự án thực hành bài 18: Đại lý tự động viết - chạy thử - sửa lỗi code
Dự án thực hành của bài học này là xây dựng một Graph Engine có trạng thái giả lập bằng Python để triển khai luồng Agent giải toán thông minh:
Start ➔ Node Coder (Viết hàm Python) ➔ Node Tester (Chạy
thử Unit Test) ➔ Rẽ nhánh điều kiện:
- Nếu chạy Test lỗi: Quay lại Node Coder kèm Log lỗi để sửa (tối đa 3 lần).
- Nếu chạy Test thành công: Chuyển sang Node Approver (Human-in-the-loop).
import urllib.request
import json
import sys
import re
# 1. Định nghĩa cấu trúc State (Trạng thái đồ thị)
class AgentState:
def __init__(self, task_description):
self.task = task_description
self.code = ""
self.test_log = ""
self.error_count = 0
self.approved = False
# 2. Định nghĩa các Nodes (Nút xử lý hành động)
def coder_node(state: AgentState):
print("\n[Node Coder]: Đang viết code Python để giải quyết bài toán...")
url = "http://localhost:11434/api/chat"
# Prompt yêu cầu sinh code sạch, không chứa markdown giải thích ngoài khối code
prompt = f"""Bạn là một lập trình viên Python chuyên nghiệp. Hãy viết duy nhất một hàm Python có tên là `solve_problem()` giải quyết yêu cầu sau:
Yêu cầu: {state.task}
Lưu ý: Chỉ trả về duy nhất code Python bên trong cặp thẻ markdown ```python và ```. Không giải thích gì thêm.
"""
# Nếu có lỗi từ lượt chạy trước, đưa log lỗi vào prompt để Coder sửa
if state.test_log:
prompt += f"\nLần chạy trước bị lỗi Unit Test sau đây, hãy sửa lại code:\nLog lỗi: {state.test_log}"
payload = {
"model": "llama3",
"messages": [{"role": "user", "content": prompt}],
"stream": False
}
try:
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as response:
res = json.loads(response.read().decode("utf-8"))
content = res["message"]["content"]
# Trích xuất đoạn code Python nằm giữa ```python ... ```
code_match = re.search(r'```python(.*?)```', content, re.DOTALL)
if code_match:
state.code = code_match.group(1).strip()
else:
state.code = content.strip()
except Exception as e:
# Fallback giả lập sinh code nếu không có Ollama chạy local
state.code = "def solve_problem():\n return 100 / 0 # Cố tình tạo lỗi chia cho 0"
print("-> Code đã sinh ra:\n", state.code)
return state
def tester_node(state: AgentState):
print("\n[Node Tester]: Đang chạy thử nghiệm code vừa sinh...")
# Tạo môi trường chạy code độc lập cục bộ
local_env = {}
try:
# Biên dịch và thực thi chuỗi code Python
exec(state.code, local_env)
# Kiểm tra xem hàm solve_problem có tồn tại không
if "solve_problem" in local_env:
# Chạy thử hàm giải bài toán
result = local_env["solve_problem"]()
state.test_log = "SUCCESS"
print(f"-> Chạy thử thành công! Kết quả hàm trả về: {result}")
else:
state.test_log = "Lỗi: Không tìm thấy định nghĩa hàm solve_problem()."
state.error_count += 1
print("-> Thất bại: Thiếu định nghĩa hàm.")
except Exception as e:
state.test_log = f"Runtime Error: {str(e)}"
state.error_count += 1
print(f"-> Thất bại! Phát hiện lỗi khi chạy thử: {state.test_log}")
return state
def human_approval_node(state: AgentState):
print("\n[Node Human-in-the-loop]: Đang chờ phê duyệt từ người quản trị...")
print(f"Mã nguồn đề xuất:\n{state.code}")
# Dừng luồng chờ input từ bàn phím người dùng
user_input = input("Bạn có phê duyệt lưu file code này không? (y/n): ").strip().lower()
if user_input == 'y':
state.approved = True
print("-> Đã được con người phê duyệt!")
else:
state.approved = False
print("-> Bị con người từ chối.")
return state
# 3. Lập trình Stateful Graph Engine điều khiển luồng rẽ nhánh điều kiện
def run_stateful_agent_graph(task_description):
# Khởi tạo trạng thái ban đầu của Graph
state = AgentState(task_description)
max_retries = 3
# Bắt đầu luồng chạy
while True:
# Bước 1: Gọi Node Coder
state = coder_node(state)
# Bước 2: Gọi Node Tester
state = tester_node(state)
# Cạnh điều kiện (Conditional Edge): Kiểm tra kết quả chạy thử
if state.test_log == "SUCCESS":
print("\n>> Điều kiện đạt! Chuyển tiếp tới bước Phê duyệt.")
break
else:
if state.error_count >= max_retries:
print(f"\n>> Đạt giới hạn sửa lỗi tối đa ({max_retries} lần). Kết thúc đồ thị thất bại.")
return
print(f"\n>> Phát hiện lỗi. Tự động quay lại Node Coder để sửa lại (Lần sửa thứ {state.error_count}).")
# Bước 3: Điểm ngắt Human-in-the-loop
state = human_approval_node(state)
# Bước 4: Kiểm tra kết quả phê duyệt cuối cùng
if state.approved:
# Xuất file code ra ổ cứng
filename = "solved_output.py"
with open(filename, "w", encoding="utf-8") as f:
f.write(state.code)
print(f"\n🎉 Thành công: Đã ghi nhận mã nguồn vào tệp '{filename}'!")
else:
print("\n❌ Thất bại: Mã nguồn bị hủy bỏ do con người từ chối phê duyệt.")
if __name__ == "__main__":
print("=== Trình mô phỏng Đồ thị Agent có trạng thái ===")
# Bài toán lập trình yêu cầu Agent thực hiện
problem = "Hãy viết một hàm giải phép chia lấy phần dư của 17 cho 5."
# Kích hoạt đồ thị chạy
run_stateful_agent_graph(problem)
import re khiến Agent luôn thất bại âm thầm
coder_node gọi
re.search(...) để trích xuất code Python từ phản hồi Ollama, nhưng phần import ở đầu file
chỉ có urllib.request, json, sys — thiếu hẳn
import re. Hậu quả (đã kiểm chứng bằng Python): khi Ollama không chạy, lỗi kết nối xảy ra trước khi chạm tới dòng
re.search, nên bug này bị che khuất hoàn toàn — mọi thứ trông như vẫn
hoạt động "bình thường" nhờ cơ chế fallback. Nhưng khi Ollama
chạy thành công và trả về code hợp lệ, dòng re.search sẽ ném ra
NameError: name 're' is not defined, bị khối except Exception ở ngay bên dưới
nuốt lặng lẽ, rồi âm thầm thay code thật của LLM bằng hàm giả cố tình lỗi (100 / 0). Kết
quả: Agent sẽ luôn thất bại sau đúng 3 lần thử, bất kể LLM viết code đúng hay sai — vì code thật không
bao giờ được dùng tới. Đã thêm import re vào phần đầu file để khắc phục triệt để. Đây là
minh chứng rõ ràng cho nguyên tắc: khối except Exception quá rộng có thể che giấu cả những
lỗi lập trình cơ bản nhất, khiến hệ thống "trông như chạy được" trong khi thực chất đã hỏng từ gốc.
Tóm tắt bài học & Cầu nối kiến thức
- Đạt được: Thiết kế luồng xử lý Agent dưới dạng đồ thị có trạng thái (State Graph) bằng LangGraph.
- Đạt được: Quản lý State tập trung và tích hợp cơ chế con người kiểm duyệt (Human-in-the-loop) để tăng độ an toàn.
Cầu nối bài tiếp theo: Đối với những bài toán chuyên biệt cần giọng điệu hoặc tri thức đặc thù mà RAG và Prompting không giải quyết triệt để, chúng ta cần tinh chỉnh trực tiếp vào trọng số mô hình bằng kỹ thuật Fine-tuning & LoRA ở Bài số 19.
Tải file code thực hành minh họa bài học
File Python langgraph_agent.py — mã nguồn xây dựng Graph Engine tự động hóa chu trình Coder
viết code, Tester chạy kiểm định và Approver duyệt lưu file (chạy
python langgraph_agent.py):
📖 Tài liệu tham khảo
- LangGraph GitHub — Thư viện mã nguồn mở chuyên dụng xây dựng các AI Agent phức tạp dưới dạng đồ thị có hướng (LangChain AI Team)
- LangGraph Concept: Human-in-the-loop — Tài liệu chính thống thuyết minh về cơ chế ngắt trạng thái và phê duyệt của con người
- Flow Engineering: Directed Acyclic Graphs as Prompt Systems — Nghiên cứu khoa học chứng minh việc thiết kế hệ thống prompt dạng đồ thị có hướng (Flow Engineering) mang lại hiệu quả vượt trội hơn so với Prompting truyền thống
Bình luận