前段时间,我用Python不到一百行代码就集成了LLM大模型,效果还挺好。考虑到公司有不少用Java的同事,我想,不妨也用Java试试看,效果会怎样呢?带着这个念头,我查阅了一些Java的资料,没想到竟然真的发现了支持集成LLM的Java框架。趁着一趟将近4个小时的高铁旅途,我就把这个入门步骤写出来了。
今年4月17日,Spring官方宣布Spring AI已经可以在Spring Initializr上使用了。这个新工具简化了与AI的交互方式,显著降低了将LLM模型集成到Java项目中的学习难度。现在,你可以在start.spring.io这个网站上尝试并构建你的Spring AI应用。
Spring AI是一个专为AI设计的工程应用框架,它秉承了Spring生态系统的设计原则,如可移植性和模块化设计,旨在推动使用POJO(Plain Old Java Object,简单Java对象)作为构建AI应用的基础。
1、Spring AI特性概览
这个API具有很强的灵活性,能够支持多种AI服务提供商之间的互操作,涉及聊天、生成图像和模型嵌入等多种功能。它提供同步和流API两种选项,并且允许用户配置参数来访问特定的模型。下面是一些主要支持的模型列表,方便用户快速了解。
1.1支持的聊天模型
| AI提供商 | 模型名称 |
|---|---|
| OpenAI | GPT系列 |
| Azure | Open AI平台 |
| Amazon | Bedrock |
| Anthropic | Claude |
| Cohere | Command |
| AI21 Labs | Jurassic-2 |
| Meta | LLama 2 |
| Amazon | Titan |
| Vertex AI | |
| HuggingFace | 多种模型(例如Llama2) |
| Ollama | 支持本地运行,无需GPU |
1.2支持的图像生成模型
| AI提供商 | 模型名称 |
|---|---|
| OpenAI | DALL-E |
| StabilityAI | 图像生成模型 |
1.3支持的向量模型
| AI提供商 | 模型名称 |
|---|---|
| OpenAI | 向量模型 |
| Azure | Open AI向量模型 |
| Ollama | 本地向量处理 |
| ONNX | 跨平台支持 |
| PostgresML | 数据库集成模型 |
| Bedrock Cohere | Cohere平台 |
| Bedrock Titan | Titan平台 |
| VertexAI |
更多细节请参考官方文档:Spring AI项目文档。通过这些文档,用户可以详细了解如何将这些模型集成到自己的Java项目中,充分发挥AI技术的强大功能。
2、Spring AI 快速入门
在IDEA中快速启动一个新项目非常简单。在开始前需要选择你所需的AI模型依赖。
这里,我以Ollama模型为例来演示整个过程:
2.1 模型选择
Ollama允许我们在不需要GPU资源的情况下,仅通过一键在本地计算机上构建大型模型,并提供了控制台和RestfulAPI,方便我们在Ollama上快速测试和集成大型模型。
从Ollama官网中,我们可以看到Ollama支持以下模型。
| 模型名称 | 描述 |
|---|---|
| wizardlm2 | 微软AI研发的最新大型语言模型,性能在复杂对话、多语言处理、推理及代理用户场景中有所提升。 |
| mixtral | Mistral AI发布的一套专家混合模型,提供开放权重,参数规模为8x7b和8x22b。 |
| gemma | Google DeepMind构建的轻量级、最先进的开放模型家族,适用于多种应用。 |
| llama2 | 一系列基础语言模型,参数规模从7B到70B不等。 |
| codegemma | 一系列强大的轻量级模型,能够执行多种编码任务,如即时代码补全、代码生成、自然语言理解和数学推理等。 |
| command-r | 专为对话交互和处理长上下文任务优化的大型语言模型。 |
| command-r-plus | 一款强大且可扩展的大型语言模型,专为现实世界的企业用例设计,表现出色。 |
| llava | 一款新型端到端训练的大型多模态模型,结合视觉编码器和Vicuna实现视觉和语言理解。 |
注意:
- Gemma是Google Meta最近发布的一款模型。
- 相较于llama2模型对中文支持不足,gemma模型对中文的支持更加友好。
2.2、Ollama 安装设置
在计算机上安装Ollama,非常简单。直接访问Ollama的官方网站,下载与你的操作系统相匹配的版本。无论是Windows、Mac还是Linux,你都能找到适合的安装包。
下载完毕后,根据提示完成安装。然后,打开命令行工具,输入以下命令来确认Ollama已经正确安装,并且检查其版本:
ollama --version
从Ollama的模型库中拉取(pull)所需的模型。打开你的终端或命令提示符,输入以下命令来下载gemma:2b模型:
ollama pull gemma:2b
下载完成后,你可以立即在本地运行这个模型。继续在命令行中输入:
ollama run gemma:2b
这个命令会启动gemma:2b模型。一旦运行成功,你就可以开始使用模型的功能了。记得,每次使用模型时,只需简单地在控制台里运行上述命令即可。
首次使用ollama命令运行模型时,系统会自动开始下载所需的模型文件。注意,这个文件大概需要3GB的存储空间,下载时间取决于你的网络速度,可能需要一段时间,请耐心等待。
模型文件下载完毕后,模型会按照之前的设置自动启动。这时,你就可以在你的控制台里进行各种测试,与模型进行交互,看看模型的表现如何。
完成使用后,如果你需要退出模型,可以在控制台输入以下命令来安全地关闭模型:
/bye
2.3、Spring依赖说明
需要特别注意的是,当你在使用Spring AI相关的依赖项时,可能会遇到一些小问题。由于这些依赖项目前还没有在Maven中央仓库中提供,所以你需要手动配置使用Spring的专用仓库。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在进行项目配置时,由于Maven官方仓库尚未收录Spring AI的相关依赖,我们需要对项目的pom.xml配置文件进行一点小修改,以确保能够正确地从Spring的专用仓库拉取所需依赖。具体地,你需要修改pom.xml文件中的仓库地址部分,指向Spring提供的专用仓库。
修改后的pom.xml文件中关于仓库配置的部分应该如下所示:
<repositories>
<!--这里使用的是快照版本-->
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
2.4配置Ollama模型
在你的项目中,要确保Ollama模型能正确发挥作用,我们需要对application.yml配置文件做一些关键性的调整。这个文件是定义项目运行时参数的核心文件,因此它的重要性不言而喻。
首先,你需要打开你项目中的application.yml文件,然后在合适的部分加入如下配置:
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
model: gemma:2b
接着,我们将配置一个专门处理聊天功能的配置类,如下所示:
package io.daasrattale.generalknowledge.chat;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatConfig {
@Bean
public ChatClient chatClient() {
return new OllamaChatClient(new OllamaApi())
.withDefaultOptions(OllamaOptions.create()
.withTemperature(0.9f));
}
}
说明一下,模型的“temperature”参数用于控制语言模型输出的随机性。这里我们将其设置为0.9,目的是让模型在回答时更具随机性,从而更愿意冒险尝试。
最后一步是创建一个简单的聊天用的REST API:
package io.daasrattale.generalknowledge.chat;
import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/v1/chat")
public class ChatController {
private final OllamaChatClient chatClient;
public ChatController(OllamaChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping
public Mono<ResponseEntity<String>> generate(@RequestParam(defaultValue = "Tell me to add a proper prompt in a funny way") String prompt) {
return Mono.just(
ResponseEntity.ok(chatClient.call(prompt))
);
}
}
2.5测试验证
我们来尝试调用一个GET请求 /v1/chat,输入一个空的提示:
那么,如果是一个简单的常识性问题呢:
2.6流访问代码测试示例
@Test
void streamChat() throws ExecutionException, InterruptedException {
// Build an asynchronous function to manually close the test function
CompletableFuture<Void> future = new CompletableFuture<>();
String message = """
year-end work summary report
""";
PromptTemplate promptTemplate = new PromptTemplate("""
You are a Java development engineer, and you are good at writing the company’s year-end work summary report.
Write a 100-word summary report based on: {message} scenario
""");
Prompt prompt = promptTemplate.create(Map.of("message", message));
chatClient.stream(prompt).subscribe(
chatResponse -> {
System.out.println("response: " + chatResponse.getResult().getOutput().getContent());
},
throwable -> {
System.err.println("err: " + throwable.getMessage());
},
() -> {
System.out.println("complete~!");
// Close the function
future.complete(null);
}
);
future.get();
}