Hands‑On Java Function Calling with Spring AI: Build an Intelligent Customer Service Bot
This article explains how Function Calling lets large language models invoke Java methods via Spring AI, walks through the four‑step workflow, shows declarative and programmatic tool definitions, and demonstrates a complete customer‑service chatbot with code examples and best‑practice guidelines.
RAG enables large models to read private documents ("knowing"), but the next challenge is enabling them to "do"; Function Calling serves as the bridge that lets a model request execution of Java code.
1. What is Function Calling?
Function Calling allows a large model to generate a JSON payload containing a function name and parameters, which Spring AI then parses and invokes the corresponding Java method.
User query → model analyzes intent.
Model decision → decides which tool to call, produces JSON with function name and arguments.
Application execution → Spring AI parses the JSON and calls the Java method.
Result return → execution result is sent back to the model, which generates the final reply.
Key insight: the model can only request a tool; the actual execution is performed by your Java code, which is a crucial security design.
2. Application Scenarios
Information retrieval : fetch real‑time data such as weather, database queries, or external APIs.
Operation execution : perform actions like sending emails, creating orders, or setting alarms.
3. Defining Tools in Spring AI
3.1 Declarative way – @Tool annotation
Recommended for its simplicity. Example:
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class DateTimeTools {
@Tool(description = "获取用户时区的当前日期和时间")
String getCurrentDateTime() {
return LocalDateTime.now().toString();
}
@Tool(description = "为指定时间设置闹钟,时间格式为 ISO-8601")
void setAlarm(@ToolParam(description = "ISO-8601 格式的时间") String time) {
System.out.println("⏰ 闹钟已设置为 " + time);
// 实际可调用闹钟服务
}
}Key parameters of @Tool:
name : tool name, defaults to the method name and must be unique per request.
description : detailed description that the model uses to decide whether to call the tool.
returnDirect : whether the result is returned directly to the client without passing back to the model. @ToolParam annotates method parameters to help the model extract values.
3.2 Programmatic way – FunctionToolCallback
Use when finer control is needed:
import org.springframework.ai.model.function.FunctionToolCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FunctionCallbackConfig {
@Bean
public FunctionToolCallback weatherFunctionCallback() {
return FunctionToolCallback.builder()
.description("获取指定城市的天气信息")
.function("getWeather", (WeatherRequest req) -> {
// 业务逻辑
return new WeatherResponse(req.city(), "晴", "25°C");
})
.inputType(WeatherRequest.class)
.build();
}
}3.3 Registering Tools with ChatClient
Runtime (single‑request) tool:
String response = ChatClient.create(chatModel)
.prompt("明天是星期几?")
.tools(new DateTimeTools()) // 仅本次可用
.call()
.content();Default (global) tool:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools()) // 默认启用
.build();3.4 Bean‑name registration
If a tool is defined as a Spring Bean, reference it by name:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Description;
import java.util.function.Function;
@Description("Get the weather in location")
@Bean
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
return request -> {
// 业务逻辑
return new WeatherResponse(request.location(), "25°C", "晴朗");
};
}4. Full Hands‑On: Intelligent Customer Service Assistant
4.1 Tool definitions
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class CustomerServiceTools {
private static final Map<String, String> ORDERS = Map.of(
"1001", "已发货,预计明天送达",
"1002", "处理中",
"1003", "已签收"
);
@Tool(description = "查询订单状态,参数为订单号")
public String queryOrder(@ToolParam(description = "订单号,如 1001") String orderId) {
if (orderId == null || orderId.trim().isEmpty()) {
return "请提供有效的订单号";
}
String result = ORDERS.get(orderId);
return result != null ? "订单 " + orderId + ":" + result : "未找到订单 " + orderId;
}
@Tool(description = "获取退款政策信息")
public String getRefundPolicy() {
return """
1. 7天无理由退货(不影响二次销售)
2. 15天质量问题换货
3. 1年质保期内免费维修
""";
}
@Tool(description = "转接人工客服")
public String transferToHuman() {
return "已为您转接人工客服,预计等待 2-3 分钟";
}
}4.2 Agent service
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class CustomerServiceAgent {
private final ChatClient chatClient;
public CustomerServiceAgent(ChatClient.Builder builder, CustomerServiceTools tools) {
this.chatClient = builder
.defaultSystem("""
你是一个专业的客服助手,可以调用工具来处理用户问题。
可用工具:
- queryOrder:查询订单状态
- getRefundPolicy:获取退款政策
- transferToHuman:转接人工客服
请友好、专业地帮助用户。
""")
.defaultTools(tools)
.build();
}
public String chat(String userInput) {
return chatClient.prompt(userInput).call().content();
}
}4.3 Controller
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/customer")
public class CustomerController {
private final CustomerServiceAgent agent;
public CustomerController(CustomerServiceAgent agent) {
this.agent = agent;
}
@PostMapping("/chat")
public Map<String, String> chat(@RequestBody Map<String, String> request) {
String response = agent.chat(request.get("message"));
return Map.of("response", response);
}
}4.4 Testing with curl
# Test order query
curl -X POST http://localhost:8080/api/customer/chat \
-H "Content-Type: application/json" \
-d '{"message":"查询订单1001"}'
# Test refund policy
curl -X POST http://localhost:8080/api/customer/chat \
-H "Content-Type: application/json" \
-d '{"message":"怎么退货?"}'
# Test transfer to human
curl -X POST http://localhost:8080/api/customer/chat \
-H "Content-Type: application/json" \
-d '{"message":"我要找人工客服"}'5. Best Practices
5.1 Write detailed tool descriptions
The model’s decision relies entirely on the description; use specific wording such as "获取指定城市的当前天气信息" instead of a vague "获取数据".
5.2 Annotate parameters with @ToolParam
This helps the model correctly extract argument values.
5.3 Implement robust error handling
Tools should catch exceptions and return friendly error messages rather than letting the dialogue break.
5.4 Keep tool responsibilities single‑purpose
Design fine‑grained tools so the model can combine them flexibly.
5.5 Consider security
Since the model can only request tool execution, enforce permission checks and audit logs in the Java code.
💡代码地址在第一期获取
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
