LangChain4J-(7)-Function Calling
一、Function Calling 是什么?
Function Calling(函数调用) 是大语言模型(LLM)与外部工具、系统或数据库进行交互的核心能力。简单来说,它让原本只能进行文本生成的 AI,能够像程序员一样 “调用函数”—— 发送指令给外部工具(如计算器、搜索引擎、API 接口等),获取工具返回的结果后,再结合自身逻辑生成最终回答。
它解决了传统大语言模型的两大核心局限:
知识滞后:模型训练数据有截止日期,无法获取实时信息(如最新天气、股票价格)。
能力边界有限:无法直接完成复杂计算、访问私有数据、控制硬件设备等任务。
二、Function Calling 能干什么?(核心应用场景)
Function Calling 的价值在于扩展 AI 的 “行动力”,从 “只能说” 升级为 “能做、能交互、能解决实际问题”。以下是典型应用场景:
1. 实时信息获取与更新
模型通过调用第三方 API 或搜索引擎,获取训练数据之外的实时 / 动态信息,解决 “知识过期” 问题。
示例 1:查询天气
用户问 “北京明天的天气适合户外跑步吗?”,模型会调用天气 API(如高德天气、OpenWeatherMap),获取北京明日气温、降水概率等数据,再判断是否适合跑步并生成回答。示例 2:获取实时财经数据
用户问 “贵州茅台今天的收盘价是多少?”,模型调用股票行情 API(如东方财富、同花顺),返回最新价格并解读。
2. 复杂计算与逻辑处理
大语言模型本身不擅长高精度数学计算(如微积分、复杂统计),但可通过调用计算工具或代码执行函数完成。
示例 1:数学 / 物理计算
用户问 “一个质量为 2kg 的物体从 10m 高处自由下落,落地时的动能是多少?”,模型调用计算器函数,代入公式E=mgh
(g 取 9.8N/kg),计算得 196J 后返回结果。示例 2:数据统计与分析
用户上传一份 Excel 表格,要求 “计算表格中‘销售额’列的平均值和最大值”,模型调用读取 Excel 的函数 + 统计函数,处理数据后生成分析结论。
3. 访问私有 / 专业数据库
企业或个人可通过 Function Calling 让模型访问内部私有数据(如客户档案、订单系统、医疗病历),既保证数据安全,又能利用 AI 的理解能力解读数据。
示例 1:企业客服场景
客户问 “我的订单(编号 12345)什么时候发货?”,模型调用企业订单管理系统的 API,查询该订单的物流状态,回复 “您的订单已出库,预计明日送达”。示例 2:医疗辅助场景
医生输入患者 ID,模型调用医院电子病历系统函数,获取患者病史、检查报告,再结合医学知识生成诊断建议(需严格合规)。
4. 控制外部工具与设备
通过调用硬件或软件的控制接口,AI 可直接 “操作” 物理世界或数字工具,实现 “指令执行”。
示例 1:智能家居控制
用户说 “把客厅灯调成暖光,亮度 50%”,模型调用智能家居 API(如米家、苹果 HomeKit),发送控制指令,灯执行操作后返回 “已为您调整客厅灯光”。示例 2:办公工具联动
用户说 “用我邮箱里的‘Q3 数据’文件生成一份柱状图,发送给张三”,模型依次调用:① 邮件 API 读取文件 ② 图表工具 API 生成图片 ③ 邮件 API 发送给张三,最终反馈执行结果。
5. 多工具协同解决复杂问题
面对需要多步骤的任务,模型会自动规划 “调用顺序”,串联多个工具完成目标,相当于一个 “AI 指挥官”。
示例:“帮我规划周末去青岛的行程,预算 1500 元 / 人,包含海鲜餐,避开热门景点”
模型的执行逻辑:调用机票 / 高铁 API:查询出发地到青岛的往返交通费用(过滤超预算选项);
调用酒店 API:根据交通站点和预算筛选住宿;
调用本地生活 API:搜索青岛非热门景点(如小麦岛公园、信号山公园)和评价高的海鲜小店;
调用计算器函数:汇总交通 + 住宿 + 餐饮费用,确保不超预算;
整合所有信息,生成结构化行程单。
三、Function Calling 的工作流程(核心逻辑)
它的运行遵循一套标准化的 “思考 - 调用 - 整合” 流程,无需人工干预:
用户提问解析:模型理解用户需求,判断 “是否需要调用工具”(比如 “2+2 等于几” 可直接回答,“明天天气” 需调用工具)。
函数选择与参数生成:若需调用工具,模型自动选择合适的函数(如 “get_weather”),并提取参数(如城市 “北京”、日期 “明日”)。
发送调用请求:将函数名和参数以结构化格式(如 JSON)发送给外部工具 / 系统。
接收工具返回结果:工具执行后,将数据(如 “气温 25℃,无降水”)返回给模型。
生成最终回答:模型结合自身语言能力,将工具返回的原始数据转化为自然、易懂的回答。
总结
Function Calling 是大语言模型从 “文本模型” 进化为 “实用工具” 的关键技术。它本身不直接产生数据或执行操作,而是通过 “连接外部能力”,让 AI 具备了实时性、准确性、行动力和个性化,广泛应用于智能助手、企业服务、智能家居、医疗辅助等领域,是实现 “通用人工智能(AGI)” 的重要基石。
四、撸代码
1、ChatModel and ToolSpecification
在 LangChain4j 中,ToolSpecification
是用于定义工具规格的一个重要概念,它允许开发者明确地告知大语言模型(LLM)可以使用的工具及其相关信息。以下是关于ToolSpecification
的详细介绍:
作用:LLM 本身不能直接调用工具,而是在响应中表达调用特定工具的意图。
ToolSpecification
就是用来提供工具的详细信息,以便 LLM 能够根据这些信息决定是否调用该工具以及如何正确使用它。包含信息:
name
:工具的名称,是一个明确的功能标识,用于唯一标识该工具,例如"getWeather"
。description
:工具的描述,用来说明工具的功能及适用场景,比如"返回指定城市的天气预报"
,清晰的描述有助于 LLM 判断是否需要调用该工具。parameters
:工具的参数及其描述,通常使用JsonObjectSchema
来定义。例如,可以通过addStringProperty
、addEnumProperty
等方法来添加不同类型的参数,并对每个参数的语义进行定义,如addStringProperty("city", "应返回天气预报的城市")
。
Step1
构建一个大模型调用的功能接口FunctionAssistant接口
package com.xxx.demo.service;public interface FunctionAssistant {//客户指令:出差住宿发票开票,// 开票信息: 公司名称xxx// 税号序列: xx// 开票金额: xxx.00元String chat(String message);
}
Step2
构建一个LLMConfig--
package com.xxx.demo.config;import com.bbchat.demo.service.FunctionAssistant;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Map;@Configuration
public class LLMConfig {@Beanpublic ChatModel chatModel(){return OpenAiChatModel.builder().apiKey(System.getenv("aliqwen-apikey")).modelName("qwen-plus").baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").build();}/*** @Description Low Level Tool API* https://docs.langchain4j.dev/tutorials/tools#low-level-tool-api* @Auther: zzyybs@126.com*/@Beanpublic FunctionAssistant functionAssistant(ChatModel chatModel){// 工具说明 ToolSpecificationToolSpecification toolSpecification = ToolSpecification.builder().name("开具发票助手").description("根据用户提交的开票信息,开具发票").parameters(JsonObjectSchema.builder().addStringProperty("companyName", "公司名称").addStringProperty("dutyNumber", "税号序列").addStringProperty("amount", "开票金额,保留两位有效数字").build()).build();// 业务逻辑 ToolExecutorToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {System.out.println(toolExecutionRequest.id());System.out.println(toolExecutionRequest.name());String arguments1 = toolExecutionRequest.arguments();System.out.println("arguments1****》 " + arguments1);return "开具成功";};return AiServices.builder(FunctionAssistant.class).chatModel(chatModel).tools(Map.of(toolSpecification, toolExecutor)) // Tools (Function Calling).build();}
}
Step3
写一个Controller用来测试
package com.xxx.demo.controller;import cn.hutool.core.date.DateUtil;
import com.bbchat.demo.service.FunctionAssistant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@Slf4j
public class ChatFunctionCallingController {@Resourceprivate FunctionAssistant functionAssistant;// http://localhost:9011/chatfunction/test1@GetMapping(value = "/chatfunction/test1")public String test1(){String chat = functionAssistant.chat("开张发票,公司:BK公司 税号:1234567890 金额:99999.00");System.out.println(chat);return "success : "+ DateUtil.now() + "\t"+chat;}
}
Step4
查看下结果
2、AI Service and @Tool
在 LangChain4j 中,@Tool
注解是一个核心功能,用于将普通 Java 方法标记为可被大语言模型(LLM)调用的工具。通过这个注解,开发者可以轻松地将自定义功能暴露给 LLM,使模型能够根据需要调用这些工具来完成特定任务(如数据查询、计算、API 调用等)。
@Tool
注解的主要作用
标记方法为可供 LLM 调用的工具
提供工具的描述信息,帮助 LLM 理解工具的功能和用途
自动生成工具的元数据(
ToolSpecification
),无需手动构建
1). 标记工具方法
使用 @Tool
注解标记方法,并提供描述信息:
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P; // 用于参数描述public class CalculatorTools {// 简单的加法工具@Tool("计算两个数字的和")public int add(@P("第一个数字") int a, @P("第二个数字") int b) {return a + b;}// 天气查询工具(示例)@Tool("获取指定城市的当前温度")public String getCurrentTemperature(@P("城市名称,例如:北京") String city,@P("温度单位,可选:摄氏度、华氏度,默认:摄氏度") String unit) {// 实际实现可能调用天气APIreturn city + "当前温度:25" + (unit.equals("华氏度") ? "°F" : "°C");}
}
2). 自动生成工具规格
通过 ToolSpecifications
工具类,可以从标记了 @Tool
的类或对象中自动提取工具规格:
import dev.langchain4j.agent.tool.ToolSpecifications;
import java.util.List;public class ToolExample {public static void main(String[] args) {// 从类中提取工具规格List<ToolSpecification> toolsFromClass = ToolSpecifications.toolSpecificationsFrom(CalculatorTools.class);// 从对象中提取工具规格CalculatorTools calculator = new CalculatorTools();List<ToolSpecification> toolsFromObject = ToolSpecifications.toolSpecificationsFrom(calculator);}
}
3). 在 Agent 中使用工具
将提取的工具规格交给 Agent,使 LLM 能够根据需要调用这些工具:
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.agent.Agent;
import java.time.Duration;public class AgentWithToolsExample {public static void main(String[] args) {// 创建工具实例CalculatorTools calculator = new CalculatorTools();// 创建Agent,绑定工具Agent agent = Agent.builder().chatLanguageModel(OpenAiChatModel.withApiKey("your-api-key")).tools(calculator) // 直接传入工具对象.chatMemory(MessageWindowChatMemory.withMaxMessages(10)).build();// 向Agent提问,触发工具调用String response = agent.execute("3加5等于多少?");System.out.println(response); // 输出:3加5等于8}
}
4).关键注解说明
@Tool
:标记方法为工具,value
属性用于描述工具功能(给 LLM 看的说明)@P
:用于描述方法参数(给 LLM 看的参数说明),帮助 LLM 正确传递参数
5).注意事项
工具方法的参数类型应尽量简单(如基本类型、String、枚举等),便于 LLM 处理
工具描述应清晰具体,明确说明功能和适用场景,这直接影响 LLM 是否会正确调用工具
工具方法可以有返回值(会被传递给 LLM),也可以是 void(仅执行操作)
如果工具方法可能抛出异常,建议捕获并返回友好的错误信息,避免 Agent 中断
通过 @Tool
注解,LangChain4j 实现了工具与 LLM 的无缝集成,大大简化了智能代理(Agent)的开发流程,使开发者可以专注于工具功能的实现而非复杂的集成逻辑。
Step1
使用和风天气接口做三方接口调用,新建一个WeatherService类。
package com.xxx.demo.service;import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;@Service
public class WeatherService {//和风天气开发服务 https://dev.qweather.com/// 替换成你自己的和风天气API密钥private static final String API_KEY = "#############################################";// 调用的url地址和指定的城市,本案例以北京为例private static final String BASE_URL = "https://devapi.qweather.com/v7/weather/now?location=%s&key=%s";public JsonNode getWeatherV2(String city) throws Exception{//1 传入调用地址url和apikeyString url = String.format(BASE_URL, city, API_KEY);//2 使用默认配置创建HttpClient实例var httpClient = HttpClients.createDefault();//3 创建请求工厂并将其设置给RestTemplate,开启微服务调用和风天气开发服务HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);//4 RestTemplatew微服务调用String response = new RestTemplate(factory).getForObject(url, String.class);//5 解析JSON响应获得第3方和风天气返回的天气预报信息JsonNode jsonNode = new ObjectMapper().readTree(response);//6 想知道具体信息和结果请查看https://dev.qweather.com/docs/api/weather/weather-now/#responsereturn jsonNode;}
}
Step2
重写一个InvoiceHandle
package com.xxx.demo.service;import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class InvoiceHandler {@Tool("根据用户提交的开票信息进行开票")public String handle(@P("公司名称") String companyName,@P("税号") String dutyNumber,@P("金额保留两位有效数字") String amount) throws Exception{log.info("companyName =>>>> {} dutyNumber =>>>> {} amount =>>>> {}", companyName, dutyNumber, amount);//----------------------------------// 这块写自己的业务逻辑,调用redis/rabbitmq/kafka/mybatis/顺丰单据/医疗化验报告/支付接口等第3方//----------------------------------System.out.println(new WeatherService().getWeatherV2("101010100"));return "开票成功";}
}
Step3
改写下之前的controlle-
package com.xx.demo.controller;import cn.hutool.core.date.DateUtil;
import com.bbchat.demo.service.FunctionAssistant;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;@Controller
public class ChatFunctionCallingController {@Resourceprivate FunctionAssistant functionAssistant;// http://localhost:9011/chatfunction/test2@GetMapping(value = "/chatfunction/test2")public String test2(){String chat = functionAssistant.chat("开张发票,公司:BK公司 税号:1234567890 金额:99999.00,顺便帮我查一下今天的天气");System.out.println(chat);return "success : "+ DateUtil.now() + "\t"+chat;}
}
Step4
顺便测试下工具调用错误的情况下的效果