Planning và reflection: ReAct so với Plan-and-Execute
Từ một vòng lặp đến nhiều chiến lược
Bài trước mình dừng ở vòng lặp ReAct: Thought → Action → Observation, lặp đến khi đủ thông tin thì trả lời. Nhưng ReAct chỉ là một cách để agent lên kế hoạch. Cùng một task, có agent chia nhỏ kế hoạch ngay trong lúc chạy, có agent vẽ sẵn cả bản đồ rồi mới bước. Chọn sai kiểu planning là lý do số một khiến agent chạy 40 bước mà vẫn không xong việc.
Sau bài này bạn sẽ:
- Phân biệt planning kiểu interleaved (ReAct) và planning kiểu upfront (Plan-and-Execute).
- Nhận ra dấu hiệu ReAct đang đốt token vô ích vì thiếu kế hoạch tổng thể.
- Mô tả được reflection và biết khi nào nó giúp, khi nào nó phá.
- Chọn được chiến lược planning theo độ phức tạp của task.
ReAct: kế hoạch nằm rải trong từng bước
ReAct không có biến plan nào cả. Mỗi Thought là một mẩu kế hoạch cực ngắn, sinh ra ngay trước khi hành động, dựa trên Observation vừa nhận. Người ta gọi đây là planning interleaved: nghĩ và làm xen kẽ.
Ví dụ có sẵn: task "Giá vé máy bay Hà Nội đi Đà Nẵng cuối tuần này rẻ nhất là bao nhiêu?"
- Thought: mình chưa biết hãng nào rẻ, cần tra.
- Action:
search_flights("Hà Nội", "Đà Nẵng", "2025-06-14") - Observation: 3 chuyến, rẻ nhất 1.290.000đ của hãng A.
- Thought: giá này sát ngày nên có thể còn rẻ hơn nếu bay sáng sớm, tra thêm một nguồn.
- Action:
search_flights(..., time="06:00") - Observation: 990.000đ. Đủ rồi, trả lời.
Điểm mạnh: agent không cần biết trước task dài bao nhiêu bước. Dữ liệu mới xuất hiện thì nó đổi hướng ngay. Điểm yếu: nó chỉ thấy một bước phía trước, nên dễ goal drift, tức là trôi dần khỏi mục tiêu ban đầu vì mỗi bước chỉ tối ưu cho chính nó. Số bước không đoán được, chi phí cũng vậy.
Plan-and-Execute: vẽ bản đồ trước rồi mới đi
Ở kiến trúc này có hai vai tách rời. Planner nhận task, sinh ra một danh sách bước có thứ tự. Executor chạy từng bước, mỗi bước có thể là một mini-agent ReAct. Sau mỗi bước, một replanner xem kết quả có lệch kỳ vọng không và sửa plan nếu cần.
plan = planner_llm(task) # ["Đọc nguồn 1", "Đọc nguồn 2", "Đối chiếu số liệu", "Viết outline", "Viết báo cáo"]
ket_qua = []
for step in plan:
out = executor_agent(step, context=ket_qua)
ket_qua.append(out)
if lech_ky_vong(out):
plan = replanner_llm(task, plan, ket_qua)
Ví dụ có sẵn: task "Soạn báo cáo 5 trang về thị trường xe điện Việt Nam từ 3 nguồn tôi đưa". Thứ tự công việc rõ rành rành: đọc từng nguồn, đối chiếu số liệu, viết outline, viết bài. Đây là đất của Plan-and-Execute. Bạn thấy trước toàn cảnh, có thể chạy bước "đọc nguồn" song song, và báo được tiến độ 3/5 bước cho người dùng.
Cái giá phải trả: plan sai từ đầu thì cả chuỗi sai theo. Và nếu dữ liệu giữa đường lật kèo (nguồn thứ hai nói ngược hẳn nguồn thứ nhất), plan cũ lỗi thời ngay. Vì thế replanner không phải đồ trang trí.
Reflection: bắt agent soi lại kết quả của chính nó
Reflection là bước agent tự chấm output của mình theo một bộ tiêu chí, rồi sửa, thay vì trả luôn cho người dùng. Về bản chất nó là một lần gọi LLM nữa, nhưng prompt đổi vai: từ "làm bài" sang "chấm bài".
Ví dụ có sẵn: executor viết email xin lỗi khách hàng. Reflector nhận rubric 4 điểm: xin lỗi rõ ràng, nhận trách nhiệm, có đề xuất bù đắp, không đổ lỗi cho bên khác. Nó chấm bản nháp 3/4, thiếu "đề xuất bù đắp", và sinh ra ghi chú sửa. Vòng hai viết lại, thêm câu đề xuất voucher. Đây chính là tinh thần của Reflexion: biến lỗi thành bài học ngay trong phiên chạy.
Warning: Reflection không có rubric rõ ràng thường phản tác dụng. Agent sẽ tự khen bản nháp của mình, hoặc sửa vòng này qua vòng khác mà không hội tụ. Luôn kèm tiêu chí chấm cụ thể và một giới hạn số vòng (thường 1, tối đa 2).
Reflection chỉ đáng giá khi bạn viết được tiêu chí chấm thành lời. Nếu không viết nổi rubric, đừng thêm reflection.
Chọn chiến lược theo độ phức tạp của task
flowchart TD
Start["🎯 Task mới"] --> Q1{"Thứ tự các bước biết trước?"}
Q1 -->|"Không, phải dò từng bước"| ReAct["🔄 ReAct"]
Q1 -->|"Có, chia bước rõ ràng"| PAE["🗺️ Plan-and-Execute"]
PAE --> Inner["ReAct con trong từng step"]
ReAct --> Ref{"Output có tiêu chí chất lượng rõ?"}
PAE --> Ref
Ref -->|"Có"| Refl["🪞 Thêm một vòng reflection"]
Ref -->|"Không"| Done["Trả kết quả"]
Refl --> Done
| Dấu hiệu của task | Chiến lược nên chọn |
|---|---|
| 1 đến 3 bước, dữ liệu chỉ biết khi chạy | ReAct thuần |
| Nhiều bước, thứ tự biết trước, cần báo tiến độ | Plan-and-Execute |
| Output có rubric rõ (email, báo cáo, code review) | thêm 1 vòng reflection |
| Task dài, nhiều ràng buộc, chạy nhiều giờ | Plan-and-Execute bọc ngoài, ReAct trong từng step, reflection ở cuối |
Một lưu ý về từ vựng: "planning" trong ReAct là planning vi mô, còn trong Plan-and-Execute là planning vĩ mô. Hai thứ không loại trừ nhau, kiến trúc tốt thường dùng cả hai ở hai tầng khác nhau.
Trong ReAct, planning diễn ra _____, còn Plan-and-Execute tạo _____. Plan-and-Execute thường có thêm _____ để sửa kế hoạch khi một bước lệch kỳ vọng. Reflection chỉ hiệu quả khi có _____ và giới hạn số vòng.
Mẹo thực chiến khi chọn planning
- Đếm bước trước khi chọn kiến trúc. Nếu bạn mô tả được task trong 3 bước bằng lời, đó là tín hiệu Plan-and-Execute. Nếu bạn phải nói "còn tùy kết quả tra được gì", đó là ReAct.
- Đừng over-engineer. Task hai bước mà dựng cả planner, replanner, reflector là tự tạo thêm ba điểm hỏng. Bắt đầu bằng ReAct, chỉ nâng cấp khi thấy goal drift thật.
- Giới hạn cứng số vòng. Cả ReAct lẫn reflection đều cần
max_stepsvàmax_reflection_rounds. Không có giới hạn thì chi phí không có trần. - Log plan ra ngoài. Với Plan-and-Execute, in danh sách bước ra log. Đây là công cụ debug rẻ nhất: nhìn plan biết ngay agent hiểu sai task ở đâu.
- Reflection đặt sai chỗ thì vô dụng. Reflect sau khi đã trả lời là quá muộn; reflect ở mỗi bước thì quá đắt. Đặt ở cuối mỗi bước lớn là hợp lý nhất.
Front: Trong ReAct, planning diễn ra khi nào? Back: Ngay trong từng bước: mỗi Thought là một mẩu kế hoạch nhỏ, không có kế hoạch tổng thể trước.
Front: Plan-and-Execute gồm mấy pha chính? Back: Hai pha: planner sinh danh sách bước, executor chạy từng bước.
Front: Nhược điểm lớn nhất của Plan-and-Execute? Back: Plan sai từ đầu thì cả chuỗi sai theo, và plan nhanh lỗi thời khi dữ liệu đổi giữa đường.
Front: Nhược điểm lớn nhất của ReAct? Back: Dễ goal drift và không biết trước số bước nên chi phí khó đoán.
Front: Replanner là gì? Back: Thành phần xem lại plan sau mỗi bước và sửa khi kết quả lệch kỳ vọng.
Front: Reflection là gì? Back: Agent tự chấm output của mình theo tiêu chí rồi sửa, thay vì trả luôn cho người dùng.
Front: Khi nào reflection phản tác dụng? Back: Khi thiếu rubric rõ ràng: agent tự khen bản nháp hoặc sửa mãi không hội tụ.
Front: Task 1 đến 3 bước, dữ liệu chỉ biết khi chạy thì chọn gì? Back: ReAct thuần.
Front: Task nhiều bước, thứ tự biết trước, cần báo tiến độ? Back: Plan-and-Execute.
Front: Cách kết hợp phổ biến cho task dài nhiều ràng buộc? Back: Plan-and-Execute bọc ngoài, ReAct trong từng step, reflection ở cuối.
Front: Goal drift là gì? Back: Agent trôi khỏi mục tiêu ban đầu vì mỗi bước chỉ tối ưu cho chính bước đó.
Front: Dấu hiệu bạn đang over-engineer planning? Back: Task hai bước mà đã dựng planner, replanner và reflector.
Front: Đặt reflection ở đâu là hợp lý? Back: Ở cuối mỗi bước lớn, không phải sau mỗi action cũng không phải sau khi đã trả lời.
Ba điều mang sang bài checkpoint
- ReAct là planning vi mô, xen kẽ nghĩ và làm; Plan-and-Execute là planning vĩ mô, chia bước trước rồi mới chạy.
- Reflection chỉ đáng thêm vào khi bạn viết được rubric và đặt được giới hạn số vòng.
- Quy tắc chọn nhanh: thứ tự bước biết trước thì plan trước, không biết thì để ReAct dò.
Bài tiếp theo là checkpoint của module: mình sẽ dựng vài tình huống agent chạy sai, và bạn sẽ thử chỉ ra lỗi nằm ở planning, ở tool, hay ở bước observation.