章
目
录
本文将详细介绍如何在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将整个函数调用流程进行了框架化封装,大大简化了开发过程,我们只需专注于函数的定义和描述即可。
文章目录 Spring AI是什么?有啥优势? 如何在项目中使用Spring AI? Spring AI详细功 […]