一、前言

    在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

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;

}

    调用结果:

    

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;

}

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

    调用结果:

 

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输出

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;

}

调用结果:

五、附带说明

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

}

}

参考链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: