Spring AI 是 Spring 官方社区项目,旨在简化 Java AI 应用程序开发,让 Java 开发者像使用 Spring 开发普通应用一样开发 AI 应用。
Spring Cloud Alibaba AI 以 Spring AI 为基础,并在此基础上提供阿里云通义系列大模型全面适配,让用户在 5 分钟内开发基于通义大模型的 Java AI 应用。
Spring AI 主要对接的是国外的OpenAI,Microsoft,Amazon,Google和Huggingface 。所以阿里在 Spring AI 的基础上面提供了对国内的模型的适配,例如完成通义系列大模型的接入。
Spring Cloud Alibaba AI 现在已经做了这些应用开发的接入,如:
- 聊天对话应用
- 文生图应用
- 文生语音应用
- 模型输出解析OutputParser(实现从 String 到自动 POJO 映射)
- 使用 Prompt Template
- 让 AI 模型接入外部数据(Prompt Stuff)
所以接下来一起入手体验一下Spring Cloud Alibaba AI
环境准备:JDK23
第一步,新建一个springboot项目
SpringBoot入门:如何新建SpringBoot项目(保姆级教程)
我们的pom文件为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.masiyi</groupId>
<artifactId>spring-ai</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-ai</name>
<description>spring-ai</description>
<properties>
<java.version>23</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.12</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-ai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2023.0.1.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.masiyi.springai.SpringAiApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>
如果不配置仓库地址,则会出现包拉不下来的情况,如果jdk版本不对也会出现编译不了的情况,这个pom里面我们使用的jdk编译版本为17版本,但是spring-cloud-starter-alibaba-ai的jdk编译版本为17,所以我们使用比17高的jdk版本就行了
第二步,申请api-key
阿里的apikey是需要付费的,所以我们通过bailian.console.aliyun.com/ 购买对应的服务之后就可以申请我们的api-key
第三步,编写yml配置文件
server:
port: 8999
spring:
application:
name: tongyi-example
cloud:
ai:
tongyi:
connection:
api-key: sk-6670e214d0414bdbad173b7xxxx
我们按照上面配置好,如果是2023.0.1.2版本就按照上面写,官网的文档估计没有更新到最新版本,这部分还是作者看源码才知道要这么配置的。
配置好之后我们就可以来上手spring-cloud-starter-alibaba-ai了
第四步,新建一个web控制层
package com.masiyi.springai.controller;/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.masiyi.springai.service.TongYiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.CrossOrigin;
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 java.util.Map;
/**
* TongYi models Spring Cloud Alibaba Controller.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@RestController
@RequestMapping("/ai")
@CrossOrigin
public class TongYiController {
@Autowired
@Qualifier("tongYiSimpleServiceImpl")
private TongYiService tongYiSimpleService;
@GetMapping("/example")
public String completion(
@RequestParam(value = "message", defaultValue = "Tell me a joke")
String message
) {
return tongYiSimpleService.completion(message);
}
@GetMapping("/stream")
public Map<String, String> streamCompletion(
@RequestParam(value = "message", defaultValue = "请告诉我西红柿炖牛腩怎么做?")
String message
) {
return tongYiSimpleService.streamCompletion(message);
}
}
这里面的有两个方法,一个是返回一个字符串,一个是返回一个steam流,但是官网的写法后面还需要改造一下,这里留个坑,就不做研究。
第五步,新建service
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.masiyi.springai.service;
import java.util.Map;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
public interface TongYiService {
/**
* Hello World example.
*
* @param message conversation content question.
* @return AI answer.
*/
String completion(String message);
/**
* Stream call.
*
* @param message conversation content question.
* @return AI answer.
*/
Map<String, String> streamCompletion(String message);
}
这个是可以不用的,可以直接实现上面的接口,但是官方用了,这里就按照官方的写法来
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.masiyi.springai.service;
import java.util.Map;
/**
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
public abstract class AbstractTongYiServiceImpl implements TongYiService {
private static final String INFO_PREFIX = "please implement ";
private static final String INFO_SUFFIX = "() method.";
@Override
public String completion(String message) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread().getStackTrace()[2].getMethodName());
}
@Override
public Map<String, String> streamCompletion(String message) {
throw new RuntimeException(INFO_PREFIX + Thread.currentThread()
.getStackTrace()[2].getMethodName() + INFO_SUFFIX);
}
}
核心的实现类:这里面我们注入了ChatModel和StreamingChatModel两个对话类
package com.masiyi.springai.service.impl;/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.masiyi.springai.service.AbstractTongYiServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.StreamingChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import java.util.Map;
/**
* The Chat simple example service implementation.
* There is optional message parameter whose default value is "Tell me a joke".
* pl The response to the request is from the TongYi models Service.
*
* @author yuluo
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
* @since 2023.0.0.0
*/
@Service
public class TongYiSimpleServiceImpl extends AbstractTongYiServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TongYiSimpleServiceImpl.class);
private final ChatModel chatModel;
private final StreamingChatModel streamingChatModel;
@Autowired
public TongYiSimpleServiceImpl(ChatModel chatModel, StreamingChatModel streamingChatModel) {
this.chatModel = chatModel;
this.streamingChatModel = streamingChatModel;
}
@Override
public String completion(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return chatModel.call(prompt).getResult().getOutput().getContent();
}
@Override
public Map<String, String> streamCompletion(String message) {
StringBuilder fullContent = new StringBuilder();
streamingChatModel.stream(new Prompt(message))
.flatMap(chatResponse -> Flux.fromIterable(chatResponse.getResults()))
.map(content -> content.getOutput().getContent())
.doOnNext(fullContent::append)
.last()
.map(lastContent -> Map.of(message, fullContent.toString()))
.block();
logger.info(fullContent.toString());
return Map.of(message, fullContent.toString());
}
}
第六步,调用接口
启动我们的项目之后,我们可以调用这两个接口试一下效果
ai/example接口
ai/strem接口
也调通了,但是这个吐字有重叠是有问题的。这里我们查看源码发现线上的版本是注释掉了这个参数,所以吐字会返回原来已经回答过的内容,这里我们知道就好,不做详细研究
至此,对话chat的示例我们就上手完成了
还有很多官方的示例,例如prompt模板(引导语言模型生成特定内容)、Function Call(使模型能调用外部函数)、文生图(使用给出的文字生成对应的图片)、向量数据库(高效存储和检索高维向量)等,但是这里就不一一介绍了,大家可以去官方的示例demo里面clone下来去看:官方示例
除此之外,demo里面也提供了一个前端文件,就在resources/static
目录下,使用浏览器打开 index.html 文件,输入问题,即可获得输出响应
但是确保对得上你的后端端口:
至此,Spring Cloud Alibaba AI 我们就体验完了,其实任何模型只要暴露api,我们就可以使用原生的java代码去调用他,但是往往很麻烦,因为我们需要考虑很多东西,而阿里则把他集成到了springboot中,也继承了springboot的优良特性,引入即使用。但是值得注意的是,这些都不是免费的,而是需要按量收费的。