我的前面一篇文章(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 {
@Autowired
private 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形式:
@Autowired
private 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秒 3600000L
sseEmitter = 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 {
@Autowired
SSEUtils 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
接收用户文本输入,并将模型生成的图片作为输出返回。
@Autowired
private 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
接收用户文本输入,并将模型合成的语音作为输出返回。支持流式响应
文字转语音:
@Autowired
private 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());
}
语音转文字:
@Autowired
AudioTranscriptionModel 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();
}