90%的人写Function Calling,只写了"把参数传给工具执行"这一步,剩下的参数校验、错误重试、超时控制、结果格式化,全是空白。

上一篇我们从零实现了Java AI Agent的核心框架,但很多同学反馈:Agent框架跑起来了,但工具调用这块问题一大堆------参数传错、工具超时、调用失败不知道怎么重试、返回结果大模型看不懂。

这很正常。90%的人写Function Calling,只写了"把参数传给工具执行"这一步,剩下的参数校验、错误重试、超时控制、结果格式化,全是空白。 Demo能跑,一上生产全是bug。

今天把Function Calling的生产级实现讲透,从工具定义到异常处理,每个环节都给完整Java代码,做完直接写进简历。


一、先搞懂:Function Calling到底是什么?

很多人以为Function Calling是大模型直接执行代码,完全不是。

Function Calling的本质是一个协议

  1. 你把工具的名称、描述、参数定义(JSON Schema)告诉大模型
  2. 大模型根据用户问题,自主判断是否需要调用工具、调用哪个、传什么参数
  3. 大模型返回的不是执行结果,而是一个"调用指令"(工具名+参数JSON)
  4. 真正执行工具的是你的后端代码,执行完把结果回传给大模型
  5. 大模型根据工具结果,生成自然语言回答用户
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个生产级细节

  1. 参数校验:大模型传错参数时,返回明确的错误信息+正确格式,大模型会自动修正重试
  2. 超时控制:每个工具独立超时,用Future.get()实现,避免工具卡死拖垮Agent
  3. 失败重试:可重试异常(超时、网络错误)自动重试,参数错误不重试
  4. 结果截断:工具结果超过2000字符自动截断,防止Token爆炸
  5. 关键/非关键降级:关键工具失败终止任务,非关键工具失败降级,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实战」合集

相关推荐
黄啊码1 小时前
【黄啊码】Jev 的出现,Agent 进化速度突然实现日行千里
人工智能
Luhui Dev1 小时前
大角几何 3D 渲染 API 上线:让立体题图进入自动化生产流程
人工智能·数学·luhuidev
斯维赤1 小时前
LangChain4j 入门教学(Java 后端狂喜版
java·后端
欣欣之王来了1 小时前
目标驱动:以结果为导向的执行模式
人工智能
Java_2017_csdn1 小时前
Java 8 Stream API 中的 map 和 flatMap 详解
java
合米AI SOP系统1 小时前
标杆客户真实改变,合米科技AI SOP视觉防错系统上线之后制造产线发生了什么?
人工智能·科技·ai
AI深栈1 小时前
第 15 章 · 第一个 AI 工作流:Hello Graph 从 START 跑到 END
java·人工智能
Hilaku1 小时前
为什么死磕 1px 的团队,用户体验反而更差?
前端·javascript·程序员
搜狐技术产品小编20231 小时前
解锁Kotlin Serialization高阶玩法:详解4种自定义序列化器与动态上下文策略
java·人工智能