SpringAI——ChatModel

发布于:2025-07-18 ⋅ 阅读:(13) ⋅ 点赞:(0)

 我的前面一篇文章(SpringAI——ChatClient配置与使用)中讲了ChatClient,它是一个构建于 ChatModel 之上的高层封装,它提供了更丰富的对话交互能力。可以这么说ChatModel相当于发动机,ChatClient相当于一台含有发动机、方向盘、座椅等的一台可以由驾驶员操控的的完整的车。

什么是 Chat Model?

 Chat Model 是 Spring AI 定义的文本对话模型接口,抽象了应用与大模型的交互逻辑,对话模型,接收一系列消息(Message)作为输入,与模型 LLM 服务进行交互,并接收返回的聊天消息(Chat Message)作为输出:

    输入 :使用 Prompt封装用户输入,,支持纯文本及多角色对话(如系统指令、用户问题)。

    输出 :通过 ChatResponse 返回结构化结果,包含模型生成的文本内容及元数据(如 Token 消耗)。


ChatModel工作原理

  1. 模型封装与适配层:ChatModel 是一个统一接口层,它的作用是将不同平台的语言模型(如 OpenAI、阿里云 DashScope、百度文心一言、自建模型等)统一抽象为相同的调用方式。
  2. 核心方法说明:支持同步调用和流式调用
  3. 内部工作机制流程图

  

 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();
    }


网站公告

今日签到

点亮在社区的每一天
去签到