当前位置: 首页 > news >正文

LangChain4j入门AI(七)Function Calling整合实际业务

前言

        我们通过优化提示词来增强智能体的专业知识水平,但这仍受限于大模型本身的能力,难以实现专业业务领域的实际应用。因此,我们需要借助Function Calling机制来解决这一关键问题。

一、Function Calling是什么?

        Function Calling 的核心在于让大模型(LLM)能够“主动”以结构化的方式,告诉后端“我需要调用哪个函数、带哪些参数”,然后由后端执行对应逻辑,最后将结果返给模型,模型再基于该结果生成最终回复。具体流程可概括为以下三步:

① 模型输出函数调用请求

        在发送用户请求时,开发者先向模型注册一组可调用的函数(FunctionDefinition),包括函数名、参数结构(JSON Schema)和说明。模型在理解用户意图后,如果认为需要执行某项后端操作,就不会直接输出自然语言答案,而是调用已注册的接口。

通过 function_call 实际调用接口,以下样例代表模型认为“我打算调用 functionA”,并把参数打包成 JSON。

{"function_call": {"name": "functionA","arguments": {"param1": "...", "param2": "..."}}
}

② 后端执行真实业务逻辑

        开发者接收到模型的函数调用信息后,将 JSON 参数反序列化,调用对应的 Java 方法执行具体的业务事件流。

③ 把调用结果传回模型,完成最终回答

        后端把函数执行结果封装成另一个结构化内容(FunctionResponse),再次发给模型。模型拿到真实数据后,再结合对话上下文生成自然语言答案。至此,一次 Function Calling 闭环完成。

二、Function Calling 的核心价值

① 业务逻辑与模型能力分离

        将实际的后端处理(数据库查询、接口调用、计算等)抽象为“函数”,使模型专注于“决策:应当什么时候调用哪个函数”以及“语言组织”。这样一来,业务逻辑可以由 Java 端负责实现,模型只需返回结构化的调用请求,降低了模型在业务细节上的出错率。

        长期维护时,若业务变化,只须调整后端函数,而无需大规模修改 Prompt 或让模型重新学习业务细节,提升代码与 Prompt 的可维护性。

② 输出结构化、可解析

        模型不会以纯文本“写出要调用哪个接口和参数”,而是直接生成 JSON 格式的 function_call,包含函数名与具体参数。后端无需再用正则或自然语言解析,只要直接反序列化 JSON 即可,显著减少语义歧义和解析成本。

        在需要返回复杂对象(例如订单信息、报表结构、配置项等)时,也可让函数返回 JSON Schema 结构,模型再基于这些结构继续生成,更有利于后续处理和展示。

③ 安全与可审计

        每一次函数调用都会留下明确记录:模型“决定”调用某个函数、传递哪些参数、函数执行后返回什么结果。整个过程清晰可追踪,方便日志记录、错误排查和审计合规。

        限制模型只能调用事先注册的函数,避免其“凭空”构造任意命令执行,从安全和可控角度大大提升了应用可用性。

④ 提升自动化与用户体验

        模型可以在对话过程中自动触发后端业务,例如查询实时数据(天气、库存、账单)、执行计算(汇率换算、统计分析)、发起流程(创建工单、提交审批)。用户只需用自然语言提出需求,系统即可无缝对接后端完成操作,从而给出“全程自动化、实时交互”的体验。

        避免用户自行理解或描述接口规范,模型负责“翻译”自然语言意图并触发对应后端流程,降低用户沟通成本。

三、Function Calling 使用注意项

        虽然 Function Calling 带来了显著优势,但在实际落地过程中,仍需关注以下几点,以优化性能、成本与可靠性。

① 函数定义精准化与分层管理

        对每个 Function 都明确描述其参数类型与含义,使用严格的 JSON Schema 或 POJO 映射,避免模型在自动生成参数时出现歧义。

        根据业务粒度合理拆分函数,将“粗粒度函数”与“细粒度函数”分层管理。

        通过分层管理,不仅让模型更清晰地知道何时调用哪个层级函数,也便于团队职责划分:前端/交互层只关注“触发流程”,中台/后端根据接口规范聚合调用。

② 多轮对话与上下文维护

        每次函数调用后,都要将“上一次模型的 function_call”与“函数执行结果”都透传给下一轮请求,让模型充分利用对话历史进行决策。

        如果一个流程需要连续多次函数调用,建议在后端统一管理“对话 ID”或“会话上下文”,不让模型自己决定过多轮数,而是由后台限定最大轮数或关键节点,让交互更可控。

        对于复杂业务流,设计“序列化的对话状态机”,在 Java 端帮模型维护状态(例如:客户已填写地址 → 等待确认配送方式 → 等待支付),模型只需每一步返回“调用哪一步”的信号。

③ 异常与降级设计

        后端函数执行时,可能会出现网络超时、第三方接口异常、数据校验失败等情况。此时不要直接抛出异常,而应以统一的错误格式(例如 { "errorCode": "WEATHER_API_TIMEOUT", "message": "天气查询超时" })返回给模型,让模型以自然语言形式告知用户或主动执行备用方案。

        在函数定义中预留“错误返回字段”,或在 JSON Schema 里定义一个通用的 error 域,让模型能够理性判断“这是执行失败还是正常结果”,避免因模型误以为函数已正常执行而继续生成不准确的回复。

        对于可能失败的关键步骤(如支付、下单、批准操作),可设计“重试机制”或“人工干预触发”,让模型在出现特定错误码时,输出类似“对不起,系统繁忙,我正在重试”或“请稍后手动联系客服”的提示。

④ Token 与成本优化

(1) 条件暴露函数

        并非每次对话都需要 Function Calling。可以在前端加一层简单意图识别(关键词或正则),只有在检测到“查询”“下单”“统计”等明显场景时,才将函数列表传给模型,减少不必要的函数调用与多轮 API 请求。

(2) 批量合并请求

        对于同一轮对话中可能连续调用的多个“查询”接口,如果后端能够一次性返回多种数据(例如一次请求返回同时包含库存信息与价格信息),可在 Java 层合并,向模型只呈现一次函数响应,减少往返次数。

(3) 限制参数详情

        若函数参数非常长(如提交整个购物车对象、用户历史记录等),可以在最初阶段让模型只返回“key fields”或“ID 列表”,后端再基于这些 ID 做二次查询与聚合,避免传输过多冗余数据。

⑤ 对模型策略的微调与引导

        在系统消息或 FunctionDefinition 的 description 中明确指示:“如果用户意图只是闲聊或信息补充,请勿调用任何函数”;“在符合业务场景时再调用”。这样能降低模型“过度调用”的风险。

        如果某些函数不需模型自行判断何时调用,而是由业务逻辑层决定,也可让后端在生成请求时动态控制可用函数列表。例如:在整个订单流程里,只有“确认付款”这一步才将 submitPayment 暴露给模型;在填写运送地址阶段,则只暴露 saveAddress。

        可以先在一小部分流量里观察模型调用的准确率、误判率、函数调用次数等指标,然后根据统计数据调整 Prompt 引导、函数描述或参数结构,不断优化。

四、LangChain4j 实现 Function Calling

① 创建业务接口

        模拟一个创建订单的接口用于保存模拟订单。

(1) 创建模拟订单表

/*Navicat Premium Data TransferSource Server         : 192.168.30.129Source Server Type    : MySQLSource Server Version : 80300 (8.3.0)Source Host           : 192.168.30.129:3306Source Schema         : pm-langChain4jTarget Server Type    : MySQLTarget Server Version : 80300 (8.3.0)File Encoding         : 65001Date: 21/05/2025 16:33:38
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for order_demo
-- ----------------------------
DROP TABLE IF EXISTS `order_demo`;
CREATE TABLE `order_demo`  (`id` bigint NOT NULL COMMENT '主键ID',`order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单号',`consignee` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收货人名称',`consignee_phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收货人联系方式',`consignee_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收货人邮箱',`consignee_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收货人地址',`consigner` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发货人名称',`consigner_phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发货人联系方式',`consigner_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发货人邮箱',`consigner_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发货人地址',`goods_description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '货物描述',`goods_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '货物类型',`packaging_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '包装类型',`total_weight` decimal(10, 2) NULL DEFAULT NULL COMMENT '货物总重量',`total_pieces` int NULL DEFAULT NULL COMMENT '获取总件数',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '样例订单表' ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;

(2) 添加 mysql 依赖

<!--mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version>
</dependency>

(3) 添加 mysql 配置

  datasource:driver-class-name: com.mysql.cj.jdbc.Driver# 这里需要配置你自己的数据库连接信息url: jdbc:mysql://192.168.30.129:3306/pm-langChain4j?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8username: rootpassword: 123456

(4) 创建基础代码

        实体类

package com.ceair.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;/*** <p>* 样例订单表* </p>** @author wangbaohai* @since 2025-05-21*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("order_demo")
public class OrderDemo implements Serializable {@Serialprivate static final long serialVersionUID = 1L;/*** 主键ID*/@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;/*** 订单号*/@TableField("order_no")private String orderNo;/*** 收货人名称*/@TableField("consignee")private String consignee;/*** 收货人联系方式*/@TableField("consignee_phone")private String consigneePhone;/*** 收货人邮箱*/@TableField("consignee_email")private String consigneeEmail;/*** 收货人地址*/@TableField("consignee_address")private String consigneeAddress;/*** 发货人名称*/@TableField("consigner")private String consigner;/*** 发货人联系方式*/@TableField("consigner_phone")private String consignerPhone;/*** 发货人邮箱*/@TableField("consigner_email")private String consignerEmail;/*** 发货人地址*/@TableField("consigner_address")private String consignerAddress;/*** 货物描述*/@TableField("goods_description")private String goodsDescription;/*** 货物类型*/@TableField("goods_type")private String goodsType;/*** 包装类型*/@TableField("packaging_type")private String packagingType;/*** 货物总重量*/@TableField("total_weight")private BigDecimal totalWeight;/*** 获取总件数*/@TableField("total_pieces")private Integer totalPieces;}

        接口

package com.ceair.service;import com.ceair.entity.OrderDemo;
import com.baomidou.mybatisplus.extension.service.IService;/*** <p>* 样例订单表 服务类* </p>** @author wangbaohai* @since 2025-05-21*/
public interface IOrderDemoService extends IService<OrderDemo> {}

        接口实现

package com.ceair.service.impl;import com.ceair.entity.OrderDemo;
import com.ceair.mapper.OrderDemoMapper;
import com.ceair.service.IOrderDemoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;/*** <p>* 样例订单表 服务实现类* </p>** @author wangbaohai* @since 2025-05-21*/
@Service
public class OrderDemoServiceImpl extends ServiceImpl<OrderDemoMapper, OrderDemo> implements IOrderDemoService {}

        mapper

package com.ceair.mapper;import com.ceair.entity.OrderDemo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;/*** <p>* 样例订单表 Mapper 接口* </p>** @author wangbaohai* @since 2025-05-21*/
@Mapper
public interface OrderDemoMapper extends BaseMapper<OrderDemo> {}

        xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ceair.mapper.OrderDemoMapper"><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="com.ceair.entity.OrderDemo"><id column="id" property="id" /><result column="order_no" property="orderNo" /><result column="consignee" property="consignee" /><result column="consignee_phone" property="consigneePhone" /><result column="consignee_email" property="consigneeEmail" /><result column="consignee_address" property="consigneeAddress" /><result column="consigner" property="consigner" /><result column="consigner_phone" property="consignerPhone" /><result column="consigner_email" property="consignerEmail" /><result column="consigner_address" property="consignerAddress" /><result column="goods_description" property="goodsDescription" /><result column="goods_type" property="goodsType" /><result column="packaging_type" property="packagingType" /><result column="total_weight" property="totalWeight" /><result column="total_pieces" property="totalPieces" /></resultMap></mapper>

② 创建 工具集

        需要在工具集中使用 【@Tool】 申明 Function Calling 工具方法,指定工具名以及工具作用说明。并且工具中实际调用业务服务完成模拟订单创建功能。

package com.ceair.aiService.tools;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import com.ceair.entity.OrderDemo;
import com.ceair.service.IOrderDemoService;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;import java.math.BigDecimal;/*** @author wangbaohai* @ClassName AirfreightAssistantTools* @description: 航空货运助手业务工具* @date 2025年05月21日* @version: 1.0.0*/
@Component
@RequiredArgsConstructor
public class AirfreightAssistantTools {private final IOrderDemoService orderDemoService;@Tool(name = "createOrder", value = "请用户确认所有参数,如果参数缺失则提示补充,用户确认参数无误后,执行创建订单")public String createOrderDemo(@P(value = "收货人名称") String consignee,@P(value = "收货人联系方式") String consigneePhone,@P(value = "收货人邮箱") String consigneeEmail,@P(value = "收货人地址") String consigneeAddress,@P(value = "发货人名称") String consigner,@P(value = "发货人联系方式") String consignerPhone,@P(value = "发货人邮箱") String consignerEmail,@P(value = "发货人地址") String consignerAddress,@P(value = "货物描述") String goodsDescription,@P(value = "货物类型") String goodsType,@P(value = "包装类型") String packagingType,@P(value = "货物总重量") BigDecimal totalWeight,@P(value = "获取总件数") Integer totalPieces) {// 初始化OrderDemo orderDemo = new OrderDemo();// 设置插入数据orderDemo.setConsignee(consignee);orderDemo.setConsigneePhone(consigneePhone);orderDemo.setConsigneeEmail(consigneeEmail);orderDemo.setConsigneeAddress(consigneeAddress);orderDemo.setConsigner(consigner);orderDemo.setConsignerPhone(consignerPhone);orderDemo.setConsignerEmail(consignerEmail);orderDemo.setConsignerAddress(consignerAddress);orderDemo.setGoodsDescription(goodsDescription);orderDemo.setGoodsType(goodsType);orderDemo.setPackagingType(packagingType);orderDemo.setTotalWeight(totalWeight);orderDemo.setTotalPieces(totalPieces);// UUID 生成 orderNoorderDemo.setOrderNo(UUID.fastUUID().toString());// 保存orderDemoService.save(orderDemo);return "创建订单成功,订单号:" + orderDemo.getOrderNo();}}

③ 向 Aiservice 注册工具集

        在原先的ai接口里的【@AiService】注解中增加【tools】属性指向新建的工具集Bean即可。

五、功能验证

        由于我们是在上一篇提示词的ai接口的基础上增加的功能,所以我们使用上一篇文章的测试接口即可。

① 初次下单请求

        第一次请求,我们只提出需要下单的简单请求,看看大模型是否要求我们补全订单明细信息,可以看到确实提示。

② 第二次下单请求

        我们根据提示完善订单详情:货物类型:电子产品。件数:总共40件。总重量:1000 以千克(kg)为单位。包装方式:木箱。发货人信息:姓名:111,联系电话:123,电子邮件:123@gmail.com,发货地址:测试地址1;收货人信息:姓名:222,联系电话:456,电子邮件:456@gmail.com,收货地址:测试地址2,预计发货日期:20250529。需要防震运输。

        可以看到下单成功,生成了订单号。

③ 数据库验证

        可以看到实际入库数据与请求数据一致的。

后记

        按照一篇文章一个代码分支,本文的后端工程的分支都是 LangChain4j-6,前端工程的分支是 LangChain4j-1。

后端工程仓库:后端工程

前端工程仓库:前端工程

相关文章:

  • fatload使用方式
  • CYT4BB Dual Bank - 安全启动
  • Word2Vec模型学习和Word2Vec提取相似文本体验
  • 网络安全--PHP第一天
  • 数据结构核心知识总结:从基础到应用
  • Flask-SQLAlchemy核心概念:模型类与数据库表、类属性与表字段、外键与关系映射
  • 如何使用redis做限流(golang实现小样)
  • PHP 扇形的面积(Area of a Circular Sector)
  • 创建一个element plus项目
  • 使用Starrocks制作拉链表
  • 【DB2】SQL1639N 处理
  • 【寻找Linux的奥秘】第七章:虚拟地址空间
  • 1.设计师界面进行ui设计
  • Python Lambda 表达式
  • C语言求1到n的和(附带源码和解析)
  • 解决RedisTemplate的json反序列泛型丢失问题
  • Java基础 5.21
  • 如何用ipmitool修改FRU信息?
  • [Vue]路由基础使用和路径传参
  • 小米汽车二期工厂下月将竣工,产能提升助力市场拓展
  • 网站首页关键词如何优化/郑州网站推广优化公司
  • 网页设计图片怎么放进去/seo双标题软件
  • 泉州网站建设网站/seo优化评论