IA agentique en Java : construire des agents autonomes avec Spring AI
Agents IA autonomes en Java : architecture, tool use, orchestration multi-agents avec Spring AI et LangChain4j. Exemples concrets pour passer du chatbot à l'agent.
En 2024, on parlait de LLMs et de chatbots. En 2026, la tendance est aux agents autonomes : des systèmes qui planifient, agissent, observent et s’adaptent sans intervention humaine à chaque étape. Plus d’un quart du code produit chez Google est généré par des agents IA. Cette technologie sort des labos et entre en production.
Voici comment construire un agent IA robuste en Java.
Ce qui distingue un agent d’un simple LLM
Un LLM répond à un prompt. Un agent utilise un LLM comme moteur de raisonnement, mais il peut :
- Appeler des outils (fonctions Java, APIs, requêtes SQL)
- Observer les résultats de ces appels
- Planifier la prochaine action en fonction de ce qu’il a observé
- Itérer jusqu’à atteindre l’objectif
Le pattern dominant est ReAct (Reasoning + Acting) :
Thought: Je dois trouver le prix du produit X
Action: search_product(name="X")
Observation: {"id": 42, "price": 19.99, "stock": 12}
Thought: J'ai le prix. Je dois maintenant vérifier si le stock est suffisant.
Action: check_availability(product_id=42, quantity=5)
Observation: {"available": true}
Thought: Je peux passer la commande.
Action: create_order(product_id=42, quantity=5)
Final Answer: Commande créée pour 5 unités de X à 19,99 €.
Chaque itération est un appel LLM. L’agent décide seul quand s’arrêter.
Dépendances Maven
<!-- Spring AI avec Claude (Anthropic) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Ou LangChain4j (plus de features agents) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-anthropic</artifactId>
<version>0.32.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.32.0</version>
</dependency>
Construire un agent avec LangChain4j
1. Définir les outils
Les outils sont de simples méthodes Java annotées. Le LLM décide quand les appeler en lisant leurs descriptions.
@Component
public class ProductTools {
@Autowired
private ProductRepository productRepository;
@Tool("Recherche un produit par nom. Retourne son ID, son prix et son stock.")
public ProductInfo searchProduct(@P("Nom du produit à rechercher") String name) {
return productRepository.findByName(name)
.map(p -> new ProductInfo(p.getId(), p.getName(), p.getPrice(), p.getStock()))
.orElseThrow(() -> new ToolException("Produit '" + name + "' introuvable"));
}
@Tool("Vérifie si une quantité donnée est disponible en stock pour un produit.")
public AvailabilityResult checkAvailability(
@P("ID du produit") Long productId,
@P("Quantité souhaitée") int quantity) {
Product p = productRepository.findById(productId)
.orElseThrow(() -> new ToolException("Produit ID " + productId + " introuvable"));
boolean available = p.getStock() >= quantity;
return new AvailabilityResult(available, p.getStock());
}
@Tool("Crée une commande pour un produit. Requiert la confirmation que le stock est disponible.")
public OrderResult createOrder(
@P("ID du produit") Long productId,
@P("Quantité à commander") int quantity,
@P("Email du client") String customerEmail) {
// Logique métier : vérification, décrémentation stock, envoi confirmation
Order order = orderService.create(productId, quantity, customerEmail);
return new OrderResult(order.getId(), order.getTotalPrice());
}
}
2. Définir l’interface agent
public interface ShoppingAgent {
@SystemMessage("""
Tu es un assistant e-commerce. Tu aides les clients à trouver des produits,
vérifier leur disponibilité et passer des commandes.
Utilise toujours les outils disponibles pour obtenir des informations réelles.
Ne confirme jamais une commande sans avoir vérifié le stock au préalable.
""")
String chat(@MemoryId String sessionId, @UserMessage String userMessage);
}
3. Assembler l’agent
@Configuration
public class AgentConfig {
@Bean
public ShoppingAgent shoppingAgent(ProductTools tools) {
return AiServices.builder(ShoppingAgent.class)
.chatLanguageModel(
AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-sonnet-4-6")
.maxTokens(4096)
.build()
)
.tools(tools)
.chatMemoryProvider(memoryId ->
MessageWindowChatMemory.withMaxMessages(20)
)
.build();
}
}
4. Exposer via un endpoint REST
@RestController
@RequestMapping("/api/agent")
public class AgentController {
@Autowired
private ShoppingAgent agent;
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(
@RequestHeader("X-Session-Id") String sessionId,
@RequestBody ChatRequest request) {
String response = agent.chat(sessionId, request.message());
return ResponseEntity.ok(new ChatResponse(response));
}
}
Test :
curl -X POST http://localhost:8080/api/agent/chat \
-H "Content-Type: application/json" \
-H "X-Session-Id: user-123" \
-d '{"message": "Je veux commander 3 unités du produit Java Handbook"}'
L’agent va automatiquement :
- Chercher le produit
search_product("Java Handbook") - Vérifier le stock
check_availability(id, 3) - Créer la commande si disponible
create_order(id, 3, email) - Répondre à l’utilisateur avec le récapitulatif
Gestion des erreurs et des cas limites
Un agent en production doit être robuste. Les LLMs peuvent tenter d’appeler des outils avec des paramètres incorrects, ou boucler.
@Tool("Envoie un email de confirmation au client.")
public EmailResult sendConfirmation(@P("Adresse email") String email,
@P("Contenu du message") String message) {
// Validation : jamais faire confiance à ce que le LLM passe
if (!email.matches("^[^@]+@[^@]+\\.[^@]+$")) {
throw new ToolException("Email invalide : " + email);
}
if (message.length() > 2000) {
throw new ToolException("Message trop long (max 2000 caractères)");
}
// Envoi réel
emailService.send(email, message);
return new EmailResult(true, "Email envoyé à " + email);
}
Timeout sur le cycle agent :
@Bean
public ShoppingAgent shoppingAgent(ProductTools tools) {
return AiServices.builder(ShoppingAgent.class)
.chatLanguageModel(
AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-sonnet-4-6")
.maxTokens(4096)
.timeout(Duration.ofSeconds(60)) // timeout global
.build()
)
.tools(tools)
.maxSequentialToolCalls(10) // max 10 appels d'outils par tour
.build();
}
Agents multi-étapes avec Spring AI
Spring AI (depuis 1.0) propose une API ChatClient fluente qui permet de chaîner des appels avec des outils déclarés comme des @Bean :
@Service
public class AnalysisAgentService {
private final ChatClient chatClient;
public AnalysisAgentService(ChatClient.Builder builder,
DataAnalysisTools tools) {
this.chatClient = builder
.defaultSystem("Tu es un analyste de données. Utilise les outils pour répondre.")
.defaultFunctions("fetchSalesData", "computeStats", "generateReport")
.build();
}
public String analyze(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
@Component
public class DataAnalysisTools {
@Bean
@Description("Récupère les données de ventes pour une période donnée")
public Function<SalesRequest, SalesData> fetchSalesData() {
return req -> salesRepository.findByPeriod(req.from(), req.to());
}
@Bean
@Description("Calcule des statistiques (moyenne, max, tendance) sur un jeu de données")
public Function<SalesData, Statistics> computeStats() {
return data -> statisticsService.compute(data);
}
@Bean
@Description("Génère un rapport structuré à partir des statistiques")
public Function<Statistics, Report> generateReport() {
return stats -> reportGenerator.generate(stats);
}
}
Orchestration multi-agents
Pour les tâches complexes, plusieurs agents spécialisés se coordonnent. Un agent orchestrateur délègue aux agents spécialistes.
@Service
public class OrchestratorAgent {
private final ShoppingAgent shoppingAgent;
private final SupportAgent supportAgent;
private final AnalysisAgent analysisAgent;
public String route(String sessionId, String userMessage) {
// L'orchestrateur classe l'intention et délègue
String intent = classifyIntent(userMessage);
return switch (intent) {
case "ORDER" -> shoppingAgent.chat(sessionId, userMessage);
case "SUPPORT" -> supportAgent.chat(sessionId, userMessage);
case "ANALYSIS" -> analysisAgent.analyze(userMessage);
default -> "Je ne comprends pas votre demande.";
};
}
private String classifyIntent(String message) {
// Appel LLM léger (Haiku) pour la classification
return classifierModel.classify(message, List.of("ORDER", "SUPPORT", "ANALYSIS", "OTHER"));
}
}
Points de vigilance en production
Actions irréversibles : gardez un humain dans la boucle pour les actions critiques (suppression de données, virements, envois massifs). Implémentez un step de confirmation explicite.
Logging complet : chaque appel d’outil, chaque réponse LLM, chaque itération doit être tracé. Sans logs, déboguer un agent est presque impossible.
Coûts : un agent avec 10 itérations consomme 10× les tokens d’un appel simple. Surveillez votre usage via l’API Anthropic et mettez des alertes.
Tests : testez chaque outil indépendamment avec des mocks, puis testez le flux agent avec un LLM réel sur les scénarios critiques.
Les agents IA en Java sont maintenant accessibles et productifs. Spring AI et LangChain4j fournissent les briques pour passer d’un PoC à la production en quelques semaines. La clé : commencer par des outils simples, bien délimités, avec des validations strictes des entrées/sorties.
Sur le même sujet
- MCP (Model Context Protocol) : connecter vos LLMs à vos outils — Le protocole standard pour exposer vos APIs et bases de données à un agent sans réécrire l’intégration dans chaque application.
- Platform Engineering : construire une plateforme développeur interne — Comment industrialiser le déploiement des agents et microservices IA via un IDP avec ArgoCD et Backstage.
- DevSecOps : intégrer la sécurité dans votre pipeline CI/CD Java — Les security gates indispensables avant de déployer un agent IA en production (scan des dépendances, SAST, secrets).
Amine MEGDICHE
Développeur AEM & Java Full Stack — Freelance depuis 2013