langchain4j-tool-function-calling-patterns
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill langchain4j-tool-function-calling-patterns
Agent 安装分布
Skill 文档
LangChain4j Tool & Function Calling Patterns
Define tools and enable AI agents to interact with external systems, APIs, and services using LangChain4j’s annotation-based and programmatic tool system.
Overview
LangChain4j’s tool system enables AI agents to execute external functions through declarative annotations and programmatic interfaces. Tools are defined using the @Tool annotation and automatically registered with AI services, allowing LLMs to perform actions beyond text generation such as database queries, API calls, and calculations.
When to Use This Skill
Use this skill when:
- Building AI applications that need to interact with external APIs and services
- Creating AI assistants that can perform actions beyond text generation
- Implementing AI systems that need access to real-time data (weather, stocks, etc.)
- Building multi-agent systems where agents can use specialized tools
- Creating AI applications with database read/write capabilities
- Implementing AI systems that need to integrate with existing business systems
- Building context-aware AI applications where tool availability depends on user state
- Developing production AI applications that require robust error handling and monitoring
Instructions
Follow these steps to implement tools with LangChain4j:
1. Define Tool Methods
Create methods annotated with @Tool in a class:
public class WeatherTools {
@Tool("Get current weather for a city")
public String getWeather(
@P("City name") String city,
@P("Temperature unit (celsius or fahrenheit)", required = false) String unit) {
// Implementation
return weatherService.getWeather(city, unit);
}
}
2. Configure Parameter Descriptions
Use @P annotation for clear parameter descriptions that help the LLM understand how to call the tool:
@Tool("Calculate total order amount")
public double calculateOrderTotal(
@P("List of product IDs") List<String> productIds,
@P("Customer discount percentage", required = false) Double discount) {
// Implementation
}
3. Register Tools with AI Service
Connect tools to an AI service using the AiServices builder:
MathAssistant assistant = AiServices.builder(MathAssistant.class)
.chatModel(chatModel)
.tools(new Calculator(), new WeatherService())
.build();
4. Handle Tool Execution Errors
Implement error handling for tool failures:
AiServices.builder(Assistant.class)
.chatModel(chatModel)
.tools(new ExternalServiceTools())
.toolExecutionErrorHandler((request, exception) -> {
log.error("Tool execution failed: {}", exception.getMessage());
return "An error occurred while processing your request";
})
.build();
5. Monitor Tool Usage
Track tool calls for debugging and analytics:
Result<String> result = assistant.chat(question);
result.toolExecutions().forEach(execution ->
log.info("Executed tool: {} in {}ms",
execution.request().name(),
execution.duration().toMillis())
);
Setup and Configuration
Basic Tool Registration
// Define tools using @Tool annotation
public class CalculatorTools {
@Tool("Add two numbers")
public double add(double a, double b) {
return a + b;
}
}
// Register with AiServices builder
interface MathAssistant {
String ask(String question);
}
MathAssistant assistant = AiServices.builder(MathAssistant.class)
.chatModel(chatModel)
.tools(new CalculatorTools())
.build();
Builder Configuration Options
AiServices.builder(AssistantInterface.class)
// Static tool registration
.tools(new Calculator(), new WeatherService())
// Dynamic tool provider
.toolProvider(new DynamicToolProvider())
// Concurrent execution
.executeToolsConcurrently()
// Error handling
.toolExecutionErrorHandler((request, exception) -> {
return "Error: " + exception.getMessage();
})
// Memory for context
.chatMemoryProvider(userId -> MessageWindowChatMemory.withMaxMessages(20))
.build();
Core Patterns
Basic Tool Definition
Use @Tool annotation to define methods as executable tools:
public class BasicTools {
@Tool("Add two numbers")
public int add(@P("first number") int a, @P("second number") int b) {
return a + b;
}
@Tool("Get greeting")
public String greet(@P("name to greet") String name) {
return "Hello, " + name + "!";
}
}
Parameter Descriptions and Validation
Provide clear parameter descriptions using @P annotation:
public class WeatherService {
@Tool("Get current weather conditions")
public String getCurrentWeather(
@P("City name or coordinates") String location,
@P("Temperature unit (celsius, fahrenheit)", required = false) String unit) {
// Implementation with validation
if (location == null || location.trim().isEmpty()) {
return "Location is required";
}
return weatherClient.getCurrentWeather(location, unit);
}
}
Complex Parameter Types
Use Java records and descriptions for complex objects:
public class OrderService {
@Description("Customer order information")
public record OrderRequest(
@Description("Customer ID") String customerId,
@Description("List of items") List<OrderItem> items,
@JsonProperty(required = false) @Description("Delivery instructions") String instructions
) {}
@Tool("Create customer order")
public String createOrder(OrderRequest order) {
return orderService.processOrder(order);
}
}
Advanced Features
Memory Context Integration
Access user context using @ToolMemoryId:
public class PersonalizedTools {
@Tool("Get user preferences")
public String getPreferences(
@ToolMemoryId String userId,
@P("Preference category") String category) {
return preferenceService.getPreferences(userId, category);
}
}
Dynamic Tool Provisioning
Create tools that change based on context:
public class ContextAwareToolProvider implements ToolProvider {
@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
String message = request.userMessage().singleText().toLowerCase();
var builder = ToolProviderResult.builder();
if (message.contains("weather")) {
builder.add(weatherToolSpec, weatherExecutor);
}
if (message.contains("calculate")) {
builder.add(calcToolSpec, calcExecutor);
}
return builder.build();
}
}
Immediate Return Tools
Return results immediately without full AI response:
public class QuickTools {
@Tool(value = "Get current time", returnBehavior = ReturnBehavior.IMMEDIATE)
public String getCurrentTime() {
return LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
Error Handling
Tool Error Handling
Handle tool execution errors gracefully:
AiServices.builder(Assistant.class)
.chatModel(chatModel)
.tools(new ExternalServiceTools())
.toolExecutionErrorHandler((request, exception) -> {
if (exception instanceof ApiException) {
return "Service temporarily unavailable: " + exception.getMessage();
}
return "An error occurred while processing your request";
})
.build();
Resilience Patterns
Implement circuit breakers and retries:
public class ResilientService {
private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("external-api");
@Tool("Get external data")
public String getExternalData(@P("Data identifier") String id) {
return circuitBreaker.executeSupplier(() -> {
return externalApi.getData(id);
});
}
}
Integration Examples
Multi-Domain Tool Service
@Service
public class MultiDomainToolService {
public String processRequest(String userId, String request, String domain) {
String contextualRequest = String.format("[Domain: %s] %s", domain, request);
Result<String> result = assistant.chat(userId, contextualRequest);
// Log tool usage
result.toolExecutions().forEach(execution ->
analyticsService.recordToolUsage(userId, domain, execution.request().name()));
return result.content();
}
}
Streaming with Tool Execution
interface StreamingAssistant {
TokenStream chat(String message);
}
StreamingAssistant assistant = AiServices.builder(StreamingAssistant.class)
.streamingChatModel(streamingChatModel)
.tools(new Tools())
.build();
TokenStream stream = assistant.chat("What's the weather and calculate 15*8?");
stream
.onToolExecuted(execution ->
System.out.println("Executed: " + execution.request().name()))
.onPartialResponse(System.out::print)
.onComplete(response -> System.out.println("Complete!"))
.start();
Best Practices
Tool Design Guidelines
- Descriptive Names: Use clear, actionable tool names
- Parameter Validation: Validate inputs before processing
- Error Messages: Provide meaningful error messages
- Return Types: Use appropriate return types that LLMs can understand
- Performance: Avoid blocking operations in tools
Security Considerations
- Permission Checks: Validate user permissions before tool execution
- Input Sanitization: Sanitize all tool inputs
- Audit Logging: Log tool usage for security monitoring
- Rate Limiting: Implement rate limiting for external APIs
Performance Optimization
- Concurrent Execution: Use
executeToolsConcurrently()for independent tools - Caching: Cache frequently accessed data
- Monitoring: Monitor tool performance and error rates
- Resource Management: Handle external service timeouts gracefully
Examples
Simple Calculator Tool
public class CalculatorTools {
@Tool("Add two numbers")
public double add(@P("First number") double a,
@P("Second number") double b) {
return a + b;
}
@Tool("Multiply two numbers")
public double multiply(@P("First number") double a,
@P("Second number") double b) {
return a * b;
}
}
// Usage
interface MathAssistant {
String ask(String question);
}
MathAssistant assistant = AiServices.builder(MathAssistant.class)
.chatModel(chatModel)
.tools(new CalculatorTools())
.build();
String result = assistant.ask("What is 15 times 7 plus 3?");
Database Access Tool
@Component
public class DatabaseTools {
private final CustomerRepository repository;
@Tool("Get customer information by ID")
public Customer getCustomer(@P("Customer ID") Long customerId) {
return repository.findById(customerId)
.orElseThrow(() -> new IllegalArgumentException("Customer not found"));
}
@Tool("Update customer email address")
public String updateEmail(
@P("Customer ID") Long customerId,
@P("New email address") String newEmail) {
Customer customer = repository.findById(customerId)
.orElseThrow(() -> new IllegalArgumentException("Customer not found"));
customer.setEmail(newEmail);
repository.save(customer);
return "Email updated successfully";
}
}
REST API Tool
@Component
public class ApiTools {
private final WebClient webClient;
@Tool("Get current stock price")
public String getStockPrice(@P("Stock symbol") String symbol) {
return webClient.get()
.uri("/api/stocks/{symbol}", symbol)
.retrieve()
.bodyToMono(String.class)
.block();
}
}
Context-Aware Tool with Memory ID
public class UserPreferencesTools {
@Tool("Get user preferences for a category")
public String getPreferences(
@ToolMemoryId String userId,
@P("Preference category (e.g., theme, language)") String category) {
return preferencesService.getPreferences(userId, category);
}
@Tool("Set user preference")
public String setPreference(
@ToolMemoryId String userId,
@P("Preference category") String category,
@P("Preference value") String value) {
preferencesService.setPreference(userId, category, value);
return "Preference saved";
}
}
Dynamic Tool Provider
public class DynamicToolProvider implements ToolProvider {
private final Map<String, Object> availableTools = new HashMap<>();
public void registerTool(String name, ToolSpecification spec, ToolExecutor executor) {
availableTools.put(name, new ToolWithSpec(spec, executor));
}
@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
var builder = ToolProviderResult.builder();
String message = request.userMessage().singleText().toLowerCase();
// Dynamically filter tools based on user message
if (message.contains("weather")) {
builder.add(weatherToolSpec, weatherExecutor);
}
if (message.contains("calculate") || message.contains("math")) {
builder.add(calculatorToolSpec, calculatorExecutor);
}
return builder.build();
}
}
Reference Documentation
For detailed API reference, examples, and advanced patterns, see:
- API Reference – Complete API documentation
- Implementation Patterns – Advanced implementation examples
- Examples – Practical usage examples
Common Issues and Solutions
Tool Not Found
Problem: LLM calls tools that don’t exist
Solution: Implement hallucination handler:
.hallucinatedToolNameStrategy(request -> {
return ToolExecutionResultMessage.from(request,
"Error: Tool '" + request.name() + "' does not exist");
})
Parameter Validation Errors
Problem: Tools receive invalid parameters
Solution: Add input validation and error handlers:
.toolArgumentsErrorHandler((error, context) -> {
return ToolErrorHandlerResult.text("Invalid arguments: " + error.getMessage());
})
Performance Issues
Problem: Tools are slow or timeout
Solution: Use concurrent execution and resilience patterns:
.executeToolsConcurrently(Executors.newFixedThreadPool(5))
.toolExecutionTimeout(Duration.ofSeconds(30))
Related Skills
langchain4j-ai-services-patternslangchain4j-rag-implementation-patternslangchain4j-spring-boot-integration
References
- LangChain4j Tool & Function Calling – API References
- LangChain4j Tool & Function Calling – Implementation Patterns
- LangChain4j Tool & Function Calling – Examples
Constraints and Warnings
- Tools with side effects should have clear descriptions warning about potential impacts.
- AI models may call tools in unexpected orders or with unexpected parameters.
- Tool execution can be expensive; implement rate limiting and timeout handling.
- Never pass sensitive data (API keys, passwords) in tool descriptions or responses.
- Large tool sets can confuse AI models; consider using dynamic tool providers.
- Tool execution errors should be handled gracefully; never expose stack traces to AI models.
- Be cautious with tools that modify data; AI models may call them multiple times.
- Parameter descriptions should be precise; vague descriptions lead to incorrect tool usage.
- Tools with long execution times should implement timeout handling.
- Test tools thoroughly before exposing them to AI models to prevent unexpected behavior.