ReAct Framework
ReAct — Suy luận kết hợp Hành động
AI bịa đặt (hallucinate) khi trả lời câu hỏi cần dữ liệu thực tế. Cách tốt nhất để giảm ảo giác?
Hình minh họa
Vòng lặp ReAct: Thought → Action → Observation
Chọn một kịch bản, bấm "Bước tiếp" để xem agent lập luận và gọi công cụ.
Đây là câu hỏi về dữ liệu thực tế, có thể thay đổi theo thời gian. Tôi không nên dựa vào ký ức, cần tra cứu nguồn chính thức.
web_search('dân số Hà Nội 2024 Tổng cục Thống kê')
Tổng cục Thống kê (TCTK) 01/2024: dân số Hà Nội khoảng 8,5 triệu người; tăng 1,2% so với 2023.
Đã có số liệu và nguồn. Trả lời kèm năm và nguồn để người dùng kiểm chứng được.
Dân số Hà Nội năm 2024 khoảng 8,5 triệu người (nguồn: Tổng cục Thống kê, 01/2024).
CoT đoán theo ký ức cũ và ngoại suy — sai. ReAct tra cứu nguồn chính thức — đúng.
AI suy luận bằng CoT: 'Paris là thủ đô nước Đức' rồi trả lời sai. Nếu dùng ReAct, bước nào sẽ phát hiện lỗi?
- Hỏi đáp cần dữ liệu thời gian thực: giá cổ phiếu, thời tiết, tỉ giá.
- Bài toán nhiều bước có thể giao phần tính toán cho công cụ ngoài.
- Trợ lý cần tra cứu tài liệu nội bộ qua RAG trước khi trả lời.
- Không đặt max_steps quá lớn — chi phí LLM tăng tuyến tính theo số vòng.
- Luôn có fallback: nếu mọi tool fail, agent vẫn phải trả lời chứ không treo.
- Cẩn thận prompt injection từ Observation: nội dung tool trả về có thể chứa chỉ thị độc hại.
- Gộp Observation dài thành bản tóm tắt trước khi đưa lại vào context — tiết kiệm token.
- Ghi log từng bước Thought/Action/Observation để debug khi agent ra kết quả lạ.
- Đặt whitelist công cụ rõ ràng — đừng để LLM tự bịa tên tool.
- Câu hỏi chỉ cần kiến thức tĩnh trong LLM — ReAct làm chậm vô ích.
- Tác vụ đã có pipeline cứng (classify đơn giản) — dùng prompt một lượt sẽ rẻ hơn.
- Môi trường không có công cụ đáng tin — Observation rác sẽ làm agent lạc lối.
Giải thích
ReAct (Yao et al., 2023) đề xuất một giao thức prompt trong đó LLM luân phiên sinh ra các dòng có nhãn Thought:, Action: và Observation:. Tầng orchestrator bóc tách từng dòng, thực thi các Action, rồi chèn Observation trở lại context cho đến khi LLM phát ra Final Answer:.
- Thought: AI phân tích tình huống, lập kế hoạch bước tiếp. Reasoning trace giải thích quyết định — cực kỳ hữu ích khi debug.
- Action: AI chọn công cụ và tham số (search, lookup, calculate, code_exec). Đây là điểm acting — nơi agent tương tác với thế giới.
- Observation: Kết quả thật trả về. Có thể là JSON, text, thậm chí lỗi. Agent dùng nó làm input cho Thought tiếp theo.
Một cách hình thức, trạng thái ở vòng là gồm câu hỏi , lịch sử và quan sát mới nhất . LLM học chính sách phát ra Thought+Action. Runtime thực thi , lấy , rồi tiếp tục cho tới khi action đặc biệt .
# LangChain ReAct agent — phiên bản gần với sản xuất
from langchain.agents import create_react_agent, AgentExecutor
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_experimental.tools import PythonREPLTool
from langchain_openai import ChatOpenAI
from langchain import hub
# 1. Công cụ mà agent được phép gọi
search = DuckDuckGoSearchRun()
python_repl = PythonREPLTool()
tools = [search, python_repl]
# 2. Prompt ReAct chuẩn (chứa format Thought/Action/Observation)
prompt = hub.pull("hwchase17/react")
# 3. Model với nhiệt độ thấp để ổn định lập luận
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 4. Tạo agent và executor
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # in Thought/Action/Observation ra console
max_iterations=5, # chặn loop vô hạn
handle_parsing_errors=True,
return_intermediate_steps=True,
)
# 5. Chạy thử
question = "Tổng GDP Nhật Bản và Hàn Quốc năm 2023 là bao nhiêu tỷ USD?"
result = executor.invoke({"input": question})
print("Câu trả lời:", result["output"])
for step in result["intermediate_steps"]:
action, observation = step
print("[Action]", action.tool, action.tool_input)
print("[Observation]", observation[:200], "...")So sánh các pattern lập luận
| Pattern | Reasoning | Acting | Grounding | Khi nào dùng |
|---|---|---|---|---|
| Direct prompt | Không có | Không có | Không | Câu hỏi đơn giản, kiến thức tĩnh trong LLM. Rẻ nhất, nhưng dễ bịa. |
| Chain-of-Thought | Có, nhiều bước | Không có | Không | Toán, logic thuần. Hiệu quả khi dữ liệu đã có sẵn trong prompt/ngữ cảnh. |
| ReAct | Có | Có — gọi tool mỗi vòng | Mạnh | QA thực tế, đa bước, cần tra cứu/tính toán. Pattern chủ đạo cho agent hiện đại. |
| Plan-and-Solve | Lập kế hoạch trước | Thực thi theo plan | Một phần | Bài toán dài, nhiều nhánh. Planner tách ý, executor (có thể là ReAct) chạy. |
| Reflexion | Có + tự phê bình | Có | Mạnh | Tác vụ lặp (coding, RL). Agent học từ lỗi lần trước, tích luỹ memory. |
Một ReAct agent liên tục lặp Thought → Action → Observation mà không bao giờ đưa ra Final Answer. Nguyên nhân phổ biến nhất là gì?
Giải thích
Muốn hiểu sâu, hãy tự viết một vòng lặp ReAct tối giản không dùng framework. Dưới đây là sườn Python rất sát bản gốc của bài báo, phù hợp để thử nghiệm và debug.
import re
from typing import Callable
# Registry công cụ: tên -> hàm Python thực thi
TOOLS: dict[str, Callable[[str], str]] = {
"search": lambda q: fake_search(q),
"calculator": lambda expr: str(eval(expr, {"__builtins__": {}}, {})),
}
ACTION_RE = re.compile(r"Action:\s*(\w+)\((.*)\)")
def react_loop(question: str, llm: Callable[[str], str], max_steps: int = 6) -> str:
context = (
"Bạn là một agent ReAct. Luân phiên viết các dòng bắt đầu bằng "
"'Thought:', 'Action:' hoặc 'Final Answer:'. Sau mỗi Action, "
"hệ thống sẽ chèn dòng 'Observation:'.\n"
f"Question: {question}\n"
)
for step in range(max_steps):
# 1. Sinh Thought + có thể kèm Action hoặc Final Answer
chunk = llm(context)
context += chunk
# 2. Đã trả lời?
if "Final Answer:" in chunk:
return chunk.split("Final Answer:", 1)[1].strip()
# 3. Có Action? Nếu có, thực thi và thêm Observation
m = ACTION_RE.search(chunk)
if not m:
# Agent không gọi tool và cũng không kết luận => nhắc nhở
context += "\nObservation: (không hành động hợp lệ, hãy thử lại)\n"
continue
tool_name, raw_args = m.group(1), m.group(2).strip().strip("'\"")
if tool_name not in TOOLS:
obs = f"Lỗi: không có công cụ '{tool_name}'."
else:
try:
obs = TOOLS[tool_name](raw_args)
except Exception as e:
obs = f"Lỗi khi chạy {tool_name}: {e}"
context += f"\nObservation: {obs}\n"
return "Đã hết số bước cho phép, không tìm được câu trả lời."Dạng low-level này giúp bạn thấy rõ: LLM chỉ sinh text, mọi thứ khác (parse, thực thi, giới hạn) là trách nhiệm của code phía ngoài. Các framework như LangChain, LlamaIndex, AutoGen đều đóng gói đúng ý tưởng trên.
- ReAct = Reasoning + Acting: vòng lặp Thought → Action → Observation cho tới khi có đủ thông tin.
- Observation từ bên ngoài (API, search, code_exec) 'grounding' suy luận — giảm hallucination một cách cấu trúc.
- Khác CoT: CoT chỉ suy nghĩ nội bộ, ReAct còn kiểm chứng mỗi bước bằng dữ liệu thật từ thế giới bên ngoài.
- Luôn đặt max_iterations, whitelist tool, và fallback để tránh vòng lặp vô hạn hay gọi tool không hợp lệ.
- Trong sản xuất, ưu tiên phiên bản function-calling: LLM trả JSON theo schema, ít lỗi parse hơn so với regex.
- Là nền tảng của hầu hết AI agent framework hiện đại: LangChain, LlamaIndex, AutoGen, OpenAI Assistants đều dựa trên ReAct pattern.
Kiểm tra hiểu biết
ReAct khác Chain-of-Thought (CoT) thuần túy ở điểm nào?