【人工智能应用技术】-基础实战-环境搭建(基于springAI+通义千问)(一)

在自己的项目中接入通义千问大模型,使其具有AI应用能力。需要提前申请 通义千问 API Key:
https://www.explinks.com/blog/how-to-obtain-the-tongyi-qianwen-api-key-step-by-step-guide/#title-2

然后通过SpringAI集成到自己的项目中

下面详细说明接入步骤

运行环境要求:

◦ JDK 17 或更高版本

◦ Spring Boot 3.x 系列

项目代码如下

一、 POM.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>spring-ai-qwen-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>通义千问整合案例</name>
	<description>Spring Boot 3.x 整合通义千问 SDK 2.10.0 案例(森林防火问答)</description>

	<properties>
		<java.version>17</java.version>
		<spring-ai.version>1.0.0-M6</spring-ai.version>
		<dashscope-sdk.version>2.10.0</dashscope-sdk.version>
		<gson.version>2.10.1</gson.version>
		<lombok.version>1.18.32</lombok.version>
	</properties>

	<dependencies>
		<!-- Spring Web 核心(提供HTTP接口支持) -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- Spring AI 核心(预览版) -->
		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-core</artifactId>
			<version>${spring-ai.version}</version>
		</dependency>

		<!-- 通义千问官方 SDK(核心依赖) -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>dashscope-sdk-java</artifactId>
			<version>${dashscope-sdk.version}</version>
			<exclusions>
				<!-- 排除冲突的SLF4J实现(使用Spring Boot默认的logback) -->
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-simple</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<!-- Gson 依赖(通义千问SDK序列化所需) -->
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
			<version>${gson.version}</version>
		</dependency>

		<!-- Lombok(简化代码:@Slf4j、@Data等) -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>${lombok.version}</version>
			<optional>true</optional> <!-- 编译时生效,不传递依赖 -->
		</dependency>

		<!-- Spring Boot 测试依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.hikvision.dec.client</groupId>
			<artifactId>dec-client</artifactId>
			<version>2.2.0</version>
		</dependency>
		<dependency>
			<groupId>org.springdoc</groupId>
			<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
			<version>2.2.0</version>
		</dependency>
		<!-- 替代手动引入 hibernate-validator 和 jakarta.validation-api -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
	</dependencies>

	<!-- 仓库配置(国内加速+预览版支持) -->
	<repositories>
		<repository>
			<id>maven-central</id>
			<name>Maven Central</name>
			<url>https://repo1.maven.org/maven2/</url>
			<releases><enabled>true</enabled></releases>
			<snapshots><enabled>false</enabled></snapshots> <!-- 快照版关闭,避免不稳定 -->
		</repository>
		<repository>
			<id>aliyun-maven</id>
			<name>Alibaba Maven Repository</name>
			<url>https://maven.aliyun.com/repository/public</url>
			<releases><enabled>true</enabled></releases>
			<snapshots><enabled>true</enabled></snapshots>
		</repository>
	</repositories>

	<pluginRepositories>
		<pluginRepository>
			<id>aliyun-maven</id>
			<url>https://maven.aliyun.com/repository/public</url>
		</pluginRepository>
	</pluginRepositories>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<!-- 排除Lombok依赖传递 -->
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

二、优化后的配置文件(application.yml)

yaml

yaml 复制代码
# 服务器配置
server:
  port: 8080 # 统一端口配置
  servlet:
    context-path: / # 根路径(可选,默认即可)

# 通义千问配置(集中管理)
dashscope:
  api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # 替换为你的API Key
  model: qwen-turbo # 模型名称(qwen-turbo/qwen-plus/qwen-max,按需选择)
  # 可选配置(默认使用官方端点,无需修改可注释)
  http-base-url: https://dashscope.aliyuncs.com/api/v1
  websocket-base-url: wss://dashscope.aliyuncs.com/api-ws/v1/inference/
  # SDK调用参数(集中配置,便于修改)
  temperature: 0.6
  max-tokens: 1000

# Spring 日志配置(优化日志输出)
logging:
  level:
    root: INFO
    com.example.demo: DEBUG # 本项目包日志级别设为DEBUG,便于调试
    com.alibaba.dashscope: WARN # 通义千问SDK日志级别设为WARN,减少冗余

三、优化后的核心代码

1. 配置类(DashScopeConfig.java)
java 复制代码
package com.example.springaiqwen;

import com.alibaba.dashscope.utils.Constants;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;

/**
 * 通义千问客户端配置类
 * 职责:初始化SDK配置(API Key、端点、超时等)
 */
@Slf4j
@Configuration
public class DashScopeConfig {

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

    @Value("${dashscope.http-base-url}")
    private String httpBaseUrl;

    @Value("${dashscope.websocket-base-url}")
    private String websocketBaseUrl;

    /**
     * 初始化通义千问配置(Bean初始化后执行)
     * 增加参数校验,避免空配置导致后续异常
     */
    @PostConstruct
    public void initDashScopeConfig() {
        // 1. 校验核心配置(API Key不能为空)
        Assert.hasText(apiKey, "通义千问API Key不能为空!请在application.yml中配置 dashscope.api-key");
        Assert.hasText(httpBaseUrl, "HTTP端点地址不能为空!");
        Assert.hasText(websocketBaseUrl, "WebSocket端点地址不能为空!");

        // 2. 设置全局配置
        Constants.apiKey = this.apiKey;
        Constants.baseHttpApiUrl = this.httpBaseUrl;
        Constants.baseWebsocketApiUrl = this.websocketBaseUrl;

        // 3. 可选:调整超时时间(根据业务需求配置)
        Constants.CONNECT_TIMEOUT = 15000; // 连接超时15秒(默认10秒)
        Constants.SOCKET_TIMEOUT = 60000; // 读写超时60秒(默认30秒)

        // 4. 日志输出(便于调试)
        log.info("通义千问SDK初始化成功!");
        log.debug("API Key:{}", maskApiKey(apiKey)); // 脱敏输出API Key,避免泄露
        log.debug("HTTP端点:{}", httpBaseUrl);
        log.debug("WebSocket端点:{}", websocketBaseUrl);
    }

    /**
     * API Key脱敏(只显示前6位和后4位)
     */
    private String maskApiKey(String apiKey) {
        if (apiKey.length() < 10) {
            return apiKey; // 异常长度直接返回(避免报错)
        }
        return apiKey.substring(0, 6) + "******" + apiKey.substring(apiKey.length() - 4);
    }
}
2. 配置属性类(DashScopeProperties.java)
java 复制代码
package com.example.springaiqwen;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Positive;
import javax.validation.constraints.PositiveOrZero;

/**
 * 通义千问配置属性类(绑定application.yml中的dashscope配置)
 * 采用@ConfigurationProperties+@Validated,替代分散的@Value,更规范
 */
@Data
@Validated
@Component
@ConfigurationProperties(prefix = "dashscope")
public class DashScopeProperties {

    /** API Key(必填) */
    @NotBlank(message = "通义千问API Key不能为空")
    private String apiKey;

    /** 模型名称(必填,如qwen-turbo) */
    @NotBlank(message = "模型名称不能为空")
    private String model;

    /** 生成温度(0-1,越小越稳定) */
    @PositiveOrZero(message = "temperature必须大于等于0")
    private Float temperature = 0.6f;

    /** 最大生成Token数(正整数) */
    @Positive(message = "max-tokens必须大于0")
    private Integer maxTokens = 1000;

    /** HTTP端点地址 */
    @NotBlank(message = "HTTP端点地址不能为空")
    private String httpBaseUrl;

    /** WebSocket端点地址 */
    @NotBlank(message = "WebSocket端点地址不能为空")
    private String websocketBaseUrl;
}
3. 服务类(FireProtectionService.java)
java 复制代码
package com.example.springaiqwen;

import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

/**
 * 森林防火问答服务
 * 职责:封装通义千问SDK调用,提供业务逻辑处理
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FireProtectionService {

    // 注入配置属性类(集中管理配置,更规范)
    private final DashScopeProperties dashScopeProperties;

    // 通义千问生成客户端(单例,无需每次创建)
    private final Generation generationClient = new Generation();

    /**
     * 生成森林防火问答结果
     * @param question 用户问题(非空)
     * @return AI专业回答
     */
    public String getFireProtectionAnswer(String question) {
        // 1. 入参校验(提前拦截无效请求)
        Assert.hasText(question, "提问内容不能为空!");
        log.debug("收到森林防火提问:{}", question);

        // 2. 构建SDK调用参数(从配置属性类获取,便于维护)
        GenerationParam param = GenerationParam.builder()
                .model(dashScopeProperties.getModel())
                .prompt(buildProfessionalPrompt(question))
                .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                .temperature(dashScopeProperties.getTemperature())
                .maxTokens(dashScopeProperties.getMaxTokens())
                .build();

        // 3. 调用SDK并处理结果
        try {
            GenerationResult result = generationClient.call(param);
            String answer = parseAnswer(result);
            log.debug("森林防火回答生成成功,长度:{}字", answer.length());
            return answer;
        } catch (NoApiKeyException e) {
            log.error("通义千问API Key未配置", e);
            return "❌ 系统错误:通义千问API Key未配置,请联系管理员";
        } catch (InputRequiredException e) {
            log.warn("提问内容为空", e);
            return "❌ 输入错误:提问内容不能为空";
        } catch (ApiException e) {
            log.error("通义千问SDK调用失败,错误信息:{}", e.getMessage(), e);
            return String.format("❌ 调用失败:%s", e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("参数配置错误", e);
            return "❌ 系统错误:参数配置无效(可能是模型名称/端点地址错误),请联系管理员";
        } catch (Exception e) {
            log.error("未知异常", e);
            return "❌ 系统错误:服务暂时不可用,请稍后重试";
        }
    }

    /**
     * 构建专业提示词(引导AI生成精准、专业的回答)
     */
    private String buildProfessionalPrompt(String question) {
        return String.format("""
                你是资深森林防火专家,具备丰富的林区火灾防控、应急处置经验。
                请严格遵循以下要求回答:
                1. 专业性:基于森林防火行业规范和科学知识,拒绝不专业、不准确的内容;
                2. 简洁性:避免冗长,直击要点,控制在300字以内;
                3. 实用性:给出可操作的建议,而非纯理论;
                4. 安全性:优先强调人员安全,再提及财产保护。
                
                用户问题:%s
                """, question);
    }

    /**
     * 解析SDK返回结果(封装解析逻辑,便于后续修改)
     */
    private String parseAnswer(GenerationResult result) {
        Assert.notNull(result, "SDK返回结果不能为空");
        Assert.notNull(result.getOutput(), "SDK返回Output不能为空");
        Assert.notEmpty(result.getOutput().getChoices(), "SDK返回Choices不能为空");

        return result.getOutput().getChoices().get(0).getMessage().getContent().trim();
    }
}
4. 控制器类(FireProtectionController.java)
java 复制代码
package com.example.springaiqwen;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 森林防火问答控制器
 * 职责:提供HTTP接口,接收用户请求,返回结果
 */
@Slf4j
@RestController
@RequestMapping("/api/v1/fire-protection") // 接口路径规范(版本+业务模块)
@RequiredArgsConstructor
@Tag(name = "森林防火问答", description = "提供森林防火相关问题的AI问答接口")
public class FireProtectionController {

    private final FireProtectionService fireProtectionService;

    /**
     * 森林防火问答接口
     * 访问示例:http://localhost:8080/api/v1/fire-protection/ask?question=35℃的林区如何防控火灾
     */
    @GetMapping("/ask")
    @Operation(
            summary = "提问接口",
            description = "输入森林防火相关问题,获取AI专业回答",
            parameters = {
                    @Parameter(
                            name = "question",
                            description = "用户问题(如:35℃的林区如何防控火灾)",
                            required = true,
                            schema = @Schema(type = "string")
                    )
            },
            responses = {
                    @ApiResponse(responseCode = "200", description = "回答成功", content = @Content(schema = @Schema(type = "string"))),
                    @ApiResponse(responseCode = "400", description = "参数错误(问题为空)"),
                    @ApiResponse(responseCode = "500", description = "服务器内部错误")
            }
    )
    public ResponseEntity<String> askFireQuestion(
            @RequestParam(required = true, value = "question") String question) {
        try {
            String answer = fireProtectionService.getFireProtectionAnswer(question);
            return ResponseEntity.ok(answer);
        } catch (IllegalArgumentException e) {
            log.warn("参数错误:{}", e.getMessage());
            return ResponseEntity.badRequest().body("❌ " + e.getMessage());
        } catch (Exception e) {
            log.error("接口处理异常", e);
            return ResponseEntity.internalServerError().body("❌ 服务异常,请稍后重试");
        }
    }
}
5. 主启动类(SpringAiQwenDemoApplication.java)
java 复制代码
package com.example.springaiqwen;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringAiQwenApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringAiQwenApplication.class, args);
	}

}

运行测试验证

访问示例:http://localhost:8080/api/v1/fire-protection/ask?question=35℃的林区如何防控火灾

这个案例具备了 Agent 的 "雏形",但还不属于严格意义上的「AI Agent」------ 更准确地说,它是一个「领域专用的 AI

问答应用」(聚焦森林防火场景)。要成为真正的 Agent,还需要补充核心的 Agent 能力

案例已经实现了 Agent 的「基础交互逻辑」

场景聚焦:通过专业提示词(buildProfessionalPrompt)将通用大模型(通义千问)约束为「森林防火专家」,具备了 Agent

的「角色定位」能力;但本质上,它的核心逻辑是「单一轮次的 Prompt + 大模型调用」,缺少 Agent

最关键的「自主决策、工具调用、多轮规划」能力。 下一章详细介绍Agent基础开发

相关推荐
HDU-Dade22 分钟前
【校招笔试真题】简要介绍 Self-Attention 的前沿算法
人工智能·算法
在所不辞兄1 小时前
【零基础学智能仿真-63】ABACUS作业工具与MCP接口
人工智能·深度学习·神经网络·机器学习·智能仿真
楚山之石1 小时前
【Medical Image Analysis】深度学习医学图像合成综述:伪CT、合成MRI与合成PET的架构、性能与临床边界
图像处理·人工智能·学习·mr·mri
blackappleD1 小时前
百度 Unlimited OCR 2 拆解:SR-SWA 如何让百页文档的 KV 缓存不再涨
人工智能·llm
数字新视界1 小时前
国产动环数据库在智能机房环境监控中的应用与优势分析
大数据·人工智能·数据中心·机房管理·模块化机房
holly就是我1 小时前
AI 编程助手为什么跑偏?用开源 ccglass 看清 Prompt、工具调用和 Token 消耗
人工智能·后端
雨中飘荡的记忆1 小时前
额度又见底了?这个开源项目,给 AI 编程助手接上 61 家"加油站"
人工智能
天远Date Lab2 小时前
零信任架构实战:基于天远企业年报信息核验构建自动化商业保理财务与社保穿透评估网关
人工智能·架构·自动化
EnCi Zheng2 小时前
后端怎么把AI接口稳定安全低成本接进业务:超时重试、密钥隔离与成本熔断清单
人工智能·后端·安全·架构·api
loulanyue_2 小时前
给概率大模型套上确定性缰绳:GraphRAG、神经符号与知识超图重构可信AI底座——读陈华钧杨博等CNCC2026第十届知识图谱论坛实录
人工智能·rag