Quarkus extension that integrates LangChain4j's agentic capabilities, enabling developers to build AI agent-based applications using declarative patterns with support for multiple agent types, agent-to-agent communication, and CDI integration.
This document describes how to handle errors gracefully with error context information and recovery strategies.
Provides context information when an error occurs during agent execution.
/**
* Context information for errors during agent execution
*/
public interface ErrorContext {
/**
* Gets the name of the agent that encountered the error
* @return Agent name
*/
String agentName();
/**
* Gets the exception that occurred
* @return The exception
*/
Throwable exception();
/**
* Gets access to the agentic scope
* @return AgenticScope instance for reading/writing state
*/
AgenticScope agenticScope();
}Usage Example:
import dev.langchain4j.agentic.agent.ErrorContext;
import dev.langchain4j.agentic.agent.ErrorRecoveryResult;
public interface ErrorAwareAgent {
@Agent(description = "Processes with error handling", outputKey = "result")
String process(@V("data") String data);
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
// Access error information
String agentName = context.agentName();
Throwable error = context.exception();
AgenticScope scope = context.agenticScope();
// Log error details
System.err.println("Error in agent: " + agentName);
System.err.println("Error type: " + error.getClass().getSimpleName());
System.err.println("Error message: " + error.getMessage());
// Read state to understand context
String input = scope.readState("data");
System.err.println("Input that caused error: " + input);
// Decide recovery strategy
return ErrorRecoveryResult.throwException();
}
@ChatModelSupplier
static ChatModel chatModel() {
return new OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
}
}Indicates how to recover from an error (retry or throw).
/**
* Result indicating error recovery strategy
*/
public class ErrorRecoveryResult {
/**
* Retry the failed operation
* @return Recovery result indicating retry
*/
static ErrorRecoveryResult retry();
/**
* Re-throw the exception (no recovery)
* @return Recovery result indicating throw
*/
static ErrorRecoveryResult throwException();
}Usage Example:
import dev.langchain4j.agentic.agent.ErrorContext;
import dev.langchain4j.agentic.agent.ErrorRecoveryResult;
public interface RetryableAgent {
@Agent(description = "Agent with retry logic", outputKey = "result")
String process(@V("data") String data);
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
AgenticScope scope = context.agenticScope();
// Track retry attempts
Integer retries = scope.readState("retryCount", 0);
if (retries < 3) {
scope.writeState("retryCount", retries + 1);
System.out.println("Retrying... Attempt " + (retries + 1));
return ErrorRecoveryResult.retry();
}
System.err.println("Max retries exceeded, giving up");
return ErrorRecoveryResult.throwException();
}
@ChatModelSupplier
static ChatModel chatModel() {
return new OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
}
}import dev.langchain4j.agentic.agent.MissingArgumentException;
import dev.langchain4j.agentic.agent.ErrorContext;
import dev.langchain4j.agentic.agent.ErrorRecoveryResult;
public interface MissingArgHandler {
@Agent(description = "Handles missing arguments", outputKey = "result")
String process(@V("required") String required, @V("optional") String optional);
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
if (context.exception() instanceof MissingArgumentException mEx) {
String missingArg = mEx.argumentName();
// Provide default for optional parameters
if ("optional".equals(missingArg)) {
context.agenticScope().writeState("optional", "default_value");
return ErrorRecoveryResult.retry();
}
// Cannot recover from missing required parameter
System.err.println("Required parameter missing: " + missingArg);
}
return ErrorRecoveryResult.throwException();
}
@ChatModelSupplier
static ChatModel chatModel() {
return new OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
}
}import java.util.concurrent.TimeoutException;
public interface TimeoutHandler {
@Agent(description = "Handles timeouts", outputKey = "result")
String process(@V("query") String query);
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
if (context.exception() instanceof TimeoutException) {
AgenticScope scope = context.agenticScope();
Integer timeouts = scope.readState("timeoutCount", 0);
if (timeouts < 2) {
scope.writeState("timeoutCount", timeouts + 1);
System.out.println("Timeout occurred, retrying with backoff");
// Could implement exponential backoff here
return ErrorRecoveryResult.retry();
}
System.err.println("Multiple timeouts, failing");
}
return ErrorRecoveryResult.throwException();
}
@ChatModelSupplier
static ChatModel chatModel() {
return new OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.timeout(Duration.ofSeconds(30))
.build();
}
}public interface FallbackAgent {
@Agent(description = "Agent with fallback", outputKey = "result")
String process(@V("input") String input, @V("useAdvanced") boolean useAdvanced);
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
AgenticScope scope = context.agenticScope();
// Try simpler approach on error
Boolean useAdvanced = scope.readState("useAdvanced", true);
if (useAdvanced) {
System.out.println("Advanced processing failed, falling back to simple mode");
scope.writeState("useAdvanced", false);
return ErrorRecoveryResult.retry();
}
// Both approaches failed
System.err.println("All processing strategies failed");
scope.writeState("error", context.exception().getMessage());
return ErrorRecoveryResult.throwException();
}
@ChatModelSupplier
static ChatModel chatModel() {
return new OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
}
}public interface CircuitBreakerAgent {
@Agent(description = "Agent with circuit breaker", outputKey = "result")
String process(@V("request") String request);
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
AgenticScope scope = context.agenticScope();
// Track consecutive failures
Integer consecutiveFailures = scope.readState("consecutiveFailures", 0);
scope.writeState("consecutiveFailures", consecutiveFailures + 1);
// Open circuit after threshold
if (consecutiveFailures >= 5) {
scope.writeState("circuitOpen", true);
System.err.println("Circuit breaker opened after " + consecutiveFailures + " failures");
return ErrorRecoveryResult.throwException();
}
// Check if circuit is open
Boolean circuitOpen = scope.readState("circuitOpen", false);
if (circuitOpen) {
System.err.println("Circuit is open, failing fast");
return ErrorRecoveryResult.throwException();
}
// Normal retry logic
return ErrorRecoveryResult.retry();
}
@ChatModelSupplier
static ChatModel chatModel() {
return new OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
}
}public interface MonitoredAgent {
@Agent(description = "Agent with error monitoring", outputKey = "result")
String process(@V("data") String data);
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
// Log comprehensive error information
logError(context);
// Store error in scope for later analysis
context.agenticScope().writeState("lastError", Map.of(
"agentName", context.agentName(),
"errorType", context.exception().getClass().getSimpleName(),
"errorMessage", context.exception().getMessage(),
"timestamp", System.currentTimeMillis()
));
// Send metrics/alerts (in production)
// metricsService.incrementErrorCount(context.agentName());
// alertingService.sendAlert(context);
return ErrorRecoveryResult.throwException();
}
static void logError(ErrorContext context) {
System.err.println("=== Agent Error ===");
System.err.println("Agent: " + context.agentName());
System.err.println("Exception: " + context.exception().getClass().getName());
System.err.println("Message: " + context.exception().getMessage());
System.err.println("Stack trace:");
context.exception().printStackTrace();
}
@ChatModelSupplier
static ChatModel chatModel() {
return new OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
}
}public interface ResilientWorkflow {
@SequenceAgent(
outputKey = "finalResult",
subAgents = { StepA.class, StepB.class, StepC.class }
)
ResultWithAgenticScope<String> execute(@V("input") String input);
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
String failedAgent = context.agentName();
AgenticScope scope = context.agenticScope();
System.err.println("Workflow failed at: " + failedAgent);
// Agent-specific recovery strategies
switch (failedAgent) {
case "StepA":
// Step A failures are recoverable with default data
scope.writeState("stepAResult", "default");
return ErrorRecoveryResult.retry();
case "StepB":
// Step B failures: retry once
Integer stepBRetries = scope.readState("stepBRetries", 0);
if (stepBRetries < 1) {
scope.writeState("stepBRetries", stepBRetries + 1);
return ErrorRecoveryResult.retry();
}
break;
case "StepC":
// Step C failures are terminal
System.err.println("Final step failed, cannot recover");
break;
}
return ErrorRecoveryResult.throwException();
}
@ChatModelSupplier
static ChatModel chatModel() {
return new OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
}
}Exception thrown when an error occurs during agent invocation or execution.
/**
* Exception thrown during agent invocation or execution
* Note: This exception does not have a getMessage() method override.
* Access error details through the ErrorContext in @ErrorHandler methods.
*/
public class AgentInvocationException extends RuntimeException {
/**
* Constructor with cause
* @param cause The underlying exception
*/
public AgentInvocationException(Throwable cause);
/**
* Gets the underlying cause
* @return The cause throwable
*/
public Throwable getCause();
}Package: dev.langchain4j.agentic.agent
Usage: This exception wraps other exceptions that occur during agent execution. To access error details, use the ErrorContext provided to @ErrorHandler methods rather than calling methods on the exception directly.
Example:
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
// Access the underlying exception through ErrorContext
Throwable error = context.exception();
// If it's an AgentInvocationException, get the cause
if (error instanceof AgentInvocationException aiEx) {
Throwable cause = aiEx.getCause();
System.err.println("Agent invocation failed due to: " + cause.getClass().getName());
// Handle specific causes
if (cause instanceof TimeoutException) {
return ErrorRecoveryResult.retry();
}
}
return ErrorRecoveryResult.throwException();
}Thrown when a required parameter is missing during agent execution.
/**
* Exception thrown when a required argument is missing
*/
public class MissingArgumentException extends RuntimeException {
/**
* Gets the name of the missing argument
* @return The argument name
*/
public String argumentName();
}Package: dev.langchain4j.agentic.agent
Usage: This exception is typically caught in @ErrorHandler methods to provide default values or alternative recovery strategies.
Example:
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
if (context.exception() instanceof MissingArgumentException mEx) {
String argName = mEx.argumentName();
System.err.println("Missing required argument: " + argName);
// Provide default value
context.agenticScope().writeState(argName, getDefaultValue(argName));
return ErrorRecoveryResult.retry();
}
return ErrorRecoveryResult.throwException();
}Install with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-agentic