上一篇我们从零实现了Java AI Agent的核心框架,但很多同学反馈:Agent框架跑起来了,但工具调用这块问题一大堆------参数传错、工具超时、调用失败不知道怎么重试、返回结果大模型看不懂。
这很正常。90%的人写Function Calling,只写了"把参数传给工具执行"这一步,剩下的参数校验、错误重试、超时控制、结果格式化,全是空白。 Demo能跑,一上生产全是bug。
今天把Function Calling的生产级实现讲透,从工具定义到异常处理,每个环节都给完整Java代码,做完直接写进简历。
一、先搞懂:Function Calling到底是什么?
很多人以为Function Calling是大模型直接执行代码,完全不是。
Function Calling的本质是一个协议:
- 你把工具的名称、描述、参数定义(JSON Schema)告诉大模型
- 大模型根据用户问题,自主判断是否需要调用工具、调用哪个、传什么参数
- 大模型返回的不是执行结果,而是一个"调用指令"(工具名+参数JSON)
- 真正执行工具的是你的后端代码,执行完把结果回传给大模型
- 大模型根据工具结果,生成自然语言回答用户
json
用户:北京今天天气怎么样?
↓
大模型(思考):需要调用天气查询工具
↓
大模型返回:{"tool":"get_weather","args":{"city":"北京"}}
↓
你的后端执行:调用天气API,拿到结果{"temp":"25℃","weather":"晴"}
↓
结果回传大模型
↓
大模型回答:北京今天晴,气温25℃,适合出行。
关键点:大模型不执行任何代码,它只做决策------决定调什么工具、传什么参数。执行是你后端的事,所以生产级的容错全在你这边。
二、生产级工具定义:不只是名称和描述
很多人定义工具只写个名称和描述,结果大模型经常传错参数、调错工具。生产级的工具定义需要3层信息。
第1层:工具元信息(给大模型看的)
java
package com.aiproject.agent.function;
import java.lang.annotation.*;
/**
* 工具定义注解
* 生产级工具定义需要:名称、描述、参数Schema、超时时间、是否需要重试
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionTool {
String name(); // 工具名称(大模型调用用)
String description(); // 工具描述(大模型根据描述决定是否调用,写清楚)
String parameters(); // JSON Schema格式的参数定义
long timeoutMs() default 30000; // 工具执行超时时间,默认30秒
int maxRetry() default 2; // 失败最大重试次数,默认2次
boolean critical() default false; // 是否关键工具(关键工具失败直接终止,非关键降级)
}
第2层:参数Schema(大模型传参的依据)
参数定义必须用JSON Schema写清楚,大模型才知道传什么类型、哪些是必填。
java
// 示例:天气查询工具的参数Schema
String PARAMS_SCHEMA = """
{
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,必须是中文城市名,如北京、上海、广州"
},
"date": {
"type": "string",
"description": "查询日期,格式YYYY-MM-DD,默认今天",
"default": "today"
}
},
"required": ["city"]
}
""";
坑:参数description写得越清楚,大模型传错的概率越低。比如"city"不要只写"城市",要写"城市名称,必须是中文城市名,如北京、上海"。
第3层:工具执行器(真正干活的)
java
package com.aiproject.agent.function;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aiproject.agent.core.LLMClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.*;
/**
* 工具执行器
* 生产级实现:参数校验 + 超时控制 + 失败重试 + 异常降级
*/
@Slf4j
@Component
public class FunctionExecutor {
@Resource
private FunctionRegistry registry;
/** 工具执行线程池,和业务线程池隔离 */
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 50, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(200),
r -> {
Thread t = new Thread(r, "function-executor");
t.setDaemon(true);
return t;
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
/**
* 执行工具调用(生产级完整流程)
*/
public ToolResult execute(String toolName, String argumentsJson) {
// 1. 查找工具
FunctionRegistry.FunctionMethod function = registry.get(toolName);
if (function == null) {
return ToolResult.fail("工具不存在: " + toolName);
}
// 2. 参数校验
ValidationResult validation = validateParams(function, argumentsJson);
if (!validation.isValid()) {
// 参数校验失败,返回明确的错误信息,大模型会根据错误修正参数重试
return ToolResult.fail("参数校验失败: " + validation.getErrorMsg()
+ ",请修正参数后重新调用。正确的参数格式: " + function.getParameters());
}
// 3. 带超时+重试执行
int maxRetry = function.getAnnotation().maxRetry();
for (int attempt = 1; attempt <= maxRetry + 1; attempt++) {
try {
return executeWithTimeout(function, argumentsJson, function.getAnnotation().timeoutMs());
} catch (TimeoutException e) {
log.warn("工具[{}]执行超时,第{}次尝试", toolName, attempt);
if (attempt <= maxRetry) {
continue; // 重试
}
return handleFailure(function, "工具执行超时(" + function.getAnnotation().timeoutMs() + "ms)");
} catch (Exception e) {
log.warn("工具[{}]执行异常,第{}次尝试: {}", toolName, attempt, e.getMessage());
if (attempt <= maxRetry && isRetryable(e)) {
continue; // 可重试异常才重试
}
return handleFailure(function, "工具执行异常: " + e.getMessage());
}
}
return ToolResult.fail("工具执行失败");
}
/**
* 带超时控制的执行
*/
private ToolResult executeWithTimeout(FunctionRegistry.FunctionMethod function,
String argumentsJson, long timeoutMs) throws Exception {
Future<ToolResult> future = executor.submit(() -> {
try {
Method method = function.getMethod();
Object result = method.invoke(function.getInstance(), argumentsJson);
// 结果格式化:确保返回字符串,且不超过长度限制
String formatted = formatResult(result);
return ToolResult.success(formatted);
} catch (Exception e) {
throw e;
}
});
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
}
/**
* 参数校验
*/
private ValidationResult validateParams(FunctionRegistry.FunctionMethod function, String argumentsJson) {
try {
JSONObject args = JSON.parseObject(argumentsJson);
// 解析JSON Schema,校验必填字段和类型
JSONObject schema = JSON.parseObject(function.getParameters());
JSONObject properties = schema.getJSONObject("properties");
// 必填字段校验
if (schema.containsKey("required")) {
for (String required : schema.getJSONArray("required").toJavaList(String.class)) {
if (!args.containsKey(required) || args.getString(required).isEmpty()) {
return ValidationResult.fail("缺少必填参数: " + required);
}
}
}
// 类型校验(简化版,实际使用时做完整类型转换)
for (Map.Entry<String, Object> entry : properties.entrySet()) {
String propName = entry.getKey();
if (args.containsKey(propName)) {
// 可以在这里做类型校验和值范围校验
}
}
return ValidationResult.ok();
} catch (Exception e) {
return ValidationResult.fail("参数JSON格式错误: " + e.getMessage());
}
}
/**
* 结果格式化:限制长度,避免工具结果太长撑爆Token
*/
private String formatResult(Object result) {
if (result == null) return "执行成功,无返回数据";
String str = result.toString();
// 工具结果超过2000字符截断,避免Token爆炸
if (str.length() > 2000) {
return str.substring(0, 2000) + "\n...(结果过长,已截断)";
}
return str;
}
/**
* 判断是否可重试异常
*/
private boolean isRetryable(Exception e) {
// 网络超时、连接拒绝等可重试;参数错误、业务异常不可重试
String msg = e.getMessage() == null ? "" : e.getMessage();
return msg.contains("timeout") || msg.contains("connect")
|| msg.contains("Connection refused") || e instanceof java.net.SocketException;
}
/**
* 失败处理:关键工具失败终止,非关键工具降级
*/
private ToolResult handleFailure(FunctionRegistry.FunctionMethod function, String errorMsg) {
if (function.getAnnotation().critical()) {
// 关键工具失败,返回明确错误,Agent终止
return ToolResult.fail("关键工具[" + function.getName() + "]执行失败: " + errorMsg
+ ",无法继续完成任务,请告知用户。");
} else {
// 非关键工具失败,降级返回,Agent可以继续用其他方式回答
return ToolResult.fail("工具[" + function.getName() + "]执行失败: " + errorMsg
+ ",可以尝试用其他方式回答用户问题,或告知用户该功能暂时不可用。");
}
}
// ===== 内部类 =====
public static class ToolResult {
private boolean success;
private String data;
public static ToolResult success(String data) {
ToolResult r = new ToolResult();
r.success = true;
r.data = data;
return r;
}
public static ToolResult fail(String error) {
ToolResult r = new ToolResult();
r.success = false;
r.data = error;
return r;
}
public boolean isSuccess() { return success; }
public String getData() { return data; }
}
private static class ValidationResult {
private boolean valid;
private String errorMsg;
public static ValidationResult ok() {
ValidationResult r = new ValidationResult();
r.valid = true;
return r;
}
public static ValidationResult fail(String msg) {
ValidationResult r = new ValidationResult();
r.valid = false;
r.errorMsg = msg;
return r;
}
public boolean isValid() { return valid; }
public String getErrorMsg() { return errorMsg; }
}
}
这个执行器包含了5个生产级细节:
- 参数校验:大模型传错参数时,返回明确的错误信息+正确格式,大模型会自动修正重试
- 超时控制:每个工具独立超时,用Future.get()实现,避免工具卡死拖垮Agent
- 失败重试:可重试异常(超时、网络错误)自动重试,参数错误不重试
- 结果截断:工具结果超过2000字符自动截断,防止Token爆炸
- 关键/非关键降级:关键工具失败终止任务,非关键工具失败降级,Agent继续用其他方式回答
三、工具注册中心:支持注解自动注册
java
package com.aiproject.agent.function;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 工具注册中心
* 扫描所有@FunctionTool注解的方法,自动注册
*/
@Slf4j
@Component
public class FunctionRegistry implements BeanPostProcessor {
private final Map<String, FunctionMethod> functions = new ConcurrentHashMap<>();
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
for (Method method : bean.getClass().getMethods()) {
FunctionTool annotation = method.getAnnotation(FunctionTool.class);
if (annotation != null) {
register(annotation.name(), annotation.description(), annotation.parameters(),
annotation, bean, method);
log.info("注册Agent工具: {} - {}", annotation.name(), annotation.description());
}
}
return bean;
}
private void register(String name, String description, String parameters,
FunctionTool annotation, Object instance, Method method) {
if (functions.containsKey(name)) {
log.warn("工具名重复: {},将被覆盖", name);
}
functions.put(name, new FunctionMethod(name, description, parameters, annotation, instance, method));
}
public FunctionMethod get(String name) {
return functions.get(name);
}
public Map<String, FunctionMethod> getAll() {
return functions;
}
/**
* 获取所有工具定义(转为大模型需要的格式)
*/
public java.util.List<com.alibaba.fastjson.JSONObject> getToolDefinitions() {
java.util.List<com.alibaba.fastjson.JSONObject> list = new java.util.ArrayList<>();
for (FunctionMethod fm : functions.values()) {
com.alibaba.fastjson.JSONObject tool = new com.alibaba.fastjson.JSONObject();
tool.put("type", "function");
com.alibaba.fastjson.JSONObject function = new com.alibaba.fastjson.JSONObject();
function.put("name", fm.name);
function.put("description", fm.description);
function.put("parameters", com.alibaba.fastjson.JSON.parseObject(fm.parameters));
tool.put("function", function);
list.add(tool);
}
return list;
}
public static class FunctionMethod {
String name;
String description;
String parameters;
FunctionTool annotation;
Object instance;
Method method;
public FunctionMethod(String name, String description, String parameters,
FunctionTool annotation, Object instance, Method method) {
this.name = name;
this.description = description;
this.parameters = parameters;
this.annotation = annotation;
this.instance = instance;
this.method = method;
}
public String getName() { return name; }
public String getParameters() { return parameters; }
public FunctionTool getAnnotation() { return annotation; }
public Method getMethod() { return method; }
public Object getInstance() { return instance; }
}
}
四、一个生产级工具示例:知识库检索工具
光有框架不够,看一个真实的生产级工具怎么写:
java
package com.aiproject.agent.tools;
import com.aiproject.agent.function.FunctionTool;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 知识库检索工具
* 生产级实现:参数校验、结果格式化、异常处理
*/
@Slf4j
@Component
public class KnowledgeBaseTool {
@Resource
private VectorSearchService vectorSearchService; // 你的向量检索服务
/**
* 知识库检索
*/
@FunctionTool(
name = "search_knowledge_base",
description = "从企业知识库中检索相关文档片段,用于回答用户的业务问题。" +
"当用户询问公司制度、产品文档、技术规范等内部知识时使用此工具。",
parameters = """
{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "检索查询语句,应该是具体的问题或关键词,而不是泛泛的描述"
},
"top_k": {
"type": "integer",
"description": "返回最相关的文档数量,默认3,最多10",
"default": 3
}
},
"required": ["query"]
}
""",
timeoutMs = 10000, // 检索10秒超时
maxRetry = 1, // 检索失败重试1次
critical = false // 非关键工具,失败可降级
)
public String search(String argumentsJson) {
try {
JSONObject args = JSON.parseObject(argumentsJson);
String query = args.getString("query");
Integer topK = args.getInteger("top_k");
if (topK == null || topK < 1 || topK > 10) topK = 3;
// 调用向量检索服务
List<String> results = vectorSearchService.search(query, topK);
if (results == null || results.isEmpty()) {
return "知识库中未找到与[" + query + "]相关的内容,请尝试换个关键词检索,或告知用户知识库中暂无相关信息。";
}
// 结果格式化:编号+内容,方便大模型理解
StringBuilder sb = new StringBuilder();
sb.append("从知识库中检索到").append(results.size()).append("条相关内容:\n");
for (int i = 0; i < results.size(); i++) {
sb.append("【文档").append(i + 1).append("】").append(results.get(i)).append("\n");
}
return sb.toString();
} catch (Exception e) {
log.error("知识库检索异常", e);
throw new RuntimeException("知识库检索异常: " + e.getMessage());
}
}
}
五、Agent执行器集成Function Calling
把工具执行器集成到Agent执行器中,替换上一篇的简化版:
java
package com.aiproject.agent.core;
import com.aiproject.agent.function.FunctionExecutor;
import com.aiproject.agent.function.FunctionRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* Agent执行器(生产级版)
* 集成Function Calling完整流程:工具调用→执行→结果回传→继续推理
*/
@Slf4j
@Component
public class AgentExecutor {
@Resource
private LLMClient llmClient;
@Resource
private FunctionRegistry functionRegistry;
@Resource
private FunctionExecutor functionExecutor;
private static final int MAX_ITERATIONS = 10;
public String execute(String userInput, String systemPrompt) throws Exception {
List<LLMClient.ChatMessage> messages = new ArrayList<>();
messages.add(new LLMClient.ChatMessage("system", systemPrompt));
messages.add(new LLMClient.ChatMessage("user", userInput));
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
// 1. 大模型推理(带工具定义)
String responseJson = llmClient.chatWithTools(messages, functionRegistry.getToolDefinitions());
JSONObject response = JSON.parseObject(responseJson);
JSONObject message = response.getJSONArray("choices").getJSONObject(0).getJSONObject("message");
// 2. 判断是否有工具调用
JSONArray toolCalls = message.getJSONArray("tool_calls");
if (toolCalls == null || toolCalls.isEmpty()) {
String finalAnswer = message.getString("content");
messages.add(new LLMClient.ChatMessage("assistant", finalAnswer));
log.info("Agent完成任务,共{}轮推理", iteration + 1);
return finalAnswer;
}
// 3. 有工具调用,加入assistant消息
messages.add(new LLMClient.ChatMessage("assistant", message.getString("content")));
// 4. 逐个执行工具调用
for (int i = 0; i < toolCalls.size(); i++) {
JSONObject toolCall = toolCalls.getJSONObject(i);
String toolCallId = toolCall.getString("id");
JSONObject function = toolCall.getJSONObject("function");
String toolName = function.getString("name");
String arguments = function.getString("arguments");
log.info("Agent调用工具[{}],参数: {}", toolName, arguments);
// 使用生产级工具执行器
FunctionExecutor.ToolResult result = functionExecutor.execute(toolName, arguments);
// 工具结果回传大模型
LLMClient.ChatMessage toolMsg = new LLMClient.ChatMessage("tool", result.getData());
toolMsg.setTool_call_id(toolCallId);
toolMsg.setName(toolName);
messages.add(toolMsg);
}
// 5. 回到循环,大模型根据工具结果继续推理
}
return "抱歉,任务复杂度超出处理能力(已达到最大推理轮次),请简化问题后重试。";
}
}
六、可写进简历的项目描述
基于Java+SpringBoot设计并实现AI Agent的Function Calling工具调用框架,支持注解式工具自动注册; 实现生产级工具执行器,包含参数校验(JSON Schema)、超时控制(独立线程池+Future)、失败重试(可重试异常判断)、结果截断(防Token爆炸)、关键/非关键工具降级5大机制; 集成大模型Function Calling协议,支持多工具并行调用与结果回传,单轮任务平均3次工具调用内完成; 已接入知识库检索、天气查询、代码执行等多个工具,框架可扩展。
📦 落地资源推荐
Function Calling和Agent开发需要稳定的测试环境,我平时调试工具调用、跑向量检索都用阿里云轻量应用服务器,2核4G跑SpringBoot+Milvus+大模型调用完全够用,新用户首年特惠性价比高再加上申请到的9者优惠券,适合自己动手搭一个Agent测试环境。有部署需求www.aliyun.com/daily-act/e...
💡 完整资料
本文完整Function Calling生产级实现源码,含工具注解、注册中心、执行器(参数校验+超时+重试+降级)、知识库检索工具示例、Agent执行器集成全套代码。
📢 明日预告
明天踩坑日记:《做AI Agent第一周,我踩了3个低级错误(附排查思路)》 工具调用跑起来了,但大模型经常调错工具、参数传错、陷入死循环,这些低级错误90%的人都踩过。附完整排查思路和解决方案,记得关注。
本文属于「Java AI Agent实战」合集