Alibaba DASD-4B Thinking 对话工具 Java 集成开发指南:SpringBoot 微服务实战
最近在做一个内部知识库问答系统,后端用的是SpringBoot,前端希望有个能聊天的智能助手。找了一圈,发现Alibaba DASD-4B Thinking这个对话工具挺合适,它提供了标准的API接口,正好可以嵌入到我们的Java微服务里。但真开始动手,发现要把一个AI对话能力平滑地集成到现有的SpringBoot项目里,还是有不少细节要考虑,比如怎么管理多轮对话的状态、怎么处理异步的流式响应、怎么和现有的用户体系打通。
折腾了几天,总算跑通了。今天就把这个过程整理一下,如果你也在用Java技术栈,想给系统加个“智能大脑”,这篇实战指南应该能帮你省不少时间。我们不讲太多复杂的理论,就聚焦在怎么用SpringBoot快速、稳定地把这个对话工具用起来。
1. 项目准备与环境搭建
在开始写代码之前,我们得先把基础环境准备好。这里假设你已经有一个正在运行的SpringBoot项目,版本在2.7.x或3.x都可以。我们主要的工作是引入必要的依赖,并配置好连接DASD-4B Thinking服务的客户端。
1.1 添加项目依赖
首先,我们需要在项目的pom.xml文件里添加几个关键的依赖。除了SpringBoot的基础Web功能,我们还需要一个HTTP客户端来调用AI服务的API,以及一个处理JSON的工具。这里我选择用OkHttp和Jackson,它们用起来比较顺手。
<dependencies> <!-- SpringBoot Web Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- OkHttp:一个高效的HTTP客户端 --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <!-- Jackson:处理JSON序列化与反序列化 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!-- 如果涉及用户认证,可能需要Spring Security --> <!-- <dependency> --> <!-- <groupId>org.springframework.boot</groupId> --> <!-- <artifactId>spring-boot-starter-security</artifactId> --> <!-- </dependency> --> <!-- 其他你项目已有的依赖... --> </dependencies>1.2 配置服务连接参数
接下来,我们要把AI服务的地址、密钥这些信息配置起来。最好不要硬编码在代码里,而是放在SpringBoot的配置文件(比如application.yml)中,这样不同环境(开发、测试、生产)切换起来也方便。
打开你的application.yml文件,添加如下配置:
# AI对话服务配置 ai: dasd: # DASD-4B Thinking API 的基础地址,根据你实际部署的镜像地址填写 base-url: http://your-dasd-mirror-host:port/v1 # 调用API所需的认证密钥,通常由镜像服务提供 api-key: your-secret-api-key-here # 请求超时时间(毫秒) timeout: 30000 # 是否启用流式响应(用于实现打字机效果) stream-enabled: true这里有几个关键点:
base-url:需要替换成你实际部署的DASD-4B Thinking镜像服务的访问地址和端口,后面的/v1通常是API的版本路径。api-key:这是调用接口的凭证,务必妥善保管,不要提交到公开的代码仓库。stream-enabled:设置为true后,我们可以接收到AI模型一个字一个字返回的流式响应,体验更好。
1.3 创建服务客户端配置类
配置好参数后,我们需要创建一个Spring的配置类,来初始化一个全局可用的HTTP客户端,并把这些配置参数注入到Spring容器中,方便其他地方使用。
package com.yourproject.ai.config; import lombok.Data; import okhttp3.OkHttpClient; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Duration; @Configuration @ConfigurationProperties(prefix = "ai.dasd") @Data public class DasdAiConfig { private String baseUrl; private String apiKey; private int timeout; private boolean streamEnabled; /** * 配置一个全局的OkHttpClient实例。 * 设置了连接超时、读取超时和写入超时。 */ @Bean public OkHttpClient okHttpClient() { return new OkHttpClient.Builder() .connectTimeout(Duration.ofMillis(timeout)) .readTimeout(Duration.ofMillis(timeout)) .writeTimeout(Duration.ofMillis(timeout)) .build(); } }这个类使用了@ConfigurationProperties,它会自动将application.yml中ai.dasd前缀下的属性映射到类的字段上。@Bean注解的方法okHttpClient()会生成一个单例的HTTP客户端供整个应用使用。
2. 核心服务层设计与实现
环境搭好了,现在我们来写最核心的部分——调用AI对话API的服务。这里我们会设计一个服务类,它负责组装请求、发送HTTP调用、处理响应,并且初步处理对话的历史记录。
2.1 定义请求与响应数据结构
在和服务端通信前,我们先定义好双方“对话”的语言,也就是请求和响应的Java对象。这能让我们的代码更清晰,也方便Jackson帮我们自动转换JSON。
API请求体:我们需要告诉AI模型用户说了什么,以及之前聊过什么。
package com.yourproject.ai.model; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class ChatCompletionRequest { // 模型名称,根据DASD-4B Thinking镜像提供的模型名填写 private String model = "dasd-4b-thinking"; // 对话消息列表,包含历史对话和当前用户问题 private List<Message> messages = new ArrayList<>(); // 是否启用流式输出(true:流式, false:非流式) private boolean stream = false; // 其他可选参数,如温度(控制随机性)、最大生成长度等 private Double temperature; private Integer maxTokens; // 内部类:表示单条消息 @Data public static class Message { private String role; // “system”, “user”, “assistant” private String content; // 消息内容 public Message(String role, String content) { this.role = role; this.content = content; } } }API响应体:用于接收非流式(一次性)的响应。
package com.yourproject.ai.model; import lombok.Data; import java.util.List; @Data public class ChatCompletionResponse { private String id; private String object; private Long created; private String model; private List<Choice> choices; private Usage usage; @Data public static class Choice { private Integer index; private Message message; // 注意:流式和非流式响应中,这个字段结构可能不同 private String finishReason; } @Data public static class Message { private String role; private String content; } @Data public static class Usage { private Integer promptTokens; private Integer completionTokens; private Integer totalTokens; } }流式响应块:用于处理流式输出时,服务端返回的每一个数据块。
package com.yourproject.ai.model; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class StreamResponseChunk { private String id; private String object; private Long created; private String model; private List<StreamChoice> choices; @Data public static class StreamChoice { private Integer index; private Delta delta; @JsonProperty(“finish_reason”) private String finishReason; } @Data public static class Delta { private String role; private String content; } }2.2 实现AI对话服务
有了数据结构,我们就可以实现服务类了。这个类会封装所有与DASD-4B Thinking API交互的细节。
package com.yourproject.ai.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.yourproject.ai.config.DasdAiConfig; import com.yourproject.ai.model.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @Service @Slf4j @RequiredArgsConstructor public class DasdAiService { private final DasdAiConfig dasdAiConfig; private final OkHttpClient okHttpClient; private final ObjectMapper objectMapper; private static final MediaType JSON = MediaType.get(“application/json; charset=utf-8”); /** * 同步调用:发送消息并一次性获取完整回复。 * 适用于不需要“打字机”效果的场景。 */ public String chatSync(List<ChatCompletionRequest.Message> messages) throws IOException { ChatCompletionRequest request = new ChatCompletionRequest(); request.setMessages(messages); request.setStream(false); String requestBody = objectMapper.writeValueAsString(request); Request httpRequest = buildHttpRequest(requestBody); try (Response response = okHttpClient.newCall(httpRequest).execute()) { if (!response.isSuccessful()) { throw new IOException(“Unexpected code ” + response.code() + “, body: ” + response.body().string()); } ChatCompletionResponse completionResponse = objectMapper.readValue(response.body().string(), ChatCompletionResponse.class); if (completionResponse.getChoices() != null && !completionResponse.getChoices().isEmpty()) { return completionResponse.getChoices().get(0).getMessage().getContent(); } return “”; } } /** * 流式调用:发送消息并通过SSE(Server-Sent Events)返回流式响应。 * 适用于需要实时显示生成过程的场景。 */ public void chatStream(List<ChatCompletionRequest.Message> messages, SseEmitter emitter) throws IOException { ChatCompletionRequest request = new ChatCompletionRequest(); request.setMessages(messages); request.setStream(true); String requestBody = objectMapper.writeValueAsString(request); Request httpRequest = buildHttpRequest(requestBody); okHttpClient.newCall(httpRequest).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { log.error(“Stream call failed”, e); emitter.completeWithError(e); } @Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) { emitter.completeWithError(new IOException(“Stream error: ” + response.code())); return; } try (ResponseBody body = response.body()) { if (body != null) { // 处理流式响应,逐行读取 body.source().forEachLine(line -> { if (line.startsWith(“data: ”)) { String data = line.substring(6).trim(); if (“[DONE]”.equals(data)) { emitter.complete(); return; } try { StreamResponseChunk chunk = objectMapper.readValue(data, StreamResponseChunk.class); if (chunk.getChoices() != null && !chunk.getChoices().isEmpty()) { String content = chunk.getChoices().get(0).getDelta().getContent(); if (content != null) { // 通过SSE发送每一个内容块到前端 emitter.send(SseEmitter.event().data(content)); } } } catch (IOException e) { log.warn(“Failed to parse stream chunk”, e); } } }); emitter.complete(); } } } }); } /** * 构建HTTP请求对象 */ private Request buildHttpRequest(String requestBody) { return new Request.Builder() .url(dasdAiConfig.getBaseUrl() + “/chat/completions”) .post(RequestBody.create(requestBody, JSON)) .addHeader(“Authorization”, “Bearer ” + dasdAiConfig.getApiKey()) .addHeader(“Content-Type”, “application/json”) .build(); } }这个服务类提供了两种调用方式:
chatSync(同步):简单直接,一次性拿到全部回复。适合后台任务、不需要即时反馈的场景。chatStream(流式):稍微复杂点,它利用SSE技术,把AI模型生成的内容像流水一样,一个字一个字地推送给前端。这样用户就能看到“正在输入”的打字机效果,体验好很多。这里我们用SseEmitter这个Spring提供的工具来简化SSE的实现。
3. 对话状态管理与业务集成
现在我们已经能调用AI接口了。但在真实场景里,比如智能客服,对话通常不是一问一答就结束的,它是有上下文、有状态的。同时,我们还需要把这个AI能力和我们自己的业务(比如用户系统)结合起来。
3.1 设计对话会话与上下文管理
我们需要一个地方来存储一次完整对话的历史记录。一个简单的办法是,为每个用户或每次对话创建一个唯一的会话ID,然后把属于这个会话的所有问答都存起来。
package com.yourproject.ai.service; import com.yourproject.ai.model.ChatCompletionRequest; import org.springframework.stereotype.Component; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component public class ConversationManager { // 使用线程安全的Map在内存中存储会话上下文。生产环境可考虑Redis。 private final Map<String, ConversationSession> sessionStore = new ConcurrentHashMap<>(); /** * 获取或创建一个会话 */ public ConversationSession getOrCreateSession(String sessionId) { return sessionStore.computeIfAbsent(sessionId, id -> new ConversationSession(id)); } /** * 向会话中添加一轮对话(用户问题+AI回复) */ public void addInteraction(String sessionId, String userMessage, String aiResponse) { ConversationSession session = getOrCreateSession(sessionId); session.addMessage(“user”, userMessage); session.addMessage(“assistant”, aiResponse); // 可选:限制历史消息条数,避免上下文过长 session.trimMessages(20); // 保留最近20轮对话 } /** * 获取构建API请求所需的完整消息历史 */ public ChatCompletionRequest.Message[] getHistoryForApi(String sessionId) { ConversationSession session = sessionStore.get(sessionId); if (session == null || session.getMessages().isEmpty()) { // 如果是新会话,可以添加一个系统指令,设定AI的角色 return new ChatCompletionRequest.Message[]{ new ChatCompletionRequest.Message(“system”, “你是一个有帮助的助手。”) }; } // 将存储的消息转换为API需要的格式 return session.getMessages().stream() .map(m -> new ChatCompletionRequest.Message(m.getRole(), m.getContent())) .toArray(ChatCompletionRequest.Message[]::new); } /** * 内部类:表示一个对话会话 */ @Data public static class ConversationSession { private final String sessionId; private final List<SimpleMessage> messages = new CopyOnWriteArrayList<>(); public void addMessage(String role, String content) { messages.add(new SimpleMessage(role, content)); } public void trimMessages(int maxSize) { if (messages.size() > maxSize) { // 简单策略:保留最新的N条,可根据需要调整(如保留开头系统消息) List<SimpleMessage> trimmed = messages.subList(messages.size() - maxSize, messages.size()); messages.clear(); messages.addAll(trimmed); } } @Data public static class SimpleMessage { private final String role; private final String content; } } }这个管理器做了几件事:为每个会话保存历史;提供方法获取构建API请求所需的历史记录;还能对历史记录长度做裁剪,因为AI模型能处理的上下文长度是有限的。
3.2 集成用户认证与权限控制
在微服务里,AI能力通常不是对所有人开放的。我们需要把它和现有的安全框架(比如Spring Security)集成起来。
假设你的项目已经用了Spring Security,我们可以创建一个简单的权限注解和对应的检查逻辑。
package com.yourproject.ai.security; import java.lang.annotation.*; /** * 自定义注解:标记需要AI对话权限的方法 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresAiAccess { } // 在Spring Security配置中,添加对应的权限规则 // 例如,在SecurityConfig类中: @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers(“/api/ai/chat/**”).hasAuthority(“AI_CHAT_ACCESS”) // 保护AI聊天接口 .anyRequest().authenticated() ) // ... 其他配置(登录、CSRF等) return http.build(); } } // 在Controller的方法上使用注解 @RestController @RequestMapping(“/api/ai”) public class AiChatController { @PostMapping(“/chat”) @RequiresAiAccess // 使用自定义注解 public ResponseEntity<?> chat(@RequestBody ChatRequest request, @AuthenticationPrincipal User user) { // 方法内可以直接获取到当前登录的用户信息 String sessionId = “user_” + user.getId(); // 基于用户ID生成会话ID // ... 调用AI服务 } }这样,只有拥有AI_CHAT_ACCESS权限的用户才能访问聊天接口,并且我们在业务逻辑里也能方便地拿到当前用户的信息,用来生成唯一的会话ID。
4. 构建RESTful API控制器
最后,我们把上面的服务组装起来,通过HTTP接口暴露给前端或其他服务调用。这里我们设计两个主要的接口:一个用于同步聊天,一个用于流式聊天。
package com.yourproject.ai.controller; import com.yourproject.ai.model.ChatCompletionRequest; import com.yourproject.ai.service.ConversationManager; import com.yourproject.ai.service.DasdAiService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.Arrays; import java.util.List; @RestController @RequestMapping(“/api/ai”) @Slf4j @RequiredArgsConstructor public class AiChatController { private final DasdAiService dasdAiService; private final ConversationManager conversationManager; /** * 同步聊天接口 * @param request 前端传来的请求,包含用户消息和会话ID * @return AI的完整回复 */ @PostMapping(value = “/chat/sync”, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<ChatResponse> chatSync(@RequestBody ChatRequest request) { try { // 1. 获取当前会话的历史消息 ChatCompletionRequest.Message[] history = conversationManager.getHistoryForApi(request.getSessionId()); // 2. 构建本次请求的消息列表(历史 + 新问题) List<ChatCompletionRequest.Message> messages = new ArrayList<>(Arrays.asList(history)); messages.add(new ChatCompletionRequest.Message(“user”, request.getMessage())); // 3. 调用AI服务 String aiResponse = dasdAiService.chatSync(messages); // 4. 将会话记录保存到管理器 conversationManager.addInteraction(request.getSessionId(), request.getMessage(), aiResponse); // 5. 返回结果 return ResponseEntity.ok(new ChatResponse(aiResponse, request.getSessionId())); } catch (IOException e) { log.error(“Sync chat failed”, e); return ResponseEntity.internalServerError().body(new ChatResponse(“服务暂时不可用,请稍后重试。”, request.getSessionId())); } } /** * 流式聊天接口 * @param request 前端请求 * @return SSE流,用于推送流式响应 */ @GetMapping(value = “/chat/stream”, produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter chatStream(@RequestParam String sessionId, @RequestParam String message) { SseEmitter emitter = new SseEmitter(60000L); // 设置超时时间60秒 // 异步处理流式响应,避免阻塞当前线程 CompletableFuture.runAsync(() -> { try { // 获取历史并构建消息 ChatCompletionRequest.Message[] history = conversationManager.getHistoryForApi(sessionId); List<ChatCompletionRequest.Message> messages = new ArrayList<>(Arrays.asList(history)); messages.add(new ChatCompletionRequest.Message(“user”, message)); // 调用流式服务 dasdAiService.chatStream(messages, emitter); // 流式响应结束后,需要手动组装完整的AI回复并保存会话(这里简化处理) // 实际应用中,前端需要将收到的所有chunk拼接成完整回复传回,或服务端缓存chunk。 } catch (Exception e) { log.error(“Stream chat failed”, e); emitter.completeWithError(e); } }); return emitter; } // 简单的请求响应对象 @Data public static class ChatRequest { private String sessionId; private String message; } @Data @AllArgsConstructor public static class ChatResponse { private String reply; private String sessionId; } }这样,我们就有了两个清晰的接口:
POST /api/ai/chat/sync:发送一条消息,等待并接收完整的AI回复。GET /api/ai/chat/stream:发送一条消息,通过SSE连接实时接收AI的流式回复。
前端可以根据需求选择调用哪个接口。
5. 总结
走完这一套流程,一个具备基础对话管理能力的AI微服务就基本成型了。回顾一下,我们主要做了几件事:用SpringBoot搭建了服务框架;通过HTTP客户端封装了对DASD-4B Thinking API的调用,并实现了同步和流式两种方式;设计了一个简单的内存会话管理器来维护多轮对话的上下文;最后通过RESTful API将能力暴露出去,并考虑了与现有用户体系的集成。
在实际使用中,你可能还会遇到一些需要优化的地方,比如会话管理从内存移到Redis这样的外部存储以支持分布式部署;给AI的回复内容增加敏感词过滤或审核逻辑;或者根据业务需求,在系统消息里给AI设定更具体的角色和指令。这些都可以在我们这个基础框架上很方便地进行扩展。
整体集成过程比想象中要顺畅,DASD-4B Thinking提供的API兼容性不错,减少了我们很多适配工作。如果你正在寻找一种方式为你的Java应用注入AI对话能力,不妨按这个思路试试看。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。