535 lines
16 KiB
Markdown
535 lines
16 KiB
Markdown
# 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
|