Spring AI 如何调用外部API实战
本文将详细介绍如何在Spring AI框架下,借助Spring Boot实现函数调用功能,让LLM能够调用外部API获取实时数据,进而准确回答用户的问题。通过实际示例,帮助读者深入理解并掌握这一技术。
一、函数调用的工作原理
从本质上讲,大语言模型类似于强化版的自动补全程序,它们凭借预先训练的历史知识,在生成文本方面表现出色。然而,大语言模型自身无法从远程API获取实时信息,也不能替我们进行科学计算。
因此,我们需要编写特定的函数来获取回答用户问题所需的实时信息。当向大语言模型传递用户提示时,除了其他元数据,还必须提供有关这个函数的信息。
当大语言模型需要相关信息来回答用户问题时,会返回一个函数执行请求。聊天机器人应用接收到请求后,会调用相应的服务方法或函数,获取所需的实时信息。然后,聊天机器人应用将JSON格式的响应发送回大语言模型。大语言模型接收到JSON响应后,会对其中的信息进行解读,最终结合实时信息,以文本形式回复用户。
二、基于Spring Boot和Spring AI实现函数调用
在Spring AI中实现函数调用功能,主要需要完成两个关键步骤:定义函数并将其配置为Spring bean,以及在与大语言模型交互时,在聊天选项中指定函数。
(一)定义函数
在Spring AI里,我们把函数定义为Function
类型的bean。当大语言模型需要实时信息时,就会调用这个bean。在下面的示例中,当函数被调用时,它会执行StockPriceService::getStockPrice
方法。
import com.howtodoinjava.ai.demo.StockPriceService.Stock; import java.util.function.Function; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description; // 配置类,用于定义Spring bean,proxyBeanMethods = false表示不使用CGLIB代理创建bean,提升性能 @Configuration(proxyBeanMethods = false) public class Functions { // 定义一个名为priceByStockNameFunction的bean @Bean // 为bean添加描述,帮助大语言模型理解其功能 @Description("Get price by stock name") public Function<Stock, Double> priceByStockNameFunction(StockPriceService stockPriceService) { // 返回一个方法引用,当函数被调用时,会执行StockPriceService的getStockPrice方法 return stockPriceService::getStockPrice; } }
在StockPriceService
类中,getStockPrice()
方法用于执行API调用或进行计算。通过这种方式,将API调用逻辑与大语言模型的函数调用逻辑分离开来。
import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @Service public class StockPriceService { // 使用ConcurrentHashMap存储股票名称和价格的映射关系,保证线程安全 private static final Map<Stock, Double> data = new ConcurrentHashMap<>(); // 静态代码块,初始化一些股票价格数据,仅用于演示 static { data.put(new Stock("Google"), 101.00); data.put(new Stock("Microsoft"), 100.00); // 可继续添加其他股票数据 } // 根据股票名称获取股票价格的方法 Double getStockPrice(Stock stock) { // 实际应用中,这里应是调用外部服务获取数据的逻辑 // 此处为演示,从预定义的Map中查找股票价格 return data.keySet().stream() .filter(s -> s.name().equalsIgnoreCase(stock.name())) .map(s -> data.get(s)) .findFirst() .orElse(-1.0); } // 定义一个记录类,用于表示股票,包含股票名称属性 public record Stock(String name) { } }
需要注意的是,bean的描述非常重要,它能帮助大语言模型理解函数的功能,从而判断在当前对话场景下是否需要调用该函数。在实际应用中,股票价格数据通常是通过HTTP调用外部服务获取的,这里为了演示方便,使用了预定义的Map数据。
(二)在聊天选项中传递函数契约
函数定义完成后,需要将函数契约信息发送给大语言模型。ChatClient.ChatClientRequestSpec
类提供了functions()
方法,通过这个方法可以在向大语言模型发送用户提示时,同时传递函数bean的名称。
import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; @Service public class ChatService { // 注入ChatClient实例,用于与大语言模型进行交互 private final ChatClient chatClient; // 构造函数,通过ChatClient.Builder构建ChatClient实例 ChatService(ChatClient.Builder chatClientBuilder) { this.chatClient = chatClientBuilder.build(); } // 根据股票名称获取股票价格的方法 String getPriceByStockName(String stockName) { // 定义用户提示模板,其中{stockName}是占位符 var userPromptTemplate = "Get the latest price for {stockName}."; // 使用ChatClient发送提示并获取响应 return chatClient.prompt() .user(userSpec -> userSpec // 设置用户提示文本,并填充stockName参数 .text(userPromptTemplate) .param("stockName", stockName) ) // 传递函数契约,指定要调用的函数bean名称 .functions("priceByStockNameFunction") .call() .content(); } }
如果使用ChatModel
类,也可以通过OpenAiChatOptions
构建器来传递函数信息。
// 定义用户提示模板 var userPromptTemplate = "Get the latest price for {stockName}."; // 创建提示模板对象 PromptTemplate promptTemplate = new PromptTemplate(userPromptTemplate); // 根据提示模板和参数创建消息对象 Message message = promptTemplate.createMessage(Map.of("stockName", stockName)); // 使用ChatModel发送提示并获取响应 ChatResponse response = chatModel.call( new Prompt(List.of(message), OpenAiChatOptions.builder() // 添加要调用的函数信息 .withFunction("priceByStockNameFunction").build())); // 返回响应中的内容 return response.getResult().getOutput().getContent();
(三)创建控制器
最后,为了让终端用户能够与大语言模型进行交互,我们在聊天机器人应用中通过REST端点来暴露ChatService
。
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class ChatController { // 注入ChatService实例 private final ChatService chatService; // 构造函数,用于初始化ChatService实例 ChatController(ChatService chatService) { this.chatService = chatService; } // 处理GET请求,根据用户传入的股票名称获取股票价格并返回 @GetMapping("/chat/function") String chat(@RequestParam String stockName) { return chatService.getPriceByStockName(stockName); } }
三、演示
在本演示中,我们使用OpenAI GPT模型来响应用户提示。首先,需要在项目中添加相关依赖。如果是新建项目,可以参考《Spring AI入门指南》进行项目搭建。
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
别忘了在环境变量或属性文件中指定OpenAI API密钥,配置如下:
spring.ai.openai.api-key=${OPENAI_API_KEY}
完成上述配置后,将Spring Boot应用作为Web应用启动。然后,通过如下命令调用股票价格API:
curl --location 'http://localhost:8080/chat/function?stockName=Microsoft'
执行上述命令后,会得到如下响应:
The latest price for Microsoft is $100.00.
四、总结
通过本文的学习,我们掌握了如何在Spring AI和Spring Boot的框架下,利用函数调用实现大语言模型对外部API的调用。需要明确的是,大语言模型本身不具备调用外部API的能力,这就需要我们编写相应的函数,并在与大语言模型交互时提供准确的函数信息和描述,以便大语言模型判断是否调用该函数。Spring AI将整个函数调用流程进行了框架化封装,大大简化了开发过程,我们只需专注于函数的定义和描述即可。