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
大模型选型
自研大模型,AI算法岗(985,211)
云端大模型,阿里百炼
本地 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: baseurl: https://dashscope.aliyuncs.com/compatible-mode 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) { 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 @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" ; 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 @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, 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() .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(); 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 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" ); 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 () ) .build(); } }
再在yml文件中配置
1 2 3 4 logging: level: org.springframework.ai: debug 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) .defaultSystem("你是小黑" ) .defaultAdvisors( new SimpleLoggerAdvisor (), 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