Grab dùng AI Agent tăng năng suất: giải thích chi tiết kèm ví dụ
1. Tổng quan
Nhiều công ty công nghệ quy mô lớn như Grab đang chuyển từ “dùng chatbot hỏi đáp” sang “dùng AI Agent tự hành động” để tăng năng suất kỹ thuật: tự review code, tự tóm tắt pull request, tự triage incident. Bài này giải thích bản chất AI Agent khác gì so với một LLM call đơn thuần, kiến trúc Planner – Tool – Executor phổ biến, và cách bạn tự dựng một agent tương tự bằng Java 21 + Spring AI. Đọc xong, bạn sẽ tự viết được một agent nhỏ gọi tool thật (đọc file, gọi API nội bộ) và biết khi nào không nên đưa agent vào production.
2. AI Agent khác gì một LLM call thông thường
Một LLM call đơn thuần nhận prompt, trả về text — không có trạng thái, không hành động. Một AI Agent thì có ba đặc điểm:
- Tự lập kế hoạch (planning): chia một yêu cầu lớn thành các bước nhỏ.
- Gọi tool (tool calling / function calling): agent có thể gọi API, đọc file, chạy query — không chỉ sinh text.
- Vòng lặp quan sát – hành động (observe – act loop): agent nhìn kết quả tool trả về, quyết định bước tiếp theo, lặp lại đến khi xong việc.
Ví dụ thực tế ở các đội engineering lớn: agent đọc diff của một pull request, gọi tool “lấy coding convention của team”, gọi tool “chạy static analysis”, rồi tổng hợp thành comment review — tất cả không cần người ngồi chờ từng bước.
Lưu ý: nếu việc của bạn chỉ là “tóm tắt văn bản” hay “dịch ngôn ngữ”, một LLM call thuần là đủ. Đừng gắn mác “agent” cho mọi thứ gọi OpenAI API — phức tạp hoá không cần thiết sẽ làm hệ thống khó debug.
3. Kiến trúc agent cơ bản: Planner – Tool – Executor
Mô hình phổ biến nhất gồm ba phần:
User request
│
▼
[Planner] -- sinh ra danh sách bước cần làm, hoặc chọn tool kế tiếp
│
▼
[Tool Executor] -- gọi tool thật: API, DB, file system
│
▼
[Observation] -- kết quả tool được đưa lại cho Planner
│
└── lặp lại cho đến khi Planner báo "done"
Planner thường là một LLM được cấp “menu” các tool (tên, mô tả, schema tham số). LLM không tự chạy code — nó chỉ quyết định gọi tool nào với tham số gì; code của bạn mới là người thực thi thật.
3.1. Dependency cần thiết
Spring AI cung cấp abstraction cho pattern này khá gọn:
<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
4. Ví dụ thực hành: Code Review Agent
Giả sử bạn muốn một agent nhận diff của một Order service, tự kiểm tra convention và gợi ý sửa. Trước tiên, khai báo tool mà agent được phép gọi — ở đây là tool đọc coding convention từ file nội bộ.
@Component
public class ConventionTool {
@Tool(description = "Lấy coding convention hiện hành của team cho một ngôn ngữ")
public String getConvention(String language) {
return switch (language.toLowerCase()) {
case "java" -> "Dùng record cho DTO; không @Autowired field; log bằng SLF4J.";
default -> "Chưa có convention riêng cho ngôn ngữ này.";
};
}
}
@Toollà annotation của Spring AI đánh dấu method này là một function mà LLM có thể gọi.descriptionchính là thứ LLM đọc để quyết định “có nên gọi tool này không” — viết rõ, LLM mới chọn đúng.
Tiếp theo là service orchestration — nơi đóng vai Planner + Executor:
@Service
public class CodeReviewAgent {
private final ChatClient chatClient;
public CodeReviewAgent(ChatClient.Builder builder, ConventionTool conventionTool) {
this.chatClient = builder
.defaultTools(conventionTool)
.build();
}
public String review(String diff) {
return chatClient.prompt()
.system("Bạn là reviewer. Dùng tool nếu cần convention trước khi nhận xét.")
.user("Review đoạn diff sau:\n" + diff)
.call()
.content();
}
}
defaultTools(conventionTool)đăng ký tool cho mọi request củachatClient.- Khi gọi
review(diff), Spring AI tự gửi “menu tool” kèm prompt cho model. Nếu model quyết định gọigetConvention("java"), framework tự thực thi method Java tương ứng, lấy kết quả, gửi lại cho model để model sinh câu trả lời cuối — toàn bộ vòng lặp này xảy ra trong một lệnhcall().
Gọi thử:
@RestController
@RequestMapping("/api/review")
public class ReviewController {
private final CodeReviewAgent agent;
public ReviewController(CodeReviewAgent agent) {
this.agent = agent;
}
@PostMapping
public String review(@RequestBody String diff) {
return agent.review(diff);
}
}
Đầu ra điển hình:
Convention team yêu cầu dùng record cho DTO và SLF4J cho logging.
Trong diff này, class OrderDto đang là class thường với getter/setter —
nên đổi sang record. Dòng 42 dùng System.out.println thay vì logger,
cần sửa theo convention.
Điểm then chốt: bạn không viết logic “nếu thấy class thường thì gợi ý record” — model tự suy luận dựa trên convention lấy được từ tool. Đó là lý do agent “tăng năng suất”: review không còn phụ thuộc vào người nhớ convention thủ công.
5. Agent gọi nhiều tool: PR Summary Agent
Thực tế các đội lớn thường cần agent gọi nhiều tool liên tiếp — ví dụ: lấy danh sách file thay đổi, lấy test coverage, rồi mới tóm tắt PR cho reviewer.
@Component
public class PullRequestTools {
private final GitService gitService;
private final CoverageService coverageService;
public PullRequestTools(GitService gitService, CoverageService coverageService) {
this.gitService = gitService;
this.coverageService = coverageService;
}
@Tool(description = "Lấy danh sách file đã thay đổi trong một PR theo id")
public List<String> listChangedFiles(long prId) {
return gitService.changedFiles(prId);
}
@Tool(description = "Lấy phần trăm test coverage của một file")
public double getCoverage(String filePath) {
return coverageService.coverageOf(filePath);
}
}
- Hai tool độc lập, mỗi tool làm một việc nhỏ, dễ test riêng.
- Model sẽ tự quyết định thứ tự gọi: thường gọi
listChangedFilestrước, rồi gọigetCoveragecho từng file trả về — đây chính là vòng lặp “observe – act” nói ở phần 3.
@Service
public class PullRequestSummaryAgent {
private final ChatClient chatClient;
public PullRequestSummaryAgent(ChatClient.Builder builder, PullRequestTools tools) {
this.chatClient = builder.defaultTools(tools).build();
}
public String summarize(long prId) {
return chatClient.prompt()
.system("Tóm tắt PR gồm: số file thay đổi, file nào coverage dưới 70% cần chú ý.")
.user("Tóm tắt PR id " + prId)
.call()
.content();
}
}
Agent kiểu này giải quyết đúng vấn đề năng suất: reviewer không cần tự mở từng file kiểm tra coverage — agent đã tổng hợp sẵn trong một đoạn tóm tắt.
6. Kiểm thử agent
Phần khó nhất khi đưa agent vào thực tế không phải là gọi được LLM, mà là đảm bảo tool được gọi đúng khi input thay đổi. Vì vậy nên tách riêng unit test cho tool (deterministic) và test tích hợp cho agent (có thể mock ChatClient).
class ConventionToolTest {
private final ConventionTool tool = new ConventionTool();
@Test
void shouldReturnJavaConvention() {
String result = tool.getConvention("Java");
assertThat(result)
.contains("record")
.contains("SLF4J");
}
@Test
void shouldReturnDefaultMessageForUnknownLanguage() {
String result = tool.getConvention("cobol");
assertThat(result).isEqualTo("Chưa có convention riêng cho ngôn ngữ này.");
}
}
- Test tool giống test một method thuần, không cần mock LLM — đây là phần bạn kiểm soát được 100%.
- Với phần agent gọi LLM thật, nên viết test ở mức “contract”: assert rằng response chứa các từ khóa bắt buộc (ví dụ “record”, “coverage”), tránh assert exact string vì LLM output không ổn định tuyệt đối giữa các lần gọi.
@Test
void reviewShouldMentionConventionKeyword() {
String diff = "class OrderDto { private String id; }";
String result = codeReviewAgent.review(diff);
assertThat(result.toLowerCase()).containsAnyOf("record", "convention");
}
Lưu ý: test gọi LLM thật tốn tiền và chậm — nên tách riêng một test suite (ví dụ tag
@Tag("llm-integration")) và chỉ chạy trong pipeline riêng, không chạy ở mọi commit.
7. Lỗi hay gặp khi triển khai agent
- Tool description mơ hồ: nếu mô tả tool không rõ, model chọn sai tool hoặc không gọi tool khi cần. Viết description như viết Javadoc cho người lạ đọc lần đầu.
- Không giới hạn số vòng lặp: agent có thể rơi vào loop gọi tool liên tục nếu logic không rõ điểm kết thúc. Luôn đặt
maxIterationshoặc timeout ở tầng orchestration. - Cho agent quyền ghi dữ liệu nhạy cảm: đừng để agent có tool “deploy production” hay “xoá dữ liệu” mà không qua bước xác nhận của người. Agent nên có quyền đọc rộng, quyền ghi hẹp.
- Không log lại quá trình gọi tool: khi agent trả lời sai, bạn cần biết nó đã gọi tool nào, với tham số gì, để debug. Thiếu log này khiến việc trace lỗi gần như bất khả thi.
- Dùng agent cho việc có thể giải quyết bằng rule đơn giản: nếu logic “nếu X thì Y” cố định, viết code thường nhanh hơn, rẻ hơn, và dễ test hơn agent.
8. Khi nào không nên dùng AI Agent
Agent phù hợp với việc có nhiều bước không cố định, cần tổng hợp thông tin từ nhiều nguồn, và chấp nhận sai số nhỏ (review gợi ý, tóm tắt). Agent không phù hợp với:
- Tác vụ cần độ chính xác tuyệt đối (tính tiền, xử lý giao dịch tài chính).
- Tác vụ có quy trình cố định, ít thay đổi — rule engine hoặc workflow engine (Camunda, Temporal) vẫn ổn định và dễ audit hơn.
- Hệ thống cần latency thấp, vì mỗi vòng lặp tool-calling tốn thêm một round-trip tới LLM.
9. Kết luận
AI Agent mang lại giá trị năng suất rõ rệt khi được áp dụng đúng chỗ: những tác vụ engineering lặp lại, nhiều bước, cần tổng hợp thông tin rải rác — review code, tóm tắt PR, triage lỗi. Thay vì viết agent “biết làm mọi thứ”, bạn nên bắt đầu từ vài tool nhỏ, rõ ràng, có test riêng, rồi để Planner (LLM) quyết định cách phối hợp. Quan trọng nhất là tách bạch quyền hạn: cho agent đọc nhiều, ghi ít, và luôn giữ người review ở bước cuối với những hành động có hậu quả lớn.