?我的前面一篇文章(SpringAI——ChatClient配置與使用)中講了ChatClient,它是一個構建于 ChatModel 之上的高層封裝,它提供了更豐富的對話交互能力。可以這么說ChatModel相當于發動機,ChatClient相當于一臺含有發動機、方向盤、座椅等的一臺可以由駕駛員操控的的完整的車。
什么是 Chat Model?
?Chat Model 是 Spring AI 定義的文本對話模型接口,抽象了應用與大模型的交互邏輯,對話模型,接收一系列消息(Message)作為輸入,與模型 LLM 服務進行交互,并接收返回的聊天消息(Chat Message)作為輸出:
? ? 輸入 :使用 Prompt封裝用戶輸入,,支持純文本及多角色對話(如系統指令、用戶問題)。
? ? 輸出 :通過 ChatResponse 返回結構化結果,包含模型生成的文本內容及元數據(如 Token 消耗)。
ChatModel工作原理
- 模型封裝與適配層:ChatModel 是一個統一接口層,它的作用是將不同平臺的語言模型(如 OpenAI、阿里云 DashScope、百度文心一言、自建模型等)統一抽象為相同的調用方式。
- 核心方法說明:支持同步調用和流式調用
- 內部工作機制流程圖
??
?ChatModel 的核心組件解析
1. Message:對話的基本單元
Spring AI 使用 Message 表示對話中的每一條消息,包括:
- 系統提示詞:SystemMessage
- 用戶輸入:UserMessage
- 模型輸出:AssistantMessage
2. ChatOptions:控制模型行為的參數???
?每個 ChatModel 實現都需要支持一組通用的配置參數,比如:temperature,maxTokens,topP,frequencyPenalty,presencePenalty
?
3. ChatResponse:模型輸出的標準化封裝
ChatModel的運用
ChatModel
文本聊天交互模型,支持純文本格式作為輸入,并將模型的輸出以格式化文本形式返回。
spring-ai-alibaba-starter自動配置了ChatModel,所以使用的時候就直接引入就可以了。
chatModel.call()方法可以傳入Prompt,Message集合,或者直接傳入字符串。
首先是Prompt形式:
@RestController
@RequestMapping("/ai/v1")
public class ChatModelController {@Autowiredprivate ChatModel chatModel;@RequestMapping("/chatModel/chat")public String chat(String input) {ChatOptions options = ChatOptions.builder().temperature(0.9).maxTokens(1024).build();Prompt query = new Prompt(input,options);ChatResponse call = chatModel.call(query);return call.getResult().getOutput().getContent();}
我也可以是配置模型參數,通過 ChatOptions 在每次調用中調整模型參數?
Message形式:
@Autowiredprivate ChatModel chatModel;@RequestMapping("/chatModel/chat")public String chat(String input) {UserMessage userMessage = new UserMessage(input);SystemMessage systemMessage = new SystemMessage("你作為一個資深的java程序員,請根據你的專業知識回答下面問題,如果不是你專業范圍內的問題,請直接說不知道,不要做任何解釋:");return chatModel.call(userMessage,systemMessage);}
Message有不同的角色,其實現類有UserMessage,SystemMessage,?AssistantMessage
結果:因為我通過SystemMessage限制了大模型的回答
字符串形式的我就不解釋了,接下來我們看一下流式響應
ChatModel的流式響應
我是通過SseEmitter來實現服務器與客戶端的單向通道消息推送,這里就不過多解釋,如果不熟悉可以提前了解一下。
簡單寫一個SseEmitter的工具類:
@Component
@Slf4j
public class SSEUtils {private final Map<String, SseEmitter> pool = new ConcurrentHashMap<String, SseEmitter>();/*** sse發送消息** @param ids* @param content*/public void sendMessageBySSE(List<String> ids, String content) {log.info("SSE對象暫存池數據={}" ,pool );for (String id : ids) {SseEmitter sseEmitter = pool.get(id);if (sseEmitter == null) {
//0L表示一致存在sseEmitter = new SseEmitter(0L);
// sseEmitter.onCompletion(()->pool.remove(id));sseEmitter.onTimeout(() -> pool.remove(id));pool.put(id, sseEmitter);}try {sseEmitter.send(content);} catch (IOException e) {e.printStackTrace();}}}public SseEmitter subscribe(String id) {SseEmitter sseEmitter = pool.get(id);if (sseEmitter == null) {
//1000秒 3600000LsseEmitter = new SseEmitter(0L);
// sseEmitter.onCompletion(() -> pool.remove(id));sseEmitter.onTimeout(() -> pool.remove(id));pool.put(id, sseEmitter);}return sseEmitter;}public void loginOut(String id) {SseEmitter sseEmitter = pool.get(id);if (sseEmitter != null) {pool.remove(id);}}
}
?連接SSE的接口:
@RequestMapping("/ai/v1")
@RestController
public class SseController {@AutowiredSSEUtils sseUtils;@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter subscribe(@RequestParam("id") String id){return sseUtils.subscribe(id);}
}
?流式消息推送的接口:
@Autowired
SSEUtils sseUtils;@RequestMapping("/chatModel/stream")
public String chatStream(String id,String input) {Prompt query = new Prompt( input);Flux<ChatResponse> stream = chatModel.stream(query);// 訂閱流并處理每個響應stream.subscribe(response -> {// 提取并打印響應內容String content = response.getResults().get(0).getOutput().getContent();sseUtils.sendMessageBySSE(Collections.singletonList(id),content);});return "success";}
使用apiPost?測試:
ImageModel
接收用戶文本輸入,并將模型生成的圖片作為輸出返回。
@Autowiredprivate ImageModel imageModel;@RequestMapping("/chatModel/image")public String image(String input) throws IOException {ImageOptions options = ImageOptionsBuilder.builder().height(1024).width(1024).build();ImagePrompt query = new ImagePrompt(input,options);return "redirect:" + imageModel.call(query).getResult().getOutput().getUrl();}
?ImageOptions 可以設置模型名稱,圖片的大小等。
AudioModel
接收用戶文本輸入,并將模型合成的語音作為輸出返回。支持流式響應
文字轉語音:
@Autowiredprivate SpeechSynthesisModel speechModel;@RequestMapping("/chatModel/speech")public void speech(String input, HttpServletResponse response) throws IOException {SpeechSynthesisPrompt prompt = new SpeechSynthesisPrompt( input);SpeechSynthesisResponse call = speechModel.call(prompt);ByteBuffer audio = call.getResult().getOutput().getAudio();response.getOutputStream().write(audio.array());}
?語音轉文字:
@AutowiredAudioTranscriptionModel audioTranscriptionModel;@PostMapping("/chatModel/audio")public String audio2() throws IOException {Resource resource = new UrlResource("https://dashscope.oss-cn-beijing.aliyuncs.com/samples/audio/paraformer/hello_world_female2.wav");DashScopeAudioTranscriptionOptions options = DashScopeAudioTranscriptionOptions.builder().withModel("sensevoice-v1").build();AudioTranscriptionPrompt query = new AudioTranscriptionPrompt(resource,options);return audioTranscriptionModel.call(query).getResult().getOutput();}