Grab usa AI Agent para aumentar produtividade: explicação detalhada com exemplos
1. Visão geral
Muitas grandes empresas de tecnologia como Grab estão transitando de “usar chatbots para perguntas e respostas” para “usar AI Agents que atuam por conta própria” para aumentar a produtividade técnica: fazer review de código automaticamente, resumir pull requests automaticamente, fazer triagem de incidentes automaticamente. Este artigo explica o que diferencia um AI Agent de uma simples chamada LLM, a arquitetura Planner – Tool – Executor comum, e como você constrói um agent similar usando Java 21 + Spring AI. Depois de ler, você será capaz de escrever um agent que chama tools de verdade (ler arquivos, chamar APIs internas) e saber quando não deve colocar um agent em produção.
2. Como um AI Agent é diferente de uma simples chamada LLM
Uma simples chamada LLM recebe um prompt e retorna texto — sem estado, sem ação. Um AI Agent, por sua vez, tem três características:
- Auto-planejamento (planning): divide uma solicitação grande em passos menores.
- Chamada de tools (tool calling / function calling): o agent pode chamar APIs, ler arquivos, executar queries — não apenas gerar texto.
- Loop de observação – ação (observe – act loop): o agent observa o resultado que a tool retorna, decide o próximo passo, repete até terminar.
Exemplo real em grandes equipes de engenharia: um agent lê a diff de um pull request, chama a tool “obter convenção de código do time”, chama a tool “executar análise estática”, depois sintetiza tudo em um comentário de review — tudo isso sem precisar de uma pessoa esperando cada passo.
Nota: se tudo que você precisa fazer é “resumir texto” ou “traduzir idioma”, uma simples chamada LLM é suficiente. Não rotule tudo que chama a API OpenAI como “agent” — complexidade desnecessária tornará o sistema difícil de debugar.
3. Arquitetura básica de um agent: Planner – Tool – Executor
O padrão mais comum consiste em três partes:
Requisição do usuário
│
▼
[Planner] -- gera lista de passos a fazer, ou escolhe próxima tool
│
▼
[Tool Executor] -- chama tool real: API, BD, sistema de arquivo
│
▼
[Observation] -- resultado da tool é retornado ao Planner
│
└── repetir até Planner indicar "concluído"
O Planner é geralmente um LLM fornecido com um “menu” de tools (nome, descrição, schema de parâmetros). O LLM não executa código por si próprio — apenas decide qual tool chamar com quais parâmetros; seu código é quem realmente executa.
3.1. Dependências necessárias
Spring AI fornece uma abstração bastante limpa para este padrão:
<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. Exemplo prático: Code Review Agent
Suponha que você queira um agent que receba a diff de um serviço Order, verifique automaticamente a convenção e sugira correções. Primeiro, declare a tool que o agent pode chamar — neste caso, uma tool que lê a convenção de código de um arquivo interno.
@Component
public class ConventionTool {
@Tool(description = "Obtém a convenção de código atual do time para uma linguagem")
public String getConvention(String language) {
return switch (language.toLowerCase()) {
case "java" -> "Use record para DTO; sem @Autowired em fields; log com SLF4J.";
default -> "Nenhuma convenção específica para essa linguagem.";
};
}
}
@Toolé a anotação do Spring AI que marca este método como uma função que o LLM pode chamar.descriptioné o que o LLM lê para decidir “devo chamar esta tool?” — escreva de forma clara para que o LLM faça a escolha correta.
A seguir vem o serviço de orquestração — que faz o papel de 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("Você é um revisor. Use a tool se precisar da convenção antes de dar seu parecer.")
.user("Revise a seguinte diff:\n" + diff)
.call()
.content();
}
}
defaultTools(conventionTool)registra a tool para toda requisição dochatClient.- Quando você chama
review(diff), Spring AI automaticamente envia o “menu da tool” junto com o prompt para o modelo. Se o modelo decidir chamargetConvention("java"), o framework executa automaticamente o método Java correspondente, obtém o resultado, envia de volta ao modelo para que ele gere a resposta final — todo este loop acontece em um único comandocall().
Experimente chamar:
@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);
}
}
Saída típica:
A convenção do time exige usar record para DTO e SLF4J para logging.
Nesta diff, a classe OrderDto está como uma classe comum com getter/setter —
deveria ser convertida para record. A linha 42 usa System.out.println em vez de logger,
precisa ser corrigido conforme a convenção.
O ponto-chave é: você não escreve a lógica “se vir uma classe comum, sugira record” — o modelo deduz isso automaticamente baseado na convenção obtida da tool. É por isso que agents “aumentam produtividade”: a review não depende mais de alguém lembrando manualmente das convenções.
5. Agent chamando múltiplas tools: PR Summary Agent
Na prática, grandes equipes geralmente precisam de agents que chamem múltiplas tools em sequência — por exemplo: obter a lista de arquivos alterados, obter a cobertura de testes, e então resumir a PR para o revisor.
@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 = "Obtém a lista de arquivos alterados em uma PR por id")
public List<String> listChangedFiles(long prId) {
return gitService.changedFiles(prId);
}
@Tool(description = "Obtém a porcentagem de cobertura de testes de um arquivo")
public double getCoverage(String filePath) {
return coverageService.coverageOf(filePath);
}
}
- Duas tools independentes, cada uma faz um trabalho pequeno, fácil de testar isoladamente.
- O modelo decidirá automaticamente a ordem de chamada: geralmente chama
listChangedFilesprimeiro, depois chamagetCoveragepara cada arquivo retornado — este é exatamente o loop “observe – act” mencionado na seção 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("Resuma a PR incluindo: número de arquivos alterados, quais arquivos com cobertura abaixo de 70% precisam de atenção.")
.user("Resuma a PR id " + prId)
.call()
.content();
}
}
Este tipo de agent resolve corretamente o problema de produtividade: o revisor não precisa abrir cada arquivo para verificar cobertura — o agent já sintetizou tudo em um resumo.
6. Testando um agent
A parte mais difícil ao colocar um agent em produção não é conseguir chamar o LLM, mas garantir que as tools sejam chamadas corretamente quando as entradas mudam. Por isso é importante separar testes unitários para tools (determinísticos) e testes de integração para o agent (que podem mockar 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("Nenhuma convenção específica para essa linguagem.");
}
}
- Testar uma tool é como testar um método comum, não precisa mockar o LLM — esta é a parte que você controla 100%.
- Para a parte do agent chamando o LLM real, você deve escrever testes no nível “contract”: afirme que a resposta contém palavras-chave obrigatórias (como “record”, “coverage”), evite afirmar strings exatas porque a saída do LLM não é absolutamente consistente entre chamadas.
@Test
void reviewShouldMentionConventionKeyword() {
String diff = "class OrderDto { private String id; }";
String result = codeReviewAgent.review(diff);
assertThat(result.toLowerCase()).containsAnyOf("record", "convention");
}
Nota: testar chamando o LLM real é caro e lento — você deve separar um test suite (por exemplo com a tag
@Tag("llm-integration")) e executá-lo apenas em um pipeline dedicado, não em cada commit.
7. Erros comuns ao implantar um agent
- Descrição de tool vaga: se a descrição não for clara, o modelo escolherá a tool errada ou não a chamará quando necessário. Escreva a descrição como faria com Javadoc para alguém lendo pela primeira vez.
- Sem limite de iterações: o agent pode cair em um loop de chamadas de tools contínuas se a lógica não tiver um ponto de término claro. Sempre defina
maxIterationsou timeout na camada de orquestração. - Dar ao agent permissão para escrever dados sensíveis: não deixe o agent ter uma tool “fazer deploy em produção” ou “deletar dados” sem passar por uma etapa de confirmação humana. O agent deve ter permissão leitura ampla, permissão escrita restrita.
- Não registrar o processo de chamada de tool: quando o agent responde incorretamente, você precisa saber qual tool chamou, com quais parâmetros, para debugar. A falta deste log torna o rastreamento de erros praticamente impossível.
- Usar agent para problemas que podem ser resolvidos com regras simples: se a lógica “se X então Y” for fixa, escrever código é geralmente mais rápido, mais barato e mais fácil de testar do que usar um agent.
8. Quando não usar um AI Agent
Um agent é apropriado para tarefas com muitos passos não fixos, que precisam sintetizar informações de múltiplas fontes e podem aceitar pequenas margens de erro (reviews de sugestão, resumos). Um agent não é apropriado para:
- Tarefas que exigem precisão absoluta (cálculo de valores, processamento de transações financeiras).
- Tarefas com processos fixos, que mudam pouco — rule engines ou workflow engines (Camunda, Temporal) ainda são mais estáveis e fáceis de auditar.
- Sistemas que precisam de baixa latência, pois cada loop de tool-calling gasta um round-trip adicional ao LLM.
9. Conclusão
Os AI Agents trazem valor claro de produtividade quando aplicados no lugar certo: tarefas técnicas repetitivas, com muitos passos, que precisam sintetizar informações dispersas — review de código, resumo de PR, triagem de erros. Em vez de escrever um agent que “sabe fazer tudo”, comece com algumas tools pequenas, claras, com testes próprios, e deixe o Planner (LLM) decidir como coordená-las. O mais importante é separar as permissões: dê ao agent muita capacidade de leitura, pouca capacidade de escrita, e sempre mantenha o revisor humano como etapa final para ações com grandes consequências.