5 Pattern của AI Agent - giải thích chi tiết kèm ví dụ Java
1. Tổng quan
Khi bạn ghép một LLM với vài dòng code để nó “tự chạy”, bạn đã có một AI Agent sơ khai — nhưng agent kiểu đó thường hỏi gì trả lời gì, không biết tự sửa sai, không biết gọi công cụ, không biết chia việc. Bài này giải thích 5 pattern thiết kế giúp agent “thông minh” hơn: Reflection, Tool Use, Planning, Multi-Agent và ReAct. Mỗi pattern có ví dụ code Java (Spring AI) ngắn, chạy được, kèm giải thích khi nào nên và không nên dùng. Đọc xong, bạn có thể chọn đúng pattern cho bài toán của mình, thay vì ghép tất cả vào một prompt khổng lồ.
Trước khi vào chi tiết, cần dependency Spring AI cho các ví dụ dưới:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-mini
Lưu ý: Code trong bài minh họa cấu trúc pattern, không phải sản phẩm production — bạn cần thêm retry, timeout, logging khi deploy thật.
2. Pattern Reflection: tự phê bình và sửa câu trả lời
Reflection là pattern đơn giản nhất: agent tạo ra câu trả lời, rồi dùng chính LLM (hoặc một LLM khác) để chấm điểm/phê bình câu trả lời đó, sau đó sửa lại. Vòng lặp này giống việc bạn viết draft rồi tự đọc lại trước khi gửi.
2.1. Luồng hoạt động
- Generate: agent sinh câu trả lời đầu tiên.
- Critique: agent (hoặc agent khác) đóng vai “reviewer”, chỉ ra lỗi.
- Revise: agent sửa lại dựa trên critique, có thể lặp lại 2–3 bước đến khi đạt chất lượng.
@Service
public class ReflectionAgent {
private final ChatClient chatClient;
public ReflectionAgent(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String answerWithReflection(String question, int maxRounds) {
String draft = chatClient.prompt()
.user("Trả lời ngắn gọn câu hỏi: " + question)
.call()
.content();
for (int round = 0; round < maxRounds; round++) {
String critique = chatClient.prompt()
.user("""
Bạn là reviewer khó tính. Chỉ ra lỗi sai, thiếu sót
trong câu trả lời sau. Nếu đã tốt, trả về "OK".
Câu hỏi: %s
Câu trả lời: %s
""".formatted(question, draft))
.call()
.content();
if (critique.strip().equalsIgnoreCase("OK")) {
break;
}
draft = chatClient.prompt()
.user("Sửa lại câu trả lời dựa trên góp ý: " + critique
+ "\nCâu trả lời cũ: " + draft)
.call()
.content();
}
return draft;
}
}
answerWithReflection sinh draft đầu, sau đó lặp tối đa maxRounds lần: gọi LLM đóng vai reviewer để chấm, nếu reviewer trả “OK” thì dừng, không thì sinh bản sửa. Kết quả là câu trả lời có xu hướng chính xác hơn so với gọi LLM một lần.
Lưu ý: Reflection tốn gấp 2–3 lần số lời gọi LLM so với bình thường. Chỉ nên dùng cho tác vụ cần độ chính xác cao (code review, tính toán, viết hợp đồng), không nên dùng cho chat thông thường vì chậm và tốn chi phí.
3. Pattern Tool Use: để agent gọi công cụ bên ngoài
LLM không biết giờ hiện tại, không truy được database, không tính toán chính xác số lớn. Tool Use pattern cho agent “gọi hàm” khi cần — LLM quyết định gọi tool nào, code thực thi tool đó rồi trả kết quả lại cho LLM tiếp tục suy luận.
@Component
public class OrderTools {
private final OrderRepository orderRepository;
public OrderTools(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Tool(description = "Tra cứu trạng thái đơn hàng theo mã order")
public String getOrderStatus(String orderId) {
return orderRepository.findById(orderId)
.map(Order::getStatus)
.orElse("Không tìm thấy đơn hàng " + orderId);
}
@Tool(description = "Tính tổng tiền đơn hàng gồm thuế 10%")
public double calculateTotal(double subtotal) {
return subtotal * 1.1;
}
}
@Service
public class CustomerSupportAgent {
private final ChatClient chatClient;
public CustomerSupportAgent(ChatClient.Builder builder, OrderTools orderTools) {
this.chatClient = builder
.defaultTools(orderTools)
.build();
}
public String handle(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.call()
.content();
}
}
@Tool đánh dấu method như một function mà LLM có thể gọi, description giúp model hiểu khi nào nên dùng. Khi defaultTools(orderTools) được đăng ký, Spring AI tự động: gửi schema của các tool cho LLM, nhận lại yêu cầu gọi tool (dạng JSON), thực thi method Java tương ứng, rồi gửi kết quả về cho LLM hoàn thiện câu trả lời.
Ví dụ user hỏi “Đơn ORD-001 đang ở đâu rồi?”, agent tự quyết định gọi getOrderStatus("ORD-001") mà bạn không cần viết logic parse intent thủ công.
User: Đơn ORD-001 đang ở đâu rồi?
Agent: Đơn hàng ORD-001 hiện đang ở trạng thái "Đang giao hàng".
Test đơn giản với JUnit 5 + Mockito để kiểm tra tool hoạt động đúng, không cần gọi LLM thật:
class OrderToolsTest {
private final OrderRepository repository = mock(OrderRepository.class);
private final OrderTools tools = new OrderTools(repository);
@Test
void shouldReturnStatusWhenOrderExists() {
when(repository.findById("ORD-001"))
.thenReturn(Optional.of(new Order("ORD-001", "Đang giao hàng")));
String result = tools.getOrderStatus("ORD-001");
assertThat(result).isEqualTo("Đang giao hàng");
}
}
Lưu ý: Luôn validate input trước khi tool chạm vào database hay gọi API thật — LLM có thể truyền tham số sai dạng (ví dụ orderId rỗng) nếu prompt không rõ ràng.
4. Pattern Planning: chia việc lớn thành các bước nhỏ
Khi yêu cầu phức tạp (“viết báo cáo so sánh 3 nhà cung cấp, kèm bảng giá và khuyến nghị”), bắt LLM làm một lần dễ bỏ sót bước. Planning pattern tách thành hai vai: Planner lập danh sách bước, Executor thực thi từng bước tuần tự.
public record PlanStep(int order, String description) {}
@Service
public class PlannerAgent {
private final ChatClient chatClient;
public PlannerAgent(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public List<PlanStep> createPlan(String goal) {
String json = chatClient.prompt()
.user("""
Chia nhỏ mục tiêu sau thành tối đa 5 bước thực thi,
trả về JSON array dạng [{"order":1,"description":"..."}]:
%s
""".formatted(goal))
.call()
.content();
return parseSteps(json);
}
}
@Service
public class ExecutorAgent {
private final ChatClient chatClient;
public ExecutorAgent(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String runPlan(String goal, List<PlanStep> steps) {
StringBuilder context = new StringBuilder();
for (PlanStep step : steps) {
String result = chatClient.prompt()
.user("Mục tiêu: " + goal
+ "\nBối cảnh đã làm: " + context
+ "\nThực hiện bước: " + step.description())
.call()
.content();
context.append("Bước ").append(step.order())
.append(": ").append(result).append("\n");
}
return context.toString();
}
}
PlannerAgent chỉ chịu trách nhiệm sinh danh sách bước dưới dạng JSON, ExecutorAgent chạy từng bước và tích lũy context để bước sau biết bước trước đã làm gì. Tách planner/executor giúp bạn dễ debug: nếu kết quả sai, kiểm tra plan trước (có hợp lý không) rồi mới xét đến từng bước thực thi.
Plan cho "viết báo cáo so sánh 3 nhà cung cấp":
1. Thu thập thông tin giá và tính năng từng nhà cung cấp
2. Lập bảng so sánh
3. Phân tích ưu nhược điểm
4. Đưa ra khuyến nghị
5. Viết phần tóm tắt executive summary
Lưu ý: Planning pattern phát huy tác dụng khi task có nhiều bước phụ thuộc nhau. Với câu hỏi một bước (ví dụ “tóm tắt đoạn văn này”), thêm planner chỉ làm chậm và tốn token không cần thiết.
5. Pattern Multi-Agent: nhiều agent chuyên biệt hợp tác
Thay vì một agent “biết tuốt”, Multi-Agent pattern chia việc cho các agent chuyên môn hóa: agent nghiên cứu, agent viết, agent kiểm duyệt… rồi một Orchestrator điều phối luồng giữa chúng. Giống mô hình một team nhỏ, mỗi người giỏi một việc.
@Service
public class ResearchAgent {
private final ChatClient chatClient;
public ResearchAgent(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String research(String topic) {
return chatClient.prompt()
.user("Liệt kê 5 thông tin quan trọng nhất về: " + topic)
.call()
.content();
}
}
@Service
public class WriterAgent {
private final ChatClient chatClient;
public WriterAgent(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String write(String topic, String researchNotes) {
return chatClient.prompt()
.user("Viết bài 200 từ về '" + topic
+ "' dựa trên dữ liệu: " + researchNotes)
.call()
.content();
}
}
@Service
public class OrchestratorAgent {
private final ResearchAgent researchAgent;
private final WriterAgent writerAgent;
public OrchestratorAgent(ResearchAgent researchAgent, WriterAgent writerAgent) {
this.researchAgent = researchAgent;
this.writerAgent = writerAgent;
}
public String produceArticle(String topic) {
String notes = researchAgent.research(topic);
return writerAgent.write(topic, notes);
}
}
OrchestratorAgent không tự gọi LLM mà chỉ điều phối: gọi ResearchAgent lấy dữ liệu thô, đưa cho WriterAgent viết thành bài hoàn chỉnh. Mỗi agent có prompt riêng, tối ưu cho đúng một việc, nên chất lượng từng phần thường tốt hơn so với dồn hết vào một prompt dài.
Test với mock để kiểm tra luồng điều phối đúng, không phụ thuộc LLM:
class OrchestratorAgentTest {
@Test
void shouldPassResearchNotesToWriter() {
ResearchAgent research = mock(ResearchAgent.class);
WriterAgent writer = mock(WriterAgent.class);
when(research.research("Spring AI")).thenReturn("note-A, note-B");
when(writer.write("Spring AI", "note-A, note-B")).thenReturn("Bài viết hoàn chỉnh");
OrchestratorAgent orchestrator = new OrchestratorAgent(research, writer);
String article = orchestrator.produceArticle("Spring AI");
assertThat(article).isEqualTo("Bài viết hoàn chỉnh");
verify(writer).write("Spring AI", "note-A, note-B");
}
}
Lưu ý: Multi-Agent tăng độ trễ (nhiều lời gọi LLM nối tiếp) và độ phức tạp hệ thống. Chỉ nên tách agent khi mỗi agent thực sự có prompt/context khác biệt rõ ràng — tách quá nhỏ (mỗi agent 1 câu) chỉ gây overhead không cần thiết.
6. Pattern ReAct: suy luận xen kẽ hành động
ReAct (Reasoning + Acting) là pattern kết hợp Tool Use và vòng lặp suy luận: agent nghĩ (Thought) → hành động (Action, ví dụ gọi tool) → quan sát kết quả (Observation) → lặp lại cho đến khi đủ thông tin để trả lời. Khác Tool Use đơn thuần (gọi 1 tool rồi trả lời), ReAct cho phép agent gọi nhiều tool theo trình tự linh hoạt, tự quyết định bước tiếp theo dựa trên observation trước đó.
@Service
public class ReActAgent {
private final ChatClient chatClient;
private final List<Object> tools;
public ReActAgent(ChatClient.Builder builder, WeatherTool weatherTool, SearchTool searchTool) {
this.tools = List.of(weatherTool, searchTool);
this.chatClient = builder.defaultTools(tools.toArray()).build();
}
public String solve(String question) {
return chatClient.prompt()
.system("""
Khi cần dữ liệu thực tế, hãy gọi tool phù hợp trước khi trả lời.
Suy luận từng bước: Thought -> Action -> Observation -> Thought...
Chỉ trả lời cuối cùng khi đã đủ thông tin.
""")
.user(question)
.call()
.content();
}
}
Với câu hỏi “Tôi nên mang áo mưa khi đi Đà Lạt cuối tuần này không?”, agent có thể tự suy luận: gọi weatherTool.forecast("Đà Lạt") để lấy dự báo, quan sát kết quả “mưa 70%”, rồi mới trả lời — toàn bộ chuỗi Thought/Action/Observation này diễn ra bên trong framework mà bạn không phải viết loop thủ công, vì Spring AI tự xử lý vòng lặp gọi tool nhiều lần cho đến khi model quyết định dừng.
Thought: Cần biết thời tiết Đà Lạt cuối tuần.
Action: forecast("Đà Lạt")
Observation: Mưa 70%, 18-22 độ C
Thought: Nên mang áo mưa.
Answer: Bạn nên mang áo mưa, dự báo Đà Lạt cuối tuần có mưa 70%.
Lưu ý: ReAct dễ bị “loop vô hạn” nếu agent cứ gọi tool mà không hội tụ về câu trả lời. Luôn đặt giới hạn số lần gọi tool tối đa (
maxToolCallshoặc timeout) để tránh tốn chi phí API không kiểm soát.
7. So sánh nhanh 5 pattern
| Pattern | Giải quyết vấn đề gì | Chi phí (số lời gọi LLM) | Khi nên dùng |
|---|---|---|---|
| Reflection | Chất lượng câu trả lời thấp | Cao (2–3x) | Task cần độ chính xác, có thể tự kiểm tra |
| Tool Use | LLM không truy cập được dữ liệu/hệ thống thật | Thấp–trung bình | Cần tra cứu, tính toán, gọi API |
| Planning | Task nhiều bước, dễ bỏ sót | Trung bình | Task phức tạp, có cấu trúc rõ |
| Multi-Agent | Một agent không đủ chuyên môn cho mọi việc | Cao | Pipeline rõ vai trò (nghiên cứu/viết/duyệt) |
| ReAct | Cần tool nhưng số bước không biết trước | Biến động | Tác vụ cần dò tìm thông tin linh hoạt |
Trong thực tế, các pattern này không loại trừ nhau — một agent hỗ trợ khách hàng có thể dùng Tool Use để tra đơn hàng, Planning để xử lý yêu cầu phức tạp, và Reflection để kiểm tra lại câu trả lời nhạy cảm trước khi gửi.
8. Kết luận
5 pattern trên — Reflection, Tool Use, Planning, Multi-Agent, ReAct — là những viên gạch cơ bản để xây AI Agent có khả năng tự sửa sai, tương tác với hệ thống thật, xử lý task phức tạp và linh hoạt dò tìm thông tin. Bạn không cần dùng hết cả 5 cho mọi bài toán; hãy bắt đầu từ Tool Use (hầu như agent nào cũng cần), rồi thêm Planning hoặc ReAct khi task phức tạp hơn, và chỉ dùng Reflection/Multi-Agent khi chi phí tăng thêm đáng đổi lấy chất lượng. Chọn đúng pattern theo bài toán quan trọng hơn là cố nhồi tất cả vào một agent duy nhất.