Spring Ai 入门(包含SpringAi alibaba / ollama本地部署与调用)

Spring Ai

一、简介

Spring AI 是一个应用框架,旨在将 Spring 生态系统的设计原则应用于 AI 领域。其目标是通过提供便携的 API 支持,简化 AI 应用的开发过程。Spring AI 支持多种 AI 模型,包括聊天模型、文本到图像模型和嵌入模型。

中文文档:简介 :: Spring AI 中文文档

官方文档:Spring AI

二、环境与准备

Java17

Springboot3.5.3

SpringAi1.0.0

SpringAi alibaba1.0.0.2

(注意:springAi只支持java17以上的版本,springBoot3.5以上,注意版本冲突)

准备一、硅基流动 key的获取

硅基流动官方地址:硅基流动 SiliconFlow - 致力于成为全球领先的 AI 能力提供商

注册登录后打开模型广场

新建密钥

准备二、阿里百炼key的获取

阿里云百炼地址阿里云-计算,为了无法计算的价值

方式与准备一相同

准备三、ollama本地部署

官方地址Ollama

下载到本地计算机后

更改安装路径

如果需要将 Ollama 安装到其他路径,需要在下载后通过命令来安装,例如:

OllamaSetup.exe /DIR="d:\ "

这样可以将 Ollama 安装到其他目录。

验证安装

打开cmd或管理员终端,输入以下命令

ollama --version

如果显示版本号,则说明安装成功。

(注意:ollama默认安装到C盘)

运行/下载大模型

ollama run deepseek-r1:8b

如果该模型未下载,则会运行,如图:

(注意:如果你的电脑自带N卡,那么ollama会自动调用GPU运行模型,反之调用CPU)

查看本地模型

ollama list

三、SpringAi的应用

我的项目结构为:

  

  1. gjldSpringAi  pom文件依赖

 

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>



        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

<!--        JdbcChatMemory存储库-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
        </dependency>
<!--        向量储存依赖-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-advisors-vector-store</artifactId>
        </dependency>

2、application.properties配置

spring.application.name=springai
server.port=8899

spring.ai.openai.api-key= 上方在硅基流动创建的key
spring.ai.openai.base-url=https://api.siliconflow.cn/
spring.ai.openai.chat.options.model=Qwen/QwQ-32B
spring.ai.openai.chat.options.temperature=0.7

#使用BAAI/bge-m3模型处理文本嵌入
spring.ai.openai.embedding.options.model=BAAI/bge-m3

#配置JDBC数据库存储聊天历史记录
spring.ai.chat.memory.repository.jdbc.initialize-schema=always
#设置MySQL数据库平台,自动初始化数据库,使用classpath下的MySQL脚本文件

spring.ai.chat.memory.repository.jdbc.schema=classpath: schema-mysql.sql (注意:此处可以不设置手动创建数据库和表)
spring.ai.chat.memory.repository.jdbc.platform=mysql




# MySQL
spring.datasource.url=jdbc:mysql://localhost:3306/spring_ai_chat_memory?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

  1. 文本对话功能

(一)、简单的对话

private final ChatClient chatClient;
//创建ChatClient
public gjldChatController(ChatClient.Builder chatClientBuiler) {
    this.chatClient = chatClientBuiler.build();
}

//实现简单的对话
@GetMapping("/chat")
public String chat(@RequestParam(value = "message", defaultValue = "你是谁") String message) {
    return chatClient.prompt()
            .user(message)  //用户输入
            .call()  //请求模型
            .content();  //返回文本
}

(二)、流式对话

//流式对话  produces = "text/html;charset=UTF-8确保不输出乱码
@GetMapping(value = "/chat-stream" ,produces = "text/html;charset=UTF-8")
public Flux<String> chatStream(@RequestParam(value = "message", defaultValue = "你是谁") String message) {
    return chatClient.prompt()
            .user(message)
            .stream()
            .content();
}

(三)、ChatModel  /  Prompt  的用法

@Autowired
private ChatModel chatModel;

@GetMapping("/chat-with-model")
public String chatWithModel(@RequestParam(value = "message", defaultValue = "你是谁") String message){
    String result = chatModel.call(message);
    return result;
}


//这段代码构造Prompt对象并设置模型及温度参数,调用chatModel生成回复,最后提取并返回回复文本。
@GetMapping("/chat-with-model2")
public String chatWithModel2(@RequestParam(value = "message", defaultValue = "你是谁") String message){
    ChatResponse chatResponse = chatModel.call(
            new Prompt(
                    message,
                    ChatOptions.builder()
                            .model("Qwen/QwQ-32B")
                            .temperature(0.7)
                            .build()
            )
    );
    String content = chatResponse.getResult().getOutput().getText();
    return content;
}

(三)、Prompt  的用法

@Autowired
private ChatModel chatModel;


@GetMapping("/chat-with-prompt")
public String chatWithPrompt(@RequestParam("name") String name,
                             @RequestParam("voice") String voice) {
    //设置用户输入信息
    String userInput = """
            我喜欢你
            """;

    UserMessage userMessage = new UserMessage(userInput);
    //设置系统提示 信息

    String systemPrompt = """
    我的名字是{name},你是我的女性朋友,我将要和你表白,你将{voice}我
    """;

    //使用PromptTempLate设置信息
    SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);

    //替换占位符
    Message systemMessage = systemPromptTemplate.createMessage(
            Map.of("name", name, "voice", voice)
    );

    //使用Prompt封装 信息
    Prompt prompt = new Prompt(List.of(systemMessage, userMessage));

    //调用chatModel
    var chatResponse = chatModel.call(prompt);
    String content = chatResponse.getResult().getOutput().getText();
    return content;
}

(四)、defaultSystem  的用法(身份)

首先我们先创建一个工具类:

@Bean
// 创建一个ChatClient对象
public ChatClient chatClient(ChatClient.Builder chatClientBuiler) {
    return chatClientBuiler.defaultSystem("你是一个叫坤坤的学生,喜欢唱跳rap篮球").build();
}

在控制层里注入ChatClient后

@GetMapping("/chat-with-role")
public String chatWithRole(@RequestParam(value = "message", defaultValue = "你是谁") String message) {
    return chatClient.prompt()
            .user(message)
            .call()
            .content();
}

大模型就会了解到自己的身份

如图:

(五)、  Functions的用法

首先,我们创建一个工具类:

构造两个函数:

  @Configuration

public class CalculatorService {



    public record AddOperation(int a, int b) {



    }



    public record MulOperation(int a, int b) {



    }



    @Bean(name = "addOperation")

    @Description("加法运算")

    public Function<AddOperation, Integer> addOperation() {

        return operation -> operation.a() + operation.b() + 100; //此处为了测试是否用了函数,加100

    }



    @Bean(name = "mulOperation")

    @Description("乘法运算")

    public Function<MulOperation, Integer> mulOperation() {

        return operation -> operation.a() * operation.b();

    }
 

然后在控制层里:

@GetMapping(value = "/chat-with-functions", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public String chatWithFunctions(@RequestParam( name = "message" ,defaultValue = "2 2  加法运算") String message) {


    return ChatClient.builder(chatModel)
            .build()
            .prompt()

            .system("""
            您是算术计算器的代理。
            您能够支持加法运算、乘法运算等操作,其余功能将在后续版本中添加,如果用户问的问题不支持请告知详情。
            在提供加法运算、乘法运算等操作之前,您必须从用户处获取如下信息:两个数字,运算类型。
            请调用自定义函数执行加法运算、乘法运算。
            请讲中文。
            """)
            .user(message)
            .toolNames("addOperation", "mulOperation")
            .call()
            .content();
}

结果为:

说明模型已经使用了我们定义的函数。。。。

  • 聊天记忆

    1. 内存存储

创建一个配置类,并在配置类中注入、初始化创建一个Memory:

@Autowired
private ChatMemory chatMemory;

// 初始化Memory
@PostConstruct
public void init() {
    // 创建一个Memory
    this.chatMemory = MessageWindowChatMemory.builder()
            .maxMessages(10) // 最多保存10条消息(有10条的记忆)
            .build();
}

接下来手动注入初始化一个ChatClient公共类:

@Bean
public ChatClient girlfriendChatClient(OpenAiChatModel chatModel) {
    return ChatClient
            .builder(chatModel)
            .defaultSystem("你叫小花,是我的女朋友")
            .defaultAdvisors(new SimpleLoggerAdvisor(), //记录聊天内容
                    MessageChatMemoryAdvisor
                            .builder(chatMemory)

                            .build())

            .build();

}

最后,在控制层中写出调用:

@Autowired

//由于项目中创建了多个ChatClient所以我们用@Qualifier区分
@Qualifier("girlfriendChatClient")
 private ChatClient chatClient;

@GetMapping(value = "/memory/chat",produces = "text/html;charset=UTF-8")
public Flux<String> chat(@RequestParam(name = "message", defaultValue = "你以后就叫小花,是我的对象,明白了吗") String prompt) {
    return chatClient.prompt()
            .user(prompt)
            .stream()
            .content();
}

注意:由于是在本地内存中存储的,会受本地配置影响,以及断电后会丢失。。

    1. 数据库存储

在配置类中手动注入,设置使用JDBC存储库:

@Bean
public ChatMemory chatMemory(JdbcChatMemoryRepository repository) {
    // 使用 JDBC 存储库创建带窗口限制的记忆
    return MessageWindowChatMemory.builder()
            .chatMemoryRepository(repository)  // 使用 JDBC 存储库
            .maxMessages(20)  // 最多保存20条消息
            .build();
}

依旧是,,,

@Bean
public ChatClient girlfriendJdbcChatClient(OpenAiChatModel chatModel, ChatMemory chatMemory) {
    return ChatClient
            .builder(chatModel)
            .defaultSystem("你叫小花,是我的女朋友")
            .defaultAdvisors(new SimpleLoggerAdvisor(),
                    MessageChatMemoryAdvisor
                            .builder(chatMemory)
                            .build())
            .build();
}

控制层代码同上需要注意的是其配置:(application.properties里)

#总是初始化数据库表结构
spring.ai.chat.memory.repository.jdbc.initialize-schema=always
#指定MySQL数据库的表结构SQL文件位置
spring.ai.chat.memory.repository.jdbc.schema=classpath:

schema-mysql.sql
#设置数据库平台为MySQL
spring.ai.chat.memory.repository.jdbc.platform=mysql

自然的,也要连接数据库的基本配置:

spring.datasource.url=jdbc:mysql://localhost:3306/spring_ai_chat_memory?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

注意!!

这里我使用了sql语句实现了自动化创建表,在resources下的schema-mysql.sql文件里

内容为

# -- schema-mysql.sql
# -- 创建数据库(如果使用 root 用户执行脚本)
# CREATE DATABASE IF NOT EXISTS spring_ai_chat_memory;
# USE spring_ai_chat_memory;
#
# -- 删除旧表(如果存在)
# DROP TABLE IF EXISTS spring_ai_chat_memory;
-- schema-mysql.sql
CREATE TABLE IF NOT EXISTS spring_ai_chat_memory (
                                                     conversation_id VARCHAR(36) NOT NULL,
                                                     content TEXT NOT NULL,
                                                     type VARCHAR(10) NOT NULL,
                                                     `timestamp` TIMESTAMP NOT NULL,
                                                     CONSTRAINT TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
);

CREATE INDEX IF NOT EXISTS SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX
    ON SPRING_AI_CHAT_MEMORY(conversation_id, `timestamp`);

当然了如果你懒得这样,可以自己手动创建一个数据库,和一个spring_ai_chat_memory

的表,但是要注意表结构/字段符合要求

如图:

(七)、  检索增强生成(RAG

RAG(Retrieval-Augmented Generation)是一种结合了信息检索和文本生成的技术,主要用于增强大语言模型的能力。

RAG的主要优势:

实时信息更新:无需重新训练模型即可使用最新的外部数据

减少幻觉:通过引用真实数据源减少模型生成虚假信息的可能性

领域专业化:可以针对特定领域提供专业化的回答

成本效益:相比重新训练模型,更新知识库更加经济高效

RAG的核心概念:

检索(Retrieval)

当接收到用户查询时,首先从外部知识库或文档中检索相关信息

使用向量搜索或传统搜索技术找到与查询最相关的内容

生成(Generation)

将检索到的信息与原始查询一起作为输入提供给大语言模型

模型基于这些信息生成更准确、更相关的回答

首先,创建一个工具类注入、创建一个vectorStore:

@Bean
VectorStore vectorStore(EmbeddingModel embeddingModel) {
    SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel)
            .build();

    //生成一个说明文档
    List<Document> documents = List.of(
            new Document("产品说明:名称:星辰共鸣坊" +
                    "产品描述:星辰共鸣坊是一个免费的BBS交流博客" +
                    "论坛的作者是由“一只游鱼”的大学生在学习中构建创造出来的" +
                    "技术栈是由spring boot + vue + spring ai + mybatis-plus等流行框架制作完成" +
                    "“星辰共鸣坊”目前有软件和网站两个平台。目前有博客交流、发布通告、私聊、ai聊天、ai桌宠等功能。" +
                    "未来还可能添加充值、商店、影视等功能,取决于作者学到什么。"));
    //向量化 文档储存
    vectorStore.add(documents);
    return vectorStore;
}

然后在控制层中:

@Autowired
private VectorStore vectorStore;
@Autowired
@Qualifier("chatClientRag")
private ChatClient chatClient;

@GetMapping(value = "/rag/chat",produces = "text/html;charset=UTF-8")
public Flux<String> rag(@RequestParam(name = "message", defaultValue = "介绍一下这个网站") String prompt) {
    return chatClient.prompt()
            .user(prompt)
            // 使用顾问类 QuestionAnswerAdvisor
            .advisors(new QuestionAnswerAdvisor(vectorStore))
            .stream()
            .content();
}

结果为:

可以看出ai已经对我们给的文档进行了检索。。。

需要注意的是要找支持文本嵌入的模型。

四、多模态(语音、图像生成)

由于SpringAi目前只提供国外api的支持,所以这里我们改为使用springAi alibaba来调用阿里百炼的服务,详情看准备二。

Pom文件如下:

 

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        阿里巴巴模型-->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <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>

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

        </dependencies>
    </dependencyManagement>

图像生成:

@Autowired
    private DashScopeImageModel imageModel;

    @GetMapping("/albb/image")
    public void generateImage(
            @RequestParam(defaultValue = "生成一只小猫") String prompt,
            @RequestParam(defaultValue = "512x1024") String size,
            @RequestParam(defaultValue = "1") int n,
            @RequestParam(defaultValue = "standard") String quality,
            HttpServletResponse response) {

        // 解析宽高
        String[] dimensions = size.split("x");
        int width = Integer.parseInt(dimensions[0]);
        int height = Integer.parseInt(dimensions[1]);

        // 配置图片选项
        ImageOptions imageOptions = ImageOptionsBuilder.builder()
                .width(width)
                .height(height)
                .N(n)
                .build() ;

        // 创建图片生成请求
        ImagePrompt imagePrompt = new ImagePrompt(prompt, imageOptions);

        // 调用模型
        ImageResponse imageResponse = imageModel.call(imagePrompt);

        // 检查结果
        if (imageResponse == null || imageResponse.getResult() == null ||
                imageResponse.getResult().getOutput() == null) {
            throw new RuntimeException("图像生成失败,请检查API Key或网络连接");
        }

        // 获取图片地址
        String imageUrl = imageResponse.getResult().getOutput().getUrl();

        try {
            URL url = new URI(imageUrl).toURL();
            InputStream inputStream = url.openStream();
            response.setHeader("Content-Type", "image/png");
            response.getOutputStream().write(inputStream.readAllBytes());
            response.getOutputStream().flush();
        } catch (Exception e) {
            throw new RuntimeException("图片下载失败", e);
        }
    }

声音模型:

  @Autowired
    private  DashScopeSpeechSynthesisModel dashScopeSpeechSynthesisModel;

    private static final String TEXT = "你好,我是一只游鱼";
    private static final String PATH = "E:\\springai\\springai\\albbSpringAi\\src\\main\\resources\\tts\\mp3.mp3";

    @GetMapping("/albb/sound")
    public void tts() throws IOException {
    //使用构建器模式创建DashscopeSpeechsynthesisoptions实例并设置参数
        DashScopeSpeechSynthesisOptions options = DashScopeSpeechSynthesisOptions.builder()
                .speed(1.0f)   //语速
                .pitch(1.0)    //音调
                .volume(50)   //音量
                .sampleRate(16000)  //采样率
                .build();

        SpeechSynthesisResponse response = dashScopeSpeechSynthesisModel.call(new SpeechSynthesisPrompt(
                TEXT, options
        ));

        File file = new File(PATH);

//创建文件输出流
        try (FileOutputStream fos = new FileOutputStream(file)){
            ByteBuffer byteBuffer = response.getResult().getOutput().getAudio();
            fos.write(byteBuffer.array());
        } catch (IOException e) {
            throw new IOException(e.getMessage());
        }

五、调用本地大模型(ollama)

首先要完成本地模型的部署,详细请看准备三。。。

Ollama本地默认api为:http://127.0.0.1:11434
 

在配置中:

spring.application.name=ollamaSpringAi
server.port=8900
spring.ai.ollama.base-url=http://127.0.0.1:11434
spring.ai.ollama.chat.options.model:deepseek-r1:8b
spring.ai.ollama.chat.options.temperature=0.7

其方法与其他相同,如:

@Autowired
private OllamaChatModel ollamaChatModel;

@GetMapping("/ollama")
public String chat(@RequestParam(value = "message", defaultValue = "你是谁") String message) {
    String result = ollamaChatModel.call( message);
    return result;
}

需要注意的是这里注入的是OllamaChatModel。。。

最后,如果有感兴趣的同学,可以去看一下作者的学习源码~~~

YoyuDev/SpringAi-StartLearn: 作者的SpringAi入门学习笔记。。。。。。。。。

制作不易,麻烦给个赞~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值