Spring AI 入门

前置知识参考:Spring AI 中文网
用于快速接入大模型

以下是 Spring AI、Spring Boot 与 JDK 的版本兼容性整理:

Spring AI 版本 Spring Boot 版本 JDK 版本
1.0.x 3.1.x - 3.5.x Java 17
1.1.x 3.5.x Java 17
2.x 4.0.x 及以上 Java 21

大模型选型

  1. 自研大模型,AI算法岗(985,211)
  2. 云端大模型,阿里百炼
  3. 本地 ollama 部署开源大模型

github 大模型评分

调用大模型入门

使用百炼大模型创建API-KEY
导入maven:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
</dependencies>

设置application.yml配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
application:
name: spring-ai-promote
ai:
openai:
#去掉v1,springai会自动加。加上的话在运行时会报404 NOT_FOUND from POST https://dashscope.aliyuncs.com/compatible-mode/v1/v1/chat/completions
baseurl: https://dashscope.aliyuncs.com/compatible-mode # 云模型提供的地址
# 为了防止泄密,这里建议使用环境变量的方式,将 API Key 配置在环境变量中
api-key: ${OPENAI_API_KEY}
chat:
options:
#这个模型支持非思考模式,适用于快速响应的场景
model: qwen-max

创建controller demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
class AController {

private final ChatClient chatClient;

public AController(ChatClient.Builder chatClientBuilder) {
//build()会创建一个新的对象,如果这个对象已经存在spring中,则直接使用注解注入
this.chatClient = chatClientBuilder.build();
}

@GetMapping("/ai")
String generation(String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.content();
}
}

chatClientBuilder 会显示:Could not autowire. No beans of ‘Builder’ type found. 但是不影响

启动程序,访问:http://localhost:8080/ai?userInput=hello ,就可以看到回答

角色预设

创建config/Config.java:

1
2
3
4
5
6
7
8
9
10
@Configuration
class Config {

@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate")
.build();
}

}

此时由于配置类里面注入了 ChatClient,控制层中的构造器注入就需要变成注解注入,不然build()会失败,因为spring默认是单列,现在已经存在了。

流式响应

将call修改为stream,返回类型修改为Flux,同时需要注意响应头编码,不然会乱码。

1
2
3
4
5
6
7
8
9
// 没有 produces = "text/html;charset=UTF-8"高速响应头返回的数据编码是什么格式,会导致乱码
@GetMapping(value = "/stream",produces = "text/html;charset=UTF-8")
Flux<String> Stream(String userInput) {
Flux<String> output = chatClient.prompt()
.user(userInput)
.stream()
.content();
return output;
}

代码梯子

本地使用了梯子,但是程序无法访问openai。这时候就需要使用openai官方访问,需要设置代理。

1
2
3
4
5
6
String proxy = "127.0.0.1"; // 100.100.101.235 8811示例,里面填具体的代理ip
int port = 7890; //代理的端口,
System.setProperty("proxyType", "4");
System.setProperty("proxyPort", Integer.toString(port));
System.setProperty("proxyHost", proxy);
System.setProperty("proxySet", "true");

ChatClient和ChatModel区别

ChatClient的底层是调用的ChatModel。当我们使用ChatModel时,可以配置运行时属性,修改模型、设置温度等。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//chatModel这里会有红色波浪线:Could not autowire. No beans of 'ChatModel' type found.
//但是实际运行没有任何影响,因为引入的jar包中存在自动配置类,已经帮我们配置好了
@Autowired
private ChatModel chatModel;

@GetMapping(value = "/chat/model", produces = "text/html;charset=UTF-8")
String chatModel(@RequestParam(value = "message", defaultValue = "你是谁") String message) {
ChatResponse response = chatModel.call(
new Prompt(
message,// 等同于 new UserMessage(message)
OpenAiChatOptions.builder()
.model("deepseek-r1")
.temperature(0.4)
.build()
));
return response.getResult().getOutput().getText();
}

文生图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@GetMapping(value = "/textToImg")
String textToImg(@RequestParam(value = "message", defaultValue = "画个猫") String message) {

ImageResponse response = openaiImageModel.call(
new ImagePrompt(message,
OpenAiImageOptions.builder()
// .model("openai")//需要支持的模型,需要自己找
.quality("hd")//质量,可以再官网查看
.withModel(OpenAiImageApi.DEFAULT_IMAGE_MODEL)
.N(4)
.height(1024)
.width(1024).build())

);

return response.getResult().getOutput().getUrl();
}

文生语音

也是需要模型支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Autowired
private OpenAiAudioSpeechModel openAiAudioSpeechModel;

@GetMapping(value = "/textToAudio")
String textToAudio(@RequestParam(value = "message", defaultValue = "画个猫") String message) {

OpenAiAudioSpeechOptions speechOptions = OpenAiAudioSpeechOptions.builder()
.model(OpenAiAudioApi.TtsModel.TTS_1.value)
.voice(OpenAiAudioApi.SpeechRequest.Voice.ALLOY)//人声
.responseFormat(OpenAiAudioApi.SpeechRequest.AudioResponseFormat.MP3)
.speed(1.0f)
.build();

SpeechPrompt speechPrompt = new SpeechPrompt("hello world!", speechOptions);
SpeechResponse response = openAiAudioSpeechModel.call(speechPrompt);
byte[] body = response.getResult().getOutput();
// 将byte[]存为mp3文件
try {
writeByteArrayToMp3(body, System.getProperty("user.dir"));
} catch (IOException e) {
throw new RuntimeException(e);
}
return "ok";
}

public static void writeByteArrayToMp3(byte[] audioBytes, String outputFilePath) throws IOException {
//创建FileoutputStream实例
FileOutputStream fos = new FileOutputStream(outputFilePath + "/demo.mp3");
// 将字节数组写入文件
fos.write(audioBytes);
// 关闭文件输出流
fos.close();
}

语音翻译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Autowired
private OpenAiAudioTranscriptionModel openAiAudioTranscriptionModel;
@GetMapping(value = "/audio2text")
String audio2text() {
var transcriptionOptions = OpenAiAudioTranscriptionOptions.builder()
.responseFormat(OpenAiAudioApi.TranscriptResponseFormat.TEXT)
.temperature(0f)
.build();
var audioFile = new ClassPathResource("/hello.mp3");//resources下

AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(audioFile, transcriptionOptions);
AudioTranscriptionResponse response = openAiAudioTranscriptionModel.call(transcriptionRequest);
return response.getResult().getOutput();
}

多模态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@GetMapping(value = "/multi")
String multi(@RequestParam(value = "message", defaultValue = "你从这个图片中看出了什么") String message) throws IOException {
var imageResource = new ClassPathResource("/11.png");

var userMessage = new UserMessage(
message, // 内容
new Media(MimeTypeUtils.IMAGE_PNG, imageResource));

ChatResponse response = chatModel.call(new Prompt(userMessage, OpenAiChatOptions.builder()
.model(OpenAiApi.ChatModel.GPT_4_TURBO_PREVIEW.getValue()) //需要模型支持
.build()));
return response.getResult().getOutput().getText();

}

Advisors

添加ai日志

角色预设的chatmodel修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
class Config {

@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate")
.defaultAdvisors(
new SimpleLoggerAdvisor() //记录日志;1.0.0以后,所有的advisors都采用的是builder模式
)
.build();
}

}

再在yml文件中配置

1
2
3
4
logging:
level:
org.springframework.ai: debug # AI 对话的日志级别
com.study: debug # 本项目的日志级别

会话记忆功能

先注入ChatMemory

1
2
3
4
@Bean
public ChatMemory chatMemory(){
return new InMemoryChatMemory();
}

再调用:

1
2
3
4
5
6
7
8
9
10
11
@Bean
public ChatClient chatClient(OllamaChatModel model,ChatMemory chatMemory){
return ChatClient.builder(model)
//类似ds这样的推理模型,不支持system设定
.defaultSystem("你是小黑")
.defaultAdvisors(
new SimpleLoggerAdvisor(), //记录日志;1.0.0以后,所有的advisors都采用的是builder模式
new MessageChatMemoryAdvisor(chatMemory)
)
.build();
}

现在拥有了记忆,但是此时会存在记忆混乱的问题;也就是不同的会话使用的是用一个记忆。
那怎么解决呢?

只需要再请求中带上不同的会话ID,添加到chatClient 中advisors(a -> a.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY,chatId))

但是在Spring1.0.0中,稍有变化需要注意。主要看ChatMemory和ChatMemoryRepository ,会通过实现类去具体实现

提示词工程

通过优化提示词,使大模型生成出尽可能理想的内容,这一过程叫提示词工程。

  • 清晰明确的指令:多加限定条件,或者要求。
  • 使用分隔符标记输入:防止prompt注入。
  • 按步骤拆解复杂任务:步骤1做什么,步骤2做什么,最后输出什么告诉大模型。
  • 提供输入输出示例:如题。
  • 明确要求输出格式:如题。
  • 给模型设定一个角色:如题。

本地部署模型

ollama安装

调用本地大模型

引入依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>

配置yml文件:

1
2
3
4
5
6
7
8
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
model: deepseek-r1:7b
options:
temperature: 0.8

参考

https://www.cnblogs.com/hujunwei/p/18939557