开源模型应用落地-工具使用篇-Spring AI(七)
作者:mmseoamin日期:2024-03-20

一、前言

    在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

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

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

    3.2、创建Maven项目

        SpringBoot版本为3.2.3

    
        org.springframework.boot
        spring-boot-starter-parent
        3.2.3
         
    

    3.3、导入Maven依赖包

    
    	org.projectlombok
    	lombok
    	true
    
    
    	ch.qos.logback
    	logback-core
    
    
    	ch.qos.logback
    	logback-classic
    
    
    	cn.hutool
    	hutool-core
    	5.8.24
    
    
    	org.springframework.ai
    	spring-ai-openai-spring-boot-starter
    	0.8.0
    
    
    	org.springframework.ai
    	spring-ai-ollama-spring-boot-starter
    	0.8.0
    

    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 response = openAiChatClient.call(prompt).getResults();
    	String result = "";
    	for (Generation generation : response){
    		String content = generation.getOutput().getContent();
    		result += content;
    	}
    	return result;
    }

        调用结果:

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

    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 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;
    }

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

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

        调用结果:

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

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

    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 response = ollamaChatClient.call(prompt).getResults();
    	String result = "";
    	for (Generation generation : response){
    		String content = generation.getOutput().getContent();
    		result += content;
    	}
    	return result;
    }

    调用结果:

    Ollam的server.log输出

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

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

    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 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;
    }

    调用结果:

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

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


    五、附带说明

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

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

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

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

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

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

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

          应该在返回内容为字符串null的时候关闭开源模型应用落地-工具使用篇-Spring AI(七),第13张

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

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

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

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

    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 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 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 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 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);
        }
    }