Java集成openai,ollama,千帆,通义千问实现文本聊天

本文所使用的环境如下

Win10 + JDK17 + SpringBoot3.2.4

集成Open AI

pom文件

<?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.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.whiteBrocade</groupId>
    <artifactId>ai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ai-demo</name>
    <description>ai-demo</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M1</spring-ai.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-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.26</version>
        </dependency>
        <!-- Open AI-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>
        <!-- ollama AI-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
        </dependency>

    </dependencies>
    <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>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <!-- 这里指定了Srping的仓库, 因为现在市面上的Mvaven仓库还没有Spring-ai模块, 例如阿里云, 腾讯云等等都是下载不到的-->
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

yaml文件

spring:
  # 代码中的配置会覆盖yaml中相关AI参数配置
  ai:
    openai:
      # Api-Key, 没有的可以去淘宝搜索购买, 替换成你自己的key
      api-key: sk-xxxxxxxxx
      chat:
        options:
          # 模型, 默认为gpt-3.5-turbo,目前这个代理api支持以下模型:
          # gpt-3.5-turbo, gpt-3.5-turbo-0301, gpt-3.5-turbo-0613, gpt-3.5-turbo-0125, gpt-3.5-turbo-16k
          # gpt-3.5-turbo-1706, gpt-3.5-turbo-16k-0613, text-embedding-3-small, text-embedding-3-large
          model: gpt-3.5-turbo
          # 温度
          temperature: 0.4
      # 直连地址/代理地址
      # 切换成你自己的地址
      base-url: https://xxxx.com

AiMessage

import lombok.Data;
import org.springframework.ai.chat.messages.Message;

import java.util.Date;

/**
 * @author whiteBrocade
 * @description: 通用的Message抽象对象
 */
@Data
public class AiMessage {
    /**
     * id
     */
    private Long id;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 角色: user/assistant
     */
    private String role;

    /**
     * AI类型: ollama, openai
     */
    private String type;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * message内容
     */
    private Message message;
}

Controller

import cn.hutool.core.collection.BoundedPriorityQueue;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.RandomUtil;
import com.whitebrocade.aidemo2.domain.AiMessage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.Date;
import java.util.List;
 
@Slf4j
@RestController
@RequestMapping("/openai")
public class OpenaiController {
    /**
     * Open AI 聊天模型
     */
    @Resource
    private OpenAiChatModel openAiChatModel;

    /**
     * 同步方式实现聊天功能
     *
     * @param prompt 提示词
     * @return 聊天结果
     */
    @PostMapping("/chat")
    public String chat(String prompt) {
        String ret = openAiChatModel.call(prompt);
        log.info(ret);
        return ret;
    }

    /**
     * 同步方式实现聊天功能
     *
     * @param prompt 提示词
     * @return 聊天结果
     */
    @PostMapping("/chat2")
    public String chat2(String prompt) {
        ChatResponse chatResponse = openAiChatModel.call(new Prompt(prompt));
        String ret = chatResponse.getResult().getOutput().getContent();
        log.info(ret);
        return ret;
    }

    /**
     * OpenAiChatOptions.builder() 传入的一个参数, 可以控制大模型的设置
     * 参数可以再代码中配置, 也可以在文件中配置, 如果代码中写了关于gpt的参数, 配置文件中也配置了参数, 那么以代码中为主
     * @param prompt 提示词
     * @return 聊天结果
     */

    @PostMapping("/chat3")
    public Object chat3(String prompt){
        ChatResponse chatResponse = openAiChatModel.call(new Prompt(prompt, OpenAiChatOptions.builder()
                //.withModel("gpt-4-32k")  // gpt的版本 , 32K是参数, 参数越高, 回答问题越准确
                .withTemperature(0.4F)  // 温度值, 温度越高, 回答的准确率越低, 温度越低, 回答的准确率越高
                .build()));
        String ret = chatResponse.getResult().getOutput().getContent();
        log.info(ret);
        return ret;
    }


    /***
     * 流式方式实现聊天功能
     * @param prompt 提示词
     * @return 聊天结果流
     */
    @PostMapping(value = "/stream1", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream1(String prompt) {
        return openAiChatModel.stream(prompt);
    }


    /**
     * 对话上下文轮数
     */
    private final int limit = 2;

    /**
     * 有界队列, 这里限2条信息, 并且删除最先发送的那条消息
     */
    private final BoundedPriorityQueue<AiMessage> messageQueue = new BoundedPriorityQueue<>(limit * 2,
            (o1, o2) -> -(DateUtil.compare(o1.getCreateTime(), o2.getCreateTime())));

    // 初始化java角色
    {
        // 系统前置消息
        Message systemMessage = new SystemMessage("你是一个Java行业的专家, 现在根据我的提问进行相关解答");

        AiMessage aiMessage = new AiMessage();
        aiMessage.setId(RandomUtil.randomLong());
        aiMessage.setUserId(1L);
        aiMessage.setRole(OpenAiApi.ChatCompletionMessage.Role.SYSTEM.name());
        aiMessage.setType("openai");
        aiMessage.setCreateTime(new Date());
        aiMessage.setMessage(systemMessage);
        messageQueue.offer(aiMessage);
    }

    /**
     * 流式方式实现聊天功能
     * @param prompt 提示词
     * @return 聊天结果流
     */
    @PostMapping(value = "/stream2", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream2(String prompt){
        // 构造用户回答
        UserMessage userMessage = new UserMessage(prompt);
        AiMessage userAiMessage = new AiMessage();
        userAiMessage.setId(RandomUtil.randomLong());
        userAiMessage.setUserId(1L);
        userAiMessage.setRole("user");
        userAiMessage.setType("openai");
        userAiMessage.setCreateTime(new Date());
        userAiMessage.setMessage(userMessage);
        messageQueue.offer(userAiMessage);
        List<Message> messageList = messageQueue.stream()
                .map(AiMessage::getMessage)
                .toList();

        // 模型填参
        Flux<ChatResponse> flux = openAiChatModel.stream(new Prompt(messageList, OpenAiChatOptions.builder()
                //.withModel("gpt-4-32k")  //gpt的版本 ,32K是参数,参数越高,回答问题越准确
                .withTemperature(0.4F)  //温度值,温度越高,回答的准确率越低,温度越低,回答的准确率越高
                .build()));

        // strBuilder用于追加AI的回答
        StrBuilder strBuilder = StrBuilder.create();
        return flux.map(response -> {
            String content = response.getResult().getOutput().getContent();
            if (content != null) {
                strBuilder.append(content);
                return content;
            } else { // null表示结束, 此时我们返回"", 前端判断返回结果是否为""
                return "";
            }
        }).doOnComplete(() -> {
            AssistantMessage assistantMessage = new AssistantMessage(strBuilder.toString());
            AiMessage assistantAiMessage = new AiMessage();
            assistantAiMessage.setId(RandomUtil.randomLong());
            assistantAiMessage.setUserId(1L);
            assistantAiMessage.setRole("system");
            assistantAiMessage.setType("openai");
            assistantAiMessage.setCreateTime(new Date());
            assistantAiMessage.setMessage(assistantMessage);
            messageQueue.offer(assistantAiMessage);
            log.info("Open AI模型响应结果: {}", strBuilder);
            log.info("执行完了");
        });
    }
}

集成Ollama

Ollama使用

如果本地没下载和或者没有使用Ollama, 那么这里你得看看, 如果已经使用过了, 那么跳过过这里, 去直接看代码集成

Ollama的下载, 安装以及使用

Ollama官网下载, 并按照, 需要说明的是Ollama并非是一个大模型, 而是一个大模型的管理, 类似于Docker一样的角色, 而大模型类似于镜像的角色

进入PowerShell命令行中输入以下命令

ollama -v

如果显示了版本, 那么就说明安装成功

在这里插入图片描述

Ollama模型存储位置修改

此时不着急下载模型, Ollama默认会将模型存储如下位置

  • Linux/var/lib/ollama/models
  • WindowsC:\ProgramData\Ollama\models

Windos(我用的是这个)由于ollama是默认存储在C盘, 所以修改一下模型存储位置, 修改步骤如下:

  1. 在环境变量中新增OLLAMA_MODELS变量名, 注意了这个变量名一定不能变, 变量值指向你要存放的文件夹, 如下
    在这里插入图片描述

  2. 重启电脑(不重启电脑不生效!), 然后本地下载的模型就自动迁移的到你配置的目录了!

Ollama基本命令
  • ollama serve: 启动

  • ollama list: 查看所有可用模型

  • ollama run 模型名称: 运行选定模型

    • 如果本地没有这个模型, 那么就会下载
  • ollama show 模型名称: 来获取更详细的模型信息, 包括模型的描述、版本、大小等

  • ollama download 模型名称: 下载模型

  • ollama update 模型名称: 来更新已下载的模型

  • ollama rm 模型名称

  • ollama ps: 查看正在运行的模型

这里先简单过一眼, 后续使用到会介绍

模型下载

点击Models进入模型下载页面
在这里插入图片描述

搜索阿里的千问2的模型
在这里插入图片描述

选择0.5b的参数的模型

这里的b是指模型的训练的参数量, 1b=1亿参数, 参数越多, AI模型越智能, 对应的硬盘越大, 所需要的GPU和内存要求越高, 所以此处选择0.5的, 不懵逼, 不伤脑

在这里插入图片描述

复制此命令到, 命令行中执行, 然后ollama就会下载该模型, 耐心等待即可, 下载完成会运行该模型

在这里插入图片描述

模型的启动和使用

直接输入你的问题即可, Ctrl + D退出模型对话
在这里插入图片描述

代码集成Ollama本地模型

pom文件
<?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.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.whiteBrocade</groupId>
    <artifactId>ai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ai-demo</name>
    <description>ai-demo2</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M1</spring-ai.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-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.26</version>
        </dependency>
        <!-- Open AI-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>
        <!-- ollama AI-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
        </dependency>

    </dependencies>
    <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>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <!-- 这里指定了Srping的仓库, 因为现在市面上的Mvaven仓库还没有Spring-ai模块, 例如阿里云, 腾讯云等等都是下载不到的-->
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

yaml文件
spring:
  # 代码中的配置会覆盖yaml中相关AI参数配置
  ai:
    ollama:
      chat:
        options:
          # 注意了, ollama中要有这个模型
          model: qwen:0.5b
          temperature: 0.4
      # 直连地址/代理地址
      # 这里填的是我本地的ollama的启动地址,
      base-url: http://localhost:11434
Controller
import cn.hutool.core.text.StrBuilder;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

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

/**
 * @author whiteBrocade
 * @description: OllamaController
 */
@Slf4j
@RestController
@RequestMapping("/ollama")
public class OllamaController {
    @Resource
    private OllamaChatModel ollamaChatModel;

    /**
     * 一次性聊天, 不会记录上下文
     * @param prompt 提示词
     * @return
     */
    @PostMapping(value = "/chat1")
    public Object chat1(String prompt) {
        String ret = ollamaChatModel.call(prompt);
        log.info(ret);
        return ret;
    }


    /**
     * 这里可以将消息存入MySQL
     */
    private final List<Message> messageList = new ArrayList<>();
    {
        // 系统前置消息
        Message systemMessage = new SystemMessage("你是一个Java行业的专家, 现在根据我的提问进行相关解答");
        messageList.add(systemMessage);
    }

    /**
     * 一次性聊天, 不会记录上下文
     * @param prompt 提示词
     * @return
     */
    @RequestMapping(value = "/chat2")
    public Object chat2(String prompt) {
        Message userMessage = new UserMessage(prompt);
        messageList.add(userMessage);

        ChatResponse chatResponse = ollamaChatModel.call(new Prompt(messageList));
        String content = chatResponse.getResult().getOutput().getContent();
        log.info(content);
        return content;
    }

    /**
     * 流式方式实现聊天功能
     * @param prompt 提示词
     * @return 聊天结果流
     */
    @PostMapping(value = "/stream1", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream1(String prompt) {
        return ollamaChatModel.stream(prompt);
    }

    /**
     * 流式方式实现聊天功能
     * @param prompt 提示词
     * @return 聊天结果流
     */
    @PostMapping(value = "/stream2", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream2(String prompt) {
        UserMessage userMessage = new UserMessage(prompt);
        messageList.add(userMessage);

        Flux<ChatResponse> flux = ollamaChatModel.stream(new Prompt(messageList, OllamaOptions.create()
                .withTemperature(0.4F)  //温度值,温度越高,回答的准确率越低,温度越低,回答的准确率越高
        ));
        // sb用于追加AI的回答
        StrBuilder strBuilder = StrBuilder.create();
        return flux.map(response -> {
            String content = response.getResult().getOutput().getContent();
            if (content != null) {
                strBuilder.append(content);
                return content;
            } else { // null表示结束, 此时我们返回"", 前端判断返回结果是否为""
                return "";
            }
        }).doOnComplete(() -> {
            AssistantMessage assistantMessage = new AssistantMessage(strBuilder.toString());
            messageList.add(assistantMessage);
            log.info("ollama AI模型响应结果: {}", strBuilder);
            log.info("执行完了");
        });
    }
}

通义千问对接

SDK

pom文件
<?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>
    <groupId>com.whiteBrocade</groupId>
    <artifactId>ai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ai-demo</name>
    <description>ai-demo</description>
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.2</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--阿里AI的SDK-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dashscope-sdk-java</artifactId>
            <version>2.15.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-simple</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Reactor Core -->
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.4.6</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.26</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.whitebrocade.aidemo.AiDemoApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
yaml文件
spring:
  ai:
    qwen:
      # 义通千问的api-key, 没有的去阿里云申请
      api-key: sk-xxxxxx
      # 模型
      model: qwen-plus
      # 随机数种
      seed: 1234
      # 消耗的token数目
      # 对于中文文本来说, 千问模型的1个token平均对应1.5-1.8个汉字;对于英文文本来说, 1个token通常对应一个单词或词根
      maxTokens: 10
      # 温度
      temperature: 0.4
      # 是否允许联网查询
      enableSearch: true
      # 返回内容是否不需要追加, true不追加, false追加
      incrementalOutput: true
Controller
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
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.aigc.generation.GenerationUsage;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import io.reactivex.Flowable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@Slf4j
@RestController
@RequestMapping("/qwen")
public class QwenController {

    /**
     * 随机数种
     */
    @Value("${spring.ai.qwen.seed}")
    private Integer seed;

    /**
     * AI模型
     */
    @Value("${spring.ai.qwen.model}")
    private String model;

    /**
     * AI密钥
     */
    @Value("${spring.ai.qwen.api-key}")
    private String apiKey;

    /**
     * 生成最大的token
     */
    @Value("${spring.ai.qwen.maxTokens}")
    private Integer maxTokens;

    /**
     * 温度
     */
    @Value("${spring.ai.qwen.temperature}")
    private Float temperature;

    /**
     * 是否联网
     */
    @Value("${spring.ai.qwen.enableSearch}")
    private boolean enableSearch;

    /**
     * 返回内容是否不需要追加
     */
    @Value("${spring.ai.qwen.incrementalOutput}")
    private boolean incrementalOutput;

    /**
     * AI生成对象
     */
    private final Generation generation = new Generation();

    // -------------------------- 方法 --------------------------

    /**
     * 单轮对话
     *
     * @param prompt 提示词
     * @return 返回结果
     */
    @PostMapping(value = "/chat")
    public String chat(String prompt) {
        try {
            // 用户与模型的对话历史。list中的每个元素形式为{“role”:角色, “content”: 内容}。
            Message userMessage = this.buildMessage(Role.USER, prompt);

            // 填充参数
            GenerationParam param = this.getBaseGenerationParam()
                    .messages(Collections.singletonList(userMessage))
                    .build();

            GenerationResult generationResult = generation.call(param);
            log.info("千问输出结果:{}", JSONUtil.toJsonStr(generationResult));
            return generationResult.getOutput().getChoices().get(0).getMessage().getContent();
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * message存储, 这里模拟数据查询出来的Message集合
     */
    private final List<Message> messageList = new ArrayList<>();

    {
        // 初始化系统角色
        Message systemMsg = Message.builder()
                .role(Role.ASSISTANT.getValue())
                .content("你是Java高级工程师, 现在根据我的提问进行回答") // 初始化系统角色
                .build();
        messageList.add(systemMsg);
    }


    /**
     * 流式单论对话
     *
     * @param prompt 提示词
     * @return 返回结果
     */
    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flowable<String> stream(String prompt) throws NoApiKeyException, InputRequiredException {
        // 用户与模型的对话历史。list中的每个元素形式为{"role":角色, "content": 内容}。
        Message userMessage = this.buildMessage(Role.USER, prompt);
        messageList.add(userMessage);

        // 填充参数
        GenerationParam param = this.getBaseGenerationParam()
                .messages(messageList)
                .build();

        Flowable<GenerationResult> flux = generation.streamCall(param);
        return flux.map(generationResult -> {
            Message repMessage = generationResult.getOutput().getChoices().get(0).getMessage();
            log.info("千问模型响应结果, 角色:{}, 内容: {}", repMessage.getRole(), repMessage.getContent());
            String content = repMessage.getContent();
            return content;
        }).doOnComplete(() -> {
            log.info("结果响应完了");
        }).doOnError(e -> {
            log.error("结果响应发生异常");
            throw new RuntimeException(e);
        });
    }

    /**
     * 流式单论对话, 当相应内容完成后, 返回""
     *
     * @param prompt 多轮对话提示词
     * @return 返回结果
     */
    @PostMapping(value = "/stream2", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flowable<String> stream2(String prompt) throws NoApiKeyException, InputRequiredException {
        // 初始化用户信息
        Message userMessage = this.buildMessage(Role.USER, prompt);

        // 支持对话轮数
        if (messageList.size() == 7) {
            messageList.remove(0);
        }
        messageList.add(userMessage);

        // 填充参数
        GenerationParam param = this.getBaseGenerationParam()
                .messages(messageList)
                .build();

        // 流式返回
        Flowable<GenerationResult> flux = generation.streamCall(param);
        // 响应的消息
        StrBuilder strBuilder = new StrBuilder();
        return flux.map(generationResult -> {
            // token消耗情况
            GenerationUsage usage = generationResult.getUsage();
            Integer inputTokens = usage.getInputTokens();
            Integer outputTokens = usage.getOutputTokens();
            Integer totalTokens = usage.getTotalTokens();
            log.info("输入token: {}, 输出token: {}, 总token: {}", inputTokens, outputTokens, totalTokens);

            Message assistantMessage = generationResult.getOutput().getChoices().get(0).getMessage();
            String role = assistantMessage.getRole();
            String content = assistantMessage.getContent();
            if (StrUtil.isNotEmpty(content)) {
                strBuilder.append(content);
                log.info("千问模型响应结果, 角色: {}, 内容: {}", role, content);
            }
            return content;
        }).doOnComplete(() -> {
            // 添加assistant返回到messages列表,user/assistant消息必须交替出现
            if (messageList.size() == 7) {
                messageList.remove(0);
            }
            // 将响应消息添加到集合中
            Message assistantMessage = Message.builder()
                    .role(Role.ASSISTANT.getValue())
                    .content(strBuilder.toString())
                    .build();
            messageList.add(assistantMessage);
            log.info("结果响应完了");
        }).doOnError(e -> {
            log.error("结果响应发生异常");
            throw new RuntimeException(e);
        });
    }

    /**
     * 构建Message
     * @param role 角色
     * @param content 内容
     * @return 构建好的Message
     */
    private Message buildMessage(Role role, String content) {
        return Message.builder()
                .role(role.getValue())
                .content(content)
                .build();
    };

    /**
     * 构建公共param
     *
     * @return GenerationParam
     */
    private GenerationParam.GenerationParamBuilder<?, ?> getBaseGenerationParam() {
        // 模型参数
        return GenerationParam.builder()
                .seed(seed) // 随机数种
                .model(model) // AI模型
                .apiKey(apiKey) // Api Key
                .maxTokens(maxTokens) // 生成结果消耗的最大token值
                .temperature(temperature) // 温度
                .enableSearch(enableSearch)
                .incrementalOutput(incrementalOutput) // 后续输出不追加之前输出的内容
                .resultFormat(GenerationParam.ResultFormat.MESSAGE); // 返回值信息
    }
}

HTTP

api文档

pom文件
<?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>
    <groupId>com.whiteBrocade</groupId>
    <artifactId>ai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ai-demo</name>
    <description>ai-demo</description>
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.2</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Reactor Core -->
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.4.6</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.26</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.whitebrocade.aidemo.AiDemoApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
yaml文件
spring:
  ai:
    qwen:
      # 义通千问的api-key, 没有的去阿里云申请
      api-key: sk-xxxxxxx
      # 模型
      model: qwen-plus
      # 随机数种
      seed: 1234
      # 消耗的token数目
      maxTokens: 1000
      # 温度
      temperature: 0.4
      # 是否允许联网查询
      enableSearch: true
      # 返回内容是否不需要追加, true不追加, false追加
      incrementalOutput: true
      # http请求地址
      url: https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation



Request
QwenRequest
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.util.List;

/**
 * 千问请求参数
 */
@Data
public class QwenRequest implements Serializable {
    /**
     * AI模型
     */
    private String model;

    /**
     * message输入
     */
    private Input input;

    /**
     * 其它参数
     */
    private Parameters parameters;

    @Data
    public static class Input implements Serializable {
        private List<Message> messages;
    }

    @Data
    public static class Message implements Serializable {
        /**
         * 角色
         */
        private String role;

        /**
         * 内容
         */
        private String content;
    }

    /**
     * 参数
     */
    public static class Parameters implements Serializable {
        /**
         * 随机数种
         */
        @Getter
        @Setter
        @JsonProperty("seed")
        private Integer seed;

        /**
         * 限制模型生成token的数量
         */
        @Getter
        @Setter
        @JsonProperty("max_tokens")
        private Integer maxTokens;

        /**
         * 温度
         */
        @Getter
        @Setter
        @JsonProperty("temperature")
        private Float temperature;

        /**
         * 是否互联网搜索
         */
        @Getter
        @Setter
        @JsonProperty("enable_search")
        private boolean enableSearch;

        /**
         * 响应结果格式: text
         */
        @Getter
        @JsonProperty("result_format")
        private String resultFormat = GenerationParam.ResultFormat.TEXT;
    }
}
QwenStreamRequest
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/**
 * @author whiteBrocade
 * @description: 流式请求
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class QwenStreamRequest extends QwenRequest implements Serializable {

    public static class Parameters extends QwenRequest.Parameters implements Serializable {
        /**
         * 是否增量输出
         */
        @Getter
        @Setter
        @JsonProperty("incremental_output")
        private boolean incrementalOutput;

        /**
         * 响应结果格式: message
         */
        @Getter
        @JsonProperty("result_format")
        private String resultFormat = GenerationParam.ResultFormat.MESSAGE;
    }
}

Response
QwenBaseResponse
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.io.Serializable;

/**
 * @author whiteBrocade
 * @description: 千问普通请求响应结果
 */
@Data
public class QwenBaseResponse implements Serializable {
    /**
     * 本次调用使用的token信息。
     */
    private Usage usage;

    /**
     * 本次请求的系统唯一码
     */
    @JsonProperty("request_id")
    private String requestId;

    @Data
    public static class Usage implements Serializable {
        /**
         * 本次对话消耗的token
         */
        @JsonProperty("total_tokens")
        private int totalTokens;

        /**
         * 响应消耗的token
         */
        @JsonProperty("output_tokens")
        private int outputTokens;

        /**
         * 请求消耗的token
         */
        @JsonProperty("input_tokens")
        private int inputTokens;
    }
}
QwenResponse
package com.whitebrocade.aidemo.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;

/**
 * @author whiteBrocade
 * @description: 千问普通请求响应结果
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class QwenResponse extends QwenBaseResponse implements Serializable {
    /**
     * 响应输出结果
     */
    private QwenResponse.Output output;

    /**
     * 响应输出结果
     */
    @Data
    public static class Output implements Serializable {
        /**
         * 结束原因
         * 有三种情况:
         *   1. 正在生成时为null
         *   2. 生成结束时如果由于停止token导致则为stop
         *   3. 生成结束时如果因为生成长度过长导致则为length。当result_format设置为text时返回该字段
         */
        @JsonProperty("finish_reason")
        private String finishReason;

        /**
         * 响应结果文本
         */
        private String text;
    }
}

QwenStreamResponse
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;

/**
 * @author whiteBrocade
 * @description: 流式响应结果
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class QwenStreamResponse extends QwenBaseResponse implements Serializable {
    /**
     * 响应输出结果
     */
    private QwenStreamResponse.Output output;

    /**
     * 响应输出结果
     */
    @Data
    public static class Output implements Serializable {
        /**
         * 响应结果
         */

        @JsonProperty("choices")
        private Choices[] choices;
    }

    /**
     * 响应结果
     */
    @Data
    public static class Choices implements Serializable {
        /**
         * 停止原因
         * 1. null: 生成过程中
         * 2. stop: stop token导致结束
         * 3. length: 生成长度导致结束
         */
        @JsonProperty("finish_reason")
        private String finish_reason;

        /**
         * 信息
         */
        private Message message;
    }

    /**
     * message每个元素形式为{"role":角色, "content": 内容}。角色可选值:system、user、assistant。content为模型输出的内容
     */
    @Data
    public static class Message implements Serializable {
        /**
         * 角色
         */
        private String role;

        /**
         * 内容
         */
        private String content;
    }
}
Controller
package com.whitebrocade.aidemo.web;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.dashscope.common.Role;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.whitebrocade.aidemo.request.QwenRequest;
import com.whitebrocade.aidemo.request.QwenStreamRequest;
import com.whitebrocade.aidemo.response.QwenResponse;
import com.whitebrocade.aidemo.response.QwenStreamResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;

/**
 * @author whiteBrocade
 * @version 1.0
 * @date 2024/7/4 下午1:02
 * @description: TODO
 */
@Slf4j
@RestController
@RequestMapping("/qwen/http")
public class QwenHttpController {
    /**
     * 请求地址
     */
    @Value("${spring.ai.qwen.url}")
    private String url;

    /**
     * 随机数种
     */
    @Value("${spring.ai.qwen.seed}")
    private Integer seed;

    /**
     * AI模型
     */
    @Value("${spring.ai.qwen.model}")
    private String model;

    /**
     * AI密钥
     */
    @Value("${spring.ai.qwen.api-key}")
    private String apiKey;

    /**
     * 生成最大的token
     */
    @Value("${spring.ai.qwen.maxTokens}")
    private Integer maxTokens;

    /**
     * 温度
     */
    @Value("${spring.ai.qwen.temperature}")
    private Float temperature;

    /**
     * 是否联网
     */
    @Value("${spring.ai.qwen.enableSearch}")
    private boolean enableSearch;

    /**
     * 返回内容是否不需要追加
     */
    @Value("${spring.ai.qwen.incrementalOutput}")
    private boolean incrementalOutput;

    // --------------------- 方法 ---------------------


    @PostMapping(value = "/chat")
    public String chat(String prompt) throws Exception {
        // 填充message
        // system message
        QwenRequest.Message systemMessage = new QwenRequest.Message();
        systemMessage.setRole(Role.SYSTEM.getValue());
        systemMessage.setContent("你是一个Java高级工程师");
        // user message
        QwenRequest.Message userMessage = new QwenRequest.Message();
        userMessage.setRole(Role.USER.getValue());
        userMessage.setContent(prompt);
        List<QwenRequest.Message> messages = List.of(systemMessage, userMessage);

        QwenRequest qwenRequest = this.getQwenRequest(messages);
        // 转换成json
        ObjectMapper objectMapper = new ObjectMapper();
        String reqJsonStr = objectMapper.writeValueAsString(qwenRequest);
        // 请求的str
        log.info("请求参数: {}", reqJsonStr);

        HttpRequest httpRequest = this.getHttpRequest()
                .body(reqJsonStr); // 请求体

        String text; // 响应文本
        try {
            String responseJsonStr = httpRequest.execute() // 发送请求
                    .body();
            log.info("响应结果: {}", responseJsonStr);

            QwenResponse qwenResponse = objectMapper.readValue(responseJsonStr, QwenResponse.class);

            text = qwenResponse.getOutput().getText();
        } catch (Exception e) {
            log.error("请求异常: {}", e.getMessage());
            throw new RuntimeException();
        }
        return text;
    }

    /**
     * 流式对话
     * @param prompt 提示词
     * @return 响应结果
     */
    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream(String prompt) throws JsonProcessingException {
        // 填充message
        // system message
        QwenRequest.Message systemMessage = new QwenRequest.Message();
        systemMessage.setRole(Role.SYSTEM.getValue());
        systemMessage.setContent("你是一个Java高级工程师");
        // user message
        QwenRequest.Message userMessage = new QwenRequest.Message();
        userMessage.setRole(Role.USER.getValue());
        userMessage.setContent(prompt);
        List<QwenRequest.Message> messages = List.of(systemMessage, userMessage);


        QwenStreamRequest qwenStreamRequest = this.getQwenStreamRequest(messages);
        // 转换成json
        ObjectMapper objectMapper = new ObjectMapper();
        String reqJsonStr = objectMapper.writeValueAsString(qwenStreamRequest);
        // 请求的str
        log.info("请求参数: {}", reqJsonStr);

        HttpRequest streamHttpRequest = this.getStreamHttpRequest()
                .body(reqJsonStr); // 请求体

        // try {
        //
        //     // text = qwenResponse.getOutput().getText();
        // } catch (Exception e) {
        //     log.error("请求异常: {}", e.getMessage());
        //     throw new RuntimeException();
        // }


        return Flux.create(sink -> {
            InputStream inputStream = streamHttpRequest.execute(true) // 异步调用
                    .bodyStream();
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
            try (inputStream) {
                StrBuilder strBuilder = StrBuilder.create();
                String line;
                while ((line = in.readLine()) != null) {
                    // 数据前缀
                    String prefix = "data:";
                    if (line.startsWith(prefix)) {
                        // 截取数据
                        String responseJsonStr = StrUtil.subAfter(line.trim(), prefix, false);
                        QwenStreamResponse qwenStreamResponse = objectMapper.readValue(responseJsonStr, QwenStreamResponse.class);

                        // 内容
                        String content = qwenStreamResponse
                                .getOutput()
                                .getChoices()[0]
                                .getMessage()
                                .getContent();
                        
                        strBuilder.append(content);

                        // 发送每个响应片段
                        // 当为""的时候, 那么就就表示响应结束
                        sink.next(content);
                    }
                }

                log.info("响应内容为: {}", strBuilder.toString());
                // todo 这里可以讲响应内容保存到数据库中

                // 完成流式传输
                sink.complete();
            } catch (Exception e) {
                // 发送错误信号
                log.error("传输错误", e);
                sink.error(e);
            }
        });
    }



    /**
     * 获取基本的QwenRequest
     * @return HttpRequest
     */
    private HttpRequest getHttpRequest() {
        return HttpUtil.createPost(url)
                .contentType(ContentType.JSON.getValue()) // Content-Type
                .auth("Bearer " + apiKey); // Authorization
    }

    /**
     * 流式请求
     * @return HttpRequest
     */
    private HttpRequest getStreamHttpRequest() {
        return this.getHttpRequest()
                .header("X-DashScope-SSE", "enable");
    }


    /**
     * 获取基本的QwenRequest
     * @param messages 消息
     * @return QwenRequest
     */
    private QwenRequest getQwenRequest(List<QwenRequest.Message> messages) {
        QwenRequest qwenRequest = new QwenRequest();
        qwenRequest.setModel(model); // 模型

        QwenRequest.Input input = new QwenRequest.Input();
        input.setMessages(messages);
        qwenRequest.setInput(input);

        QwenRequest.Parameters parameters = new QwenRequest.Parameters();
        parameters.setSeed(seed);
        parameters.setMaxTokens(maxTokens);
        parameters.setTemperature(temperature);
        parameters.setEnableSearch(enableSearch);
        qwenRequest.setParameters(parameters);

        return qwenRequest;
    }

    /**
     * 获取 QwenStreamRequest
     * @param messages 消息
     * @return QwenStreamRequest
     */
    private QwenStreamRequest getQwenStreamRequest(List<QwenRequest.Message> messages) {
        QwenRequest qwenRequest = this.getQwenRequest(messages);
        QwenStreamRequest qwenStreamRequest = new QwenStreamRequest();
        qwenStreamRequest.setModel(qwenRequest.getModel());
        qwenStreamRequest.setInput(qwenRequest.getInput());

        QwenRequest.Parameters parameters = qwenRequest.getParameters();
        QwenStreamRequest.Parameters tempparameters = new QwenStreamRequest.Parameters();
        BeanUtil.copyProperties(parameters, tempparameters);

        // 设置增量输出
        tempparameters.setIncrementalOutput(incrementalOutput);
        qwenStreamRequest.setParameters(tempparameters);

        return qwenStreamRequest;
    }
}

百度千帆大模型对接

SDK

pom
<?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>
    <groupId>com.whiteBrocade</groupId>
    <artifactId>ai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ai-demo</name>
    <description>ai-demo</description>
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.2</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!--千帆-文心一言-->
        <dependency>
            <groupId>com.baidubce</groupId>
            <artifactId>qianfan</artifactId>
            <version>0.0.9</version>
        </dependency>

        <!-- Reactor Core -->
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.4.6</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.26</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.whitebrocade.aidemo.AiDemoApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
yaml
spring:
  ai:
    # 千帆
    qianfan:
      access-key: xxxxx
      secret-key: xxxxxx
      # 使用免费的模型, 免费的模型如下ERNIE-Speed-128K, ERNIE-Speed-8K, ERNIE Speed-AppBuilder, ERNIE-Lite-8K, ERNIE-Lite-8K-0922, ERNIE-Tiny-8K, Yi-34B-Chat, Fuyu-8B, Prompt模板
      model: ERNIE-Speed-128K
      # 输出结果影响因子
      topP: 0.7
      # 温度
      temperature: 0.7
      # 输出最大的token值
      maxOutputTokens: 50
Controller
import com.baidubce.qianfan.Qianfan;
import com.baidubce.qianfan.core.StreamIterator;
import com.baidubce.qianfan.core.auth.Auth;
import com.baidubce.qianfan.core.builder.ChatBuilder;
import com.baidubce.qianfan.model.chat.ChatResponse;
import com.baidubce.qianfan.model.constant.ChatRole;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

/**
 * @author whiteBrocade
 * @version 1.0
 * @date 2024/7/3 下午7:02
 * @description: TODO
 */

@Slf4j
@RestController
@RequestMapping("/qianfan")
public class QianFanController {

    /**
     * 输出文本的多样性因子, 影响输出文本的多样性,  取值越大,  生成文本的多样性越强
     */
    @Value("${spring.ai.qianfan.topP}")
    private Double topP;

    /**
     * AI模型
     */
    @Value("${spring.ai.qianfan.model}")
    private String model;

    /**
     * accessKey
     */
    @Value("${spring.ai.qianfan.access-key}")
    private String accessKey;

    /**
     * secretKey
     */
    @Value("${spring.ai.qianfan.secret-key}")
    private String secretKey;


    /**
     * 温度
     */
    @Value("${spring.ai.qianfan.temperature}")
    private Double temperature;

    /**
     * 指定模型最大输出token数, 说明:
     * (1)如果设置此参数, 范围[2, 2048]
     * (2)如果不设置此参数, 最大输出token数为1024
     */
    @Value("${spring.ai.qianfan.maxOutputTokens}")
    private Integer maxOutputTokens;

    /**
     * 单论对话
     *
     * @param prompt 提示词
     * @return 响应结果
     */
    @PostMapping(value = "/chat")
    public String chat(String prompt) {
        // 创建
        ChatResponse response = this.getBaseChatBuilder()
                .addMessage(ChatRole.USER, prompt) // com.baidubce.qianfan.model.constant.ChatRole 这个类有相关的角色 添加用户消息 (此方法可以调用多次, 以实现多轮对话的消息传递)
                // .addUserMessage("") // 添加用户信息
                // .addAssistantMessage("") // 添加助理信息
                .execute(); // 发起请求
        String result = response.getResult();
        log.info(result);
        return result;
    }

    /**
     * 多轮对话
     *
     * @param prompt 提示词
     * @return 响应结果
     */
    @PostMapping(value = "/chat2")
    public String chat2(String prompt) {
        // 创建
        ChatResponse response = this.getBaseChatBuilder()
                .addUserMessage("你是谁?") // 添加用户信息
                .addAssistantMessage("你好,  我是一个人工智能语言模型,  可以协助你完成范围广泛的任务并提供有关各种主题的信息。作为一个计算机程序,  我没有具体的身份和个人特征,  我的目的是通过回答问题、提供信息和执行任务来帮助用户。请问你有任何问题都可以询问我。") // 添加助理信息
                .addUserMessage(prompt) // 添加用户信息
                .execute(); // 发起请求
        String result = response.getResult();
        log.info(result);
        return result;
    }

    /**
     * 流式对话
     * @param prompt 提示词
     * @return 响应结果
     */
    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> stream(String prompt) {
        return Flux.create(sink -> {
            try(StreamIterator<ChatResponse> flux = this.getBaseChatBuilder()
                    .addUserMessage(prompt)
                    .executeStream()) {
                // 当有数据的时候, 将数据返回
                while (flux.hasNext()) {
                    ChatResponse chunk = flux.next();
                    String result = chunk.getResult();
                    // 发送每个响应片段
                    // 当为""的时候, 那么就就表示响应结束
                    sink.next(result);
                }
                
                // 完成流式传输
                sink.complete();
            } catch (Exception e) {
                // 发送错误信号
                log.error("传输错误", e);
                sink.error(e);
            }
        });
    }

    /**
     * 获取初始化ChatBuilder
     *
     * @return ChatBuilder
     */
    private ChatBuilder getBaseChatBuilder() {
        Qianfan qianfan = new Qianfan(Auth.TYPE_OAUTH, accessKey, secretKey);
        return qianfan.chatCompletion()
                .topP(topP) // 输出文本的多样性因子
                .maxOutputTokens(maxOutputTokens) // 指定模型最大输出token数
                .model(model) // 使用model指定预置模型
                .temperature(temperature); // 温度
    }
}

参考资料

Spring接入阿里云通义千问Demo

java对接阿里云通义千问API多轮对话承接上下文

SpringBoot + 通义千问 + 自定义React组件, 支持EventStream数据解析!

通义千问最新api java调用

Spring AI 接入OpenAI大模型实现同步和流式对话

Spring Boot 整合 Spring AI 实现项目接入ChatGPT

Ollama全面指南:安装、使用与高级定制

Ollama 常用命令_ollama 命令

【ollama】linux、window系统更改模型存放位置, 全网首发2024!_ollama 设置模型位置

更改ollama模型存储路径

ollama如何把gemma模型部署到D盘

五个优秀的免费 Ollama WebUI 客户端推荐

[Ollama深度探索:AI大模型本地部署的全面教程_ollama训练大模型](https://blog.csdn.net/xiaobing259/article/details/139853520#:~:text=二、安装与配置 1 1、系统要求 在开始安装Ollama之前, 确保您的系统满足以下基本要求: 操作系统:macOS、Windows 10及以上版本、Linux(包括但不限于Ubuntu、Fedora) 内存:至少4GB RAM,推荐8GB或以上, 具体取决于所运行模型的大小,… 4 4、启动Ollama服务 Ollama服务可以通过命令行界面(CLI)启动。 使用以下命令启动Ollama服务: ollama serve )

open-webui

零基础入门AI:一键本地运行各种开源大语言模型 - Ollama

RAG 实践-Ollama+AnythingLLM 搭建本地知识库

超级详细Spring AI运用Ollama大模型

Kimi API 还没用起来?请看这篇无门槛快速入门指南

Moonshot AI - 开放平台

ERNIE-4.0-Turbo-8K - 千帆大模型平台 | 百度智能云文档

bce-qianfan-sdk/java at main · baidubce/bce-qianfan-sdk

使用Python调用百度千帆免费大模型接口

java ISO 8601 日期格式进行转换

Logo

尧米是由西云算力与CSDN联合运营的AI算力和模型开源社区品牌,为基于DaModel智算平台的AI应用企业和泛AI开发者提供技术交流与成果转化平台。

更多推荐