Spring Ai

一、大模型

本文是springAi + spring-ai-alibaba 进行的大模型调用方式,需要先在百炼平台注册账号并获得ApiKey 进行操作。百炼平台文档:https://help.aliyun.com/zh/model-studio/cosyvoice-java-sdk

导入的maven如下

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>

    <!-- 1. 继承 Spring Boot 父项目,它会自动导入 spring-boot-dependencies BOM -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.5</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-ai-dashscope-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-ai-dashscope-demo</name>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 定义 Spring AI 和阿里云组件的版本 -->
        <spring-ai.version>1.0.0</spring-ai.version>
        <spring-ai-alibaba.version>1.0.0.2</spring-ai-alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-bom</artifactId>
                <version>1.0.0.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

properties文件 配置解决中文乱码问题

XML 复制代码
server.port=8001
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8

chatModel 和 chatClient

chatModel 是对话大模型;chatClient 是对话客户端,其底层用的还是chatModel进行工作的。两者的区别如下:

  • ChatModel - 可以自动注入,与大模型对话使用 call 方法或 stream 方法;需手动解析响应文本。
  • ChatClient - 无法通过 resource 进行自动装入,可以再controller进行构造方法注入;手动注入且依赖ChatModel,使用自动配置的ChatClient.Builder;链式调用,支持同步和反应式(Reactive)编程模式 自动封装提示词和解析响应;支持自动映射为Java对象、支持聊天记忆/工具Tool/函数调用等功能。

多模型共存下声明bean

java 复制代码
/**
 * chatModel 多模型输出  通过配置不同的 apikey和model
 * 需要在bean中指定名称,否则多个大模型共存会报错
 * */
@Bean(name = "qwen3-max")
public ChatModel qwen3(){
    return DashScopeChatModel.builder()
            .dashScopeApi(DashScopeApi.builder().apiKey("***").build())  // 在这里配置apikey
            .defaultOptions(DashScopeChatOptions.builder().withModel("**").build())  // 在这里配置模型名称,如此可以共存多个不同的大模型
            .build();
}

多模型chatClient

java 复制代码
/**
* 多模型下的chatClient
* 可以在这里继续指定和chatModel不一样的模型名称,后者覆盖前者
*/
@Bean("qwen3Client")
public ChatClient chatClient(ChatModel dashScopeModel)
{
    return ChatClient.builder(dashScopeModel).build();
}

如下就是基于chatModel 和 chatClient 的和大模型对话。

java 复制代码
@RestController
public class HelloRest {
    @Resource  // 多模型下需要指定bean的名称
    private ChatModel chatModel;

    @Resource  // 多模型下需要指定bean的名称
    private ChatClient chatClient;

    // chatModel call 方法会将大模型的回答,一次性全部返回。
    @GetMapping(value = "/dochat")
    public String doChat(@RequestParam String msg){
        return chatModel.call(msg);
    }

    // chatModel stream 方法会将大模型的回答,以流式数据返回。
    @GetMapping(value = "/streamchat")
    public Flux<String> stream(@RequestParam String msg){
        return chatModel.stream(msg);
    }

    // chatClient 是基于提示词工作的,其call和stream和上面一样
    @GetMapping(value = "/dochatClient")
    public String dochatClient(@RequestParam String msg){
        return chatClient.prompt()  // 提示词
                .user(msg)      //用户输入的信息
                .call()         // 调用大模型
                .content();     // 返回输出的结果
    }
}

大模型常涉及以下四个关键角色,它们共同构成了模型运行的闭环:

  • 系统角色 (System) ‌:负责定义大模型的‌整体行为准则和身份设定‌,例如规定模型应以专业、友好或幽默的语气回应,或指定其为某个领域的专家。这条指令通常在对话开始时注入,对模型的长期行为有基础性影响。‌
  • 用户角色 (User) ‌:代表与大模型进行交互的‌终端使用者‌,其输入是模型生成回复的直接触发源,包含问题、指令或对话内容。‌
  • 助手角色 (Assistant) ‌:是大模型‌自身生成回复时所扮演的角色‌。当模型输出答案、提供建议或执行任务时,它就是在以"助手"的身份与用户沟通。‌
  • 工具调用角色 (Tool Call) ‌:用于‌调用外部函数或API‌(如查询天气、获取数据库信息)。当大模型判断需要外部信息时,它会生成一个特定格式的工具调用指令,由外部系统(Agent)执行后将结果返回给模型,再由模型生成最终回复。‌

系统角色指定大模型回答范围,超出范围不予回答。

java 复制代码
// system 指定 大模型能力的边界 超出边界不予恢复
@GetMapping(value = "/limitQwen")
public Flux<String> limitQwen(@RequestParam String msg){
    return chatClient.prompt()
            .system("你是一个厨师, 只回答做饭相关的问题,其他问题回复,无可奉告")
            .user(msg).stream().content();
}

提示词模板

java 复制代码
@GetMapping(value = "/mutiPromptTemplate")
public Flux<String> mutiPromptTemplate(@RequestParam String msg, @RequestParam String limit){
    // 系统提示词模板
    SystemPromptTemplate template = new SystemPromptTemplate("你是{limit}, 其他问题,回答不知道");
    Message msg1 = template.createMessage(Map.of("limit", limit));

    // 用户提示词模板
    PromptTemplate promptTemplate = new PromptTemplate("{msg}");
    Message msg2 = promptTemplate.createMessage(Map.of("msg", msg));

    Prompt prompt = new Prompt(List.of(msg1, msg2));
    return chatClient.prompt(prompt).stream().content();
}

Spring-AI 的 Advisor 为拦截、修改和增强。。Advisor 的核心功能在于对聊天交互流程进行拦截与增强,其设计理念类似于 AOP(面向切面编程)中的切面机制,可在消息发送至 AI 模型前及接收模型响应后,添加额外的处理逻辑。例如下面就是自定义实现advisor示例,自定义 Advisor 需实现 CallAdvisor 或 StreamAdvisor(或两者)接口。

java 复制代码
public class MyAdvisor implements CallAdvisor, StreamAdvisor {
    // 同步调用触发advisor时的方法
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
        System.out.println("\n\nBEFORE: " + chatClientRequest);
        // 传向下一个advisor
        ChatClientResponse response = callAdvisorChain.nextCall(chatClientRequest);
        System.out.println("\n\nnAFTER: " + chatClientRequest);
        return response;
    }

    // 流式调用触发advisor时的方法
    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
        System.out.println("\n\nBEFORE: " + chatClientRequest);
        Flux<ChatClientResponse> responseFlux = streamAdvisorChain.nextStream(chatClientRequest);
        System.out.println("\n\nnAFTER: " + chatClientRequest);
        return responseFlux;
    }

    @Override
    public String getName() {
        return "AA-BB-CC";
    }

    @Override // 有多个advisor时,会根据getOrder的值进行排序,值越小越靠前
    public int getOrder() {
        return 0;
    }
}
java 复制代码
@GetMapping(value = "/advisor")
public Flux<String> advisor(@RequestParam String msg){
    return chatClient.prompt().user(msg).advisors(new MyAdvisor()).stream().content();
}

entity 方法,可以将返回转换成指定类型,这里用的是Record类型,是JDK14中的新特新。

java 复制代码
@GetMapping(value = "/customReturn")
public Record customReturn(@RequestParam String msg){
    return chatClient.prompt().user(e -> {
        e.text("王鹏,{msg}").param("msg", msg);
    }).call().entity(TestRecord.class);
}

文生图(ImageModel)

指通过自然语言描述(文本)自动生成对应图像的过程。它是多模态 AI 的重要应用场景,实现了从抽象语义到具象视觉的转化。首先需要配置bean(其实这里不配置也可以,把baseUrl注释掉就能跑通,咱也不知道为啥)。

java 复制代码
@Bean(name = "wanx3")
public ImageModel wanx3(){
    return DashScopeImageModel.builder()
            .dashScopeApi(new DashScopeImageApi("****"))  // 设置api-Key
            .defaultOptions(DashScopeImageOptions.builder().withModel("***").build()) // 设置模型名
            .build();
}
java 复制代码
@GetMapping(value = "/t2i/image")
public String image(@RequestParam String prompt){
    return imageModel.call(new ImagePrompt(prompt)).getResult().getOutput().getUrl();
}

通过SDK调用需要先引入对应的SDK包,效果和上面一样。

XML 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dashscope-sdk-java</artifactId>
    <version>2.9.2</version>
</dependency>
java 复制代码
@GetMapping(value = "/getImage")
public String getImage(@RequestParam String msg) throws NoApiKeyException {
    Map<String, Object> parameters = new HashMap<>();
    parameters.put("prompt_extend", true);
    parameters.put("watermark", false);
    parameters.put("negative_prompt", " ");
    // 组装请求参数
    ImageSynthesisParam param =
            ImageSynthesisParam.builder()
                    .apiKey("***")   // 设置 apikey
                    .model("***")    // 设置模型名称
                    .prompt(msg)
                    .n(1)            // 生成张数
                    .size("1328*1328")   // 尺寸
                    .parameters(parameters)
                    .build();

    ImageSynthesis imageSynthesis = new ImageSynthesis();
    ImageSynthesisResult result = imageSynthesis.call(param);
    return result.getOutput().getResults().get(0).get("url");  // 解析获得图片的url
}

文生音(speechSynthesisModel)

java 复制代码
@GetMapping("/t2v/voice")
public String voice(@RequestParam String msg){
    String filePath = "d:\\" + UUID.randomUUID() + ".mp3";

    //1 语音参数设置
    DashScopeSpeechSynthesisOptions options = DashScopeSpeechSynthesisOptions.builder()
            .model(cosyvoice-v2)
            .voice(longyingcui)
            .build();

    //2 调用大模型语音生成对象
    SpeechSynthesisResponse response = speechSynthesisModel.call(new SpeechSynthesisPrompt(msg, options));

    //3 字节流语音转换
    ByteBuffer byteBuffer = response.getResult().getOutput().getAudio();

    //4 文件生成
    try (FileOutputStream fileOutputStream = new FileOutputStream(filePath))
    {
        fileOutputStream.write(byteBuffer.array());
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
    //5 生成路径OK
    return filePath;
}

对话记忆

将用户和大模型对话的历史数据记录下来,以便下次大模型的回答作为参考,这里将对话记录到Redis 中。首先对 properties文件修改。

java 复制代码
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.database=0

由于本次是向 redis 中保存聊天记录,还需要增加jedis pom。

XML 复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

修改 chatClient 的 bean,在创建bean时接收缓存参数;并且创建记忆缓存的bean。

java 复制代码
@Bean("qwen3Client")
public ChatClient qwen3Client(@Qualifier("qwen3-max") ChatModel qwen3, RedisChatMemoryRepository redisChatMemoryRepository)
{
    MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
            .chatMemoryRepository(redisChatMemoryRepository)  // 指定信息存储到哪里
            .maxMessages(10)   // 指定窗口聊天最大10条信息
            .build();

    return ChatClient.builder(qwen3)
            .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
            .build();
}

@Bean
public RedisChatMemoryRepository redisChatMemoryRepository(){
    return RedisChatMemoryRepository.builder().host("localhost").port(6379).build();
}

在调用时根据用户Id 或 对话Id 去进行缓存。

java 复制代码
@GetMapping(value = "/chatMemory")
public Flux<String> chatMemory(@RequestParam String msg, @RequestParam String userId){
    return chatClient.prompt(msg)  // 传入用户的提示词
            // 根据用户Id去进行缓存,CONVERSATION_ID 是key名
            .advisors(advisorSpec -> advisorSpec.param(CONVERSATION_ID, userId))
            .stream().content();
}

RAG(检索增强生成)

检索增强生成(RAG,Retrieval Augmented Generation)技术应运而生,解决了事实准确性差(幻觉问题)、知识时效性滞后、领域适配性弱,的问题。RAG 从向量数据库中根据用户输入的信息找到最佳匹配即 Top-K,返回给大模型。

这里使用的本地内存向量数据库,之后可以更改成中间件的向量数据库;文档内容放到文件中,这里为了演示简化了操作。首先需要调用embeddingModel 将文档数据放入向量数据库中,之后将创建好的向量数据库放入到advisor中,此时问张三是谁就有了准确的回答。

java 复制代码
@Resource
private EmbeddingModel embeddingModel;


@GetMapping(value = "/store")
public Flux<String> store(@RequestParam String msg){
    SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(embeddingModel).build();

    // 向向量存储中添加文档
    simpleVectorStore.add(new ArrayList<>(){{
        add(new Document("张三,男,汉族,1996 年 7 月 26 日出生,现年 30 岁,毕业于四川大学计算机工程系。" +
                "凭借扎实的专业教育背景,他在技术领域展现出强劲实力。\n" +
                "张三尤其擅长算法设计与优化,能以高效逻辑解决复杂问题;在编程语言方面,对 C/C++ 运用娴熟,凭借该语言的高性能优势," +
                "完成众多高质量项目。同时,他在游戏开发领域造诣颇深,从游戏逻辑搭建到画面渲染优化,都能出色完成。" +
                "此外,他对 AI 有着深入研究,善于将 AI 技术融入游戏开发,为游戏增添智能化体验,是兼具多元技能与创新思维的复合型技术人才。"));
    }});

    RetrievalAugmentationAdvisor build = RetrievalAugmentationAdvisor.builder()
            .documentRetriever(VectorStoreDocumentRetriever.builder().vectorStore(simpleVectorStore).build())
            .build();
    return chatClient.prompt().user(msg).advisors(build).stream().content();
}

ToolCalling 工具调用机制

有效解决了大语言模型(LLM)在训练完成后即固化,导致知识陈旧(如模型是2024年训练完成,那么对于2024年后的新数据,模型是不知道的),且无法直接访问或修改外部数据。该功能允许你将自定义服务注册为工具,将大语言模型与外部系统 API 连接,使 LLM 能获取实时数据并委托这些系统执行数据处理操作。Spring AI 极大简化了支持工具调用所需的编码工作,自动处理工具调用的对话交互。你只需将工具定义为带有 @Tool 注解的方法,并通过提示选项提供给模型即可调用。

java 复制代码
// 注册工具类
public class DateTimeTools {
    /* returnDirect
     *    true = tool直接返回不走大模型,直接给客户
     *    false = 默认值,拿到tool返回的结果,给大模型,最后由大模型回复
     */
    @Tool(description = "获取当前时间", returnDirect = false)
    public String getCurrentTime()
    {
        return LocalDateTime.now().toString();
    }
}

Controller 中调用有两种写法。远程调用ToolCalling,可以通过远程调用mcp服务进行解决。

java 复制代码
@RestController
public class McpRest {
    @Resource
    private ChatClient chatClient;

    @GetMapping(value = "/tool1")
    public Flux<String> tool1(@RequestParam String msg){
        return chatClient.prompt(msg).tools(new DateTimeTools()).stream().content();
    }

    @GetMapping(value = "/tool2")
    public Flux<String> tool2(@RequestParam String msg){
        // 将工具注册到工具集合里
        ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());
        // 将工具集配置进ChatOptions,并封装成提示词
        ChatOptions build = ToolCallingChatOptions.builder().toolCallbacks(tools).build();
        Prompt prompt = new Prompt(msg, build);
        return chatClient.prompt(prompt).stream().content();
    }
}

MCP服务调用

MCP是大模型相互之间调用的方式,和微服务之间调用方式一样。

mcp本地服务端(server)搭建,首先在properties文件中添加配置参数

bash 复制代码
spring.ai.mcp.server.type=async
spring.ai.mcp.server.name=customer-define-mcp-server  # mcp服务端的名称
spring.ai.mcp.server.version=1.0.0

然后添加POM文件

XML 复制代码
<!--注意: spring-ai-starter-mcp-server-webflux 不能和 spring-boot-starter-web依赖并存,否则会使用tomcat启动,而不是netty启动,从而导致mcpserver启动失败,但程序运行是正常的,mcp客户端连接不上。 -->
<dependencies>
<!--    <dependency>-->
<!--        <groupId>org.springframework.boot</groupId>-->
<!--        <artifactId>spring-boot-starter-web</artifactId>-->
<!--    </dependency>-->

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
    </dependency>

    <!--注意: 我这里创建springBoot项目时,是直接依赖的父工程建立的,不用再单独引入spring-boot-starter。如果是在maven中引入的spring,则需要加上下面的依赖。 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>

定义MCP的service方法,需要标注 @Tool 和 @Service 注解

java 复制代码
@Service
public class TimeService {
    @Tool(description = "获取当前时间", returnDirect = false)
    public String getCurrentTime()
    {
        return LocalDateTime.now().toString();
    }
}

增加暴露MCP服务的bean,将暴露的service 暴露出去。此时启动,会发现项目以netty服务启动,而不是tomcat。

java 复制代码
@Configuration
public class McpConfig {
    // 将工具方法暴露给外部mcp client 调用
    @Bean
    public ToolCallbackProvider timeTool(TimeService timeService){
        return MethodToolCallbackProvider.builder().toolObjects(timeService).build();
    }
}

mcp本地客户端(client)搭建,首先在properties文件中添加配置参数

bash 复制代码
spring.ai.mcp.client.type=async
spring.ai.mcp.client.request-timeout=60s        // 定义超时时间
spring.ai.mcp.client.toolcallback.enabled=true  // 是否开启工具调用
spring.ai.mcp.client.sse.connections.mcp-server1.url=http://localhost:8014  // 本地mcp的地址

添加maven配置

bash 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

创建 chatClinet 的bean,接收参数。

java 复制代码
@Bean("qwen3Client")
public ChatClient qwen3Client(@Qualifier("qwen3-max") ChatModel qwen3, RedisChatMemoryRepository redisChatMemoryRepository,
                              ToolCallbackProvider tools){
    MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
            .chatMemoryRepository(redisChatMemoryRepository).maxMessages(10).build();

    return ChatClient.builder(qwen3)
            .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
            .defaultToolCallbacks(tools.getToolCallbacks())  // 将mcp服务调用注入
            .build();
}

二、智能体

可以在百炼平台调用已经规划好流程的智能体,首先需要配置一个工作流并发布。这里可以选择自定义的大模型以及自定义的入参和出参格式。

然后可以拿到该应用的ID值。

在Controller中进行调用,需要传入appId。

java 复制代码
@GetMapping("/agent")
public String agent(String msg) {
    DashScopeAgentOptions agentOptions = DashScopeAgentOptions.builder()
            .withAppId("48399431bf144f59ab36220814ebb5ae").build();
    Prompt prompt = new Prompt(msg, agentOptions);
    return dashScopeAgent.call(prompt).getResult().getOutput().getText();
}
相关推荐
AC赳赳老秦2 小时前
跨境科技服务的基石:DeepSeek赋能多语言技术文档与合规性说明的深度实践
android·大数据·数据库·人工智能·科技·deepseek·跨境
绿算技术2 小时前
重塑智算存储范式:绿算技术NVMe-oF芯片解决方案全景剖析
人工智能·算法·gpu算力
试剂小课堂 Pro2 小时前
mPEG-Silane:mPEG链单端接三乙氧基硅的亲水性硅烷偶联剂
java·c语言·网络·c++·python·tomcat
终端域名2 小时前
如何选择有利于品牌宣传的网站域名
java·后端·struts·数字货币域名·网站域名
拽着尾巴的鱼儿2 小时前
Spring:定时任务@Scheduled cron 的实现原理
java·后端·spring
郑州光合科技余经理2 小时前
源码部署同城O2O系统:中台架构开发指南
java·开发语言·后端·架构·系统架构·uni-app·php
阿波罗尼亚2 小时前
Java框架中的分层架构
java·开发语言·架构
爬山算法2 小时前
Hibernate(59)Hibernate的SQL日志如何查看?
java·sql·hibernate
Jackchenyj2 小时前
基于艾宾浩斯记忆曲线的AI工具实战:ShiflowAI助力高效知识沉淀
人工智能·笔记·信息可视化·智能体