第 14 篇:AI 只会聊天?教你千问点奶茶能力的底层技术(Java+AI落地实战系列 | Function Calling 完整实现,从问答升级为业务执行助手)

本文是《Java+AI 落地实战 从入门到生产级》系列第14篇

往期回顾:

  • 第1篇:Java程序员学AI/转AI全指南,从CURD到AI应用工程师(零算法,4周落地)

  • 第2篇:Java 后端转 AI,这条完整链路 90% 的人没搞懂!AI 怎么自动干活、Java转AI链路:LLM、RAG、FunctionCall、ToolCall、Skills、Agent、MCP

  • 第3篇:别再焦虑了!Java开发做AI,根本不用学Python

  • 第4篇:10分钟跑通!SpringBoot对接通义千问,实现AI对话功能

  • 第5篇:SpringBoot实现流式输出,和ChatGPT体验一模一样

  • 第6篇:对话记忆怎么实现?Java版多轮上下文对话完整方案

  • 第7篇:从零搭建本地RAG知识库,内网可用,零API费用

  • 第8篇:支持PDF/Word/Excel!Java实现多格式文档自动解析入库

  • 第9篇:别再用内存向量库了!Milvus向量数据库Java对接全指南

  • 第10篇:RAG检索准确率太低?5个优化技巧,准确率提升50%

  • 第11篇:多租户权限隔离!企业级知识库部门级权限管控方案

  • 第12篇:一键对接企业微信!把知识库装进企业微信,全员开箱即用

  • 第 13 篇:Java 对接通义千问大模型,性能优化全攻略

上一篇我们搞定了本地大模型全链路性能优化,系统已经能稳定支撑业务并发。但目前的 AI 系统还停留在 “问答机器人” 阶段 —— 用户问什么,它就答什么,真要查个订单、查个年假、提交个工单,还是得人手动去业务系统操作。

今天我们就通过 Function Calling(工具调用) 能力,让大模型主动判断用户意图,自动调用业务接口、查询数据库,把 AI 从 “只会说话的顾问” 升级成 “能动手干活的助手”,能力边界直接上一个台阶。

先看一眼能力升级前后的差异:



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

很多人以为工具调用是大模型直接执行代码,完全不是。大模型本身不会调接口、不会查数据库,它只做一件事:判断用户的问题要不要调用工具、调用哪个工具、传什么参数

完整的工具调用流程只有 4 步:

  1. 用户提问:比如 “帮我查一下订单号 20240601 的物流状态”
  2. 意图判断:大模型识别用户需要调用「查询订单物流」工具,生成结构化参数(订单号 = 20240601)
  3. 后端执行:Java 服务拿到工具名和参数,真实调用业务接口 / 数据库,拿到执行结果
  4. 整理返回:把执行结果喂给大模型,让它整理成自然语言回答用户

说白了,大模型是 “大脑” 负责决策,Java 后端是 “手” 负责执行,两者配合就能实现所有业务场景的自动化。



二、前置准备:定义工具规范

我们先以两个最常用的工具为例,覆盖绝大多数业务场景:

  1. 查询员工剩余年假(查数据库)
  2. 查询订单物流状态(调第三方接口)

第一步:定义工具注解与基础接口

自定义注解标记工具方法,方便后续自动注册,扩展性极强。

/**
 * 工具方法注解,标记该方法可被大模型调用
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AiTool {
    String name(); // 工具名称,唯一标识
    String description(); // 工具描述,告诉大模型这个工具是干嘛的
}

/**
 * 工具执行结果
 */
@Data
@AllArgsConstructor
public class ToolResult {
    private Boolean success;
    private String data;
}

第二步:实现具体业务工具

@Component
public class BusinessTools {

    @Autowired
    private EmployeeMapper employeeMapper;
    @Autowired
    private OrderApiService orderApiService;

    /**
     * 工具1:查询员工剩余年假
     */
    @AiTool(name = "query_annual_leave", description = "查询指定员工的剩余年假天数,入参为员工姓名或员工工号")
    public ToolResult queryAnnualLeave(String employeeName) {
        try {
            Double leaveDays = employeeMapper.selectLeaveDaysByName(employeeName);
            return new ToolResult(true, "员工" + employeeName + "剩余年假:" + leaveDays + "天");
        } catch (Exception e) {
            return new ToolResult(false, "查询失败:未找到该员工信息");
        }
    }

    /**
     * 工具2:查询订单物流状态
     */
    @AiTool(name = "query_order_logistics", description = "查询指定订单的物流状态,入参为订单号")
    public ToolResult queryOrderLogistics(String orderNo) {
        try {
            String logisticsInfo = orderApiService.getLogistics(orderNo);
            return new ToolResult(true, logisticsInfo);
        } catch (Exception e) {
            return new ToolResult(false, "查询失败:订单号不存在");
        }
    }
}

重点:工具的 description 一定要写清楚,这是大模型判断要不要调用这个工具的依据,描述越准确,调用准确率越高。

三、核心实现:通义千问 Function Calling 完整流程

我们用通义千问官方 SDK 实现完整的工具调用闭环,本地大模型的实现逻辑完全一致,仅参数名略有差异。

核心服务代码

@Service
public class FunctionCallService {

    @Value("${ai.qwen.api-key}")
    private String apiKey;
    @Value("${ai.qwen.model}")
    private String model;
    @Autowired
    private BusinessTools businessTools;

    /**
     * 带工具调用的对话
     */
    public String chatWithTools(String userQuestion) {
        DashScope dashScope = new DashScope(apiKey);

        // 1. 构建消息列表
        List<Message> messages = new ArrayList<>();
        messages.add(Message.builder()
                .role(Role.SYSTEM)
                .content("你是企业内部智能助手,优先使用工具回答用户问题,没有合适工具再直接回答。")
                .build());
        messages.add(Message.builder().role(Role.USER).content(userQuestion).build());

        // 2. 注册所有可用工具,自动读取注解生成工具定义
        List<FunctionTool> tools = ToolRegister.getAllToolDefinitions();

        // 3. 构建带工具的请求
        GenerationRequest request = GenerationRequest.builder()
                .model(model)
                .input(MessageInput.builder().messages(messages).build())
                .tools(tools)
                .toolChoice("auto") // 自动判断是否调用工具
                .resultFormat(ResultFormat.MESSAGE)
                .build();

        try {
            GenerationResult result = dashScope.call(request);
            ChatMessage responseMsg = result.getOutput().getChoices().get(0).getMessage();

            // 4. 判断大模型是否要调用工具
            if (responseMsg.getToolCalls() != null && !responseMsg.getToolCalls().isEmpty()) {
                // 把大模型的工具调用消息加入上下文
                messages.add(responseMsg);

                // 遍历执行所有工具调用
                for (ToolCall toolCall : responseMsg.getToolCalls()) {
                    String toolName = toolCall.getFunction().getName();
                    String toolArgs = toolCall.getFunction().getArguments();

                    // 反射执行对应工具方法
                    ToolResult toolResult = ToolRegister.executeTool(toolName, toolArgs);

                    // 把工具执行结果加入消息上下文
                    messages.add(Message.builder()
                            .role(Role.TOOL)
                            .toolCallId(toolCall.getId())
                            .content(toolResult.getData())
                            .build());
                }

                // 5. 把工具执行结果传给大模型,生成最终回答
                GenerationRequest secondRequest = GenerationRequest.builder()
                        .model(model)
                        .input(MessageInput.builder().messages(messages).build())
                        .resultFormat(ResultFormat.MESSAGE)
                        .build();
                GenerationResult finalResult = dashScope.call(secondRequest);
                return finalResult.getOutput().getChoices().get(0).getMessage().getContent();
            }

            // 不需要调用工具,直接返回答案
            return responseMsg.getContent();

        } catch (Exception e) {
            e.printStackTrace();
            return "系统异常,请稍后再试";
        }
    }
}

效果验证

  • 用户问:“张三的年假还剩多少?”
  • 大模型自动调用query_annual_leave工具,传参employeeName=张三,执行查询后返回 “张三剩余年假 8.5 天”
  • 用户问:“今天天气怎么样?”
  • 没有对应工具,大模型直接回答 “我暂时无法查询天气信息”


四、新手必踩的 5 个坑,提前帮你避了

坑 1:工具描述写得模糊,调用准确率极低

工具描述写 “查询信息”,大模型根本不知道什么时候该调用,乱调或者不调。 解决方案:工具描述要写清楚用途、适用场景、入参含义,越具体准确率越高。

坑 2:工具参数不校验,存在安全风险

大模型生成的参数直接传去执行,可能出现 SQL 注入、越权查询的问题。 解决方案:所有工具入参必须做格式校验、权限校验,禁止直接拼接 SQL,严格控制工具可执行的操作范围。

坑 3:多工具场景混乱,大模型反复调用

工具多了之后,大模型可能循环调用、一次调用多个无关工具。 解决方案:限制单次调用工具数量,增加调用次数上限(最多 3 轮),超过直接返回结果。

坑 4:工具执行结果太长,token 爆炸

工具返回大量数据,直接丢给大模型,token 超限且回答混乱。 解决方案:工具返回结果做摘要精简,只保留核心信息,不要把全量接口返回都塞进去。

坑 5:开放高危操作,造成生产事故

直接开放创建订单、删除数据这类写操作工具,大模型误调用会造成生产事故。 解决方案:初期只开放查询类工具,写操作必须增加人工确认环节,严格控制工具权限。


五、生产环境进阶优化方向

基础版工具调用已经能解决很多重复性查询工作,企业级场景还可以继续扩展:



  1. 工具注册中心:统一管理所有工具,支持动态新增、下线,不用改核心代码
  2. 多轮工具调用:支持多步任务,比如 “先查订单再发起退款” 的链式调用
  3. 人工确认机制:写操作类工具,执行前推送企业微信确认,用户同意后再执行
  4. 工具权限管控:不同角色可调用的工具不同,普通员工不能调用财务类工具
  5. 调用统计监控:统计工具调用频率、成功率、耗时,持续优化工具体验

六、本篇小结

今天我们通过 Function Calling 实现了大模型工具调用能力,核心逻辑非常清晰:

  1. 定义业务工具,写清楚描述和参数
  2. 大模型识别用户意图,判断是否调用工具、生成参数
  3. Java 后端执行工具,把结果返回给大模型
  4. 大模型整理执行结果,输出自然语言回答

AI 系统从此从 “只能问答” 升级为 “可以执行任务”,能落地的业务场景直接翻倍。


下篇预告(下篇已更----https://mp.weixin.qq.com/s/HIAgmtb99t41cDcla34GsA)

现在系统能力越来越强,支持的场景越来越多,但上生产环境首先要解决稳定性问题 —— 流量突增把服务打挂、大模型故障导致全系统不可用,这些都会直接影响业务。 所以下一篇我们进入企业级落地篇,讲:

第 15 篇:企业级 AI 接口怎么设计?限流、降级、熔断全套方案

内容会覆盖:

  • Sentinel 对接 AI 接口完整实现
  • QPS 限流、并发线程数控制
  • 慢调用降级、异常比例熔断
  • 多级故障兜底策略
  • 保证生产环境 99.9% 可用性

🎁 粉丝福利

本篇完整代码已更新进系列源码包,包含:

  • Function Calling 完整闭环实现
  • 工具注册与自动扫描框架
  • 两个业务工具示例代码
  • 安全校验与调用次数控制

关注
_cgi-bin_mmwebwx-bin_webwxgetmsgimg__&MsgID=4502347792639468853&skey=@crypt_f25bfbc0_aa4cdb49bb11a32e7d0440680b06084e&mmweb_appid=wx_webfilehelper
,回复 系列源码 即可免费领取。 每更新一篇,我都会往资料包里新增对应源码,跟着系列就能从零搭出完整的企业级 AI 系统。

⭐ 记得星标公众号,每周更新一篇,跟着走不迷路。 觉得文章有用,欢迎点赞、在看、转发给身边的同事,大家一起少踩坑。


posted @ 2026-08-07 09:53  公众号|Java-AI工程师  阅读(4)  评论(0)    收藏  举报