SpringBoot 3.2.6 + Langchain4j 1.0.0-beta3 实战:5分钟搞定通义千问API对接(附完整代码)

最近在给一个内部知识库系统增加智能问答功能时,我面临一个选择:是直接调用各家大模型的原始HTTP API,还是找一个更优雅的封装框架?直接调用API虽然直接,但每个厂商的接口规范、参数格式、错误处理都不尽相同,代码很快就会变得臃肿且难以维护。经过一番调研,我发现了Langchain4j这个宝藏库——它就像Java生态里的“大模型中间件”,用统一的接口屏蔽了底层差异。更让我惊喜的是,它与SpringBoot的集成异常丝滑,特别是对接通义千问这类国内主流模型,几乎可以做到开箱即用。

这篇文章,我就以SpringBoot 3.2.6和Langchain4j 1.0.0-beta3为基础,带你走一遍从零开始、5分钟内完成通义千问API对接的完整流程。我会分享我实际配置中遇到的坑和解决方案,并提供可直接运行的代码。无论你是想快速验证一个AI想法,还是为现有Java应用注入智能能力,这篇实战指南都能让你少走弯路。

1. 环境准备与项目初始化

在开始敲代码之前,我们需要先把“舞台”搭好。这里我选择使用SpringBoot 3.2.6,因为它提供了稳定的Web和依赖管理基础。Langchain4j方面,我们使用其最新的1.0.0-beta3版本,这个版本对SpringBoot的支持已经相当成熟。

首先,通过Spring Initializr(start.spring.io)创建一个基础的SpringBoot项目。我习惯使用Maven,当然Gradle也同样适用。在选择依赖时,我们只需要勾选Spring Web即可,其他依赖我们会手动添加以保持清晰。

创建好项目后,打开pom.xml文件,我们需要引入Langchain4j的核心依赖。这里有个关键点:Langchain4j采用了BOM(Bill of Materials)的方式来管理其庞大的子模块版本,这能确保我们引入的各个组件版本兼容,避免令人头疼的依赖冲突。

<properties>
    <java.version>17</java.version>
    <spring-boot.version>3.2.6</spring-boot.version>
    <!-- 定义Langchain4j版本 -->
    <langchain4j.version>1.0.0-beta3</langchain4j.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- Langchain4j核心启动器,提供基础集成能力 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
    </dependency>
    <!-- 专门用于对接阿里云DashScope(通义千问)的启动器 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <!-- 引入SpringBoot官方BOM -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 引入Langchain4j官方BOM,这是统一版本的关键 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>${langchain4j.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

注意langchain4j-community-dashscope-spring-boot-starter这个依赖是社区维护的,专门用于对接阿里云的DashScope平台(即通义千问所在平台)。它内部封装了所有必要的HTTP客户端和序列化逻辑,我们无需关心底层细节。

依赖配置完成后,运行mvn clean compile确保一切正常。接下来,我们需要去获取访问大模型的“钥匙”——API Key。

2. 获取通义千问API Key与基础配置

没有API Key,我们的程序就像没有钥匙的车,无法启动。通义千问的API服务主要通过阿里云的“百炼”平台提供。别被名字吓到,申请过程其实非常简单。

  1. 访问平台:打开浏览器,访问阿里云百炼平台(Model Studio)。如果你没有阿里云账号,需要先注册一个。
  2. 开通服务:登录后,在控制台找到“模型服务”或“通义千问”相关入口。通常新用户会有一定的免费额度供体验,按照提示开通“DashScope灵积模型服务”即可。
  3. 创建API Key:在控制台侧边栏找到 “API-KEY管理” 菜单。点击“创建API-KEY”按钮。系统会生成一个以sk-开头的字符串,这个字符串只会显示一次,请务必立即复制并妥善保存到安全的地方(比如本地的密码管理器或环境变量中)。如果丢失,只能重新创建。

拿到API Key后,我们回到SpringBoot项目进行配置。我不推荐将密钥硬编码在application.yml里然后提交到代码仓库,这有安全风险。更专业的做法是使用环境变量或配置中心。

我们在src/main/resources/application.yml中做如下配置:

server:
  port: 8080

spring:
  application:
    name: langchain4j-demo

# Langchain4j 对接阿里云DashScope配置
langchain4j:
  community:
    dashscope:
      chat-model:
        # 关键:从环境变量读取API Key,避免泄露
        api-key: ${DASHSCOPE_API_KEY:sk-your-test-key-here-placeholder}
        # 指定使用的模型,qwen-max是能力较强的版本,也可用qwen-plus或qwen-turbo(更快)
        model-name: qwen-max
        # 温度参数,控制输出的随机性。0.0更确定/保守,1.0更随机/有创意。
        temperature: 0.7
        # 最大输出token数,控制回答长度
        max-tokens: 2000
        # 开启请求/响应日志,调试时非常有用
        log-requests: true
        log-responses: true

这里有几个参数值得展开说说:

  • model-name:通义千问提供了多个模型变体。qwen-max是综合能力最强的版本,适合复杂任务;qwen-turbo响应速度最快,成本也低,适合简单交互;qwen-plus则介于两者之间。你可以根据场景选择。
  • temperature:这是一个核心参数。我把它理解为“想象力开关”。当设置为接近0时(如0.1),模型的回答会非常稳定、保守,适合事实性问答。当设置为较高值(如0.9),回答会更具创造性和多样性,适合写故事、想点子。对于大多数业务场景,0.7是一个不错的平衡点
  • max-tokens:这限制了模型单次回复的最大长度。设置太小可能导致回答被截断,太大则可能浪费资源。对于对话,1024-2048通常足够。

提示:如何安全地设置${DASHSCOPE_API_KEY}?在本地运行时,可以在IDE的运行配置中设置环境变量,或者在系统终端执行export DASHSCOPE_API_KEY=your-real-key。在生产环境,则应使用K8s Secret、云厂商的密钥管理服务等。

3. 核心代码:构建你的第一个AI对话服务

配置完成后,Langchain4j的SpringBoot启动器会自动帮我们创建好一个QwenChatModel的Bean,并注入到Spring容器中。我们只需要像使用普通的Spring Bean一样注入它,就可以开始调用了。

下面我们来创建一个简单的REST控制器。我打算设计两个接口:一个用于单次对话,另一个用于模拟多轮对话(带简单的上下文记忆)。

首先,创建src/main/java/com/example/demo/controller/QwenChatController.java

package com.example.demo.controller;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.output.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/api/ai")
@Slf4j
public class QwenChatController {

    // 注入Langchain4j自动配置的Qwen模型Bean
    @Autowired
    private QwenChatModel qwenChatModel;

    /**
     * 简单单轮对话接口
     * @param question 用户问题
     * @return AI的回答
     */
    @GetMapping("/chat")
    public String simpleChat(@RequestParam String question) {
        log.info("接收到问题:{}", question);
        long start = System.currentTimeMillis();
        // 核心调用:一行代码完成与大模型的交互
        String answer = qwenChatModel.chat(question);
        long cost = System.currentTimeMillis() - start;
        log.info("模型响应耗时:{}ms", cost);
        return answer;
    }
}

启动你的SpringBoot应用,然后用浏览器、Postman或curl测试一下:

curl "http://localhost:8080/api/ai/chat?question=用Java写一个Hello World程序"

你应该能立刻收到通义千问返回的、格式良好的Java代码。是不是简单得有点不可思议?但这只是开始。现实中的对话往往需要上下文,比如你问“它有什么特点?”,模型需要知道“它”指的是上文中提到的某个事物。

接下来,我们实现一个带上下文记忆的聊天接口。Langchain4j提供了ChatMemory组件来管理对话历史。这里我们用MessageWindowChatMemory,它像一个滑动窗口,只保留最近N条消息。

我们在Controller里增加一个方法:

    /**
     * 带上下文的多轮对话接口
     * 使用内存存储对话历史,适合演示,生产环境需考虑持久化
     */
    @PostMapping("/chat/with-context")
    public String chatWithContext(@RequestBody ChatRequest request) {
        // 为每个会话(这里用userId模拟)维护独立的聊天内存
        // MessageWindowChatMemory最多保留10轮对话消息
        ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);

        // 如果有系统指令(比如设定AI角色),可以添加到内存
        if (request.getSystemPrompt() != null && !request.getSystemPrompt().isEmpty()) {
            chatMemory.add(SystemMessage.from(request.getSystemPrompt()));
        }

        // 将用户当前问题作为消息添加
        chatMemory.add(UserMessage.from(request.getQuestion()));

        // 从内存中获取所有消息作为上下文
        List<ChatMessage> messages = new ArrayList<>(chatMemory.messages());

        // 调用模型,传入完整的消息历史
        Response<AiMessage> response = qwenChatModel.generate(messages);
        AiMessage aiMessage = response.content();

        // 将AI的回答也存入内存,完成一轮对话闭环
        chatMemory.add(aiMessage);

        return aiMessage.text();
    }

    // 简单的请求体封装
    @Data
    static class ChatRequest {
        private String question;
        private String systemPrompt; // 可选,例如:“你是一个专业的Java架构师”
    }

为了使用@Data注解,你需要在pom.xml中加入Lombok依赖。这个接口通过POST JSON数据的方式工作,你可以指定一个systemPrompt来设定AI的角色,比如“你是一位幽默的科技博主”。模型会基于整个对话历史(内存中的消息)来生成回答,从而实现有上下文的多轮对话。

注意:上面的MessageWindowChatMemory是存储在内存中的,应用重启后记录会消失。在实际项目中,如果需要持久化会话上下文,你需要实现自定义的ChatMemoryStore,将会话历史存入Redis或数据库。

4. 进阶应用与实战技巧

基本的对话功能实现后,我们可以探索一些更实用的场景。Langchain4j的强大之处在于它提供了丰富的“链”(Chain)和工具(Tool)来构建复杂应用。

场景一:构建一个智能内容摘要服务 假设我们需要从长篇文章中提取核心要点。我们可以利用Langchain4j的PromptTemplate来设计一个更结构化的提示。

首先,创建一个服务类ContentSummaryService.java

package com.example.demo.service;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

@Service
public class ContentSummaryService {

    @Autowired
    private QwenChatModel qwenChatModel;

    private final PromptTemplate summaryTemplate = PromptTemplate.from(
            "请将以下文本内容总结为不超过{{max_points}}个要点的列表,并用中文输出。\n" +
                    "文本内容:{{content}}\n" +
                    "要求:要点清晰、简洁,抓住核心信息。"
    );

    public String summarize(String content, int maxPoints) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("content", content);
        variables.put("max_points", maxPoints);

        Prompt prompt = summaryTemplate.apply(variables);
        return qwenChatModel.chat(prompt.text());
    }
}

然后在Controller中调用这个服务。这种方式将提示词逻辑封装起来,更清晰也更易复用。

场景二:结构化数据提取 我们经常需要从非结构化的文本(如用户反馈、新闻)中提取出结构化的信息,比如实体、情感、分类等。我们可以通过设计特定的提示词,让模型返回JSON格式的数据,然后在代码中反序列化。

例如,提取用户评论中的产品名和情感:

public class ExtractionService {
    // ... 注入模型

    public ReviewInfo extractReviewInfo(String comment) {
        String prompt = String.format(
                "请从以下用户评论中提取信息,并以严格的JSON格式返回,只包含`productName`和`sentiment`(正面、负面、中性)两个字段。\n评论:%s",
                comment
        );
        String jsonResponse = qwenChatModel.chat(prompt);
        // 这里需要使用JSON库(如Jackson)将jsonResponse解析为ReviewInfo对象
        // 为了简化,假设模型返回了正确格式的JSON
        // ObjectMapper mapper = new ObjectMapper();
        // return mapper.readValue(jsonResponse, ReviewInfo.class);
        log.info("提取的JSON: {}", jsonResponse);
        // 实际开发中应添加健壮的解析和异常处理
        return new ReviewInfo("示例产品", "正面");
    }

    @Data
    static class ReviewInfo {
        private String productName;
        private String sentiment;
    }
}

性能与配置调优表 在实际使用中,根据不同的业务场景调整模型参数至关重要。下面这个表格总结了我的一些经验:

参数推荐范围适用场景对性能/效果的影响
temperature0.1 - 0.90.1-0.3:事实问答、代码生成
0.7-0.9:创意写作、头脑风暴
值越低,输出越确定、一致;值越高,输出越多样、有创意。
maxTokens512 - 4096512:短回复、分类
2048:常规对话、摘要
4096:长文生成、复杂分析
限制单次响应长度。设置过小回答会被截断,过大会增加不必要的延迟和成本。
topP (核采样)0.7 - 0.95通常与temperature配合使用,控制输出词汇的集中度。值越小,候选词集越集中,输出更可控;值越大,候选词集越广,输出更多样。通常保持默认即可。
modelNameqwen-turbo/max/plusturbo:高并发、实时交互
plus/max:复杂推理、高质量生成
turbo响应最快,成本最低,能力适中;max能力最强,响应稍慢,成本较高。

错误处理与重试机制 网络请求总有可能失败,或者模型服务暂时不可用。一个健壮的生产级应用必须包含错误处理。Langchain4j的模型调用可能会抛出RuntimeException,我们可以使用Spring的@ControllerAdvice进行全局处理,或者使用重试框架。

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;

@Service
public class RobustAIService {

    @Autowired
    private QwenChatModel qwenChatModel;

    // 添加重试机制:最多重试3次,每次间隔2秒
    @Retryable(value = { RuntimeException.class }, maxAttempts = 3, backoff = @Backoff(delay = 2000))
    public String reliableChat(String question) {
        return qwenChatModel.chat(question);
    }
}

记得在启动类上添加@EnableRetry注解来启用重试功能。这能有效应对短暂的网络抖动或服务端过载。

5. 部署考量与最佳实践

当你完成开发,准备将应用部署到生产环境时,有几个关键点需要特别注意。

1. API密钥管理: 绝对不要将API密钥提交到代码仓库。除了之前提到的环境变量方式,在Kubernetes中可以使用Secret:

# deployment.yaml 环境变量部分示例
env:
  - name: DASHSCOPE_API_KEY
    valueFrom:
      secretKeyRef:
        name: dashscope-secret
        key: api-key

在云服务器上,可以使用类似HashiCorp Vault的密钥管理工具。原则就是:代码和配置分离,密钥动态注入。

2. 超时与熔断配置: 大模型API的响应时间可能波动。我们需要在HTTP客户端层面设置合理的超时,并考虑引入熔断器(如Resilience4j)防止一个慢请求拖垮整个服务。虽然Langchain4j starter提供了一些配置,但在高并发场景下可能需要更精细的控制。

3. 成本监控与优化: 大模型调用按Token收费。我们需要关注:

  • 缓存:对相同或相似的查询结果进行缓存,可以显著降低成本。可以考虑使用Spring Cache,将“问题”的哈希值作为Key,答案作为Value缓存一段时间。
  • 用量日志:记录每次调用的模型、输入/输出Token数,便于后续分析和成本核算。DashScope平台本身也提供了用量监控面板。
  • 模型选型:在非关键路径或对响应速度要求极高的场景(如实时对话),使用qwen-turbo;在对质量要求高的场景(如报告生成),使用qwen-max。混合使用可以平衡成本与效果。

4. 异步与非阻塞调用: 如果你的服务需要同时处理多个AI请求,或者不想阻塞主线程,可以考虑使用异步调用。虽然QwenChatModel本身是同步接口,但你可以将其包装在CompletableFuture或使用Spring的@Async注解中,提升整体吞吐量。

@Service
public class AsyncChatService {
    @Async // 需要配置线程池
    public CompletableFuture<String> chatAsync(String question) {
        return CompletableFuture.completedFuture(qwenChatModel.chat(question));
    }
}

踩过几次坑之后,我发现最影响稳定性的往往不是代码逻辑,而是网络、密钥管理和超时设置。建议在项目初期就搭建一个简单的监控看板,至少把API调用成功率、平均响应时间和Token消耗量这几个指标监控起来。

好了,关于SpringBoot整合Langchain4j对接通义千问的核心流程和实战要点就分享到这里。从创建项目到写出第一个对话接口,可能真的用不了5分钟。但要把这个功能做得稳健、高效、可维护,就需要在配置、错误处理和架构设计上多花些心思。我提供的代码只是一个起点,你可以在此基础上,结合Langchain4j文档中关于ToolsRAG(检索增强生成)和Agents的更高级特性,去构建真正复杂而强大的AI应用。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐