开源模型应用落地-工具使用篇-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);
    }

}
相关推荐
QianLu_Helper几秒前
探索新年创意!千鹿AI Flux文生图功能打造专属IP
人工智能·ai·aigc·photoshop·ai生成词·人物形象设计·人物形象ip设计
谦虚且进步4 分钟前
keras实现TCN网络层
人工智能·深度学习·keras
keep丶38 分钟前
Spring实现Logback日志模板设置动态参数
java·spring·logback
jiayoushijie-泽宣41 分钟前
【基于语义地图的机器人路径覆盖】Radiant Field-Informed Coverage Planning (RFICP)高斯扩散场轨迹规划算法详解
人工智能·算法·3d·机器人
蚂蚁在飞-1 小时前
Golang 的AI 框架库
人工智能
3DVisionary1 小时前
Tube Qualify弯管测量系统在汽车管路三维检测中的应用
人工智能·自动化·汽车·数字图像相关dic技术·三维弯管在线检测系统·适配器检测·汽车管路三维检测报告
_xaboy1 小时前
开源的Vue低代码表单设计器 form-create-designer v3.2.9 版本发布,新增10多种功能
vue.js·低代码·开源·表单·formcreate·可视化表单设计器
sp_fyf_20241 小时前
DeepSeek-V2:强大、经济且高效的专家混合语言模型
人工智能·深度学习·神经网络·机器学习·自然语言处理·数据挖掘
厚德云1 小时前
深度求索发布DeepSeek:高效、低成本的开源大语言模型
人工智能·ai·语言模型·自然语言处理·云计算
深蓝学院1 小时前
大语言模型遇上寻路算法:LLM-A*如何让路径规划效率提升50%?
人工智能·语言模型·自然语言处理