墨语灵犀Java开发实战:集成SpringBoot构建智能问答微服务

最近在做一个内部知识库系统,需要给用户提供一个智能问答入口。传统的搜索框虽然能用,但总感觉差点意思,用户得自己提炼关键词,出来的结果还得再筛选。正好手头有墨语灵犀大模型的API,就琢磨着能不能用Java,特别是咱们最熟悉的SpringBoot,把它集成进来,做成一个能理解自然语言、直接给出答案的微服务。

说干就干。整个过程下来,发现用SpringBoot来封装大模型能力,比想象中要顺畅不少。从基础的API调用,到异步处理、结果缓存,再到应对高并发的一些小技巧,形成了一套还算完整的方案。今天就把这个实战过程梳理一下,如果你也在考虑给Java应用加点AI能力,特别是想快速搭建一个智能客服或者问答服务,或许能给你一些参考。

1. 为什么选择SpringBoot集成大模型?

你可能想问,现在Python不是搞AI的主流吗,为啥要用Java和SpringBoot?这其实得看场景。如果你的团队主力是Java技术栈,业务系统也是基于SpringCloud那一套微服务架构,那么引入一个全新的Python服务来做AI,会带来额外的运维、部署和联调成本。让Java服务直接调用模型API,是更平滑、更“企业级”的做法。

SpringBoot在这方面有几个天然优势。首先是它的自动配置和起步依赖,能让我们快速搭建一个稳健的Web服务层。其次,Spring强大的生态,比如对异步编程、缓存、连接池的支持,能很好地应对大模型API调用可能遇到的延迟、高并发等问题。最后,也是最重要的,它能让我们把AI能力像普通业务服务一样进行管理、监控和集成,无缝对接到现有的用户认证、日志、链路追踪体系中。

所以,这个方案的核心思路就是:将墨语灵犀大模型视为一个外部智能服务,通过SpringBoot构建一个轻量、高效、可靠的代理层,向上对业务提供简洁的问答接口,向下处理与模型API的复杂交互。

2. 项目搭建与基础依赖

我们从零开始。使用Spring Initializr或者IDE直接创建一个新的SpringBoot项目,我用的版本是2.7.x,当然3.x系列也完全没问题。

核心的依赖其实不多,主要就下面这几个:

<dependencies>
    <!-- Web服务基础 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 方便做参数校验和配置 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <!-- HTTP客户端,用于调用模型API -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <!-- 缓存支持,后面优化会用 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
    <!-- 工具类 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

这里特意引入了WebFlux,不是为了做响应式编程,而是看中了它底层的WebClient。相比传统的RestTemplateWebClient对异步和非阻塞的支持更友好,性能也更好,更适合用来调用外部API。

接下来,在application.yml里配置一些基础信息,特别是模型API的地址和密钥(这些敏感信息建议放在配置中心或环境变量里):

app:
  llm:
    # 墨语灵犀API的基础地址
    base-url: https://api.moyulingxi.com/v1
    # 你的API密钥
    api-key: ${LLM_API_KEY:your-api-key-here}
    # 默认使用的模型
    default-model: moyu-pro
    # 请求超时时间(毫秒)
    timeout: 30000

spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=1000, expireAfterWrite=10m

3. 核心层设计:封装模型API

直接在每个Controller里写HTTP调用代码是灾难的开始。我们需要一个专门的“服务层”来隔离这种复杂性。我设计了一个三层结构:Client -> Service -> Controller

第一步,创建API请求与响应的数据模型。 这其实就是定义Java类,对应API需要的JSON结构。

import lombok.Data;

@Data
public class ChatCompletionRequest {
    private String model;
    private List<Message> messages;
    private Double temperature = 0.7; // 控制创造性
    private Integer maxTokens = 2000; // 回复最大长度

    @Data
    public static class Message {
        private String role; // "user" 或 "assistant"
        private String content;
    }
}
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 Message message;
        private Integer index;
        private String finishReason;
    }

    @Data
    public static class Usage {
        private Integer promptTokens;
        private Integer completionTokens;
        private Integer totalTokens;
    }
}

第二步,实现HTTP客户端(Client层)。 这里用WebClient来发送请求。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;

@Component
public class LlmApiClient {

    @Value("${app.llm.base-url}")
    private String baseUrl;

    @Value("${app.llm.api-key}")
    private String apiKey;

    private WebClient webClient;

    @PostConstruct
    public void init() {
        this.webClient = WebClient.builder()
                .baseUrl(baseUrl)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();
    }

    public Mono<ChatCompletionResponse> createChatCompletion(ChatCompletionRequest request) {
        return webClient.post()
                .uri("/chat/completions")
                .bodyValue(request)
                .retrieve()
                .bodyToMono(ChatCompletionResponse.class)
                .timeout(Duration.ofMillis(30000)) // 配置超时
                .onErrorResume(e -> {
                    // 这里可以加入更精细的错误处理和日志
                    log.error("调用大模型API失败", e);
                    return Mono.error(new ServiceException("智能服务暂时不可用,请稍后重试"));
                });
    }
}

第三步,创建业务服务层(Service层)。 这一层对上提供简洁的问答方法,对下调用Client,并可以在这里加入业务逻辑,比如提示词工程。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;

@Service
public class SmartQaService {

    @Autowired
    private LlmApiClient llmApiClient;

    public String askQuestion(String userQuestion, String context) {
        // 构建系统提示词,可以引导模型扮演特定角色,比如“专业客服”
        String systemPrompt = "你是一个专业的智能助手,请根据用户的问题,提供准确、简洁、有帮助的回答。";
        if (context != null && !context.isEmpty()) {
            systemPrompt += "以下是一些参考信息:" + context;
        }

        ChatCompletionRequest request = new ChatCompletionRequest();
        request.setModel("moyu-pro");
        request.setMessages(Arrays.asList(
                new ChatCompletionRequest.Message("system", systemPrompt),
                new ChatCompletionRequest.Message("user", userQuestion)
        ));

        // 暂时使用阻塞式调用,后面会优化
        ChatCompletionResponse response = llmApiClient.createChatCompletion(request).block();

        if (response != null && 
            response.getChoices() != null && 
            !response.getChoices().isEmpty()) {
            return response.getChoices().get(0).getMessage().getContent();
        }
        return "抱歉,我暂时无法回答这个问题。";
    }
}

4. 提供RESTful API与控制层

服务层准备好了,现在通过一个Controller暴露给外部调用。设计一个简单明了的API。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

@RestController
@RequestMapping("/api/v1/qa")
public class QaController {

    @Autowired
    private SmartQaService qaService;

    @PostMapping("/ask")
    public ApiResponse<String> ask(@Valid @RequestBody QuestionRequest questionRequest) {
        String answer = qaService.askQuestion(questionRequest.getQuestion(), questionRequest.getContext());
        return ApiResponse.success(answer);
    }

    // 简单的请求体
    @Data
    public static class QuestionRequest {
        @NotBlank(message = "问题不能为空")
        private String question;
        private String context; // 可选的上下文信息
    }

    // 统一的响应包装
    @Data
    public static class ApiResponse<T> {
        private Integer code;
        private String msg;
        private T data;
        public static <T> ApiResponse<T> success(T data) {
            ApiResponse<T> resp = new ApiResponse<>();
            resp.setCode(200);
            resp.setMsg("success");
            resp.setData(data);
            return resp;
        }
    }
}

现在,启动应用,用Postman或者curl测试一下POST /api/v1/qa/ask,传入{"question": "SpringBoot有什么优点?"},你应该就能收到一段由墨语灵犀生成的、关于SpringBoot优点的回答了。基础流程这就跑通了。

5. 性能优化实战:异步、缓存与并发

基础功能有了,但直接投入生产环境肯定不行。用户提问可能很频繁,而大模型API调用通常有几百毫秒到几秒的延迟,这会很快拖慢整个服务。我们需要进行几轮优化。

优化一:异步非阻塞处理。 这是提升吞吐量的关键。我们改造Service层,让Controller调用立刻返回一个FutureCompletableFuture,而不是阻塞等待。

@Service
public class AsyncSmartQaService {

    @Autowired
    private LlmApiClient llmApiClient;

    // 注入Spring提供的异步任务执行器
    @Autowired
    private AsyncTaskExecutor taskExecutor;

    public CompletableFuture<String> askQuestionAsync(String userQuestion, String context) {
        return CompletableFuture.supplyAsync(() -> {
            // 这里是实际的、可能耗时的调用逻辑
            return doAskQuestion(userQuestion, context);
        }, taskExecutor);
    }

    private String doAskQuestion(String userQuestion, String context) {
        // ... 同之前的askQuestion方法逻辑
        ChatCompletionRequest request = buildRequest(userQuestion, context);
        ChatCompletionResponse response = llmApiClient.createChatCompletion(request).block();
        return extractAnswer(response);
    }
}

然后Controller可以这样调用:

@PostMapping("/ask-async")
public CompletableFuture<ApiResponse<String>> askAsync(@Valid @RequestBody QuestionRequest questionRequest) {
    return qaService.askQuestionAsync(questionRequest.getQuestion(), questionRequest.getContext())
            .thenApply(ApiResponse::success);
}

这样,请求进来后,Spring会立即返回一个DeferredResultCompletableFuture,释放了处理请求的线程。实际的重活(调用模型API)在另一个线程池里慢慢干,干完了再把结果填回去。服务的并发能力一下子就上来了。

优化二:引入缓存。 很多用户问的问题可能是相似的,比如“公司年假制度是什么?”。我们没必要每次都花一两秒去问大模型。可以对问题进行摘要(比如MD5哈希)作为key,将答案缓存起来。

@Service
public class CachedSmartQaService {

    @Autowired
    private SmartQaService delegate; // 委托给实际的服务

    // 使用Spring Cache抽象,底层我们配了Caffeine
    @Cacheable(value = "qaCache", key = "#userQuestion.concat(#context ?: '')")
    public String askQuestionWithCache(String userQuestion, String context) {
        // 只有当缓存未命中时,才会执行这个方法
        return delegate.askQuestion(userQuestion, context);
    }
}

这里用@Cacheable注解,Spring会自动帮我们管理缓存。key的设计很重要,要确保相同的问题(和上下文)能命中同一个key。对于更复杂的场景,你可能需要先对用户问题进行一些标准化处理(比如去除空格、转换成小写)再生成key。

优化三:应对高并发与限流。 当突发大量请求涌向模型API时,可能会触发对方的限流,或者拖垮我们自己的服务。我们需要一个“闸口”。

  1. 使用连接池:确保WebClient使用了连接池(默认是有的),避免频繁创建连接的开销。
  2. 客户端限流:可以使用Resilience4j或Sentinel这样的库,为LlmApiClient.createChatCompletion方法添加一个限流器(Rate Limiter)或熔断器(Circuit Breaker)。
  3. 服务端限流:在Controller入口,可以使用@RateLimiter等注解,或者利用网关(如Spring Cloud Gateway)的全局限流功能,保护下游服务。

这里给一个Resilience4j限流的简单示例:

import io.github.resilience4j.ratelimiter.annotation.RateLimiter;

@Service
public class RateLimitedLlmService {

    @Autowired
    private LlmApiClient llmApiClient;

    @RateLimiter(name = "llmApiRateLimiter", fallbackMethod = "rateLimitFallback")
    public Mono<ChatCompletionResponse> callWithRateLimit(ChatCompletionRequest request) {
        return llmApiClient.createChatCompletion(request);
    }

    // 降级方法,当被限流时返回友好提示
    private Mono<ChatCompletionResponse> rateLimitFallback(ChatCompletionRequest request, Throwable t) {
        log.warn("触发模型API限流降级");
        // 返回一个包含提示信息的响应,或者放入队列稍后重试
        return Mono.just(createBusyResponse());
    }
}

6. 一个更完整的实战案例:智能客服工单分类

光有问答还不够,我们来看一个更具体的应用场景:自动化工单分类。用户提交一段文字描述问题,系统需要自动判断它属于“账号问题”、“支付问题”、“技术故障”还是“产品咨询”。

我们可以利用大模型的文本理解能力,通过设计好的“提示词”(Prompt)让它来完成分类。

SmartQaService里增加一个方法:

public String classifyTicket(String description) {
    String systemPrompt = """
            你是一个工单分类助手。请根据用户对问题的描述,将其归类到以下类别之一:
            - ACCOUNT: 账号相关,如登录、注册、密码修改。
            - PAYMENT: 支付相关,如扣款失败、退款申请。
            - TECH: 技术故障,如页面打不开、功能报错。
            - PRODUCT: 产品咨询,如功能如何使用、价格咨询。
            - OTHER: 其他类别。

            请只输出类别英文代号,不要输出任何其他解释。
            用户描述:
            """;

    ChatCompletionRequest request = new ChatCompletionRequest();
    request.setModel("moyu-pro");
    request.setTemperature(0.1); // 降低创造性,让输出更确定
    request.setMaxTokens(10);
    request.setMessages(Arrays.asList(
            new ChatCompletionRequest.Message("system", systemPrompt),
            new ChatCompletionRequest.Message("user", description)
    ));

    ChatCompletionResponse response = llmApiClient.createChatCompletion(request).block();
    String rawOutput = extractAnswer(response);
    // 简单清理输出,确保是预定义的类别
    return validateCategory(rawOutput.trim());
}

这样,后端收到工单描述后,可以先调用这个分类服务,自动打上标签,再路由给相应的客服团队,大大提升了效率。

7. 部署与监控建议

开发完了,最后聊聊部署和上线后怎么管。

部署:和部署任何SpringBoot应用一样,打成JAR包,用java -jar运行,或者放在Docker容器里。关键是要妥善管理配置文件,尤其是API密钥,一定要用环境变量或配置中心注入。

监控:这是保证服务稳定的眼睛。

  1. 应用监控:集成Spring Boot Actuator,暴露/health, /metrics, /prometheus端点,监控服务状态、JVM内存、线程池情况。
  2. 业务监控:在LlmApiClientSmartQaService中关键位置埋点,记录每次调用的耗时、成功与否、Token消耗量。这些数据对成本控制和性能分析至关重要。
  3. 日志:确保所有请求、响应(可脱敏)以及异常都有清晰的日志记录,方便排查问题。
  4. 告警:为API调用平均耗时、失败率、缓存命中率等关键指标设置告警阈值。

8. 总结

走完这一趟,感觉用SpringBoot来集成像墨语灵犀这样的大模型API,是一条非常可行的路径。它没有想象中那么“黑科技”,本质上还是对外部HTTP服务的调用和封装。SpringBoot生态提供的武器,比如WebClient、异步编程、缓存、限流熔断,都能很好地应对引入AI能力后带来的新挑战——延迟、波动、成本。

这套方案的价值在于,它让AI能力以一种低侵入、易管理的方式融入到了现有的Java技术体系中。你不需要组建专门的AI运维团队,业务开发同学就能理解和维护。从简单的智能问答,到复杂的工单分类、内容审核、数据提取,都可以基于这个微服务快速扩展。

当然,实际应用中还会遇到更多细节问题,比如提示词如何迭代优化、如何评估回答质量、如何设计更高效的缓存策略。但有了这个基础框架,剩下的就是不断迭代和打磨了。如果你正准备尝试,建议先从一个小而具体的场景开始,快速验证效果,再逐步扩大应用范围。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐