开源模型应用落地-工具使用篇-Spring AI(七)

一、前言

    在AI大模型百花齐放的时代,很多人都对新兴技术充满了热情,都想尝试一下。但是,实际上要入门AI技术的门槛非常高。除了需要高端设备,还需要面临复杂的部署和安装过程,这让很多人望而却步。不过,随着开源技术的不断进步,使得入门AI变得越来越容易。通过使用Ollama,您可以快速体验大语言模型的乐趣,不再需要担心繁琐的设置和安装过程。另外,通过集成Spring AI,让更多Java爱好者能便捷的将AI能力集成到项目中,接下来,跟随我的脚步,一起来体验一把。


二、术语

2.1、Spring AI

    是 Spring 生态系统的一个新项目,它简化了 Java 中 AI 应用程序的创建。它提供以下功能:

  • 支持所有主要模型提供商,例如 OpenAI、Microsoft、Amazon、Google 和 Huggingface。
  • 支持的模型类型包括“聊天”和“文本到图像”,还有更多模型类型正在开发中。
  • 跨 AI 提供商的可移植 API,用于聊天和嵌入模型。
  • 支持同步和流 API 选项。
  • 支持下拉访问模型特定功能。
  • AI 模型输出到 POJO 的映射。

2.2、Ollama
    是一个强大的框架,用于在 Docker 容器中部署 LLM(大型语言模型)。它的主要功能是在 Docker 容器内部署和管理 LLM 的促进者,使该过程变得简单。它可以帮助用户快速在本地运行大模型,通过简单的安装指令,用户可以执行一条命令就在本地运行开源大型语言模型。

    Ollama 支持 GPU/CPU 混合模式运行,允许用户根据自己的硬件条件(如 GPU、显存、CPU 和内存)选择不同量化版本的大模型。它提供了一种方式,使得即使在没有高性能 GPU 的设备上,也能够运行大型模型。
 


三、前置条件

3.1、JDK 17+

    下载地址:https://www.oracle.com/java/technologies/downloads/#jdk17-windows

    

    类文件具有错误的版本 61.0, 应为 52.0

3.2、创建Maven项目

    SpringBoot版本为3.2.3

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.3</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

3.3、导入Maven依赖包

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-core</artifactId>
</dependency>

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
</dependency>

<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-core</artifactId>
	<version>5.8.24</version>
</dependency>

<dependency>
	<groupId>org.springframework.ai</groupId>
	<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
	<version>0.8.0</version>
</dependency>

<dependency>
	<groupId>org.springframework.ai</groupId>
	<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
	<version>0.8.0</version>
</dependency>

3.4、 科学上网的软件

3.5、 安装Ollama及部署Qwen模型

    参见:开源模型应用落地-工具使用篇-Ollama(六)-CSDN博客


四、技术实现

4.1、调用Open AI

4.1.1、非流式调用

@RequestMapping("/chat")
public String chat(){
	String systemPrompt = "{prompt}";
	SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

	String userPrompt = "广州有什么特产?";
	Message userMessage = new UserMessage(userPrompt);

	Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));

	Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

	List<Generation> response = openAiChatClient.call(prompt).getResults();

	String result = "";

	for (Generation generation : response){
		String content = generation.getOutput().getContent();
		result += content;
	}

	return result;
}

    调用结果:

    

4.1.2、流式调用

@RequestMapping("/stream")
public SseEmitter stream(HttpServletResponse response){
	response.setContentType("text/event-stream");
	response.setCharacterEncoding("UTF-8");
	SseEmitter emitter = new SseEmitter();


	String systemPrompt = "{prompt}";
	SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

	String userPrompt = "广州有什么特产?";
	Message userMessage = new UserMessage(userPrompt);

	Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));
	Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

	openAiChatClient.stream(prompt).subscribe(x -> {
		try {
			log.info("response: {}",x);
			List<Generation> generations = x.getResults();
			if(CollUtil.isNotEmpty(generations)){
				for(Generation generation:generations){
				   AssistantMessage assistantMessage =  generation.getOutput();
					String content = assistantMessage.getContent();
					if(StringUtils.isNotEmpty(content)){
						emitter.send(content);
					}else{
						if(StringUtils.equals(content,"null"))
						emitter.complete(); // Complete the SSE connection
					}
				}
			}


		} catch (Exception e) {
			emitter.complete();
			log.error("流式返回结果异常",e);
		}
	});

	return emitter;
}

流式输出返回的数据结构:

    调用结果:

 

4.2、调用Ollama API

Spring封装的很好,基本和调用OpenAI的代码一致

4.2.1、非流式调用

@RequestMapping("/chat")
public String chat(){
	String systemPrompt = "{prompt}";
	SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

	String userPrompt = "广州有什么特产?";
	Message userMessage = new UserMessage(userPrompt);

	Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));

	Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

	List<Generation> response = ollamaChatClient.call(prompt).getResults();

	String result = "";

	for (Generation generation : response){
		String content = generation.getOutput().getContent();
		result += content;
	}

	return result;
}

调用结果:

Ollam的server.log输出

4.2.2、流式调用

@RequestMapping("/stream")
public SseEmitter stream(HttpServletResponse response){
	response.setContentType("text/event-stream");
	response.setCharacterEncoding("UTF-8");
	SseEmitter emitter = new SseEmitter();


	String systemPrompt = "{prompt}";
	SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

	String userPrompt = "广州有什么特产?";
	Message userMessage = new UserMessage(userPrompt);

	Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));
	Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

	ollamaChatClient.stream(prompt).subscribe(x -> {
		try {
			log.info("response: {}",x);
			List<Generation> generations = x.getResults();
			if(CollUtil.isNotEmpty(generations)){
				for(Generation generation:generations){
					AssistantMessage assistantMessage =  generation.getOutput();
					String content = assistantMessage.getContent();
					if(StringUtils.isNotEmpty(content)){
						emitter.send(content);
					}else{
						if(StringUtils.equals(content,"null"))
							emitter.complete(); // Complete the SSE connection
					}
				}
			}


		} catch (Exception e) {
			emitter.complete();
			log.error("流式返回结果异常",e);
		}
	});

	return emitter;
}

调用结果:


五、附带说明

5.1、OpenAiChatClient默认使用gpt-3.5-turbo模型

5.2、流式输出如何关闭连接

    不能判断是否为''(即空字符串),以下代码将提前关闭连接

    流式输出会返回''的情况

      应该在返回内容为字符串null的时候关闭

5.3、配置文件中指定的Ollama的模型参数,要和运行的模型一致,即

5.4、OpenAI调用完整代码

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.chat.Generation;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api")
public class OpenaiTestController {
    @Autowired
    private OpenAiChatClient openAiChatClient;

//    http://localhost:7777/api/chat
    @RequestMapping("/chat")
    public String chat(){
        String systemPrompt = "{prompt}";
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

        String userPrompt = "广州有什么特产?";
        Message userMessage = new UserMessage(userPrompt);

        Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));

        Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

        List<Generation> response = openAiChatClient.call(prompt).getResults();

        String result = "";

        for (Generation generation : response){
            String content = generation.getOutput().getContent();
            result += content;
        }

        return result;
    }

    @RequestMapping("/stream")
    public SseEmitter stream(HttpServletResponse response){
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");
        SseEmitter emitter = new SseEmitter();


        String systemPrompt = "{prompt}";
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

        String userPrompt = "广州有什么特产?";
        Message userMessage = new UserMessage(userPrompt);

        Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));
        Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

        openAiChatClient.stream(prompt).subscribe(x -> {
            try {
                log.info("response: {}",x);
                List<Generation> generations = x.getResults();
                if(CollUtil.isNotEmpty(generations)){
                    for(Generation generation:generations){
                       AssistantMessage assistantMessage =  generation.getOutput();
                        String content = assistantMessage.getContent();
                        if(StringUtils.isNotEmpty(content)){
                            emitter.send(content);
                        }else{
                            if(StringUtils.equals(content,"null"))
                            emitter.complete(); // Complete the SSE connection
                        }
                    }
                }


            } catch (Exception e) {
                emitter.complete();
                log.error("流式返回结果异常",e);
            }
        });

        return emitter;
    }
}

5.5、Ollama调用完整代码

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.chat.Generation;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api")
public class OllamaTestController {
    @Autowired
    private OllamaChatClient ollamaChatClient;

    @RequestMapping("/chat")
    public String chat(){
        String systemPrompt = "{prompt}";
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

        String userPrompt = "广州有什么特产?";
        Message userMessage = new UserMessage(userPrompt);

        Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));

        Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

        List<Generation> response = ollamaChatClient.call(prompt).getResults();

        String result = "";

        for (Generation generation : response){
            String content = generation.getOutput().getContent();
            result += content;
        }

        return result;
    }


    @RequestMapping("/stream")
    public SseEmitter stream(HttpServletResponse response){
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");
        SseEmitter emitter = new SseEmitter();


        String systemPrompt = "{prompt}";
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

        String userPrompt = "广州有什么特产?";
        Message userMessage = new UserMessage(userPrompt);

        Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "you are a helpful AI assistant"));
        Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

        ollamaChatClient.stream(prompt).subscribe(x -> {
            try {
                log.info("response: {}",x);
                List<Generation> generations = x.getResults();
                if(CollUtil.isNotEmpty(generations)){
                    for(Generation generation:generations){
                        AssistantMessage assistantMessage =  generation.getOutput();
                        String content = assistantMessage.getContent();
                        if(StringUtils.isNotEmpty(content)){
                            emitter.send(content);
                        }else{
                            if(StringUtils.equals(content,"null"))
                                emitter.complete(); // Complete the SSE connection
                        }
                    }
                }


            } catch (Exception e) {
                emitter.complete();
                log.error("流式返回结果异常",e);
            }
        });

        return emitter;
    }
}

5.6、核心配置

spring:
  ai:
    openai:
      api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ollama:
      base-url: http://localhost:11434
      chat:
        model: qwen:1.8b-chat

5.7、启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AiApplication {

    public static void main(String[] args) {
        System.setProperty("http.proxyHost","127.0.0.1");
        System.setProperty("http.proxyPort","7078"); // 修改为你代理软件的端口
        System.setProperty("https.proxyHost","127.0.0.1");
        System.setProperty("https.proxyPort","7078"); // 同理

        SpringApplication.run(AiApplication.class, args);
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

删除的文件能恢复吗?分享3个恢复方法

我们经常会遇到文件夹里的文件不小心被删除的情况&#xff0c;面对这种情况很多人会感到焦虑和无助。但实际上文件恢复并不是一件难事。在本文中我将分享一些实用的文件恢复方法&#xff0c;并深入探讨各种方法的优缺点&#xff0c;帮助大家更好地应对文件误删的问题。 首先让我…

集简云新增通义千问qwen 72b chat、qwen1.5 等多种大语言模型,提升多语言支持能力

通义千问再开源&#xff01;继发布多模态模型后&#xff0c;通义千问 1.5 版本也在春节前上线。 此次大模型包括六个型号&#xff1a;0.5B、1.8B、4B、7B、14B 和 72B&#xff0c;性能评测基础能力在在语言理解、代码生成、推理能力等多项基准测试中均展现出优异的性能&#x…

Jupyter如何开启Debug调试功能

由于需要对算子做远程调试功能&#xff0c;需要在jupyter中开启远程断点调试功能&#xff0c;特此记录。 本文写作时用到的系统是Ubuntu22&#xff0c;Python的版本是3.8. 首先&#xff0c;创建虚拟环境。 python -m venv venv source venv/bin/activate接着&#xff0c;安装…

hardlock.sys蓝屏解决办法【windows】

微软系统有时会蓝屏无法开机&#xff0c; 需要记下导致蓝屏的文件。 这里是【hardlock.sys】文件导致的。 解决办法是找到这个文件&#xff0c;把文件改名字&#xff0c;让系统找不到这个文件。 可以参考路径&#xff1a;C盘》C:\Windows\System32\drivers\hardlock.sys 把…

回归预测 | Matlab实现BiTCN-BiGRU-Attention双向时间卷积双向门控循环单元融合注意力机制多变量回归预测

回归预测 | Matlab实现BiTCN-BiGRU-Attention双向时间卷积双向门控循环单元融合注意力机制多变量回归预测 目录 回归预测 | Matlab实现BiTCN-BiGRU-Attention双向时间卷积双向门控循环单元融合注意力机制多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.M…

金鸣识别(OCR)与人眼识别哪个更准?

关于OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;金鸣识别与人眼识别率的对比&#xff0c;确实是一个引人入胜的话题。首先&#xff0c;我们要明确一点&#xff0c;虽然OCR技术在过去几十年里取得了巨大的进步&#xff0c;但要达到与人类…

QCustomPlot / C++ 追踪点、标签绘制开发

一、项目介绍&#xff1a; QCustomPlot曲线相关 1、曲线&#xff08;折线&#xff09;的后面有一个标签&#xff1b;点击标签可移动垂直方向移动曲线 2、曲线下方有纯文本标签 3、曲线设置多个追踪点 4、追踪点可跟随鼠标沿着曲线移动 5、多条曲线移动不卡顿 二、项目展示…

[IDE工具]Ubuntu18.04 VSCode版本升级

一、下载新版本 https://code.visualstudio.com/Download 二、安装deb sudo dpkg -i code_1.87.0-1709078641_amd64.deb 升级完成&#xff01; 三、问题解决 1. 依赖于 libc6 (> 2.28)&#xff1b;然而&#xff1a;系统中 libc6:amd64 的版本为 2.27-3ubuntu1.6 1.1…

代码学习记录13

随想录日记part13 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.06 主要内容&#xff1a;今天的主要内容是二叉树的第二部分哦&#xff0c;主要有层序遍历&#xff1b;翻转二叉树&#xff1b;对称二叉树。 102.二叉树的层序遍历226.翻转二叉树101. 对称二叉…

什么是ElasticSearch的深度分页问题?如何解决?

在ElasticSearch中进行分页查询通常使用from和size参数。当我们对ElasticSearch发起一个带有分页参数的查询(如使用from和size参数)时,ElasticSearch需要遍历所以匹配的文档直到达到指定的起始点(from),然后返回从这一点开始的size个文档 在这个例子中: 1.from 参数定义…

华为配置智能升级功能升级设备示例

配置智能升级功能升级设备示例 组网图形 图1 配置智能升级功能组网图 背景信息组网需求配置思路前提条件操作步骤操作结果 背景信息 为了方便用户及时了解设备主流运行版本&#xff0c;快速完成升级修复&#xff0c;华为设备支持自动下载、自助升级功能。用户在设备Web网管…

MySQl基础入门③

上一遍内容 接下来我们都使用navicat软件来操作数据了。 1.新建数据库 先创建我门自己的一个数据库 鼠标右键点击bendi那个绿色海豚的图标&#xff0c;然后选择新建数据库。 数据库名按自己喜好的填&#xff0c;不要写中文&#xff0c; 在 MySQL 8.0 中&#xff0c;最优的字…

Text-to-SQL任务中的思维链(Chain-of-thought)探索

导语 在探索LLM在解决Text-to-SQL任务中的潜能时&#xff0c;本文提出了一种创新的‘问题分解’Prompt格式&#xff0c;结合每个子问题的表列信息&#xff0c;实现了与顶尖微调模型&#xff08;RASATPICARD&#xff09;相媲美的性能。 会议&#xff1a;EMNLP 2023链接&#x…

Vue+SpringBoot打造考研专业课程管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高校教师管理模块2.4 考研专业模块2.5 考研政策模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 考研高校表3.2.2 高校教师表3.2.3 考研专业表3.2.4 考研政策表 四、系统展示五、核…

【Flutter 面试题】什么是Flutter里的Key?有哪些分类有什么使用场景?

【Flutter 面试题】什么是Flutter里的Key?有哪些分类有什么使用场景&#xff1f; 文章目录 写在前面解答补充说明ValueKey 示例ObjectKey 示例UniqueKey 示例GlobalKey 示例 写在前面 关于我 &#xff0c;小雨青年 &#x1f449; CSDN博客专家&#xff0c;GitChat专栏作者&am…

Docker-自定义镜像

目录 1 前言 2 构建java应用的步骤及镜像结构图 2.1 构建步骤 2.2 镜像结构图 3 Dockerfile常用指令 4 Dockerfile的内容举例 4.1 一般形式 4.2 一般形式的优化 5 构建镜像 5.1 指令 5.2 实操 5.2.1 加载jdk镜像(基础镜像) 5.2.2 构建我们的镜像 5.2.3 使用我们的…

Excel中怎么求排名

使用Rank函数 1.在需要显示排名的单元格内&#xff0c;输入“RANK&#xff08;数值&#xff0c;数值列表&#xff0c;排序方式&#xff09;” 2.将“数值”替换为需要计算排名的单元格的地址&#xff0c;例如E2单元格。 3.将“数值列表”替换为排名的数值范围&#xff0c;例…

C++写食堂菜品管理系统

说明:本博文来自CSDN-问答板块,题主提问。 需要:学校拟开发一套食堂菜品管理系统,以便对菜品和同学们的评价进行管理,其中包含如下信息: 商户:商户名称、柜面位置、电话…… 菜品:菜品编号、菜品名称、价格、所属商户…… 学生:注册账号、昵称、电话…… 食堂里的商户…

ubuntu 20.04 安装 huggingface transformers 环境

1. 安装 cuda 大多数新发布的大语言模型使用了较新的 PyTorch v2.0 版本&#xff0c;Pytorch 官方认为 CUDA 最低版本是 11.8 以及匹配的 GPU 驱动版本。详情见Pytorch官方 如下图&#xff1a; 1.1 下载 cuda cuda 12.1 官方网站&#xff1a; 下载&#xff1a; $wget htt…

答题pk小程序源码技术大解析

答题pk小程序源码解析 在数字化时代&#xff0c;小程序因其便捷性、即用性而受到广泛欢迎。其中&#xff0c;答题pk小程序更是成为了一种寓教于乐的现象。它不仅为用户提供了趣味性的知识竞技平台&#xff0c;还为企业、教育机构等提供了互动营销和知识传播的新途径。本文将对…