Spring AI 如何调用外部API实战

后端 潘老师 2个月前 (02-21) 112 ℃ (0) 扫码查看

本文将详细介绍如何在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? Spring AI详细功 […]


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/14865.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】