In last tutorial we saw single-step agent example. Real-world agents often need to perform multiple dependent operations. An agent may first retrieve data and then pass that data into another function.
This tutorial shows an agent calling one function to obtain system time and another function to convert that value into a human-readable UTC timestamp.
Example
Creating Tools
import dev.langchain4j.agent.tool.Tool;
import java.time.Instant;
public class SystemTools {
@Tool("Returns the current system time in milliseconds")
public long systemMillis() {
return System.currentTimeMillis();
}
@Tool("Converts epoch milliseconds to a UTC timestamp")
public String formatMillis(long millis) {
return Instant.ofEpochMilli(millis).toString();
}
}
Multi-Tool Agent Example
package com.logicbig.example;
import dev.langchain4j.agent.tool.ToolExecutionRequest;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.agent.tool.ToolSpecifications;
import dev.langchain4j.data.message.*;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.service.tool.DefaultToolExecutor;
import dev.langchain4j.service.tool.ToolExecutor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AgentExample {
private static final Map<String, ToolExecutor> executors = new HashMap<>();
private static final List<ToolSpecification> specs = new ArrayList<>();
static {
SystemTools tools = new SystemTools();
for (Method method : SystemTools.class.getDeclaredMethods()) {
ToolSpecification spec = ToolSpecifications.toolSpecificationFrom(method);
specs.add(spec);
executors.put(spec.name(), new DefaultToolExecutor(tools, method));
}
}
public static void main(String[] args) {
ChatModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.2:latest")
.temperature(0.0)
.numCtx(4096)
.build();
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from(
"You are a helpful assistant with access to tools. " +
"You may call multiple tools in sequence. " +
"Use tool outputs as inputs to subsequent tools when needed."
));
sendUserMessageAndHandleToolCall(model, "What is the current system time?", messages);
sendUserMessageAndHandleToolCall(model, "Now convert the system time into "
+ "a human-readable UTC format.", messages);
//final message
messages.add(model.chat(messages).aiMessage());
for (ChatMessage chatMessage : messages) {
System.out.println("-- %s --".formatted(chatMessage.type()));
switch (chatMessage.type()) {
case SYSTEM -> System.out.println(((SystemMessage) chatMessage).text());
case USER -> System.out.println(((UserMessage) chatMessage).singleText());
case TOOL_EXECUTION_RESULT ->
System.out.println(((ToolExecutionResultMessage) chatMessage).text());
case AI -> {
AiMessage aiMessage = (AiMessage) chatMessage;
if(aiMessage.text()!=null){
System.out.println(aiMessage.text());
}
if(aiMessage.hasToolExecutionRequests()){
System.out.println(aiMessage.toolExecutionRequests());
}
}
}
}
}
private static void sendUserMessageAndHandleToolCall(ChatModel chatModel,
String myMessage,
List<ChatMessage> messages) {
messages.add(UserMessage.from(myMessage));
ChatRequest request = ChatRequest.builder()
.messages(messages)
.toolSpecifications(specs)
.build();
ChatResponse response = chatModel.chat(request);
AiMessage aiMessage = response.aiMessage();
messages.add(aiMessage);
if (aiMessage.hasToolExecutionRequests()) {
for (ToolExecutionRequest r : aiMessage.toolExecutionRequests()) {
String result = executors.get(r.name()).execute(r, r.id());
messages.add(ToolExecutionResultMessage.from(r, result));
}
}
}
}
Output-- SYSTEM -- You are a helpful assistant with access to tools. You may call multiple tools in sequence. Use tool outputs as inputs to subsequent tools when needed. -- USER -- What is the current system time? -- AI -- [ToolExecutionRequest { id = null, name = "systemMillis", arguments = "{}" }] -- TOOL_EXECUTION_RESULT -- 1769091187617 -- USER -- Now convert the system time into a human-readable UTC format. -- AI -- [ToolExecutionRequest { id = null, name = "formatMillis", arguments = "{"arg0":1769091187617}" }] -- TOOL_EXECUTION_RESULT -- 2026-01-22T14:13:07.617Z -- AI -- This is the current system time in a human-readable UTC format.
Understanding the Code
The model first calls a tool that returns epoch milliseconds. It then uses that output as an argument to a second tool. The application executes both tools and feeds their results back to the model.
Conclusion
The final output confirms that the agent was able to chain function calls and reuse intermediate results. This pattern is essential for building non-trivial agents in LangChain4j.
Example ProjectDependencies and Technologies Used: - langchain4j 1.10.0 (Build LLM-powered applications in Java: chatbots, agents, RAG, and much more)
- langchain4j-ollama 1.10.0 (LangChain4j :: Integration :: Ollama)
- JDK 21
- Maven 3.9.11
|