Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:28:30 +08:00
commit 171acedaa4
220 changed files with 85967 additions and 0 deletions

View File

@@ -0,0 +1,358 @@
---
name: langchain4j-tool-function-calling-patterns
description: Tool and function calling patterns with LangChain4j. Define tools, handle function calls, and integrate with LLM agents. Use when building agentic applications that interact with tools.
category: ai-development
tags: [langchain4j, tools, function-calling, "@Tool", ToolProvider, ToolExecutor, dynamic-tools, parameter-descriptions, java]
version: 1.1.0
allowed-tools: Read, Write, Bash, WebFetch
---
# 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.
## 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
## Setup and Configuration
### Basic Tool Registration
```java
// 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
```java
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:
```java
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:
```java
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:
```java
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`:
```java
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:
```java
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:
```java
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:
```java
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:
```java
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
```java
@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
```java
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
1. **Descriptive Names**: Use clear, actionable tool names
2. **Parameter Validation**: Validate inputs before processing
3. **Error Messages**: Provide meaningful error messages
4. **Return Types**: Use appropriate return types that LLMs can understand
5. **Performance**: Avoid blocking operations in tools
### Security Considerations
1. **Permission Checks**: Validate user permissions before tool execution
2. **Input Sanitization**: Sanitize all tool inputs
3. **Audit Logging**: Log tool usage for security monitoring
4. **Rate Limiting**: Implement rate limiting for external APIs
### Performance Optimization
1. **Concurrent Execution**: Use `executeToolsConcurrently()` for independent tools
2. **Caching**: Cache frequently accessed data
3. **Monitoring**: Monitor tool performance and error rates
4. **Resource Management**: Handle external service timeouts gracefully
## Reference Documentation
For detailed API reference, examples, and advanced patterns, see:
- [API Reference](./references/references.md) - Complete API documentation
- [Implementation Patterns](./references/implementation-patterns.md) - Advanced implementation examples
- [Examples](./references/examples.md) - Practical usage examples
## Common Issues and Solutions
### Tool Not Found
**Problem**: LLM calls tools that don't exist
**Solution**: Implement hallucination handler:
```java
.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:
```java
.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:
```java
.executeToolsConcurrently(Executors.newFixedThreadPool(5))
.toolExecutionTimeout(Duration.ofSeconds(30))
```
## Related Skills
- `langchain4j-ai-services-patterns`
- `langchain4j-rag-implementation-patterns`
- `langchain4j-spring-boot-integration`
## References
- [LangChain4j Tool & Function Calling - API References](./references/references.md)
- [LangChain4j Tool & Function Calling - Implementation Patterns](./references/implementation-patterns.md)
- [LangChain4j Tool & Function Calling - Examples](./references/examples.md)

View File

@@ -0,0 +1,534 @@
# LangChain4j Tool & Function Calling - Practical Examples
Production-ready examples for tool calling and function execution patterns with LangChain4j.
## 1. Basic Tool Calling
**Scenario**: Simple tools that LLM can invoke automatically.
```java
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.model.openai.OpenAiChatModel;
class Calculator {
@Tool("Add two numbers together")
int add(@P("first number") int a, @P("second number") int b) {
return a + b;
}
@Tool("Multiply two numbers")
int multiply(@P("first number") int a, @P("second number") int b) {
return a * b;
}
@Tool("Divide two numbers")
double divide(@P("dividend") double a, @P("divisor") double b) {
if (b == 0) throw new IllegalArgumentException("Cannot divide by zero");
return a / b;
}
}
interface CalculatorAssistant {
String chat(String query);
}
public class BasicToolExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.temperature(0.0) // Deterministic for tools
.build();
var assistant = AiServices.builder(CalculatorAssistant.class)
.chatModel(chatModel)
.tools(new Calculator())
.build();
System.out.println(assistant.chat("What is 25 + 37?"));
System.out.println(assistant.chat("Calculate 12 * 8"));
System.out.println(assistant.chat("Divide 100 by 4"));
}
}
```
## 2. Multiple Tool Objects
**Scenario**: LLM selecting from multiple tool domains.
```java
class WeatherService {
@Tool("Get current weather for a city")
String getWeather(@P("city name") String city) {
// Simulate API call
return "Weather in " + city + ": 22°C, Partly cloudy";
}
@Tool("Get weather forecast for next 5 days")
String getForecast(@P("city name") String city) {
return "5-day forecast for " + city + ": Sunny, Cloudy, Rainy, Sunny, Cloudy";
}
}
class DateTimeService {
@Tool("Get current date and time")
String getCurrentDateTime() {
return LocalDateTime.now().toString();
}
@Tool("Get day of week for a date")
String getDayOfWeek(@P("date in YYYY-MM-DD format") String date) {
LocalDate localDate = LocalDate.parse(date);
return localDate.getDayOfWeek().toString();
}
}
interface MultiToolAssistant {
String help(String query);
}
public class MultipleToolsExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var assistant = AiServices.builder(MultiToolAssistant.class)
.chatModel(chatModel)
.tools(new WeatherService(), new DateTimeService())
.build();
System.out.println(assistant.help("What's the weather in Paris?"));
System.out.println(assistant.help("What time is it?"));
System.out.println(assistant.help("What day is 2024-12-25?"));
}
}
```
## 3. Tool with Complex Return Types
**Scenario**: Tools returning structured objects.
```java
class UserRecord {
public String id;
public String name;
public String email;
public LocalDate createdDate;
public UserRecord(String id, String name, String email, LocalDate createdDate) {
this.id = id;
this.name = name;
this.email = email;
this.createdDate = createdDate;
}
}
class UserService {
@Tool("Look up user information by ID")
UserRecord getUserById(@P("user ID") String userId) {
// Simulate database lookup
return new UserRecord(userId, "John Doe", "john@example.com", LocalDate.now());
}
@Tool("List all users (returns top 10)")
List<UserRecord> listUsers() {
return Arrays.asList(
new UserRecord("1", "Alice", "alice@example.com", LocalDate.now()),
new UserRecord("2", "Bob", "bob@example.com", LocalDate.now())
);
}
@Tool("Search users by name pattern")
List<UserRecord> searchByName(@P("name pattern") String pattern) {
return Arrays.asList(
new UserRecord("1", "John Smith", "john.smith@example.com", LocalDate.now())
);
}
}
interface UserAssistant {
String answer(String query);
}
public class ComplexReturnTypeExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var assistant = AiServices.builder(UserAssistant.class)
.chatModel(chatModel)
.tools(new UserService())
.build();
System.out.println(assistant.answer("Who is user 123?"));
System.out.println(assistant.answer("List all users"));
System.out.println(assistant.answer("Find users named John"));
}
}
```
## 4. Error Handling in Tools
**Scenario**: Graceful handling of tool errors.
```java
class DatabaseService {
@Tool("Execute read query on database")
String queryDatabase(@P("SQL query") String query) {
// Validate query is SELECT only
if (!query.trim().toUpperCase().startsWith("SELECT")) {
throw new IllegalArgumentException("Only SELECT queries allowed");
}
return "Query result: 42 rows returned";
}
@Tool("Get user count by status")
int getUserCount(@P("status") String status) {
if (!Arrays.asList("active", "inactive", "pending").contains(status)) {
throw new IllegalArgumentException("Invalid status: " + status);
}
return 150;
}
}
interface ResilientAssistant {
String execute(String command);
}
public class ErrorHandlingExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var assistant = AiServices.builder(ResilientAssistant.class)
.chatModel(chatModel)
.tools(new DatabaseService())
// Handle tool execution errors
.toolExecutionErrorHandler((toolCall, exception) -> {
System.err.println("Tool error in " + toolCall.name() + ": " + exception.getMessage());
return "Error: " + exception.getMessage();
})
// Handle malformed tool arguments
.toolArgumentsErrorHandler((toolCall, exception) -> {
System.err.println("Invalid arguments for " + toolCall.name());
return "Invalid arguments";
})
.build();
System.out.println(assistant.execute("Execute SELECT * FROM users"));
System.out.println(assistant.execute("How many active users?"));
}
}
```
## 5. Streaming Tool Execution
**Scenario**: Tools called during streaming responses.
```java
import dev.langchain4j.service.TokenStream;
interface StreamingToolAssistant {
TokenStream execute(String command);
}
public class StreamingToolsExample {
public static void main(String[] args) {
var streamingModel = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var assistant = AiServices.builder(StreamingToolAssistant.class)
.streamingChatModel(streamingModel)
.tools(new Calculator())
.build();
assistant.execute("Calculate (5 + 3) * 4 and explain")
.onNext(token -> System.out.print(token))
.onToolExecuted(execution ->
System.out.println("\n[Tool: " + execution.request().name() + "]"))
.onCompleteResponse(response ->
System.out.println("\n--- Complete ---"))
.onError(error -> System.err.println("Error: " + error))
.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
```
## 6. Dynamic Tool Provider
**Scenario**: Select tools dynamically based on query context.
```java
interface DynamicToolAssistant {
String help(String query);
}
class MathTools {
@Tool("Add two numbers")
int add(@P("a") int a, @P("b") int b) { return a + b; }
}
class TextTools {
@Tool("Convert text to uppercase")
String toUpper(@P("text") String text) { return text.toUpperCase(); }
@Tool("Convert text to lowercase")
String toLower(@P("text") String text) { return text.toLowerCase(); }
}
public class DynamicToolProviderExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var assistant = AiServices.builder(DynamicToolAssistant.class)
.chatModel(chatModel)
// Provide tools dynamically
.toolProvider(context -> {
if (context.userMessage().contains("math") || context.userMessage().contains("calculate")) {
return Collections.singletonList(new MathTools());
} else if (context.userMessage().contains("text") || context.userMessage().contains("convert")) {
return Collections.singletonList(new TextTools());
}
return Collections.emptyList();
})
.build();
System.out.println(assistant.help("Calculate 25 + 37"));
System.out.println(assistant.help("Convert HELLO to lowercase"));
}
}
```
## 7. Tool with Memory Context
**Scenario**: Tools accessing conversation memory.
```java
class ContextAwareDataService {
private Map<String, String> userPreferences = new HashMap<>();
@Tool("Save user preference")
void savePreference(@P("key") String key, @P("value") String value) {
userPreferences.put(key, value);
System.out.println("Saved: " + key + " = " + value);
}
@Tool("Get user preference")
String getPreference(@P("key") String key) {
return userPreferences.getOrDefault(key, "Not found");
}
@Tool("List all preferences")
Map<String, String> listPreferences() {
return new HashMap<>(userPreferences);
}
}
interface ContextAssistant {
String chat(String message);
}
public class ToolMemoryExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var dataService = new ContextAwareDataService();
var assistant = AiServices.builder(ContextAssistant.class)
.chatModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.tools(dataService)
.build();
System.out.println(assistant.chat("Remember that I like Java"));
System.out.println(assistant.chat("What do I like?"));
System.out.println(assistant.chat("Also remember I use Spring Boot"));
System.out.println(assistant.chat("What are all my preferences?"));
}
}
```
## 8. Stateful Tool Execution
**Scenario**: Tools that maintain state across calls.
```java
class StatefulCounter {
private int count = 0;
@Tool("Increment counter by 1")
int increment() {
return ++count;
}
@Tool("Decrement counter by 1")
int decrement() {
return --count;
}
@Tool("Get current counter value")
int getCount() {
return count;
}
@Tool("Reset counter to zero")
void reset() {
count = 0;
}
}
interface CounterAssistant {
String interact(String command);
}
public class StatefulToolExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var counter = new StatefulCounter();
var assistant = AiServices.builder(CounterAssistant.class)
.chatModel(chatModel)
.tools(counter)
.build();
System.out.println(assistant.interact("Increment the counter"));
System.out.println(assistant.interact("Increment again"));
System.out.println(assistant.interact("What's the current count?"));
System.out.println(assistant.interact("Reset the counter"));
System.out.println(assistant.interact("Decrement"));
}
}
```
## 9. Tool Validation and Authorization
**Scenario**: Validate and authorize tool execution.
```java
class SecureDataService {
@Tool("Get sensitive data")
String getSensitiveData(@P("data_id") String dataId) {
// This should normally check authorization
if (!dataId.matches("^[A-Z][0-9]{3}$")) {
throw new IllegalArgumentException("Invalid data ID format");
}
return "Sensitive data for " + dataId;
}
@Tool("Delete data (requires authorization)")
void deleteData(@P("data_id") String dataId) {
if (!dataId.matches("^[A-Z][0-9]{3}$")) {
throw new IllegalArgumentException("Invalid data ID");
}
System.out.println("Data " + dataId + " deleted");
}
}
interface SecureAssistant {
String execute(String command);
}
public class AuthorizationExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var assistant = AiServices.builder(SecureAssistant.class)
.chatModel(chatModel)
.tools(new SecureDataService())
.toolExecutionErrorHandler((request, exception) -> {
System.err.println("Authorization/validation failed: " + exception.getMessage());
return "Operation denied: " + exception.getMessage();
})
.build();
System.out.println(assistant.execute("Get data A001"));
System.out.println(assistant.execute("Get data invalid"));
}
}
```
## 10. Advanced: Tool Result Processing
**Scenario**: Process and transform tool results before returning to LLM.
```java
class DataService {
@Tool("Fetch user data from API")
String fetchUserData(@P("user_id") String userId) {
return "User{id=" + userId + ", name=John, role=Admin}";
}
}
interface ProcessingAssistant {
String answer(String query);
}
public class ToolResultProcessingExample {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
var assistant = AiServices.builder(ProcessingAssistant.class)
.chatModel(chatModel)
.tools(new DataService())
// Can add interceptors for tool results if needed
// This would be in a future LangChain4j version
.build();
System.out.println(assistant.answer("What is the role of user 123?"));
}
}
```
## Best Practices
1. **Clear Descriptions**: Write detailed @Tool descriptions for LLM context
2. **Strong Typing**: Use specific types (int, String) instead of generic Object
3. **Parameter Descriptions**: Use @P with clear descriptions of expected formats
4. **Error Handling**: Always implement error handlers for graceful failures
5. **Temperature**: Set temperature=0 for deterministic tool selection
6. **Validation**: Validate all parameters before execution
7. **Logging**: Log tool calls and results for debugging
8. **State Management**: Keep tools stateless or manage state explicitly
9. **Timeout**: Set timeouts on long-running tools
10. **Authorization**: Validate authorization before executing sensitive operations

View File

@@ -0,0 +1,478 @@
# LangChain4j Tool & Function Calling - Implementation Patterns
Comprehensive implementation patterns for tool and function calling with LangChain4j.
## Core Tool Definition Patterns
### Basic Tool Definition with @Tool Annotation
The `@Tool` annotation converts regular Java methods into tools that LLMs can discover and execute.
**Basic Tool Definition:**
```java
public class CalculatorTools {
@Tool("Adds two given numbers")
public double add(double a, double b) {
return a + b;
}
@Tool("Multiplies two given numbers")
public double multiply(double a, double b) {
return a * b;
}
@Tool("Calculates the square root of a given number")
public double squareRoot(double x) {
return Math.sqrt(x);
}
@Tool("Calculates power of a number")
public double power(double base, double exponent) {
return Math.pow(base, exponent);
}
}
```
**Advanced Tool with Parameter Descriptions:**
```java
public class WeatherService {
@Tool("Get current weather conditions for a specific location")
public String getCurrentWeather(@P("The name of the city or location") String location) {
try {
WeatherData weather = weatherClient.getCurrentWeather(location);
return String.format("Weather in %s: %s, %.1f°C, humidity %.0f%%, wind %.1f km/h",
location, weather.getCondition(), weather.getTemperature(),
weather.getHumidity(), weather.getWindSpeed());
} catch (Exception e) {
return "Sorry, I couldn't retrieve weather information for " + location;
}
}
}
```
### Parameter Handling and Validation
**Optional Parameters:**
```java
public class DatabaseTools {
@Tool("Search for users in the database")
public List<User> searchUsers(
@P("Search term for user name or email") String searchTerm,
@P(value = "Maximum number of results to return", required = false) Integer limit,
@P(value = "Sort order: ASC or DESC", required = false) String sortOrder) {
int actualLimit = limit != null ? limit : 10;
String actualSort = sortOrder != null ? sortOrder : "ASC";
return userRepository.searchUsers(searchTerm, actualLimit, actualSort);
}
}
```
**Complex Parameter Types:**
```java
public class OrderManagementTools {
@Description("Customer order information")
public static class OrderRequest {
@Description("Customer ID who is placing the order")
private Long customerId;
@Description("List of items to order")
private List<OrderItem> items;
@Description("Shipping address for the order")
private Address shippingAddress;
@Description("Preferred delivery date (optional)")
@JsonProperty(required = false)
private LocalDate preferredDeliveryDate;
}
@Tool("Create a new customer order")
public String createOrder(OrderRequest orderRequest) {
try {
// Validation and processing logic
Order order = orderService.createOrder(orderRequest);
return String.format("Order created successfully! Order ID: %s, Total: $%.2f",
order.getId(), order.getTotal());
} catch (Exception e) {
return "Failed to create order: " + e.getMessage();
}
}
}
```
## Memory Context Integration
### @ToolMemoryId for User Context
Tools can access conversation memory context to provide personalized and contextual responses:
```java
public class PersonalizedTools {
@Tool("Get personalized recommendations based on user preferences")
public String getRecommendations(@ToolMemoryId String userId,
@P("Type of recommendation: books, movies, restaurants") String type) {
UserPreferences prefs = preferenceService.getUserPreferences(userId);
List<String> history = historyService.getSearchHistory(userId, type);
return recommendationEngine.getRecommendations(type, prefs, history);
}
}
```
## Dynamic Tool Provisioning
### ToolProvider for Context-Aware Tools
```java
public class DynamicToolProvider implements ToolProvider {
@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
String userId = extractUserId(request);
UserPermissions permissions = permissionService.getUserPermissions(userId);
String userMessage = request.userMessage().singleText().toLowerCase();
ToolProviderResult.Builder resultBuilder = ToolProviderResult.builder();
// Always available tools
addBasicTools(resultBuilder);
// Conditional tools based on permissions
if (permissions.canAccessFinancialData()) {
addFinancialTools(resultBuilder);
}
if (permissions.canModifyUserData()) {
addUserManagementTools(resultBuilder);
}
return resultBuilder.build();
}
}
```
### Programmatic Tool Definition
```java
public class ProgrammaticToolsService {
public Map<ToolSpecification, ToolExecutor> createDatabaseTools(DatabaseConfig config) {
Map<ToolSpecification, ToolExecutor> tools = new HashMap<>();
// Query tool
ToolSpecification querySpec = ToolSpecification.builder()
.name("execute_database_query")
.description("Execute a SQL query on the database")
.parameters(JsonObjectSchema.builder()
.addStringProperty("query", "SQL query to execute")
.addBooleanProperty("readOnly", "Whether this is a read-only query")
.required("query", "readOnly")
.build())
.build();
ToolExecutor queryExecutor = (request, memoryId) -> {
Map<String, Object> args = fromJson(request.arguments());
String query = args.get("query").toString();
boolean readOnly = (Boolean) args.get("readOnly");
return databaseService.executeQuery(query, readOnly);
};
tools.put(querySpec, queryExecutor);
return tools;
}
}
```
## AI Services as Tools
AI Services can be used as tools by other AI Services, enabling hierarchical architectures:
```java
// Specialized Expert Services
interface DataAnalysisExpert {
@UserMessage("You are a data analysis expert. Analyze this data and provide insights: {{data}}")
@Tool("Expert data analysis and insights")
String analyzeData(@V("data") String data);
}
// Router Agent that delegates to experts
interface ExpertRouter {
@UserMessage("""
Analyze the user request and determine which expert(s) should handle it:
- Use the data analysis expert for data-related questions
- Use the security expert for security-related concerns
User request: {{it}}
""")
String routeToExperts(String request);
}
@Service
public class ExpertConsultationService {
public ExpertConsultationService(ChatModel chatModel) {
// Build expert services
DataAnalysisExpert dataExpert = AiServices.create(DataAnalysisExpert.class, chatModel);
// Build router with experts as tools
this.router = AiServices.builder(ExpertRouter.class)
.chatModel(chatModel)
.tools(dataExpert)
.build();
}
}
```
## Advanced Tool Patterns
### Immediate Return Tools
```java
public class DirectResponseTools {
@Tool(value = "Get current user information", returnBehavior = ReturnBehavior.IMMEDIATE)
public String getCurrentUserInfo(@ToolMemoryId String userId) {
User user = userService.findById(userId);
return String.format("""
User Information:
Name: %s
Email: %s
Role: %s
""", user.getName(), user.getEmail(), user.getRole());
}
}
```
### Concurrent Tool Execution
```java
public class ConcurrentTools {
@Tool("Get stock price for a company")
public String getStockPrice(@P("Stock symbol") String symbol) {
try {
Thread.sleep(1000);
return stockApiService.getPrice(symbol);
} catch (InterruptedException e) {
return "Error retrieving stock price";
}
}
@Tool("Get company news")
public String getCompanyNews(@P("Company symbol") String symbol) {
// Similar implementation
}
}
// Configure for concurrent execution
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.tools(new ConcurrentTools())
.executeToolsConcurrently() // Execute tools in parallel
.build();
```
## Error Handling and Resilience
### Tool Execution Error Handling
```java
public class ResilientTools {
private final CircuitBreaker circuitBreaker;
private final RetryTemplate retryTemplate;
@Tool("Get external data with resilience patterns")
public String getExternalData(@P("Data source identifier") String sourceId) {
return circuitBreaker.executeSupplier(() -> {
return retryTemplate.execute(context -> {
try {
return externalApiService.fetchData(sourceId);
} catch (ApiException e) {
if (e.isRetryable()) {
throw e; // Will be retried
}
return "Data temporarily unavailable: " + e.getMessage();
}
});
});
}
}
```
### Graceful Degradation
```java
public class FallbackTools {
@Tool("Get weather information with fallback providers")
public String getWeather(@P("Location name") String location) {
// Try primary provider first
for (DataProvider provider : dataProviders) {
try {
WeatherData weather = provider.getWeather(location);
if (weather != null) {
return formatWeather(weather, provider.getName());
}
} catch (Exception e) {
// Continue to next provider
}
}
return "Weather information is currently unavailable for " + location;
}
}
```
## Streaming and Tool Execution
### Streaming with Tool Callbacks
```java
interface StreamingToolAssistant {
TokenStream chat(String message);
}
StreamingToolAssistant assistant = AiServices.builder(StreamingToolAssistant.class)
.streamingChatModel(streamingChatModel)
.tools(new CalculatorTools(), new WeatherService())
.build();
TokenStream stream = assistant.chat("What's the weather in Paris and calculate 15 + 27?");
stream
.onToolExecuted(toolExecution -> {
System.out.println("Tool executed: " + toolExecution.request().name());
System.out.println("Result: " + toolExecution.result());
})
.onPartialResponse(partialResponse -> {
System.out.print(partialResponse);
})
.start();
```
### Accessing Tool Execution Results
```java
interface AnalyticsAssistant {
Result<String> analyze(String request);
}
AnalyticsAssistant assistant = AiServices.builder(AnalyticsAssistant.class)
.chatModel(chatModel)
.tools(new DataAnalysisTools(), new DatabaseTools())
.build();
Result<String> result = assistant.analyze("Analyze sales data for Q4 2023");
// Access the response
String response = result.content();
// Access tool execution details
List<ToolExecution> toolExecutions = result.toolExecutions();
for (ToolExecution execution : toolExecutions) {
System.out.println("Tool: " + execution.request().name());
System.out.println("Duration: " + execution.duration().toMillis() + "ms");
}
```
## Complete Tool-Enabled Application
### Spring Boot Integration
```java
@RestController
@RequestMapping("/api/assistant")
@RequiredArgsConstructor
public class ToolAssistantController {
private final ToolEnabledAssistant assistant;
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
try {
Result<String> result = assistant.chat(request.getUserId(), request.getMessage());
ChatResponse response = ChatResponse.builder()
.response(result.content())
.toolsUsed(extractToolNames(result.toolExecutions()))
.tokenUsage(result.tokenUsage())
.build();
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.badRequest().body(
ChatResponse.error("Error processing request: " + e.getMessage())
);
}
}
}
interface ToolEnabledAssistant {
Result<String> chat(@MemoryId String userId, String message);
List<ToolInfo> getAvailableTools(String userId);
}
```
## Performance Optimization
### Tool Performance Monitoring
```java
@Component
public class ToolPerformanceMonitor {
@EventListener
public void handleToolExecution(ToolExecutionEvent event) {
// Record execution metrics
Timer.Sample sample = Timer.start(meterRegistry);
sample.stop(Timer.builder("tool.execution.duration")
.tag("tool", event.getToolName())
.tag("success", String.valueOf(event.isSuccessful()))
.register(meterRegistry));
// Record error rates
if (!event.isSuccessful()) {
meterRegistry.counter("tool.execution.errors",
"tool", event.getToolName(),
"error_type", event.getErrorType())
.increment();
}
}
}
```
## Testing Framework
```java
@Component
public class ToolTestingFramework {
public ToolValidationResult validateTool(Object toolInstance, String methodName) {
try {
TestAssistant testAssistant = AiServices.builder(TestAssistant.class)
.chatModel(testChatModel)
.tools(toolInstance)
.build();
String response = testAssistant.testTool(methodName);
return ToolValidationResult.builder()
.toolName(methodName)
.isValid(response != null && !response.contains("Error"))
.response(response)
.build();
} catch (Exception e) {
return ToolValidationResult.builder()
.toolName(methodName)
.isValid(false)
.error(e.getMessage())
.build();
}
}
}
```

View File

@@ -0,0 +1,402 @@
# LangChain4j Tool & Function Calling - API References
Complete API reference for tool and function calling with LangChain4j.
## Tool Definition
### @Tool Annotation
**Purpose**: Mark methods that LLM can call.
```java
@Tool(value = "Description of what this tool does")
ReturnType methodName(ParameterType param) {
// Implementation
}
// Examples
@Tool("Add two numbers together")
int add(int a, int b) { return a + b; }
@Tool("Query database for user information")
User getUserById(String userId) { ... }
@Tool("Send email to recipient")
void sendEmail(String to, String subject, String body) { ... }
```
### @P Annotation
**Purpose**: Describe tool parameters for LLM understanding.
```java
@Tool("Transfer money between accounts")
void transfer(
@P("source account ID") String fromAccount,
@P("destination account ID") String toAccount,
@P("amount in dollars") double amount
) { ... }
```
## Builder Configuration
### AiServices Builder Extensions for Tools
```java
AiServices.builder(AssistantInterface.class)
// Register tool objects
.tools(Object... tools) // Multiple tool objects
.tools(new Calculator()) // Single tool
.tools(new Calculator(), new DataService()) // Multiple
// Dynamic tool provider
.toolProvider(ToolProvider toolProvider)
// Error handlers
.toolExecutionErrorHandler(ToolExecutionErrorHandler)
.toolArgumentsErrorHandler(ToolArgumentsErrorHandler)
.build();
```
## Error Handlers
### ToolExecutionErrorHandler
**Purpose**: Handle errors during tool execution.
```java
@FunctionalInterface
interface ToolExecutionErrorHandler {
String handle(ToolExecutionRequest request, Throwable exception);
}
// Usage
.toolExecutionErrorHandler((request, exception) -> {
logger.error("Tool " + request.name() + " failed", exception);
return "Error executing " + request.name() + ": " + exception.getMessage();
})
```
### ToolArgumentsErrorHandler
**Purpose**: Handle errors in tool argument parsing/validation.
```java
@FunctionalInterface
interface ToolArgumentsErrorHandler {
String handle(ToolExecutionRequest request, Throwable exception);
}
// Usage
.toolArgumentsErrorHandler((request, exception) -> {
logger.warn("Invalid arguments for " + request.name());
return "Invalid arguments provided";
})
```
## Tool Provider
### ToolProvider Interface
**Purpose**: Dynamically select tools based on context.
```java
@FunctionalInterface
interface ToolProvider {
List<Object> getTools(ToolProviderContext context);
}
// Context available
interface ToolProviderContext {
UserMessage userMessage();
List<ChatMessage> messages();
}
```
### Dynamic Tool Selection
```java
.toolProvider(context -> {
String message = context.userMessage().singleText();
if (message.contains("calculate")) {
return Arrays.asList(new Calculator());
} else if (message.contains("weather")) {
return Arrays.asList(new WeatherService());
} else {
return Collections.emptyList();
}
})
```
## Tool Execution Models
### ToolExecutionRequest
```java
interface ToolExecutionRequest {
String name(); // Tool name from @Tool
String description(); // Tool description
Map<String, String> arguments(); // Tool arguments
}
```
### ToolExecution (for streaming)
```java
class ToolExecution {
ToolExecutionRequest request(); // The tool being executed
String result(); // Execution result
}
```
## Return Types
### Supported Return Types
**Primitives**:
```java
@Tool("Add numbers")
int add(@P("a") int x, @P("b") int y) { return x + y; }
@Tool("Compare values")
boolean isGreater(@P("a") int x, @P("b") int y) { return x > y; }
@Tool("Get temperature")
double getTemp() { return 22.5; }
```
**String**:
```java
@Tool("Get greeting")
String greet(@P("name") String name) { return "Hello " + name; }
```
**Objects (will be converted to String)**:
```java
@Tool("Get user")
User getUser(@P("id") String id) { return new User(id); }
@Tool("Get user list")
List<User> listUsers() { return userService.getAll(); }
```
**Collections**:
```java
@Tool("Search documents")
List<Document> search(@P("query") String q) { return results; }
@Tool("Get key-value pairs")
Map<String, String> getConfig() { return config; }
```
**Void**:
```java
@Tool("Send notification")
void notify(@P("message") String msg) {
notificationService.send(msg);
}
```
## Parameter Types
### Supported Parameter Types
**Primitives**:
```java
int, long, float, double, boolean, byte, short, char
```
**Strings and wrapper types**:
```java
String, Integer, Long, Float, Double, Boolean
```
**Collections**:
```java
List<String>, Set<Integer>, Collection<T>
```
**Custom objects** (must have toString() that's meaningful):
```java
@Tool("Process data")
void process(CustomData data) { ... }
```
**Dates and times**:
```java
@Tool("Get events for date")
List<Event> getEvents(LocalDate date) { ... }
@Tool("Schedule for time")
void schedule(LocalDateTime when) { ... }
```
## Annotation Combinations
### Complete Tool Definition
```java
class DataService {
// Basic tool
@Tool("Get user information")
User getUser(@P("user ID") String userId) { ... }
// Tool with multiple params
@Tool("Search users by criteria")
List<User> search(
@P("first name") String firstName,
@P("last name") String lastName,
@P("department") String dept
) { ... }
// Tool returning collection
@Tool("List all active users")
List<User> getActiveUsers() { ... }
// Tool with void return
@Tool("Archive old records")
void archiveOldRecords(@P("older than days") int days) { ... }
// Tool with complex return
@Tool("Get detailed report")
Map<String, Object> generateReport(@P("month") int month) { ... }
}
```
## Best Practices for API Usage
### Tool Design
1. **Descriptive Names**: Use clear, actionable names
```java
// Good
@Tool("Get current weather for a city")
String getWeather(String city) { ... }
// Avoid
@Tool("Get info")
String getInfo(String x) { ... }
```
2. **Parameter Descriptions**: Be specific about formats
```java
// Good
@Tool("Calculate date difference")
long daysBetween(
@P("start date in YYYY-MM-DD format") String start,
@P("end date in YYYY-MM-DD format") String end
) { ... }
// Avoid
@Tool("Calculate difference")
long calculate(@P("date1") String d1, @P("date2") String d2) { ... }
```
3. **Appropriate Return Types**: Return what LLM can use
```java
// Good - LLM can interpret
@Tool("Get user role")
String getUserRole(String userId) { return "admin"; }
// Avoid - hard to parse
@Tool("Get user info")
User getUser(String id) { ... } // Will convert to toString()
```
4. **Error Messages**: Provide actionable errors
```java
.toolExecutionErrorHandler((request, exception) -> {
if (exception instanceof IllegalArgumentException) {
return "Invalid argument: " + exception.getMessage();
}
return "Error executing " + request.name();
})
```
### Common Patterns
**Validation Pattern**:
```java
@Tool("Create user")
String createUser(@P("email") String email) {
if (!email.contains("@")) {
throw new IllegalArgumentException("Invalid email format");
}
return "User created: " + email;
}
```
**Batch Pattern**:
```java
@Tool("Bulk delete users")
String deleteUsers(@P("user IDs comma-separated") String userIds) {
List<String> ids = Arrays.asList(userIds.split(","));
return "Deleted " + ids.size() + " users";
}
```
**Async Pattern** (synchronous wrapper):
```java
@Tool("Submit async task")
String submitTask(@P("task name") String name) {
// Internally async, but returns immediately
taskExecutor.submitAsync(name);
return "Task " + name + " submitted";
}
```
## Integration with AiServices
### Complete Setup
```java
interface Assistant {
String execute(String command);
}
public class Setup {
public static void main(String[] args) {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.temperature(0.0) // Deterministic
.build();
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
// Register tools
.tools(
new Calculator(),
new WeatherService(),
new UserDataService()
)
// Error handling
.toolExecutionErrorHandler((request, exception) -> {
System.err.println("Tool error: " + exception.getMessage());
return "Tool failed";
})
// Optional: memory for context
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
// Use the assistant
String result = assistant.execute("What is the weather in Paris?");
System.out.println(result);
}
}
```
## Resource Links
- [LangChain4j Tools Documentation](https://docs.langchain4j.dev/features/tools)
- [Agent Tutorial](https://docs.langchain4j.dev/tutorials/agents)
- [GitHub Examples](https://github.com/langchain4j/langchain4j-examples)
- [OpenAI Function Calling](https://platform.openai.com/docs/guides/function-calling)