基于SpringBoot的DeepSeek-demo 深度求索-demo 支持流式输出、历史记录

文章目录

文件下载

百度网盘 提取码: jsfc

蓝奏云 密码:5kxz

效果展示

Idea
Python
Postman

注:postman中只有websocket才有流式效果

使用说明

  • 修改配置文件
  • 请求路径为/chat

  • 在请求体中传入对话列表(可以携带对话的历史记录)

    复制代码
    [
        {
          "content": "你是一个乐于助人的助手。",
          "role": "system"
        },
        {
          "content": "记住a=1,b=2",
          "role": "user"
        },
        {
          "content": "好的,我已经记住了:**a = 1**,**b = 2**。如果有其他问题或需要进一步帮助,请告诉我! ??",
          "role": "assistant"
        },
        {
          "content": "a + b等于什么",
          "role": "user"
        }
    ]
    
    响应结果如下:
    根据我记住的 **a = 1** 和 **b = 2**,**a + b = 1 + 2 = 3**。  
    结果是 **3**!还有其他问题吗? ??

核心代码

controller
复制代码
import com.deepseek.deepseekdemo.service.AiChatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;

import java.util.List;
import java.util.Map;

@RestController
public class AiChatController {
    @Autowired
    private AiChatService aiChatService;

    @GetMapping(value = "/chat", produces = "application/stream+json")
    public Flux<String> chat(@RequestBody List<Map<String, String>> messages) {
        Flux<String> responseFlux = aiChatService.chat(messages);

        return Flux.create(sink -> {
            responseFlux.subscribeOn(Schedulers.boundedElastic())
                    .subscribe(
                            sink::next, // 返回流式字符串
                            sink::error, // 返回错误,请在调用端完成错误处理
                            sink::complete // 返回完成事件
                    );
        });
    }
}
Service
复制代码
import com.deepseek.deepseekdemo.config.DeepSeekConfig;
import com.deepseek.deepseekdemo.dto.AiMessageDto;
import com.deepseek.deepseekdemo.dto.DefaultAiMessageDto;
import com.deepseek.deepseekdemo.dto.StreamingAiMessageDto;
import com.deepseek.deepseekdemo.service.AiChatService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class AiChatServiceA implements AiChatService {

    Map<String, Object> body = new HashMap<>();
    Headers headers;
    AiMessageDto aiMessageDto;

    @Autowired
    private DeepSeekConfig deepSeekConfig;
    @Autowired
    private OkHttpClient httpClient;

    // 初始化
    @PostConstruct
    public void init() {
        body.put("model", deepSeekConfig.getModel());
        body.put("frequency_penalty", deepSeekConfig.getFrequencyPenalty());
        body.put("max_tokens", deepSeekConfig.getMaxTokens());
        body.put("presence_penalty", deepSeekConfig.getPresencePenalty());
        body.put("response_format", deepSeekConfig.getResponseFormat());
        body.put("stop", deepSeekConfig.getStop());

        body.put("stream", deepSeekConfig.getStream());
        if (deepSeekConfig.getStream()) {
            body.put("stream_options", deepSeekConfig.getStreamOptions());
        }

        body.put("temperature", deepSeekConfig.getTemperature());
        body.put("top_p", deepSeekConfig.getTopP());
        body.put("tools", deepSeekConfig.getTools());
        body.put("tool_choice", deepSeekConfig.getToolChoice());

        if (deepSeekConfig.getLogprobs()) {
            body.put("logprobs", deepSeekConfig.getLogprobs());
            body.put("top_logprobs", deepSeekConfig.getTopLogprobs());
        }

        // 构建请求头
        headers = new Headers.Builder().add("Authorization", "Bearer " + deepSeekConfig.getApiKey()).add("Content-Type", "application/json").build();

        if (deepSeekConfig.getStream()) {
            aiMessageDto = new StreamingAiMessageDto();
        } else {
            aiMessageDto = new DefaultAiMessageDto();
        }
    }


    @Override
    public Flux<String> chat(List<Map<String, String>> question) {
        body.put("messages", question);

        // 将请求体转换为 JSON 字符串
        ObjectMapper objectMapper = new ObjectMapper();
        String payload;
        try {
            payload = objectMapper.writeValueAsString(body);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("无法将负载转换为JSON", e);
        }

        // 构建请求
        Request request = new Request.Builder().url(deepSeekConfig.getUrl()).post(RequestBody.create(payload, MediaType.parse("application/json"))).headers(headers).build();

        // 返回 Flux<String> 以流式返回数据
        return Flux.create(emitter -> {
            try (Response response = httpClient.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    emitter.error(new IOException("请求失败响应码: " + response.code()));
                    return;
                }
                // 处理流式响应
                ResponseBody responseBody = response.body();
                if (responseBody != null) {
                    while (!responseBody.source().exhausted()) {
                        String line = responseBody.source().readUtf8Line();
                        if (line != null && !line.isEmpty()) {
                            try {
                                emitter.next(aiMessageDto.getMessage(line)); // 逐步发送数据
                            } catch (Exception e) {
                                log.error("处理数据时发生错误: ", e);
                            }
                        }
                    }
                    emitter.complete(); // 完成流
                }
            } catch (IOException e) {
                emitter.error(e);
            }
        });
    }
}
相关推荐
小村儿1 小时前
连载04-最重要的Skill---一起吃透 Claude Code,告别 AI coding 迷茫
前端·后端·ai编程
IT_陈寒2 小时前
Vite的alias配置把我整不会了,原来是这个坑
前端·人工智能·后端
一个有温度的技术博主2 小时前
Lua语法详解:从变量声明到循环遍历的避坑指南
redis·缓存·lua
gelald2 小时前
Spring Boot - 自动配置原理
java·spring boot·后端
希望永不加班2 小时前
SpringBoot 集成测试:@SpringBootTest 与 MockMvc
java·spring boot·后端·log4j·集成测试
uzong2 小时前
软件人员可以关注的 Skill,亲测确实不错,值得试一下
人工智能·后端
掘金虾2 小时前
Hono 框架入门到实战:用 Node.js 写一个支持工具调用的流式对话 Agent
后端
用户8356290780512 小时前
Python 自动拆分 Word 文档教程:按分节符与分页符处理
后端·python
树獭叔叔3 小时前
Claude Code 工具系统深度剖析:从静态注册到动态发现
后端·aigc·openai
上海合宙LuatOS3 小时前
LuatOS扩展库API——【exmodbus】MODBUS协议
物联网·lua·luatos