Spring AI -快速开发ChatGPT应用

news2024/11/24 14:31:58
  • Spring AI介绍

Spring AI是AI工程师的一个应用框架,它提供了一个友好的API和开发AI应用的抽象,旨在简化AI应用的开发工序,例如开发一款基于ChatGPT的对话、图片、音频等应用程序。

Spring AI已经集成了OpenAI的API,因此我们不需要实现向OpenAI发送请求和接收响应的交互程序了,Spring AI已经实现了这一内容,我们只需要通过调用Spring AI为我们提供的接口即可

项目地址:https://github.com/spring-projects-experimental/spring-ai

文档地址:https://docs.spring.io/spring-ai/reference/

Spring AI能做什么?

  • 支持目前主流大语言模型平台,例如 OpenAI、Microsoft、Amazon、Google 和 Huggingface;
  • 支持阻塞与流式的文本对话;
  • 支持图像生成(当前仅限OpenAI的dall-e-*模型和SD);
  • 支持嵌入模型;
  • 支持LLM生成的内容转为POJO;
  • 支持主流的向量数据库或平台:Azure Vector Search, Chroma, Milvus, Neo4j, PostgreSQL/PGVector, PineCone, Qdrant, Redis 和 Weaviate
  • 支持函数调用
  • 支持自动装配和启动器(与Spring Boot完美集成);
  • 提供用于数据处理工程的ETL框架;
  • 项目实践

  1. 准备工作

  2. 版本说明

  • OpenAI的Key
  • OpenAI的Api
  • JDK >= 17
  • Spring 6.x;Spring Boot 3.x
  • Spring AI 0.8.1-SNAPSHOT
  1. pom引入

<!-- 仓库定义 -->
<repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>spring-snapshots</id>
      <name>Spring Snapshots</name>
      <url>https://repo.spring.io/snapshot</url>
      <releases>
        <enabled>false</enabled>
      </releases>
    </repository>
  </repositories>
<!-- 依赖管理配置 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>0.8.1-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

用于请求OpenAI平台相关模型,例如:对话用的ChatGPT、画图用的Dall-e-2/3、文本嵌入text-embedding-ada-002以及音频合成与识别的whisper和tts等相关模型。

  1. 配置文件application.yml

将相关key和api信息进行填写

spring:
  ai:
    openai:
      api-key: 123
      base-url: https://api.openai.com
  1. 快速对话ChatClient
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
    @Autowired
    private  ChatClient chatClient;
    @GetMapping("/demo")
    public String chat(String prompt){
        return chatClient.call(prompt);
    }

}

运行结果:

  1. 流式对话StreamingChatClient

流失对话的核心就是流式传输,AI的响应数据是一点一点传过来的,不用等AI将文本全部生成出来了才传过来。一定程度上能够提高使用上的响应速度,给用户一个非常好的体验。

@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
    @Autowired
    private StreamingChatClient streamingChatClient;
// 流式调用 将produces声明为文本事件流
    @GetMapping(value = "/stream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream(String prompt){
        long startTime = System.currentTimeMillis();
        Flux<String> res=streamingChatClient.stream(prompt).flatMapSequential(Flux::just);
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        log.info("流式调用执行时间:{}",duration);
        // 将流中的内容按顺序返回
        return res;
    }
}

运行结果:

  1. 上下文对话

ChatGPT上下文对话的实现原理较为简单,本质上其实就是将不同角色的聊天信息依次存储在一个队列中发送给ChatGPT即可,然后ChatGPT会根据整个聊天信息对回复内容进行判断。在OpenAI提供的接口中,每条信息的角色总共分为三类:

  • SystemMessage:系统限制信息,这种信息在对话中的权重很大,AI会优先依据SystemMessage里的内容进行回复;
  • UserMessage:用户信息
  • AssistantMessage:AI回复信息

不过,根据OpenAI的计费规则,你的消息队列越长,单次问询需要的费用就会越高,因此我们需要对这个消息列表的长度进行限制。

@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
    @Autowired
    private  ChatClient chatClient;
    // 历史消息列表
    static List<Message> historyMessage = new ArrayList<>();
    // 历史消息列表的最大长度
    static int maxLen = 10;
    @GetMapping("/context")
    public String context(String prompt) {
        // 用户输入的文本是UserMessage
        historyMessage.add(new UserMessage(prompt));
        // 发给AI前对历史消息对列的长度进行检查
        if(historyMessage.size() > maxLen){
            historyMessage = historyMessage.subList(historyMessage.size()-maxLen-1,historyMessage.size());
        }
        // 获取AssistantMessage
        ChatResponse chatResponse = chatClient.call(new Prompt(historyMessage));
        AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
        // 将AI回复的消息放到历史消息列表中
        historyMessage.add(assistantMessage);
        return assistantMessage.getContent();
    }

}
  1. 人设设定

人设设定功能来自于“提示词(prompt)工程”的理论基础,可用来提高大语言模型处理复杂任务场景的能力

上面介绍Message的时候提到SystemMessage对AI生成的内容影响权重较大,人设设定就是需要靠SystemMessage实现。我们提供一个SystemMessage放入历史消息列表中,并让SystemMessage在每次发给AI时始终在历史消息列表中。

@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
    @Autowired
    private  ChatClient chatClient;
    // 历史消息列表
    final String systemPrompt="你现在是一个喜欢扮可爱的人,说话嗲嗲的";
    List<Message> historyMessage = new ArrayList<>(List.of(new SystemMessage(systemPrompt)));
    // 历史消息列表的最大长度
    static int maxLen = 10;
    @GetMapping("/context")
    public String context(String prompt) {
        // 用户输入的文本是UserMessage
        historyMessage.add(new UserMessage(prompt));
        // 发给AI前对历史消息对列的长度进行检查
        if(historyMessage.size() > maxLen){
            historyMessage = historyMessage.subList(historyMessage.size()-maxLen-1,historyMessage.size());
        }
        // 获取AssistantMessage
        ChatResponse chatResponse = chatClient.call(new Prompt(historyMessage));
        AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
        // 将AI回复的消息放到历史消息列表中
        historyMessage.add(assistantMessage);
        return assistantMessage.getContent();
    }

}

运行结果:

  1. 模板语法PromptTemplate

Spring AI为我们提供了提示词模板,允许我们通过一些模板,快速地动态生成提示词并发起提问

@Slf4j
@RestController
@RequestMapping("/prompts")
public class PromptController {
    @Autowired
    private ChatClient chatClient;
    @Value("classpath:prompt.st")
    private Resource templateResource;
    @GetMapping("/template")
    public String promptTemplate(String author){
        // 提示词
        final String template = "请问{author}最受欢迎的书是哪本书?什么时候发布的?书的内容是什么?";
        PromptTemplate promptTemplate = new PromptTemplate(template);
        // 动态地将author填充进去
        Prompt prompt = promptTemplate.create(Map.of("author", author));
        ChatResponse chatResponse = chatClient.call(prompt);
        AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
        return assistantMessage.getContent();
    }
    @GetMapping("/config/template")
    public String promptConfigTemplate(String author) {
        PromptTemplate promptTemplate = new PromptTemplate(templateResource);
        // 动态地将author填充进去
        Prompt prompt = promptTemplate.create(Map.of("author", author));
        ChatResponse chatResponse = chatClient.call(prompt);
        AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
        return assistantMessage.getContent();
    }


}
请问{author}最受欢迎的书是哪本书?什么时候发布的?书的内容是什么?

  1. OutputParser 生成解析器

SpringAi还为我们提供了OutputParser解析器,该解析器可以将AI生成的内容解析为Java Bean对象。该解析器类似于ORM框架中的Mapper,将AI的生成内容映射为Java对象。

@Slf4j
@RestController
@RequestMapping("/parser")
public class ParserController {
    @Autowired
    private ChatClient chatClient;

    @GetMapping("/bean")
    public Movie getBookByAuthor(String actor) {
        final String template = """
                        请告诉我{actor}最受欢迎的电影是哪个?什么时间上映?大概讲了什么?
                        {format}
                """;
        // 定义一个输出解析器
        OutputParser<Movie> movieParser = new BeanOutputParser<>(Movie.class);
        PromptTemplate promptTemplate = new PromptTemplate(template);
        Prompt prompt = promptTemplate.create(Map.of("actor", actor, "format", movieParser.getFormat()));
        ChatResponse chatResponse = chatClient.call(prompt);
        AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
        // 解析为一个Bean对象
        Movie movie = movieParser.parse(assistantMessage.getContent());
        return movie;
    }
}


@Data
@AllArgsConstructor
@NoArgsConstructor
public class Movie {
    private String actor;
    private String movieName;
    private String publishedDate;
    private String description;
}

  1. 绘图ImageClient

Spring AI提供了图片生成接口,该接口可以用于与各种专门用于图像生成的人工智能模型进行交互。

在调用绘图时,我们只需要像调用对话一样传入一个Prompt:ImagePrompt。ImagePrompt中包含了我们需要绘制的图片信息,包括:ImageMessage(绘图指令)、ImageOptions(图片数、图片配置、返回的图片格式、绘图模型等)。AI拿到我们的Prompt后会根据里面的内容对图像进行生产

ImageOptions重要属性

  • model:绘图模型,默认dall-e-3
    • dall-e-3:1024 x 1024 、 1024 x 1792、1792 x 1024;
    • dall-e-2: 256 x 256、512 x 512 、 1024 x 1024;
  • responseFormat:返回的图片格式,url 和 b64_json
@Slf4j
@RestController
@RequestMapping("/image")
public class ImageController {
    @Autowired
    private ImageClient imageClient;
    @GetMapping("/image")
    public String image(String prompt) {
        ImagePrompt imagePrompt =
                new ImagePrompt(prompt, OpenAiImageOptions.builder()
                        .withModel(OpenAiImageApi.ImageModel.DALL_E_3.getValue())
                        .withHeight(1024)
                        .withWidth(1024)
                        .withResponseFormat("url") // URL or b64_json
                        .build());
        ImageResponse imageResponse = imageClient.call(imagePrompt);
        List<ImageGeneration> results = imageResponse.getResults();
        // 图片url
        String url = results.get(0).getOutput().getUrl();
        return String.format("<img src='%s' alt='%s'>",url,prompt);
    }
}
  1. AI自查实现对话和绘图

通过AI自查手段将文本模型和图片生成模型进行组合实现一个既可以生成文本也可以生成AI的接口。这个关键点就是利用提示词限制AI的回复内容以达到一个自查手段

AI自查就是让AI判断你的问题是画一个图还是简简单单的对话。

  • 用户输入文本prompt;
  • 先让AI判断文本prompt是否需要图片;
  • 如果需要图片,调用绘图模型获取绘图结果;
  • 如果不需要图片,直接调用对话模型;’
@Slf4j
@RestController
@RequestMapping("/judge")
public class JudgeByAiController {
    @Autowired
    private ChatClient chatClient;
    @Autowired
    private ImageClient imageClient;
    @Value("classpath:judge.st")
    private Resource templateResource;
    @RequestMapping("/ai")
    public String ai(String prompt){
        try {
            return judge(prompt)?image(prompt):chat(prompt);
        } catch (Exception e) {
            return "error";
        }
    }
    private boolean judge(String promptString){
        PromptTemplate promptTemplate = new PromptTemplate(templateResource);
        // 动态地将prompt填充进去
        Prompt prompt = promptTemplate.create(Map.of("prompt", promptString));
        ChatResponse chatResponse = chatClient.call(prompt);
        AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
        String judgeResult=assistantMessage.getContent();
        return judgeResult.toLowerCase().contains("yes")?true:false;
    }
    private String chat(String prompt){
        String res=chatClient.call(prompt);
        return res;
    }
    private String image(String prompt) {
        ImagePrompt imagePrompt =
                new ImagePrompt(prompt, OpenAiImageOptions.builder()
                        .withModel(OpenAiImageApi.ImageModel.DALL_E_3.getValue())
                        .withHeight(1024)
                        .withWidth(1024)
                        .withResponseFormat("url") // URL or b64_json
                        .build());
        ImageResponse imageResponse = imageClient.call(imagePrompt);
        List<ImageGeneration> results = imageResponse.getResults();
        // 图片url
        String url = results.get(0).getOutput().getUrl();
        return String.format("<img src='%s' alt='%s'>",url,prompt);
    }
}
Does this message want to generate an AI picture, image, art or anything similar? {prompt} . Simply answer with a yes or no.
  • LLM领域新浪潮即将来临

openAI真正走向CloseAI,LLM国产替代浪潮即将到来

危!OpenAI 将限制中国开发者访问 API 服务-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1986429.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【虚拟化】KVM使用virt-manager部署及管理虚拟机

目录 一、KVM 概述 二、KVM工作原理 三、部署KVM 四、新建虚拟机步骤 4.1 创建存储池并创建存储卷 4.1.1 创建存储池 4.1.2 创建存储卷 4.3 创建ISO存储池 4.4 生成新的虚拟机 一、KVM 概述 KVM 是 Kernel-based Virtual Machine 的缩写&#xff0c;是一种用于虚拟化的…

LeetCode LCR147.最小栈

LeetCode LCR147.最小栈 思路&#x1f914;&#xff1a; 建立两个栈&#xff0c;一个栈正常入栈出栈&#xff0c;一个栈只用于出入最小数&#xff0c;当push值小于minst栈顶才入栈&#xff0c;当pop值等于minst栈顶才出栈。 代码&#x1f50e;&#xff1a; class MinStack { pu…

如何通过JavaScript提升逻辑判断的可读性?

在前端开发过程中&#xff0c;我们经常会遇到需要根据不同条件执行不同逻辑的场景。对于初学者来说&#xff0c;这样的逻辑判断可能会导致代码冗长且难以维护。那么&#xff0c;如何才能写出既简洁又易读的代码呢&#xff1f;本文将带你逐步优化 JavaScript 中的条件判断&#…

重塑电商新风尚:优选免单策略的深度解析

在当今电商领域&#xff0c;一种创新的销售策略——优选免单模式正悄然兴起。这一模式巧妙融合了价格策略、激励机制与社交互动&#xff0c;旨在激发消费者的购买潜能&#xff0c;引领销售业绩的飞跃式增长。 一、合规创新&#xff0c;重塑激励机制 我们秉承合法合规的原则&am…

API-EXPLORER项目开发笔记(一)

文章目录 前言一、我为什么要做这个项目&#xff1f;二、项目简单介绍三、项目技术栈总结 前言 最近接触到了接口平台这个东西&#xff0c;非常感兴趣&#xff0c;于是就想自己也动手做一个具备核心功能的接口平台&#xff0c;本篇文章主要介绍了做这个项目的初衷以及简单介绍…

DispatcherServlet 源码分析

一.DispatcherServlet 源码分析 本文仅了解源码内容即可。 1.观察我们的服务启动⽇志: 当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet, 它来控制程序的执⾏顺序.所有请求都会先进到DispatcherServlet&#xff0c;执⾏doDispatch 调度⽅法. 如果有拦截器, 会先执⾏拦截器…

自动获取ip地址什么意思?电脑ip地址怎么设置自动获取

在当今数字化时代&#xff0c;网络连接已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;对于非技术用户而言&#xff0c;复杂的网络配置常常令人望而生畏。幸运的是&#xff0c;自动获取IP地址&#xff08;Dynamic Host Configuration Protocol, DHCP&#xff09…

小白入门机器学习被劝退的4大原因,你中了哪一个?

hi&#xff0c;喵老师&#x1f431;来啦。 很多小白朋友&#xff0c;尤其是准研究生、文科生&#xff0c;刚开始接触机器学习之后常常在短时间内就「入门即放弃」了。 其实背后主要的原因无非那么几个&#xff0c;今天喵老师就给大家盘一盘&#xff0c;看看你是哪一种&#x1…

SemanticKernel/C#:使用Ollama中的对话模型与嵌入模型用于本地离线场景

前言 上一篇文章介绍了使用SemanticKernel/C#的RAG简易实践&#xff0c;在上篇文章中我使用的是兼容OpenAI格式的在线API&#xff0c;但实际上会有很多本地离线的场景。今天跟大家介绍一下在SemanticKernel/C#中如何使用Ollama中的对话模型与嵌入模型用于本地离线场景。 开始…

redis面试(七)初识lua加锁脚本

redisson redisson如何来进行redis分布式锁实现的源码&#xff0c;基于redis实现各种各样的分布式锁的原理 https://redisson.org/ 这是官网 https://github.com/redisson/redisson/wiki/Table-of-Content 这是官方文档 开始 demo 建一个普通的工程在pom.xml里引入依赖 <…

CFA CAIA最新道德手册第14版+道德案例手册(2024年最新原创写的内容,上一版还是10年前14年写的)

纯原创CFA CAIA最新道德手册第14版道德案例手册&#xff08;2024年最新原创写的内容&#xff0c;上一版还是10年前14年写的&#xff09; standards 是CFA三个级别和CAIA两个级别重中之重&#xff0c;2014年的版本太过老旧&#xff0c;现在协会发布了新考纲&#xff0c;自己原创…

LVS(Linux virual server)

目录 一.集群和分布式简介 1.系统性能扩展方式 2.集群Cluster 3.分布式 4.集群和分布式 二.lvs(Linux virtual server) 运行原理 1.lvs介绍 2.lvs集群体系结构 3.LVS概念 4.lvs集群的类型 nat模式 nat模式数据逻辑 lvs-nat模式原理及部署方法 实验环境部署 实验流程…

Proxy302:你的一站式代理IP解决方案

一、Proxy302介绍 Proxy302&#xff0c;一款优秀的全球代理IP平台&#xff0c;以按需充值的灵活方式、覆盖广泛的代理类型及直观高效的用户上手体验与界面设计&#xff0c;赢得了市场广泛认可。Proxy302亮点不仅在于其功能的强大&#xff0c;更在于其对用户体验的深刻理解和不…

代发考生战报:7月26号北京考试通过 HCIP-Cloud云计算 H13-527

代发考生战报&#xff1a;7月26号北京考试通过 HCIP-Cloud云计算 H13-527 &#xff0c;考试遇到4个新题&#xff0c;剩下都是题库里的&#xff0c;但是没打高分&#xff0c;可能题库里的答案有问题&#xff0c;但是能考过就行&#xff0c;挺满足的&#xff0c;就是把题库都背会…

IT知识库文档查找与学习:rfc文档

RFC文档查找 RFC&#xff08;Request for Comments&#xff09;文档是互联网工程任务组&#xff08;Internet Engineering Task Force, IETF&#xff09;发布的一系列备忘录&#xff0c;旨在提供互联网技术和应用的标准、规范、指南和最佳实践。RFC文档是互联网发展的基石&…

小怡分享之String类的小练习

前言&#xff1a; &#x1f308;✨之前小怡给大家分享了String类&#xff0c;今天小怡给大家分享String类的一些小习题。 1.第一个只出现一次的字符 思路&#xff1a; 遍历字符串&#xff0c;把对应字符位置的下标开始计数&#xff0c;count[字符-‘a’]&#xff1b;再次遍历…

数模——灰色关联分析算法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 一、基本概念了解 1.什么是灰色系统&#xff1f; 2.什么是关联分析&#xff1f; 二、模型原理 三、建模过程 1.找母序列&#xff08;参考序列&am…

力扣面试150 逆波兰表达式求值 栈 模拟栈

Problem: 150. 逆波兰表达式求值 &#x1f468;‍&#x1f3eb; 参考题解 class Solution {//纯数组模拟栈实现(推荐) 3 ms 36 MBpublic static int evalRPN(String[] tokens) {int[] numStack new int[tokens.length / 2 1];int index 0;for (String s : tokens) {swit…

常见中间件漏洞(二、WebLogin合集)

目录 二、WebLogic Weblogic介绍 2.1 后台弱口令GetShell 漏洞描述 影响范围 环境搭建 漏洞复现 2.2 CVE-2017-3506 漏洞描述 影响版本 环境搭建 漏洞复现 2.3 CVE-2019-2725 漏洞描述 影响版本 环境搭建 漏洞复现 2.4 CVE-2018-2628 漏洞描述 漏洞影响 环…

STM32学习笔记2 --- GPIO输入

目录 AD/DA 按键 传感器模块 传感器模块细节 按键模块电路 传感器模块接入电路 OLED ​编辑 代码 封装驱动代码 GPIO读取函数 按键控制LED代码 部分解释 光敏传感器代码 部分解释 接线图 c知识补充 AD/DA AD&#xff1a;模拟-数字转换 DA&#xff1a;数字-模拟…