Initial commit
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user