我的前面一篇文章(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();}