springAi+阿里云百炼平台+deepseek,在业务系统中接入ai智能体,实现对业务方面的问答

【Spring AI+Spring AI Albaba从零开始的学习之旅】之RAG(向量检索增强生成)篇 摘要:本文介绍了GPT模型的局限性(仅支持2021年9月前的数据)以及三种定制AI模型整合自有数据的技术方案:微调(FineTuning)、提示词填充(PromptStuffing)和函数调用(FunctionCalling)。重点讲解了检索增强生成(RAG)技术的原理与实现,包括离线知识库构建(数据加载、文本分割、向量嵌入)和在线交互(问题处理、相似性检索、回答生成)两阶段流程。通过SpringAI框架演示了向量生成、向量存储、ETL管道等核心组件,并以SimpleVectorStore为例展示了RAG的 阅读详情
AI摘要

本文介绍了使用Spring AI框架深度集成业务系统的方法,实现本地化AI问答功能。基于Spring Boot框架,使用Spring AI接入阿里云百炼deepseek - r1文本模型,配合Elasticsearch向量数据库和Redis缓存。文章包含完整代码示例和配置说明,适合开发者快速实现业务场景的AI对话功能。

一、前言

本文介绍了使用Spring AI框架深度集成业务系统的方法,实现本地化AI问答功能。主要内容包括:

  1. 技术选型:基于SpringBoot框架,使用Spring AI接入阿里云百炼平台的deepseek-r1文本模型,配合Elasticsearch向量数据库实现RAG和Redis缓存实现上下文缓存。
  2. 实现功能:
    • 流式输出响应
    • 对话上下文管理
    • RAG检索增强
    • AI工具调用
    • 对话日志
    • 多租户隔离
  3. 介绍内容:
    • AI提示Prompt
    • 结构化输出(代码未实现)
    • 多模态(代码未实现,仅概念介绍)
    • 聊天记忆,即对话上下文管理
    • 工具调用
    • MCP模型上下文协议(代码未实现)
    • RAG检索增强与向量数据库

文章包含完整的代码示例和配置说明,适合开发者快速实现业务场景的AI对话功能。
注1:文章还记录遇到的问题,代码已完全实现以上介绍的功能,但存在一些BUG,后续会持续更新,如有解决方法还请告知,万分感谢。
注2:如果需要自行改造或进一步理解,请到springAi官网查阅文档。Spring Ai

二、使用技术

基础框架:springboot 3.4.*+
ai框架:springAi
jdk:17
文本模型:阿里云百炼平台的deepseek-r1
向量模型:text-embedding-v4
向量数据库:elasticsearch
缓存数据库:redis

三、代码实现

1、整体目录结构

在这里插入图片描述

2、maven坐标

	<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>org.dromara</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>ruoyi-chatai</artifactId>

    <description>
        openAi模块
    </description>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
    	<!-- 前缀为ruoyi的都是若依框架的模块,可替换 -->
    	<!-- 多租户模块 -->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-tenant</artifactId>
        </dependency>
          <!-- 鉴权模块 -->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-satoken</artifactId>
        </dependency>
        <!-- web模块 -->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-web</artifactId>
        </dependency>
        <!-- mybatis模块 -->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>ruoyi-common-mybatis</artifactId>
        </dependency>
		<!-- hutool工具 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-json</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
		<!-- springAi的操作elasticsearch向量数据库依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-elasticsearch-store-spring-boot-starter</artifactId>
        </dependency>
        <!-- 核心依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

3、yml配置

spring:
#elasticsearch向量数据库配置
  elasticsearch:
    uris: http://127.0.0.1:9200
    # 本地elasticsearch未开启认证,生产环境必须开启
    username: root
    password: famsakfdsfx
spring:
  ai:
    dashscope:
      api-key: sk-c7********************* #从阿里云百炼平台获取
      chat:
        options:
          model: deepseek-r1
      embedding:
        options:
          # 嵌入的向量模型
          model: text-embedding-v4
    vectorstore: #向量相关配置
      elasticsearch: #使用elasticsearch作为向量数据库,也可以使用其他的,如redis向量数据库
        initialize-schema: true #如果不存在该索引则创建
        index-name: spring-ai-rag #索引名称(这里的索引并不是mysql那种索引)
        # elasticsearch的向量维度(默认1536),需要和向量模型的维度一致。因为text-embedding-v4向量模型配置维度不生效,只能配置默认的1024
        dimensions: 1024
        similarity: cosine #相似配置枚举类型
    #以上是springAi自身的配置,以下是自定义配置
    # ai初始化配置
    client:
      enabled: false #是否开启初始化
      # 系统的默认行为和风格
      defaultSystem: 你是一个电子产品回收系统专业的助手。需要做到以下几点:1、在回答问题时,使用表格展示或分点列举的方式,以便用户更容易理解和参考;2、遇到不确定或不明确的信息时,会主动询问用户以获取更多信息;3、拒绝回答与本系统无关的问题。
      # 相似度阈值,取值区间:(0,1]
      similarity-threshold: 0.3
      # 返回匹配的向量文档数量,可以按每个向量文档的token大小来设置,token大,topK则设置小一些,否则会超出ai对话的token长度限制
      topK: 50
      # 对话上下文记忆条数
      chatHistoryWindowSize: 20
      knowledgeBasePath: chatai/knowledgeBase
      issuePath: chatai/问题提示语.txt

4、ai初始化及配置类

4.1、ai对话配置信息

读取yml配置

/**
 * ai对话配置信息
 */
@Data
@Configuration
@ConfigurationProperties("spring.ai.client")
public class ChatClientConfigInfo {

    /**
     * 是否开启ai
     */
    private Boolean enabled;

    /**
     * 系统的默认行为和风格
     */
    private String defaultSystem;

    /**
     * 相似度阈值,取值区间:(0,1]
     */
    private Double similarityThreshold;

    /**
     * 返回匹配的向量文档数量,可以按每个向量文档的token大小来设置,token大,topK则设置小一些,否则会超出ai对话的token长度限制
     */
    private Integer topK;

    /**
     * 对话上下文记忆条数
     */
    private Integer chatHistoryWindowSize;

    /**
     * 提示词
     */
    private List<String> issues;

    /**
     * 提示词文件路径(可以和提示词共存)
     */
    private String issuePath;

    /**
     * 初始知识库文件路径
     */
    private String knowledgeBasePath;
}
4.2、ai初始化

对话上下文缓存具体实现查看 章节 七、聊天记忆,即对话上下文管理

@Slf4j
@Configuration
@RequiredArgsConstructor
public class ChatClientConfig {

    private final ChatClientConfigInfo chatClientConfigInfo;
    private final ChatModel chatModel;

    /**
     * 初始化AI
     */
    @Bean
    public ChatClient chatClient() {
          return ChatClient.builder(chatModel)
            .defaultSystem(chatClientConfigInfo.getDefaultSystem())
            // 这里还可以配置一些默认的ai工具、RAG检索、MCP等
            .build();
    }

    /**
     * 注册对话上下文缓存
     */
    @Bean
    public ChatMemory chatMemory() {
        return new ChatRedisMemory(chatAiLogService);
    }

}
4.3、初始化知识库

将准备好的基本信息文件转化成向量文档存储到向量数据库中,在使用ai时检索与问题相似数据提供给ai。初始化信息可以是:系统介绍、公司介绍、操作手册、业务名称说明、业务表结构说明等等,文件类型推荐文本、word文档、pdf。

@Slf4j
@Configuration
@RequiredArgsConstructor
public class SpringAiRunner implements ApplicationRunner {

    private final ChatClientConfigInfo chatClientConfigInfo;
    private final VectorStoreService vectorStoreService;

    @Override
    public void run(ApplicationArguments args) {
        if (chatClientConfigInfo.getEnabled()) {
            knowledgeBaseInit();
        }
        readIssue();
    }

    /**
     * 初始化知识库
     */
    public void knowledgeBaseInit() {
        List<String> fileNames = new ArrayList<>();
        try {
            fileNames = FileUtils.getClassPathResourceFileName(chatClientConfigInfo.getKnowledgeBasePath());
        } catch (IOException e) {
            log.info("无初始化知识库文件!");
        }

        try {
            // 删除旧文档和添加新文档
            vectorStoreService.delete(FilterExpressionUtil.in(VectorKey.KEY, fileNames));
            for (String fileName : fileNames) {
                String filePath = chatClientConfigInfo.getKnowledgeBasePath() + "/" + fileName;
                vectorStoreService.saveFile(VectorKey.COMMON,filePath);
            }
            log.info("初始化 向量知识库 成功!");
        } catch (IOException e) {
            log.error("初始化 向量知识库 失败!原因:\n{}", e.getMessage());
        }
    }

    /**
     * 读取【问题提示语.txt】文件添加到chatClientConfigInfo
     */
    public void readIssue() {
        try {
            List<String> issues = FileUtils.readLinesFromFile(chatClientConfigInfo.getIssuePath());
            List<String> infoIssues = chatClientConfigInfo.getIssues();
            if (infoIssues != null) {
                issues.addAll(infoIssues);
            }
            chatClientConfigInfo.setIssues(issues);
        } catch (IOException e) {
            log.error("读取【{}】文件失败", chatClientConfigInfo.getIssuePath());
        }
    }

}

例:
文件示例
系统介绍示例

5、对话实现

由于ai功能模块化,为了避免耦合,由ai模块提供接口给业务模块,业务模块实现接口,通过实现类将ai工具(tool)传递到ai模块,最后由ai模块实现对话的核心逻辑。这个章节的标题已标注代码是属于ai模块或业务模块。
调用流程为:
ai模块controller->ai模块service接口->业务模块接口实现类(加入ai工具及其它方法作为参数传递)
->ai模块对话实现类

5.1、控制层(ai模块)
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/chat/v1")
public class ChatController {

    private final ChatService chatService;
    private final ChatMemory chatMemory;

    /**
     * deepSeek
     */
    @GetMapping(value = "/deepseek/rag")
    public Flux<String> ragChat(String message) {
        return chatService.ragChat(message);
    }

    /**
     * 清空上下文
     */
    @GetMapping(value = "/deepseek/clearAway")
    public R<Void> clearAway() {
        chatMemory.clear(LoginHelper.getUserId().toString());
        return R.ok();
    }

	/**
     * 中断对话(后续实现)
     */
}
5.2、ai服务接口(ai模块)

提供给外部的接口,业务模块实现该接口,本文是单例实现,可以改造成策略模式达到多实现的目的,使ai更加专注于某一块业务的处理。

/**
 * ai服务
 */
public interface ChatService {

    /**
     * 多轮对话
     */
    Flux<String> ragChat(String message);

}
5.3、实现ai服务接口(业务模块)

ai工具调用类具体功能介绍及实现查看章节 八、工具调用

/**
 * 回收报价ai服务
 */
@Service
@RequiredArgsConstructor
public class EsChatService implements ChatService {

	//ai工具调用类
    private final EquipmentTool equipmentTool;
    private final OrderTool orderTool;
    
    private final ChatClientService chatClientService;
	private final IEsPriceService esPriceService;

    @Override
    public Flux<String> ragChat(String message) {
        return chatClientService.send(message, esPriceService.priceConsumer(), equipmentTool, orderTool);
    }

}
5.4、对话核心实现(ai模块)
@Service
@RequiredArgsConstructor
public class ChatClientService {

    private final ChatClientConfigInfo chatClientConfigInfo;
    private final VectorStoreService vectorStoreService;
    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    /**
     * 发送对话
     *注:docProcessor 的功能说明及实现示例代码查看下一节 5.5、向量检索后处理实现
     *	
     * @param message      对话信息
     * @param docProcessor 向量检索后处理
     * @param toolObjects  ai对话工具
     */
    public Flux<String> send(String message, Function<List<Document>, List<Document>> docProcessor, Object... toolObjects) {
        String userId;
        try {
            userId = LoginHelper.getLoginUser().getUserId().toString();
        } catch (Exception e) {
            return Flux.just("请先登录!");
        }
 
        //先进行数据检索,在知识库中未检索到信息则认为是与本系统无关的问题
        // 这里依赖向量数据库的数据库,如果要保留这个操作,需要维护向量数据库的数据
        List<Document> documentList = vectorStoreService.search(message);
        if (documentList.isEmpty()) {
            //未命中知识库
            List<String> issues = chatClientConfigInfo.getIssues();
            Collections.shuffle(issues);
            return Flux.just("我无法回答与系统无关的问题,请换一个问题吧。" +
                "\n" + issues.get(0) + "\n" + issues.get(1) + "\n" + issues.get(2)
            );
        }
        if (docProcessor != null) {
            documentList = docProcessor.apply(documentList);
        }
        //把处理后文档拼成上下文,手动塞进 prompt
        String context = documentList.stream()
            .map(Document::getText)
            .collect(Collectors.joining("\n\n"));

        return chatClient.prompt()
            .system(chatClientConfigInfo.getDefaultSystem() + "请使用以下内容回答用户问题:\n" + context)
            .user(message)
            .advisors(new MessageChatMemoryAdvisor(chatMemory, userId, chatClientConfigInfo.getChatHistoryWindowSize()))
            .tools(toolObjects)
            //流式输出,不需要流式输出则将以下代码改为.call().content()
            .stream().content();
    }

}
5.5、向量检索后处理实现(业务模块)

这个设计的主要目的是用于检索出向量文档后,对向量文档中的数据进一步处理的函数。
**初始化默认的向量检索顾问:**在检索到向量文档后会直接加入到Prompt中,无法做去重、截断、重排序等操作。
**手动查询向量文档:**能灵活的在检索出向量文档后处理数据,处理文档数据后,手动加入到prompt的system()中。
扩展:还可以自定义顾问,在顾问内部留一个函数插槽的方式,对向量检索后数据处理。

public Function<List<Document>, List<Document>> priceConsumer() {
        return documentList -> {
            List<Document> newDocument = new ArrayList<>();
            for (Document oldDoc : documentList) {
                Map<String, Object> metadata = oldDoc.getMetadata();
                String type = String.valueOf(metadata.get(VectorKey.TYPE));
                if ("es_price".equals(type)) {
                    String text = oldDoc.getText();
                    JSONObject root = JSON.parseObject(text);

                    // 修改价格
                    for (String key : root.keySet()) {
                        JSONObject memoryObj = root.getJSONObject(key);
                        for (String memKey : memoryObj.keySet()) {
                            JSONObject finenessObj = memoryObj.getJSONObject(memKey);
                            for (String finenessKey : finenessObj.keySet()) {
                                BigDecimal oldPrice = finenessObj.getBigDecimal(finenessKey);
                                BigDecimal newPrice = calculatePrice(oldPrice);
                                finenessObj.put(finenessKey, newPrice);
                            }
                        }
                    }

                    // 重新生成 Document
                    String newText = root.toJSONString();
                    Document newDoc = Document.builder()
                            .id(oldDoc.getId())
                            .text(newText)
                            .metadata(oldDoc.getMetadata())
                            .build();

                    newDocument.add(newDoc);
                }
            }
            return newDocument;
        };
    }

6、ai对话日志

6.1、自定义日志顾问

内部属性无法使用springboot的自动注入,需要在使用时候new一个LoggerAdvisor实例,通过构造方法传入。

/**
 * 日志顾问
 */
@Slf4j
public class LoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

    private final IChatAiLogService chatAiLogService;
    private final String conversationId;
    private final String tenantId;

    public LoggerAdvisor(IChatAiLogService chatAiLogService, String tenantId,String conversationId) {
        this.chatAiLogService = chatAiLogService;
        this.tenantId = tenantId;
        this.conversationId = conversationId;
    }

    // 非流式日志
    @Override
    public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) {
        List<ChatAiLog> listLog = new ArrayList<>();
        ChatAiLog userLog = new ChatAiLog();
        userLog.setChatId(conversationId);
        userLog.setType(MessageType.USER.getValue());
        userLog.setText(request.userText());
        userLog.setChatTime(DateUtils.getNowDate());
        userLog.setTenantId(tenantId);
        listLog.add(userLog);

        AdvisedResponse response = chain.nextAroundCall(request);
        ChatAiLog systemLog = new ChatAiLog();
        systemLog.setChatId(conversationId);
        systemLog.setType(MessageType.SYSTEM.getValue());
        systemLog.setText(response.response().getResult().getOutput().getText());
        systemLog.setChatTime(DateUtils.getNowDate());
        systemLog.setTenantId(tenantId);
        listLog.add(systemLog);

        chatAiLogService.saveBatch(listLog);
        return response;
    }

    // 流式日志
    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest request, StreamAroundAdvisorChain chain) {
        ChatAiLog userLog = new ChatAiLog();
        userLog.setChatId(conversationId);
        userLog.setType(MessageType.USER.getValue());
        userLog.setText(request.userText());
        userLog.setChatTime(DateUtils.getNowDate());
        userLog.setTenantId(tenantId);
        chatAiLogService.save(userLog);

        Flux<AdvisedResponse> responses = chain.nextAroundStream(request);

        return new MessageAggregator()
            .aggregateAdvisedResponse(responses, res -> {
                    ChatAiLog systemLog = new ChatAiLog();
                    systemLog.setChatId(conversationId);
                    systemLog.setType(MessageType.SYSTEM.getValue());
                    systemLog.setText(res.response().getResult().getOutput().getText());
                    systemLog.setChatTime(DateUtils.getNowDate());
                    systemLog.setTenantId(tenantId);
                    chatAiLogService.save(systemLog);
                }
            );
    }

    @Override
    public String getName() {
        return "LoggerAdvisor";
    }

    @Override
    public int getOrder() {
        return 100;
    }
}
6.2、实体类、Mapper、xml文件、service及impl、controller

简单日志信息的存储和查询,常用的三层架构,可以改成其他的方式。
ai对话日志是为了通过分析更好的调整ai,如用户高频问题,可以编写专门的ai工具调用方法。

/**
 * ai对话日志
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_ai_log")
public class ChatAiLog  extends BaseEntity {

    @Serial
    private static final long serialVersionUID = 1L;

    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    /**
     * 对话ID(用户ID)
     */
    private String chatId;

    /**
     * 类型(system、user、assistant)
     */
    private String type;

    /**
     * 对话消息
     */
    private String text;

    /**
     * 对话时间
     */
    private Date chatTime;

}
public interface ChatAiLogMapper extends BaseMapper<ChatAiLog> {
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.chatai.mapper.ChatAiLogMapper">

</mapper>
public interface IChatAiLogService extends IService<ChatAiLog> {

    TableDataInfo<ChatAiLog> queryPageList(ChatAiLog bo, PageQuery pageQuery);

}


@Slf4j
@RequiredArgsConstructor
@Service
public class IChatAiLogServiceImpl  extends ServiceImpl<ChatAiLogMapper, ChatAiLog> implements IChatAiLogService {

    @Override
    public TableDataInfo<ChatAiLog> queryPageList(ChatAiLog bo, PageQuery pageQuery) {
        LambdaQueryWrapper<ChatAiLog> lqw = buildQueryWrapper(bo);
        Page<ChatAiLog> result = baseMapper.selectPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    private LambdaQueryWrapper<ChatAiLog> buildQueryWrapper(ChatAiLog bo) {
        LambdaQueryWrapper<ChatAiLog> lqw = Wrappers.lambdaQuery();
        lqw.orderByAsc(ChatAiLog::getId);
        lqw.like(StringUtils.isNotBlank(bo.getType()), ChatAiLog::getType, bo.getType());
        lqw.eq(StringUtils.isNotBlank(bo.getText()), ChatAiLog::getText, bo.getText());
        return lqw;
    }

}

@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/chat/log")
public class ChatAiLogController {

    private final IChatAiLogService chatAiLogService;

    /**
     * 查询ai对话日志列表
     */
    @SaCheckPermission("chat:log:list")
    @GetMapping("/list")
    public TableDataInfo<ChatAiLog> list(ChatAiLog bo, PageQuery pageQuery) {
        return chatAiLogService.queryPageList(bo, pageQuery);
    }

    /**
     * 获取ai对话日志详细信息
     *
     * @param id 主键
     */
    @SaCheckPermission("chat:log:query")
    @GetMapping("/{id}")
    public R<ChatAiLog> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
        return R.ok(chatAiLogService.getById(id));
    }

}
6.3、日志数据库sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

DROP TABLE IF EXISTS `chat_ai_log`;
CREATE TABLE `chat_ai_log`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `chat_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
  `chat_time` datetime NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  `create_by` bigint NULL DEFAULT NULL,
  `update_by` bigint NULL DEFAULT NULL,
  `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ai对话日志' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

四、AI提示Prompt

提示(Prompt),实际就是发送给ai的内容,可以包含以下内容:

  • system内容,规范ai的行为和规范
  • user内容,用户的输入
  • 上下文对话内容
  • RAG检索内容
  • AI工具获取到的内容
  • MCP获取到的内容
    这些内容显著影响ai的输出,想要ai的输出达到预期效果,设计ai提示(Prompt)是极为重要的。

五、结构化输出(代码未实现)

如标题描述的一样,让ai根据规定的格式进行输出,有两种方式:

  • 通过提示词进行规范
  • 使用转换器
    通过提示词进行规范的方式就是用自然语言告诉ai要按什么格式进行输出,可以提供模版格式,但是使用这种方法并不能百分百保证ai按格式输出。
    使用转换器的方法查看SpringAi 结构化输出

六、多模态(代码未实现)

就是配置多种ai模型(文本模型、视觉模型、语言模型),多种模型配合进行问答,功能更加强大。

七、聊天记忆,即对话上下文管理

ai模型并无记忆,要实现连续对话,就得自己记录对话信息,并在每次提问时候将之前的对话记录一并发送给ai。
核心是实现ChatMemory接口
具体怎么保存对话信息可以随意实现,以下介绍的是以redis存储上下信息。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatEntity implements Serializable {
    /**
     * 对话ID
     */
    private String chatId;

    /**
     * 类型(system、user、assistant、tool)
     */
    private String type;

    /**
     * 对话消息
     */
    private String text;
}
@Slf4j
@Component
@RequiredArgsConstructor
public class ChatRedisMemory implements ChatMemory {

    private static final String KEY_PREFIX = "chat:history:";

    @Override
    public void add(String conversationId, Message message) {
        ChatMemory.super.add(conversationId, message);
    }

    @Override
    public void add(String conversationId, List<Message> messages) {
        String key = KEY_PREFIX + conversationId;
        List<ChatEntity> listIn = new ArrayList<>();
        for (Message msg : messages) {
            //去除think过程
            String[] strs = msg.getText().split("</think>");
            String text = strs.length == 2 ? strs[1] : strs[0];

            ChatEntity ent = new ChatEntity();
            ent.setChatId(conversationId);
            ent.setType(msg.getMessageType().getValue());
            ent.setText(text);
            listIn.add(ent);
        }
        RedisUtils.setCacheList(key, listIn);
        RedisUtils.expire(key, Duration.ofMinutes(30));
    }

    @Override
    public List<Message> get(String conversationId, int lastN) {
        String key = KEY_PREFIX + conversationId;
        List<Message> listOut = new ArrayList<>();
        if (!RedisUtils.hasKey(key)) {
            return listOut;
        }
        long size = RedisUtils.getCacheList(key).size();
        if (size == 0) {
            return listOut;
        }

        int start = Math.max(0, (int) (size - lastN));
        List<Object> listTmp = RedisUtils.getCacheListRange(key, start, -1);
        ObjectMapper objectMapper = new ObjectMapper();
        for (Object obj : listTmp) {
            ChatEntity chat = objectMapper.convertValue(obj, ChatEntity.class);
            if (MessageType.USER.getValue().equals(chat.getType())) {
                listOut.add(new UserMessage(chat.getText()));
            } else if (MessageType.ASSISTANT.getValue().equals(chat.getType())) {
                listOut.add(new AssistantMessage(chat.getText()));
            } else if (MessageType.SYSTEM.getValue().equals(chat.getType())) {
                listOut.add(new SystemMessage(chat.getText()));
            }
        }

        return listOut;
    }

    @Override
    public void clear(String conversationId) {
        RedisUtils.deleteObject(KEY_PREFIX + conversationId);
    }
}

然后在调用ai时通过对话顾问添加到ai中:
在这里插入图片描述

八、工具调用

AI工具和MCP的功能有点相似,都是用于扩展ai的功能。
比如检索天气信息、业务数据库信息等。
优缺点对比:

  • 上手难度:ai工具极低,MCP需要额外学习
  • 功能实现:ai工具需要编码实现,MCP不需要
  • 共用性:ai工具仅当前spring应用,MCP可以多服务共用,且不分语言

ai工具实现示例(tool)
核心内容:@Tool和@ToolParam注解,其他可有可无,看业务需要。
只要在方法上加了@Tool注解,那该方法即是ai工具(tool)。
@Tool:使用自然语言描述标记的方法的用处,当ai根据对话内容匹配到时候就会执行该方法。
@ToolParam:使用自然语言描述标记的参数,由ai根据对话内容自动传入。
作用:
作为给ai提供数据的一种方法,或是由ai操作业务的方法,如在订票系统中:帮我订一张xxx的票。(让ai操作业务需要谨慎,ai并不是百分百准确的)
以下为@Tool的使用示例,与ai模块无关。

@Slf4j
@Component
@RequiredArgsConstructor
@Description("查询回收设备及价格服务")
public class EquipmentTool {

    private final EsEquipmentMapper equipmentMapper;
    private final EsPriceMapper priceMapper;

    @Tool(name = "查询可回收的产品、设备或型号",
        description = """
            根据设备的类型(手机、平板等)、品牌(苹果、华为等等)、系列、型号、内存参数查询可回收的设备信息
            其中品牌必填
            """)
    public List<Map<String, String>> productName(@ToolParam(description = "类型") String equipmentType,
                                                 @ToolParam(description = "品牌") String brand,
                                                 @ToolParam(description = "系列", required = false) String series,
                                                 @ToolParam(description = "型号", required = false) String model,
                                                 @ToolParam(description = "内存", required = false) String internalStorage) {
        return equipmentMapper.getProductNameList(equipmentType, brand, series, model, internalStorage);
    }

    @Tool(name = "查询价格、回收价格",
        description = """
            根据设备的类型(手机、平板等)、品牌(苹果、华为等等)、系列、型号、内存参数查询最新价格(回收价格)
            其中型号必填
            """)
    public List<Map<String, String>> selectPrice(@ToolParam(description = "类型", required = false) String equipmentType,
                                                 @ToolParam(description = "品牌", required = false) String brand,
                                                 @ToolParam(description = "系列", required = false) String series,
                                                 @ToolParam(description = "型号") String model,
                                                 @ToolParam(description = "内存", required = false) String internalStorage,
                                                 @ToolParam(description = "成色", required = false) String fineness) {
        return priceMapper.selectPriceListByTableName("es_price", equipmentType, brand, series, model, internalStorage, fineness);
    }

    @Tool(name = "查询历史价格、回收价格",
        description = """
            根据设备的类型(手机、平板等)、品牌(苹果、华为等等)、系列、型号、内存参数查询历史价格(回收价格)
            其中型号和日期必填必填,日期是Date类型的参数
            """)
    public List<Map<String, String>> selectHistoryPrice(@ToolParam(description = "类型", required = false) String equipmentType,
                                                        @ToolParam(description = "品牌", required = false) String brand,
                                                        @ToolParam(description = "系列", required = false) String series,
                                                        @ToolParam(description = "型号") String model,
                                                        @ToolParam(description = "内存", required = false) String internalStorage,
                                                        @ToolParam(description = "成色", required = false) String fineness,
                                                        @ToolParam(description = "日期") Date date) {
        String dateToStr = DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_, date);
        String tableName = String.format("%s_%s", "es_price", dateToStr);
        return priceMapper.selectPriceListByTableName(tableName, equipmentType, brand, series, model, internalStorage, fineness);
    }

}

九、MCP模型上下文协议(代码未实现)

mcp是一种标准化协议,mcp服务是遵循mcp协议开发的类似于插件一样的程序,提高ai与外部资源的交互性。
目前市面上已经有大量的mcp服务。
查看SpringAi 模型上下文协议(MCP)

十、RAG检索增强与向量数据库

RAG检索增强是检索向量数据库中的信息,将检索到的信息提供给ai。
向量数据库是一种专用数据库。如常用于缓存数据库的redis,搜索引擎elasticsearch都可以作为向量数据库,不过得用向量数据库版本的。还有很多其他的,具体查看SpringAi 向量数据库
以下是以elasticsearch作为向量数据库的代码实现。

向量数据的存储与检索(核心:VectorStore类)
VectorStore类是没有修改方法的,所以要修改数据的话只能先删除再新增。
注:本文使用elasticsearch作为向量数据库,elasticsearch的下载、安装、使用可以在网上找文章,只需要入门就能使用。

1、向量数据库操作封装类

@Slf4j
@Component
@RequiredArgsConstructor
public class VectorStoreService {

    private final ChatClientConfigInfo chatClientConfigInfo;
    private final VectorStore vectorStore;
    private final BatchingStrategy batchingStrategy;

    /**
     * 数据检索
     *
     * @param query 检索词
     */
    public List<Document> search(String query) {
        return search(query, null);
    }

    /**
     * 数据检索
     *
     * @param query      检索词
     * @param expression 过滤表达式
     */
    public List<Document> search(String query, Filter.Expression expression) {
        long startTime = System.currentTimeMillis();
        expression = buildTenantExpression(expression);
        List<Document> documents = vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .filterExpression(expression)
                .similarityThreshold(chatClientConfigInfo.getSimilarityThreshold())
                .topK(chatClientConfigInfo.getTopK())
                .build()
        );
        long endTime = System.currentTimeMillis();
        log.info("查询向量数据文档数:【{}】,耗时:【{}】毫秒", documents.size(), (endTime - startTime));
        return documents;
    }

    /**
     * 删除(这里只封装了根据过滤表达式删除文档的方法,还有一个根据id删除的方法,可以按需使用或封装)
     *
     * @param expression 过滤表达式
     */
    public void delete(Filter.Expression expression) {
        expression = buildTenantExpression(expression);
        try {
            vectorStore.delete(expression);
        }catch (IllegalStateException e){
            log.error(e.getMessage());
        }
    }

   /**
     * 新增
     *
     * @param key      数据标识
     * @param dataType 数据类型
     * @param text     新增的数据
     */
    public void save(String key, String dataType, String text) {
        String tenantId = getTenantId();
        Document document = new Document(text, Map.of(VectorKey.KEY, key, VectorKey.TYPE, dataType, VectorKey.TENANT_ID, tenantId));
        vectorStore.add(List.of(document));
    }

    public void saveFile(String dataType, String path) throws IOException {
        Resource resource = (new DefaultResourceLoader()).getResource(path);
        String text = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
        // 获取文件名称
        String fileName = Paths.get(path).getFileName().toString();
        save(fileName, dataType, text);
    }

    /**
     * 批量添加
     *
     * @param key       数据标识
     * @param dataType  数据类型
     * @param data      新增的数据
     * @param batchSize 分片数量,为0时不分片,数据量大于一定量时需要分片,否则会超出当个文件token限制
     * 注:这个参数一开始是没有6.4的EmbeddingConfig批量新增向量文档配置做的分片手段,需要手动计算token判断是否分片及分片数量,不好控制(可去除)
     * @param fun       将需要新增的数据处理成字符串的函数
     */
    public <T> void batch(String key, String dataType, List<T> data, int batchSize, Function<List<T>, String> fun) {
        String tenantId = getTenantId();
        /*
            逻辑分片: 在token分片之前,根据业务数据的格式需要进行分片
         */
        List<Document> documents = new ArrayList<>();
        if (batchSize != 0 && data.size() > batchSize) {
            List<List<T>> sharding = ListUtil.sharding(data, batchSize);
            for (List<T> s : sharding) {
                String text = fun.apply(s);
                Document document = new Document(text, Map.of(VectorKey.KEY, key, VectorKey.TYPE, dataType, VectorKey.TENANT_ID, tenantId));
                documents.add(document);
            }
        } else {
            String text = fun.apply(data);
            Document document = new Document(text, Map.of(VectorKey.KEY, key, VectorKey.TYPE, dataType, VectorKey.TENANT_ID, tenantId));
            documents.add(document);
        }
        /*
            token分片
         */
        List<List<Document>> batch = batchingStrategy.batch(documents);
        for (List<Document> list : batch) {
            vectorStore.add(list);
        }
    }

    /**
     * 批量添加
     */
    public void batch(String key, String dataType, List<String> data, int batchSize) {
        batch(key, dataType, data, batchSize, Object::toString);
    }

    /**
     * 租户数据隔离
     */
    public Filter.Expression buildTenantExpression(Filter.Expression expression) {
        if (expression == null) {
            return FilterExpressionUtil.buildTenant(getTenantId());
        } else {
            Filter.Expression tenantIdExpression = FilterExpressionUtil.buildTenant(getTenantId());
            return FilterExpressionUtil.and(expression, tenantIdExpression);
        }
    }

    /**
     * 获取租户ID
     *
     * @return 租户ID
     */
    public String getTenantId() {
        String tenantId = TenantHelper.getTenantId();
        if (tenantId == null || "000000".equals(tenantId)) {
            tenantId = VectorKey.COMMON;
        }
        return tenantId;
    }
}

2、向量数据库操作常量

public class VectorKey {


    /**
     * 来源标识
     */
    public final static String KEY = "source_key";

    /**
     * 来源类型
     */
    public final static String TYPE = "source_type";

    /**
     * 来源租户
     */
    public final static String TENANT_ID = "source_tenant_id";

    /**
     * 公共数据
     */
    public final static String COMMON = "common";

}

3、构建向量数据库过滤表达式封装工具类

构建过滤表达式与mysql数据库的where语法差不多。springAi封装的表达式构建方法有三种:

  1. 字符串,具体语法查看springAi文档中的向量数据库章节。
  2. new一个Filter.Expression,传入三个参数:表达式类型(等于、不等于、大于、包含等等)、键(使用new Filter.Key(key)包裹)、值(使用new Filter.Value(values)包裹)。
  3. new一个FilterExpressionBuilder,实际就是第二种的封装。

以下是自主封装第二种构建方法,使用了eq、in、and,如需其他方法可自行添加。

public class FilterExpressionUtil {

    public static Filter.Expression eq(String key,Object values){
        return new Filter.Expression(Filter.ExpressionType.EQ, new Filter.Key(key), new Filter.Value(values));
    }

    public static Filter.Expression in(String key, Object... values){
        return in(key,List.of(values));
    }

    public static Filter.Expression in(String key, List<Object> values){
        return new Filter.Expression(Filter.ExpressionType.IN, new Filter.Key(key), new Filter.Value(values));
    }

    public static Filter.Expression and(Filter.Expression expression1,Filter.Expression expression2){
        return new Filter.Expression(Filter.ExpressionType.AND, expression1, expression2);
    }

    public static Filter.Expression buildTenant(String tenantId){
        return eq(VectorKey.TENANT_ID,tenantId);
    }

}

4、批量新增向量文档配置

以下批量新增配置是SpringAi官方给出的代码示例

@Configuration
public class EmbeddingConfig {
    @Bean
    public BatchingStrategy customTokenCountBatchingStrategy() {
        return new TokenCountBatchingStrategy(
            EncodingType.CL100K_BASE,
            8000,//单文档最大token数
            0.1//保留百分比
        );
    }
}

5、向量数据库操作封装类的使用示例

search、delete、saveFile在前文中都有使用,sava也不多说,主要是batch批量添加方法。
batch方法的fun参数作用是提供一个构建向量文档数据结构的入口。
构建简单有效的向量文档数据结构,有以下好处:

  • 降低成本:采用简洁的数据格式能在相同token消耗下传递更多有效信息,减少向量文档检索的计费开销。
  • 提升精度:精简后的数据剔除了冗余内容,使AI检索时更聚焦于关键信息,提高结果准确性。
  • 效率增强:简化数据结构直接提升信息传输效率,确保单位时间内处理更多有效内容。
@Async
    @Override
    public void syncVectorData(String brandName) {
        //存在价格变动才更新
        if (baseMapper.selectSpreadCount(brandName) > 0) {
            EsPriceBo bo = new EsPriceBo();
            bo.setName1(brandName);
            List<EsPriceVo> vos = baseMapper.mySelectVoList(bo, 0);
            Map<String, List<EsPriceVo>> collect = vos.stream().collect(Collectors.groupingBy(
                //类型-品牌-系列-型号
                vo -> String.format("%s(类型)-%s(品牌)-%s(系列)-%s(型号)", vo.getName0(), vo.getName1(), vo.getName2(), vo.getName3())
            ));
            for (String key : collect.keySet()) {
                List<EsPriceVo> data = collect.get(key);
                // 先删除之前的再新增,根据key删除
                vectorStoreService.delete(FilterExpressionUtil.eq(VectorKey.KEY, key));
                /* 数据格式:
                    {
                        "name0(类型)-name1(品牌)-name2(系列)-name3(型号)": {
                            "name4(内存)": {
                                "finenessName(成色)": "价格"
                            }
                        }
                    }
                */
                vectorStoreService.batch(key, "es_price",data, 500, (resData) -> {
                    // 创建最外层的 JSON 对象
                    JSONObject resultJson = new JSONObject();
                    //根据内存分组
                    Map<String, List<EsPriceVo>> collect4 = data.stream().collect(Collectors.groupingBy(EsPriceVo::getName4));
                    // 创建内存级别的 JSON 对象
                    JSONObject name4Json = new JSONObject();
                    for (String key4 : collect4.keySet()) {
                        List<EsPriceVo> esEquipmentPriceVos4 = collect4.get(key4);
                        // 创建成色级别的 JSON 对象
                        JSONObject finenessJson = new JSONObject();
                        for (EsPriceVo voF : esEquipmentPriceVos4) {
                            finenessJson.put(String.format("%s(成色)", voF.getFinenessName()), voF.getPrice());
                        }
                        name4Json.put(String.format("%s(内存)", key4), finenessJson);
                    }
                    resultJson.put(key, name4Json);
                    return resultJson.toString();
                });
            }
        }
    }

十一、目前存在的问题

1、操作向量数据库时的过滤条件不生效
2、调用ai工具后有概率导致ai报错,非ai工具代码错误。

十二、总结

聊天记忆、ai工具、RAG检索增强、MCP服务等等,一切手段本质上只做了两件事情:

  • 规范ai的行为和输出
  • 更高效的给ai提供信息

所以,简化ai的调用流程可以描述为:
用户输入->以各种方法检索信息丰富ai的知识库->将用户输入的信息和检索到的附加信息一并发送给ai->ai输出->结束。

【大模型】Spring AI Alibaba 对接百炼平台大模型使用详解 Spring AI Alibaba 对接百炼平台大模型使用详解 阅读详情

相关推荐

kali设置安装burpsuite中文版

工具:通过网盘分享的文件:Burpsuite 链接:提取码: jay1。

m0_74786138的博客 5203

阿里百炼通用文本向量embeddings使用实践

本文介绍了阿里云Model Studio的API使用指南。首先需要获取与账号绑定的API KEY用于计费。调用方式支持Java、Python和curl请求,以Java SDK为例展示了文本嵌入模型的调用代码示例,包括依赖配置、参数构建和异常处理。最后说明了费用计算方式,具体计费标准可参考官方文档。该API可用于自然语言处理任务,如文本向量化等场景。

小小默:进无止境 1443

PayPal支付集成完全指南:开源库实践

本文介绍了如何从零开始集成PayPal支付功能。首先概述了PayPal提供的多语言SDK和开源库,包括PHP、Python、Node.js等版本。然后详细说明了准备工作,包括注册开发者账号、获取API凭证和使用沙盒环境测试。文章以Node.js为例,演示了安装SDK、基础配置和实现支付流程的完整代码,包括创建订单和捕获支付两个核心功能。最后提及了订阅支付等高级功能,为开发者提供了完整的支付集成方案。通过清晰的代码示例和步骤说明,帮助开发者快速实现PayPal支付功能集成。

2501_93501927的博客 1260

Spring AI 文档ETL实战:集成text-embedding-v4 与 Milvus

本文将带你一步步实现一个完整的文档处理流水线(ETL),利用 Spring AI 框架从本地 Markdown 文件中提取内容,通过通义千问的嵌入模型 text-embedding-v4 生成向量,并最终将带有丰富元数据的文档存储到高性能向量数据库 Milvus 中。整个流程简洁高效,适用于构建知识库、智能问答系统等 AI 应用。

qq_47741012的博客 1529

华为铁三角(AR、SR、FR)的考核指标设计

以和为核心,通过和实现协同。考核维度分为和,并随市场阶段动态调整权重。

从技术实践立足,从项目管理方法论延伸思考 2780

Teams会议翻译对比测试:Copilot同传 vs 人工翻译,结果出乎意料

本文通过对比测试Microsoft 365 Copilot的实时翻译与专业人工翻译在Teams会议中的表现,揭示了AI同传技术在准确率、响应速度和多语言切换方面的优势与局限。实验数据显示,Copilot在常规商务场景下接近人工翻译水平,且成本效益显著,但在专业术语和文化特定表达上仍需人工辅助。

weixin_29054195的博客 224

Spring AI 学习笔记(2)

RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合信息检索技术和 Al 内容生成的混合架构,可以解决大模型的知识时效性限制和幻觉问题。简单来说,RAG 就像给 AI 配了一个“小抄本”,让 AI 回答问题前先查一查特定的知识库来获取知识,确保回答是基于真实资料而不是凭空想象。从技术角度看,RAG 在大语言模型生成回答之前,会先从外部知识库中检索相关信息,然后将这些检索到的内容作为额外上下文提供给模型,引导其生成更准确、更相关的回答。

weixin_69163469的博客 1706

从零开始学习信号完整性(SIPI)--1 简介

信号完整性就是研究如何让驱动芯片发出的信号经过传输通道被接收芯片正确接收的学问。 信号完整性包含了三个要素: 信号:是被传输的主体,我们首先要了解、熟悉信号的特性。 芯片:分为驱动芯片和接收芯片,信号完整性工程师就是要实现信号在驱动芯片和接收芯片之间的正确传输。 传输通道:是传输信号的介质,是信号所走的“路”。如何实现信号的正确传输呢?那就要信号完整性工程师为信号“修路”、“搭桥”。

qq_29476769的博客 4万+

SpringAI构建智能体

本项目是《计算机科学与技术专业》实践课程的教学案例项目,基于DashScopeOllama构建完整的 AI 智能体应用。🔤大模型调用:DashScope 云端 API + Ollama 本地模型💬多轮对话:会话管理、上下文记忆🛠️:工具调用、智能决策📚RAG 知识库:Milvus 向量数据库、文档检索🎨多模态:文生图、语音合成⚡流式响应:SSE 实时输出多模型支持:DashScope 云端 + Ollama 本地,灵活切换完整 AI 能力:对话、文生图、语音、工具调用、RAG。

m0_74289031的博客 323

HDMI调试避坑指南:从AVI InfoFrame到通用控制包,这些DataIsland Packet细节千万别忽略

本文深入解析HDMI调试中DataIsland数据包的关键细节,包括AVI InfoFrame、通用控制包和音频时钟再生包(ACR)的配置与故障排查。通过实际案例和寄存器配置示例,帮助工程师快速定位显示异常、色彩失真和音频中断等问题,提升HDMI接口调试效率。

weixin_30897233的博客 637

解决Office 365 SMTP问题:C#邮件发送详解

本文介绍了如何在C#中使用Office 365 SMTP服务发送邮件。重点讲解了正确配置SMTP客户端的关键参数:必须使用587端口并启用TLS加密(EnableSsl=true),同时通过NetworkCredential进行身份验证。虽然SmtpClient已不推荐用于新项目,但本文提供的代码示例仍适用于临时项目或系统维护,并建议未来可考虑使用MailKit等现代邮件库。文章还包含了错误处理机制,确保程序健壮性。

2501_90323865的博客 141

JB3-9-SpringAI(一)

基础对话,通义千问,SpringAI,SpringAIAlibaba,AI对话,文生图,文生音频,文生视频,AI记忆,RAG,ETL,向量库,ToolCalling,MCP,Graph 等。

周航宇的博客 3928

OpenClaw-遇到的问题- FailoverError: Model context window too small (4096 tokens). Minimum is 16000

摘要:OpenClaw配置文件中deepseek-reasoner模型的上下文窗口被默认设置为4096 tokens(实际支持128K),导致系统报错"Model context window too small"。解决方案是删除配置文件中的contextWindow和maxTokens字段,让系统自动获取模型真实能力,并重启OpenClaw服务。配置文件通常位于用户目录下的.openclaw/openclaw.json中。

直到世界的尽头 4165

抓住 AI 人工智能的风口之第 1 章 —— 8万字熟练掌握 SpringAI 编程核心概念

SpringAI与SpringAIAlibaba开发指南 1. 核心框架介绍 SpringAI:Spring官方推出的AI开发框架,提供统一API接入OpenAI、通义千问等主流模型 SpringAIAlibaba(SAA):阿里基于SpringAI的增强实现,深度集成通义系列模型,提供智能体开发等企业级功能 2. 环境搭建 本地模型部署:使用Ollama工具离线运行开源模型(如Qwen) 开发环境要求:JDK17+、SpringBoot3.x 多模型共存。

流放深圳 841

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Java程序员进阶:SpringAI全栈学习路线

摘要:本文为Java程序员提供SpringAI学习路线图,分为三个阶段:1.基础入门(环境搭建、首个应用开发);2.核心技术(提示词工程、向量数据库、RAG、FunctionCalling);3.进阶实战(多轮对话、结构化输出、生产部署)。重点强调将Spring设计原则应用于AI领域,并推荐通过官方文档、视频教程等资源持续学习。作者建议Java开发者利用已有Spring经验,重点掌握AI特有概念,实现从传统开发到AI应用开发的平滑过渡。

2501_93873871的博客 1050

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

01:SpringAI

ai

weixin_43761434的博客 426

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

长路的AI领域技术博客汇总文档

汇总AI后端核心技术栈:涵盖LM Studio/Xinference大模型本地化部署、Milvus向量数据库Java实战、LangChain4j框架系统化教程(RAG、Function Calling、多模态VL),以及ReAct Agent智能体设计与医疗AI项目实战,提供从理论到落地的全链路技术参考。

每个人都是独一无二的,把握好自己的节奏,跟着自己的心走。 785

第01章 LangChain 概述与环境搭建

本文介绍了LangChain框架的基本概念与环境搭建方法。主要内容包括:1. LangChain概述:作为连接大模型与应用的中间层框架,用于开发智能体系统,支持模块化开发和多智能体协作。2. 开发环境准备:推荐Python 3.10+,安装langchain、langchain-openai等依赖包,配置.env文件存储API密钥。3. DeepSeek模型调用:演示了直接使用OpenAI兼容API和通过LangChain的init_chat_model两种调用方式。4. 实践案例:实现了一个简单的课程答疑

m0_60873365的博客 419

JoyAgent-JDGenie 开源端到端多智能体产品实战指南:架构、GAIA 评测、部署与二次开发

JoyAgent-JDGenie 是京东开源的端到端产品级通用多智能体,定位是"解决快速构建多智能体产品的最后一公里问题"。本文基于仓库根目录 [README.md](https://link.gitcode.com/i/ad19384ab5773ca5b1b6f2d69e8283f1) 展开,结合 [GenieController.java](https://link.gitcode.com/i

gitblog_00178的博客 339

LangChain4J:Java工程师的生产级大模型集成框架

大语言模型(LLM)应用已从原型验证迈入企业级落地阶段,而Java作为主流后端语言,亟需一套符合其工程哲学的集成范式。LangChain4J并非Python LangChain的简单移植,而是基于强类型、可注入、可监控等Java核心特性构建的原生框架,覆盖ChatModel、EmbeddingModel、Retriever与ToolExecutor四大抽象,天然适配Spring Boot生态。它通过编译期校验、AOP切面、事件钩子(如onResponse/onToolExecution)和线程安全设计,将L

congran6617的博客 524

Qwen开源Embedding模型深度解读:细节剖析与实测结果大公开!

Embedding模型的测试,并不像LLM那样直观,给了问题之后直接看答案就可以分辨出好坏,Embedding模型还是要测试在某个数据集下整体得召回率。虽然这次Qwen开源了3个尺寸的模型,0.6B、4B和8B,但对于我个人来说,0.6B尺寸还可以,4B、8B为向量还是有点重的,工业使用上对召回速度要求还是比较高的。

2401_85390073的博客 1903

新商品冷启动:基于语义Embedding与GBRT的消费指标预估技术实践

本文提出了一种基于语义理解和GBRT的新商品冷启动解决方案,用于解决电商平台新商品因缺乏用户行为数据而难以准确推荐的问题。方案通过Qwen3模型提取商品语义特征,构建1280维向量表征商品内容,再采用梯度提升树预测消费指标XTR。实验表明,该方法显著提升了新商品的转化率和GMV,优化后模型误差率降至4.5%。文章还讨论了模型过拟合风险和改进方向,包括增强正则化、引入多模态特征等,为电商平台新商品推荐提供了有效的技术路径。

AlgoCraft 1218

Spring AI+DeepSeek快速构建AI智能机器人_spring ai deepseek

本文详细介绍了如何使用Spring AI快速搭建一个具备自然语言处理能力的智能对话机器人,并结合实现前后端交互。

HUANGXIN9898的博客 5031

使用千问text-embedding-v4模型配置GraphRAG时的报错解决

在settings.yaml的embedding_models配置下添加。

weixin_43421008的博客 481

【收藏】RAG是什么?Embedding是什么?小白也能看懂的实战讲解

想象一下,你要教一个只懂数学的外星人理解“苹果”这个词。你没法直接解释“红色、圆形、能吃的水果”,但你可以把“苹果”转换成一组数字——这组数字就是向量。把人类语言中模糊的语义,转换成了计算机能计算的、精确的数字数组。比如“苹果”可能对应向量,“香蕉”对应,两个向量的数字越接近,代表语义越相似。RAG和Embedding是解决大模型幻觉问题的“黄金组合”——Embedding负责把文本变成可计算的向量,实现精准检索;RAG负责把检索结果喂给大模型,让它基于真实资料生成答案。

大模型教程的博客 983

Spring AI Alibaba 快速入门

Spring AI Alibaba 快速入门

深圳市多克创新科技有限公司 3303
上一篇: 使用ThreadPoolTaskScheduler实现定时任务调度
下一篇: springboot+mybatis拦截器+自定义注解实现业务数据权限控制、逻辑删除,分页查询存在问题修改
String_001
博客等级 码龄5年 16粉丝 10原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值