spring-AI中 Function Calling的使用

1.前言

首先是为什么会出现Spring AI Function Calling,那我们就先分析一下AI的擅长领域和不擅长领域。

擅长领域

  1. 自然语言处理
    1. 例:ChatGPT生成文章、翻译语言,或客服机器人理解用户意图。
  2. 非结构化数据分析
    1. 例:医学影像识别(X光片中的肿瘤检测),或语音转文本。
  3. 创造性内容生成
    1. 例:Stable Diffusion生成符合描述的图像,或AI作曲工具创作音乐。
  4. 复杂模式预测
    1. 例:股票市场趋势预测(基于历史数据关联性,但需注意可靠性限制)。

不擅长领域

  1. 精确计算
    1. 例:AI可能错误计算"12345 × 6789"的结果(需依赖计算器类传统程序)。
  2. 确定性逻辑验证
    1. 例:验证身份证号码是否符合规则(AI可能生成看似合理但非法的号码)。
  3. 低资源消耗场景
    1. 例:嵌入式设备(如微波炉控制程序)无法承受大模型的算力需求。
  4. 因果推理
    1. 例:AI可能误判"公鸡打鸣导致日出"的因果关系。

为了解决AI大模型存在的缺点,于是Spring AI Function Calling就诞生了,通过让AI调用Function Calling从而减少功能性缺陷。

2.FunctionCalling流程示意

大模型虽然可以理解自然语言,更清晰弄懂用户意图,但是确无法直接操作数据库、执行严格的业务规则。这个时候我们就可以整合传统应用于大模型的能力了。

简单来说,可以分为以下步骤:

  1. 我们可以把传统应用中的部分功能封装成一个个函数(Function)。
  2. 然后在提示词中描述用户的需求,并且描述清楚每个函数的作用,要求AI理解用户意图,判断什么时候需要调用哪个函数,并且将任务拆解为多个步骤(Agent)。
  3. 当AI执行到某一步,需要调用某个函数时,会返回要调用的函数名称、函数需要的参数信息。
  4. 传统应用接收到这些数据以后,就可以调用本地函数。再把函数执行结果封装为提示词,再次发送给AI。
  5. 以此类推,逐步执行,直到达成最终结果。

流程如图:
在这里插入图片描述

在这里插入图片描述
注意

并不是所有大模型都支持Function Calling,比如DeepSeek-R1模型就不支持。
我们要做的事情就简化了

编写基础提示词(不包括Tool的定义)

编写Tool(Function)

配置Advisor(SpringAI利用AOP帮我们拼接Tool定义到提示词,完成Tool调用动作)

3.FunctionCalling实现流程

3.1定义Function

所谓的Function,就是一个个函数,SpringAI提供了一个@Tool注解来标记这些特殊的函数。可以任意定义一个Spring的Bean,其中有2个关键点,一个是方法用@Tool标记,参数用@ToolParam标记,例如:

@Component
public class MyFunction {

    @Tool(description="这里需要写Function的功能描述,将来会作为提示词的一部分,大模型可以依据这里的描述来判断何时调用该函数")
    //其中的函数名最好也是见名知意
    public String func(@ToolParam(description = "参数的描述")  param) { 
        // ...
        retun "";
    }

}

接下来将举一个具体的例子来说明
新建一个tools包,在其中新建一个MyTools类:

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class MyTools {
    //这里模拟一下用户列表
    List<String> userList = List.of("小王", "小张", "小李");
    //这里模拟一下语音助手的功能,本来应该是语言信息,这里简化一下
    @Tool(description = "根据用户姓名信息来获取一个AccessToken")
    public String getAccessToken(@ToolParam(description = "用户姓名") String userName) {
        if (!userList.contains(userName)) {
            return "用户无权限";
        }
        //模拟生成一个AccessToken
        return "access";
    }

    //required = false表示该参数不是必须的,默认为true
    @Tool(description = "首先根据用户姓名来获取一个AccessToken,检查AccessToken是否有效,如果返回值为false,告诉用户无权限,如果为true,就根据AccessToken来预约一个会议,并返回给用户会议预定信息")
    public String bookMeeting(@ToolParam(description = "用户姓名") String userName,
                               @ToolParam(description = "预定时间(例如明天上午10点钟)") String time,
                               @ToolParam(description = "预定时长(例如1个小时)",required = false) String duration) {
        //模拟根据AccessToken来预约会议
        String accessToken = getAccessToken(userName);
        if(!checkAccessToken(accessToken)){
            
            return "预约失败";
        }
        //真实业务中调用对应的api即可
        return "预约成功";
    }
    @Tool(description = "检查AccessToken是否有效")
    public Boolean checkAccessToken(@ToolParam(description = "AccessToken") String accessToken) {
        if(!accessToken.equals("access")){
            return false;
        }
        return true;
    }

}

然后配置chatClient

 @Bean
    public ChatClient myChatClient(OpenAiChatModel model, ChatMemory chatMemory,MyTools myTools) {
        return ChatClient.builder(model)
                .defaultSystem("你是一个热心,可爱的人工智能助手,你的名字叫做贾维斯,请你以钢铁侠3中贾维斯的身份和语气来和我说话。")
                .defaultAdvisors(
                        new MessageChatMemoryAdvisor(chatMemory), //配置聊天记忆
                        new SimpleLoggerAdvisor()) //配置日志
                .defaultTools(myTools) //通过defauotTools就能将自定义工具类交付给大模型使用
                .build();
    }

然后测试一下

 @Autowired
    private ChatClient myChatClient;

    @Test
    public void testMyChatClient() {
        String prompt = "我是小王,请你帮我预定一个明天下午2点钟的会议,时长你来安排";
        String result = myChatClient.prompt()
                .user(prompt)
                .call()
                .content();
        System.out.println(result);
    }

在这里插入图片描述
现在我们来看一下其实现流程
在这里插入图片描述
发现AI大模型在回答我们的问题时,会将我们定义的方法生成一个id,然后会根据我们方法上面的描述与需求对参数进行校验,(其实预定时长duration,我们是没有输入的,但是AI大模型帮我们生成了)校验通过后就进行方法调用,按照要求返回结果。

spring AI 的 Function Calling 功能是一个强大的集成机制,它在 AI 应用开发中扮演着关键角色,主要作用可以概括为以下几个核心方面:

  1. 扩展 AI 能力边界
    突破训练数据限制:让 AI 模型能够访问训练数据之外的最新信息和实时数据
    执行具体操作:将 AI 的文本理解能力转化为实际系统操作(如数据库查询、API 调用等)
    多模态集成:整合非文本功能(如图像处理、语音识别等)
  2. 实现动态信息获取
    实时数据查询:获取股票行情、天气预报、交通状况等动态信息
    系统状态检查:查询订单状态、库存水平、服务器指标等业务数据
    个性化响应:基于用户档案、权限等上下文提供定制化回答
  3. 业务流程自动化
    智能工作流触发:根据对话自动创建工单、发送通知、预约会议等
    决策支持:通过调用业务逻辑提供基于数据的建议
    复杂任务分解:将用户请求拆解为多个系统操作并协调执行
  4. 系统集成桥梁
    连接异构系统:统一不同技术栈的后端服务与 AI 前端
    微服务编排:协调跨多个微服务的复杂操作
    遗留系统赋能:为传统系统添加智能交互层
  5. 安全与管控层
    权限代理:在受控环境下执行敏感操作
    输入验证:在调用实际业务逻辑前进行安全检查
    审计跟踪:记录 AI 触发的所有系统操作
  6. 性能优化机制
    缓存管理:智能缓存频繁查询的结果
    异步执行:处理耗时操作而不阻塞主线程
    批量处理:合并多个请求提高效率
    关键特性实现
    声明式集成:通过注解简单定义可调用函数
    自动参数映射:将自然语言转换为结构化参数
    上下文感知:保持会话状态跨多个函数调用
    结果后处理:对原始系统响应进行适合对话的格式化

典型应用模式
查询应答模式:
用户问:“上海今天气温多少?”
AI 调用天气 API → 返回结构化结果 → 生成自然语言响应

操作执行模式:
用户说:“帮我预约明天下午3点的会议室”
AI 调用日历服务 → 检查可用性 → 创建预约 → 返回确认

决策支持模式:
用户问:“我应该选择哪个云服务套餐?”
AI 分析使用模式 → 调用定价计算 → 生成对比建议
Spring AI Function Calling 通过将语言模型的认知能力与实际业务系统的操作能力相结合,实现了从"知道什么"到"能做什么"的关键跨越,是构建生产级 AI 应用的核心组件。

### 如何在Spring框架中调用AI Ollama函数 为了实现这一目标,在Spring应用程序中可以创建一个服务类来处理与Ollama API的交互。这通常涉及到配置HTTP客户端用于发送请求并接收响应,以及定义数据模型以便于序列化和反序列化JSON对象。 #### 配置依赖项 首先,确保项目中的`pom.xml`文件包含了必要的依赖项,比如RestTemplate或WebClient库,它们简化了RESTful Web服务之间的通信过程[^1]: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` #### 创建API接口和服务层 接着,设计一个简单的Java接口表示要访问的服务端点,并通过@Service注解声明具体业务逻辑组件。这里假设存在一个名为`ollamaService`的方法负责执行实际操作: ```java public interface IOllamaApi { String getResponse(String input); } @Service class OllamaServiceImpl implements IOllamaApi { private final RestTemplate restTemplate; @Autowired public OllamaServiceImpl(RestTemplateBuilder builder){ this.restTemplate = builder.build(); } @Override public String getResponse(String input) { Map<String, Object> params = new HashMap<>(); params.put("input", input); ResponseEntity<String> responseEntity = restTemplate.postForEntity( "https://api.example.com/ollama", params, String.class ); return Objects.requireNonNullElse(responseEntity.getBody(), ""); } } ``` 上述代码片段展示了如何利用`RestTemplate`向指定URL发起POST请求并将返回的数据作为字符串形式解析出来。需要注意的是,真实的API地址应当替换掉示例中的占位符(`https://api.example.com/ollama`)。 #### 构建链式结构 对于更复杂的场景,可能还需要考虑采用类似于提到过的链式编程风格(chain),即组合多个处理器单元形成一条完整的流水线。这种方式有助于提高代码可读性和维护性的同时也便于后续扩展功能模块。 ```java // 假设这是某个自定义Prompt对象实例 var prompt = new Prompt(); // 使用管道运算符模拟链式调用流程 chain = prompt | model | OpenAIFunctionsAgentOutputParser(); // 执行整个链条上的所有动作 String result = chain.execute(inputText); ``` 尽管这段伪代码并不直接适用于标准版Spring Boot应用内,但它提供了一种思考复杂工作流的有效方式——即将不同阶段的任务抽象成独立部件再串联起来共同完成特定使命。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值