【Spring AI快速上手 (四)】RAG解析文档构建知识库

发布于:2025-08-08 ⋅ 阅读:(10) ⋅ 点赞:(0)

一、前言

二、RAG解析文档构建知识库

        pom.xml

        application.properties

        Application

        EmbaddingTest

        文档读取器

        文档切割器

        自定义中文优化分割器

        testVectorStore向量数据库

        检索重排序

        基础RAG流程和高级RAG特性

        RAG效果评估

        AI模型评估


一、前言

 Spring AI详解:【Spring AI详解】开启Java生态的智能应用开发新时代(附不同功能的Spring AI实战项目)-CSDN博客

二、RAG解析文档构建知识库

pom.xml

阿里云DashScope大模型:提供通义千问等大语言模型能力,用于文本生成和嵌入向量计算

Ollama本地大模型:支持在本地运行Llama2等开源模型,降低云服务依赖

文档处理组件:包含Markdown和PDF文档读取器,支持从不同格式文件中提取结构化内容

向量存储:为AI应用提供向量数据库存储和检索能力,支持语义搜索

Web支持:基于Spring Boot的Web开发框架,便于构建RESTful API

<!-- 依赖管理配置(BOM模式) -->
<dependencyManagement>
    <dependencies>
        <!-- 1. Spring AI Alibaba BOM -->
        <!-- 功能:管理阿里云AI生态组件的版本(如Dashscope、百炼平台集成等) -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-bom</artifactId>
            <version>1.0.0.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- 2. Spring AI BOM -->
        <!-- 功能:管理Spring AI核心模块版本(ChatClient、RAG、工具调用等) -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- 3. Spring Boot BOM -->
        <!-- 功能:管理Spring Boot及其starter组件的版本 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- 阿里云DashScope大模型集成 (百炼平台) -->
    <!-- 提供与阿里云灵积平台(DashScope)的集成,支持通义千问等大语言模型 -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    </dependency>

    <!-- Ollama本地大模型集成 -->
    <!-- 支持在本地运行Ollama服务的大语言模型(如Llama2等) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-ollama</artifactId>
    </dependency>

    <!-- Spring Web支持 -->
    <!-- 提供Spring MVC和嵌入式Tomcat等Web开发基础功能 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 单元测试支持 -->
    <!-- 包含JUnit、Mockito等测试框架集成 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- 向量存储支持 -->
    <!-- 为AI应用提供向量数据库存储和检索能力 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-advisors-vector-store</artifactId>
    </dependency>
    
    <!-- Markdown文档读取器 -->
    <!-- 支持从Markdown文件中提取结构化内容 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-markdown-document-reader</artifactId>
    </dependency>

    <!-- PDF文档读取器 -->
    <!-- 支持从PDF文件中提取文本内容 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pdf-document-reader</artifactId>
    </dependency>
</dependencies>

application.properties

# 阿里云DashScope API密钥(建议通过环境变量ALI_AI_KEY配置)
spring.ai.dashscope.api-key=${ALI_AI_KEY}

# 指定文本嵌入(Embedding)模型使用text-embedding-v4版本
# 该模型用于将文本转换为向量表示,适用于语义搜索等场景
spring.ai.dashscope.embedding.options.model=text-embedding-v4

# 指定聊天模型使用通义千问3的4B参数版本(需提前在Ollama中拉取)
spring.ai.ollama.chat.model=qwen3:4b

# Ollama服务地址(默认本地11434端口)
spring.ai.ollama.base-url=http://localhost:11434

# 指定嵌入模型使用nomic-embed-text(轻量级开源嵌入模型)
spring.ai.ollama.embedding.model=nomic-embed-text

# 开启Spring AI模块的调试日志(可查看详细的API请求/响应)
logging.level.org.springframework.ai=debug

Application

原型作用域的RestClient构建器:为HTTP请求提供可定制的超时设置(连接和读取均为60秒)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.client.RestClientBuilderConfigurer;
import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

import java.time.Duration;

@SpringBootApplication
public class Application {

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

    @Bean
    @Scope("prototype") // 声明为原型作用域的Bean,每次依赖注入时都会创建新实例
    RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
        // 创建RestClient.Builder基础实例
        RestClient.Builder builder = RestClient.builder()
                // 配置请求工厂,设置超时参数
                .requestFactory(ClientHttpRequestFactories.get(
                        ClientHttpRequestFactorySettings.DEFAULTS
                                .withReadTimeout(Duration.ofSeconds(60)) // 读取超时60秒
                                .withConnectTimeout(Duration.ofSeconds(60)) // 连接超时60秒
                ));

        // 应用RestClientBuilderConfigurer的额外配置
        return restClientBuilderConfigurer.configure(builder);
    }
}

EmbaddingTest

验证不同AI模型的文本嵌入能力:

Ollama本地模型测试:评估本地运行的嵌入模型对中文文本的向量化效果

阿里云模型测试:测试云端服务的嵌入质量,对比向量维度和数值分布

输出分析:打印向量维度和具体数值,便于直观比较不同模型的嵌入结果

import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
import org.junit.jupiter.api.Test;
import org.springframework.ai.document.Document;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

import java.util.Arrays;

/**
 * 嵌入向量(Embedding)测试类
 * 用于测试不同AI模型的文本嵌入(Embedding)能力
 */
@SpringBootTest
public class EmbaddingTest {

    /**
     * 测试Ollama本地模型的嵌入能力
     * @param ollamaEmbeddingModel 自动注入的Ollama嵌入模型实例
     *                            使用本地运行的Ollama服务生成文本向量
     */
    @Test
    public void testEmbadding(@Autowired OllamaEmbeddingModel ollamaEmbeddingModel) {
        // 对文本"我叫小小"生成嵌入向量
        float[] embedded = ollamaEmbeddingModel.embed("我叫小小");
        
        // 输出向量维度和内容
        System.out.println("向量维度长度: " + embedded.length);  // 打印向量维度数
        System.out.println("向量内容: " + Arrays.toString(embedded));  // 打印向量具体数值
    }

    /**
     * 测试阿里云百炼模型的嵌入能力
     * @param embeddingModel 自动注入的DashScope嵌入模型实例
     *                       使用阿里云百炼平台生成文本向量
     */
    @Test
    public void testAliEmbadding(@Autowired DashScopeEmbeddingModel embeddingModel) {
        // 对文本"我叫小小"生成嵌入向量
        float[] embedded = embeddingModel.embed("我叫小小");
        
        // 输出向量维度和内容
        System.out.println("向量维度长度: " + embedded.length);  // 打印向量维度数
        System.out.println("向量内容: " + Arrays.toString(embedded));  // 打印向量具体数值
    }
}

文档读取器

纯文本读取:处理简单的TXT文件,保留原始格式

Markdown解析:可配置是否包含代码块、引用块等特定元素,支持元数据添加

PDF处理

按页读取:将每页内容作为独立文档

按段落读取:基于PDF目录结构进行智能分割,支持坐标调整和页眉过滤

统一接口:不同阅读器返回标准Document对象,便于后续处理

import org.junit.jupiter.api.Test;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.ExtractedTextFormatter;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.ParagraphPdfDocumentReader;
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.util.List;

@SpringBootTest
public class ReaderTest {

    /**
     * 测试纯文本文件读取器
     * @param resource 通过@Value注入的文本文件资源
     */
    @Test
    public void testReaderText(@Value("classpath:rag/terms-of-service.txt") Resource resource) {
        // 创建文本阅读器实例
        TextReader textReader = new TextReader(resource);
        // 读取文档内容
        List<Document> documents = textReader.read();

        // 遍历并打印文档内容
        for (Document document : documents) {
            System.out.println(document.getText());
        }
    }

    /**
     * 测试Markdown文件读取器
     * @param resource 通过@Value注入的Markdown文件资源
     */
    @Test
    public void testReaderMD(@Value("classpath:rag/9_横店影视股份有限公司_0.md") Resource resource) {
        // 配置Markdown解析选项
        MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
                .withHorizontalRuleCreateDocument(false)     // 是否将分割线作为文档分隔符
                .withIncludeCodeBlock(false)                // 是否包含代码块作为独立文档
                .withIncludeBlockquote(false)               // 是否包含引用块作为独立文档
                .withAdditionalMetadata("filename", resource.getFilename()) // 添加文件名元数据
                .build();

        // 创建Markdown阅读器实例
        MarkdownDocumentReader markdownDocumentReader = new MarkdownDocumentReader(resource, config);
        // 读取文档内容
        List<Document> documents = markdownDocumentReader.read();

        // 遍历并打印文档内容
        for (Document document : documents) {
            System.out.println(document.getText());
        }
    }

    /**
     * 测试PDF按页读取器
     * @param resource 通过@Value注入的PDF文件资源
     */
    @Test
    public void testReaderPdf(@Value("classpath:rag/平安银行2023年半年度报告摘要.pdf") Resource resource) {
        // 创建PDF按页阅读器实例(默认配置)
        PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(resource,
                PdfDocumentReaderConfig.builder().build());

        // 读取文档内容(每页作为一个文档)
        List<Document> documents = pdfReader.read();

        // 遍历并打印文档内容
        for (Document document : documents) {
            System.out.println(document.getText());
        }
    }

    /**
     * 测试PDF按段落读取器(需要PDF包含目录结构)
     * @param resource 通过@Value注入的PDF文件资源
     */
    @Test
    public void testReaderParagraphPdf(@Value("classpath:rag/平安银行2023年半年度报告.pdf") Resource resource) {
        // 配置PDF段落解析选项
        ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader(resource,
                PdfDocumentReaderConfig.builder()
                        // 修正某些PDF工具的坐标系问题
                        .withReversedParagraphPosition(true)
                        // 设置页面上边距
                        .withPageTopMargin(0)
                        // 配置文本提取格式
                        .withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
                                // 设置删除页面顶部N行(处理页眉等)
                                .withNumberOfTopTextLinesToDelete(0)
                                .build())
                        .build());

        // 读取文档内容(按段落/章节作为文档)
        List<Document> documents = pdfReader.read();

        // 遍历并打印文档内容
        for (Document document : documents) {
            System.out.println(document.getText());
        }
    }
}

文档切割器

基础Token分割:基于CL100K编码的标准分割方式

中文优化分割器:针对中文特点改进,考虑标点符号和段落结构

元数据增强

关键词提取:使用AI模型自动生成文档关键词

摘要生成:创建前/中/后三段式摘要,增强检索效果

向量存储集成:处理后的文档可直接存入向量数据库

import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
import org.junit.jupiter.api.Test;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.ai.model.transformer.SummaryMetadataEnricher;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TextSplitter;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;

import java.util.List;

@SpringBootTest
public class SplitterTest {

    /**
     * 测试基于Token的文本分割器(通用版)
     * @param resource 测试文本资源
     */
    @Test
    public void testTokenTextSplitter(@Value("classpath:rag/terms-of-service.txt") Resource resource) {
        // 1. 读取原始文档
        TextReader textReader = new TextReader(resource);
        List<Document> documents = textReader.read();

        // 2. 使用TokenTextSplitter进行分割
        TokenTextSplitter splitter = new TokenTextSplitter(); // 默认配置
        List<Document> splitDocuments = splitter.apply(documents);

        // 3. 输出分割结果
        splitDocuments.forEach(System.out::println);
    }

    /**
     * 测试中文优化的Token文本分割器
     * @param resource 测试文本资源
     */
    @Test
    public void testChineseTokenTextSplitter(@Value("classpath:rag/terms-of-service.txt") Resource resource) {
        // 1. 读取原始文档
        TextReader textReader = new TextReader(resource);
        List<Document> documents = textReader.read();

        // 2. 使用中文优化分割器
        ChineseTokenTextSplitter splitter = new ChineseTokenTextSplitter(); // 中文专用实现
        List<Document> splitDocuments = splitter.apply(documents);

        // 3. 输出分割结果
        splitDocuments.forEach(System.out::println);
    }

    /**
     * 测试关键词元数据增强器
     * @param vectorStore 向量存储
     * @param chatModel 聊天模型(用于生成关键词)
     * @param resource 测试文本资源
     */
    @Test
    public void testKeywordMetadataEnricher(
            @Autowired VectorStore vectorStore,
            @Autowired DashScopeChatModel chatModel,
            @Value("classpath:rag/terms-of-service.txt") Resource resource) {

        // 1. 读取文档并添加文件名元数据
        TextReader textReader = new TextReader(resource);
        textReader.getCustomMetadata().put("filename", resource.getFilename());
        List<Document> documents = textReader.read();

        // 2. 使用中文分割器预处理
        ChineseTokenTextSplitter splitter = new ChineseTokenTextSplitter();
        documents = splitter.apply(documents);

        // 3. 使用关键词增强器(生成5个关键词)
        KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(chatModel, 5);
        documents = enricher.apply(documents);

        // 4. 存储到向量数据库
        vectorStore.add(documents);

        // 5. 基于关键词进行检索
        /* 假如允许自定义提示词:KeywordMetadataEnricher.KEYWORDS_TEMPLATE= """
                给我按照我提供的内容{context_str},生成%s个关键字;
                允许的关键字有这些:
                ['退票','预定']
                只允许在这个关键字范围进行选择。
                """;*/
        List<Document> results = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .filterExpression("filename in ('退票')") // 文件名过滤
                        .filterExpression("excerpt_keywords in ('退票')") // 关键词过滤
                        .build());

        // 6. 输出检索结果
        for (Document document : results) {
            System.out.println(document.getText());
            System.out.println(document.getText().length());
        }
    }

    /**
     * 测试配置类 - 提供向量存储Bean
     */
    @TestConfiguration
    static class TestConfig {
        @Bean
        public VectorStore vectorStore(DashScopeEmbeddingModel embeddingModel) {
            return SimpleVectorStore.builder(embeddingModel).build();
        }
    }

    /**
     * 测试摘要元数据增强器
     * @param chatModel 聊天模型(用于生成摘要)
     * @param resource 测试文本资源
     */
    @Test
    public void testSummaryMetadataEnricher(
            @Autowired DashScopeChatModel chatModel,
            @Value("classpath:rag/terms-of-service.txt") Resource resource) {

        // 1. 读取文档并添加元数据
        TextReader textReader = new TextReader(resource);
        textReader.getCustomMetadata().put("filename", resource.getFilename());
        List<Document> documents = textReader.read();

        // 2. 使用中文分割器(带精细参数配置)
        ChineseTokenTextSplitter splitter = new ChineseTokenTextSplitter(
                130,   // 块大小
                10,    // 块重叠
                5,     // 最小块大小
                10000, // 最大输入长度
                true   // 是否计算token
        );
        List<Document> splitDocuments = splitter.apply(documents);

        // 3. 应用摘要增强器(生成前/中/后三段摘要)
        SummaryMetadataEnricher enricher = new SummaryMetadataEnricher(
                chatModel,
                List.of(
                        SummaryMetadataEnricher.SummaryType.PREVIOUS,
                        SummaryMetadataEnricher.SummaryType.CURRENT,
                        SummaryMetadataEnricher.SummaryType.NEXT
                )
        );
        List<Document> enhancedDocuments = enricher.apply(splitDocuments);

        // 4. 输出增强后的文档
        System.out.println(enhancedDocuments);
    }
}

自定义中文优化分割器

智能断句:优先在句号、问号等中文标点处分割

可配置参数

块大小、重叠区域、最小长度阈值

最大分块数限制,避免长文本处理过载

编码处理:基于CL100K_BASE编码,准确计算中英混合文本的token数量

格式保留:可选是否保留换行符等分隔符

package com.xushu.springai.rag.ELT;

import com.knuddels.jtokkit.Encodings;
import com.knuddels.jtokkit.api.Encoding;
import com.knuddels.jtokkit.api.EncodingRegistry;
import com.knuddels.jtokkit.api.EncodingType;
import com.knuddels.jtokkit.api.IntArrayList;
import org.springframework.ai.transformer.splitter.TextSplitter;
import org.springframework.util.Assert;

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

public class ChineseTokenTextSplitter extends TextSplitter {

	private static final int DEFAULT_CHUNK_SIZE = 800;

	private static final int MIN_CHUNK_SIZE_CHARS = 350;

	private static final int MIN_CHUNK_LENGTH_TO_EMBED = 5;

	private static final int MAX_NUM_CHUNKS = 10000;

	private static final boolean KEEP_SEPARATOR = true;

	private final EncodingRegistry registry = Encodings.newLazyEncodingRegistry();

	private final Encoding encoding = this.registry.getEncoding(EncodingType.CL100K_BASE);

	// The target size of each text chunk in tokens
	private final int chunkSize;

	// The minimum size of each text chunk in characters
	private final int minChunkSizeChars;

	// Discard chunks shorter than this
	private final int minChunkLengthToEmbed;

	// The maximum number of chunks to generate from a text
	private final int maxNumChunks;

	private final boolean keepSeparator;

	public ChineseTokenTextSplitter() {
		this(DEFAULT_CHUNK_SIZE, MIN_CHUNK_SIZE_CHARS, MIN_CHUNK_LENGTH_TO_EMBED, MAX_NUM_CHUNKS, KEEP_SEPARATOR);
	}

	public ChineseTokenTextSplitter(boolean keepSeparator) {
		this(DEFAULT_CHUNK_SIZE, MIN_CHUNK_SIZE_CHARS, MIN_CHUNK_LENGTH_TO_EMBED, MAX_NUM_CHUNKS, keepSeparator);
	}

	public ChineseTokenTextSplitter(int chunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks,
			boolean keepSeparator) {
		this.chunkSize = chunkSize;
		this.minChunkSizeChars = minChunkSizeChars;
		this.minChunkLengthToEmbed = minChunkLengthToEmbed;
		this.maxNumChunks = maxNumChunks;
		this.keepSeparator = keepSeparator;
	}

	public static Builder builder() {
		return new Builder();
	}

	@Override
	protected List<String> splitText(String text) {
		return doSplit(text, this.chunkSize);
	}

	protected List<String> doSplit(String text, int chunkSize) {
		if (text == null || text.trim().isEmpty()) {
			return new ArrayList<>();
		}

		List<Integer> tokens = getEncodedTokens(text);
		List<String> chunks = new ArrayList<>();
		int num_chunks = 0;
		// maxNumChunks多能分多少个块, 超过了就不管了
		while (!tokens.isEmpty() && num_chunks < this.maxNumChunks) {
			// 按照chunkSize进行分隔
			List<Integer> chunk = tokens.subList(0, Math.min(chunkSize, tokens.size()));
			String chunkText = decodeTokens(chunk);

			// Skip the chunk if it is empty or whitespace
			if (chunkText.trim().isEmpty()) {
				tokens = tokens.subList(chunk.size(), tokens.size());
				continue;
			}

			// Find the last period or punctuation mark in the chunk
			int lastPunctuation =
					Math.max(chunkText.lastIndexOf('.'),
					Math.max(chunkText.lastIndexOf('?'),
					Math.max(chunkText.lastIndexOf('!'),
					Math.max(chunkText.lastIndexOf('\n'),
					Math.max(chunkText.lastIndexOf('。'),
					Math.max(chunkText.lastIndexOf('?'),
					chunkText.lastIndexOf('!')
					))))));

			// 按照句子截取之后长度 > minChunkSizeChars
			if (lastPunctuation != -1 && lastPunctuation > this.minChunkSizeChars) {
				// 保留按照句子截取之后的内容
				chunkText = chunkText.substring(0, lastPunctuation + 1);
			}
			// 按照句子截取之后长度 < minChunkSizeChars 保留原块


			// keepSeparator=true 替换/r/n   =false不管
			String chunkTextToAppend = (this.keepSeparator) ? chunkText.trim()
					: chunkText.replace(System.lineSeparator(), " ").trim();

			// 替换/r/n之后的内容是不是<this.minChunkLengthToEmbed 忽略
			if (chunkTextToAppend.length() > this.minChunkLengthToEmbed) {
				chunks.add(chunkTextToAppend);
			}

			// Remove the tokens corresponding to the chunk text from the remaining tokens
			tokens = tokens.subList(getEncodedTokens(chunkText).size(), tokens.size());

			num_chunks++;
		}

		// Handle the remaining tokens
		if (!tokens.isEmpty()) {
			String remaining_text = decodeTokens(tokens).replace(System.lineSeparator(), " ").trim();
			if (remaining_text.length() > this.minChunkLengthToEmbed) {
				chunks.add(remaining_text);
			}
		}

		return chunks;
	}

	private List<Integer> getEncodedTokens(String text) {
		Assert.notNull(text, "Text must not be null");
		return this.encoding.encode(text).boxed();
	}

	private String decodeTokens(List<Integer> tokens) {
		Assert.notNull(tokens, "Tokens must not be null");
		var tokensIntArray = new IntArrayList(tokens.size());
		tokens.forEach(tokensIntArray::add);
		return this.encoding.decode(tokensIntArray);
	}

	public static final class Builder {

		private int chunkSize = DEFAULT_CHUNK_SIZE;

		private int minChunkSizeChars = MIN_CHUNK_SIZE_CHARS;

		private int minChunkLengthToEmbed = MIN_CHUNK_LENGTH_TO_EMBED;

		private int maxNumChunks = MAX_NUM_CHUNKS;

		private boolean keepSeparator = KEEP_SEPARATOR;

		private Builder() {
		}

		public Builder withChunkSize(int chunkSize) {
			this.chunkSize = chunkSize;
			return this;
		}

		public Builder withMinChunkSizeChars(int minChunkSizeChars) {
			this.minChunkSizeChars = minChunkSizeChars;
			return this;
		}

		public Builder withMinChunkLengthToEmbed(int minChunkLengthToEmbed) {
			this.minChunkLengthToEmbed = minChunkLengthToEmbed;
			return this;
		}

		public Builder withMaxNumChunks(int maxNumChunks) {
			this.maxNumChunks = maxNumChunks;
			return this;
		}

		public Builder withKeepSeparator(boolean keepSeparator) {
			this.keepSeparator = keepSeparator;
			return this;
		}

		public ChineseTokenTextSplitter build() {
			return new ChineseTokenTextSplitter(this.chunkSize, this.minChunkSizeChars, this.minChunkLengthToEmbed,
					this.maxNumChunks, this.keepSeparator);
		}

	}

}

testVectorStore向量数据库

演示向量数据库的核心功能:

文档存储:将文本转换为向量并索引

语义搜索:基于余弦相似度查找相关内容

实战示例:航班预订和取消政策的存储与查询

结果排序:按相似度得分返回最匹配的文档片段

阈值过滤:排除低相关性结果,提高答案质量

import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
import org.junit.jupiter.api.Test;
import org.springframework.ai.document.Document;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

import java.util.List;

/**
 * 向量存储测试类
 * 演示如何使用VectorStore进行文档的向量化存储和语义搜索
 */
@SpringBootTest
public class VectorStoreTest {

    /**
     * 测试向量存储和语义搜索功能
     * @param vectorStore 自动注入的向量存储实例
     *                   支持文档的向量化存储和相似度搜索
     */
    @Test
    public void testVectorStore(@Autowired VectorStore vectorStore) {
        // 创建第一个文档:关于航班预订的条款
        Document doc = Document.builder()
                .text("""
          预订航班:
          - 通过我们的网站或移动应用程序预订。
          - 预订时需要全额付款。
          - 确保个人信息(姓名、ID 等)的准确性,因为更正可能会产生 25 的费用。
          """)
                .build();
        
        // 创建第二个文档:关于取消预订的条款
        Document doc2 = Document.builder()
                .text("""
          取消预订:
          - 最晚在航班起飞前 48 小时取消。
          - 取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。
          - 退款将在 7 个工作日内处理。
          """)
                .build();

        // 将文档添加到向量存储中(会自动进行文本向量化)
        vectorStore.add(List.of(doc, doc2));

        // 构建搜索请求
        SearchRequest searchRequest = SearchRequest.builder()
                .query("退票")  // 搜索关键词
                .topK(2)       // 返回最相似的2个结果
                .similarityThreshold(0.5)  // 相似度阈值,过滤低分结果
                .build();

        // 执行相似度搜索
        List<Document> documents = vectorStore.similaritySearch(searchRequest);

        // 打印搜索结果
        for (Document document : documents) {
            System.out.println("匹配文档内容: \n" + document.getText());
            System.out.println("相似度得分: " + document.getScore());
            System.out.println("----------------------------------");
        }
    }
}

检索重排序

两阶段检索:先获取大量候选文档,再进行精细排序

专用重排序模型:使用DashScope的gte-rerank-v2模型优化结果

import com.alibaba.cloud.ai.advisor.RetrievalRerankAdvisor;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
import com.alibaba.cloud.ai.dashscope.rerank.DashScopeRerankModel;
import com.xushu.springai.rag.ELT.ChineseTokenTextSplitter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import java.util.List;

/**
 * 检索重排序(Rerank)功能测试类
 * 演示如何结合DashScope的嵌入模型、重排序模型和聊天模型实现高质量检索增强生成(RAG)
 */
@SpringBootTest
public class RerankTest {

    /**
     * 初始化测试数据
     * 1. 读取文本文件
     * 2. 使用中文分词器分割文档
     * 3. 将分割后的文档向量化并存储
     */
    @BeforeEach
    public void init(@Autowired VectorStore vectorStore,
                    @Value("classpath:rag/terms-of-service.txt") Resource resource) {
        // 读取文本文件(如服务条款文档)
        TextReader textReader = new TextReader(resource);
        textReader.getCustomMetadata().put("filename", resource.getFilename());
        List<Document> documents = textReader.read();

        // 使用自定义中文分词器分割文档(优化中文处理)
        ChineseTokenTextSplitter splitter = new ChineseTokenTextSplitter(
            80,   // 每个分块最大token数
            10,   // 分块重叠token数
            5,    // 最小分块token数
            10000,// 最大输入长度
            true  // 是否启用详细日志
        );
        List<Document> chunkedDocuments = splitter.apply(documents);

        // 向量化并存储文档(自动调用DashScope嵌入模型)
        vectorStore.add(chunkedDocuments);
    }

    /**
     * 测试检索重排序流程
     * 1. 通过向量存储检索相关文档
     * 2. 使用DashScope重排序模型优化结果
     * 3. 生成最终回答
     */
    @Test
    public void testRerank(@Autowired VectorStore vectorStore,
                         @Autowired DashScopeRerankModel dashScopeRerankModel,
                         @Autowired DashScopeChatModel dashScopeChatModel) {

        // 构建ChatClient(使用DashScope聊天模型)
        ChatClient chatClient = ChatClient.builder(dashScopeChatModel).build();

        // 创建检索重排序顾问(核心组件)
        RetrievalRerankAdvisor retrievalRerankAdvisor = new RetrievalRerankAdvisor(
            vectorStore, 
            dashScopeRerankModel,  // 使用gte-rerank-v2模型
            SearchRequest.builder()
                .topK(200)  // 初步检索200个候选文档
                .build()
        );

        // 执行RAG流程(带重排序优化)
        String content = chatClient.prompt()
                .user("退费费用?")  // 用户查询
                .advisors(retrievalRerankAdvisor)  // 注入重排序逻辑
                .call()
                .content();

        System.out.println("最终回答: " + content);
    }

    /**
     * 测试配置类
     * 初始化基于Ollama的向量存储(可替换为DashScopeEmbeddingModel)
     */
    @TestConfiguration
    static class TestConfig {
        @Bean
        public VectorStore vectorStore(OllamaEmbeddingModel embeddingModel) {
            return SimpleVectorStore.builder(embeddingModel).build();
        }
    }
}

基础RAG流程和高级RAG特性

基础检索增强:简单直接的问答流程

查询重写:优化用户问题的表述方式

多语言支持:通过翻译增强跨语言检索能力

元数据过滤:基于文档属性精确定位信息

import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.preretrieval.query.translation.TranslationQueryTransformer;
import org.springframework.ai.rag.preretrieval.query.transformation.RewriteQueryTransformer;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import java.util.List;

/**
 * Spring AI RAG功能集成测试类
 * 演示如何结合DashScope大模型和向量存储实现检索增强生成
 * 核心功能包括:基础检索增强、查询重写、多语言翻译增强等
 */
@SpringBootTest
public class ChatClientRagTest {

    private ChatClient chatClient;

    /**
     * 初始化测试数据
     * 向向量存储中添加航班预订和取消政策的文档
     */
    @BeforeEach
    public void init(@Autowired DashScopeChatModel chatModel, 
                    @Autowired VectorStore vectorStore) {
        // 构建测试文档
        Document doc = Document.builder()
                .text("""
                        预订航班:
                        - 通过我们的网站或移动应用程序预订。
                        - 预订时需要全额付款。
                        - 确保个人信息(姓名、ID 等)的准确性,因为更正可能会产生25的费用。
                        """)
                .build();
        Document doc2 = Document.builder()
                .text("""
                        取消预订:
                        - 最晚在航班起飞前48小时取消。
                        - 取消费用:经济舱75美元,豪华经济舱50美元,商务舱25美元。
                        - 退款将在7个工作日内处理。
                        """)
                .build();

        // 将文档向量化并存储
        vectorStore.add(List.of(doc, doc2));
    }

    /**
     * 基础RAG测试
     * 使用QuestionAnswerAdvisor实现简单的检索增强
     */
    @Test
    public void testRag(@Autowired DashScopeChatModel dashScopeChatModel,
                       @Autowired VectorStore vectorStore) {
        chatClient = ChatClient.builder(dashScopeChatModel)
                .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) // 添加日志记录
                .build();

        String content = chatClient.prompt()
                .user("退票需要多少费用?")
                .advisors(
                        // 组合使用日志顾问和检索增强顾问
                        SimpleLoggerAdvisor.builder().build(),
                        QuestionAnswerAdvisor.builder(vectorStore)
                                .searchRequest(SearchRequest.builder()
                                        .topK(5) // 返回最相关的5个文档片段
                                        .similarityThreshold(0.6) // 相似度阈值0.6
                                        .build())
                                .build()
                )
                .call()
                .content();

        System.out.println("RAG回答结果: " + content);
    }

    /**
     * 带过滤条件的RAG测试
     * 演示如何通过元数据过滤检索结果
     */
    @Test
    public void testRag2(@Autowired DashScopeChatModel dashScopeChatModel,
                        @Autowired VectorStore vectorStore) {
        chatClient = ChatClient.builder(dashScopeChatModel)
                .defaultAdvisors(SimpleLoggerAdvisor.builder().build())
                .build();

        String content = chatClient.prompt()
                .user("退票需要多少费用?")
                .advisors(
                        QuestionAnswerAdvisor.builder(vectorStore)
                                .searchRequest(SearchRequest.builder()
                                        .topK(5)
                                        .similarityThreshold(0.1) // 更宽松的相似度阈值
                                        // 可添加元数据过滤:.filterExpression("category == 'cancellation'")
                                        .build())
                                .build()
                )
                .call()
                .content();

        System.out.println("带过滤的RAG回答: " + content);
    }

    /**
     * 高级RAG流程测试
     * 使用RetrievalAugmentationAdvisor实现多阶段增强
     */
    @Test
    public void testRag3(@Autowired VectorStore vectorStore,
                         @Autowired DashScopeChatModel dashScopeChatModel) {
        chatClient = ChatClient.builder(dashScopeChatModel)
                .defaultAdvisors(SimpleLoggerAdvisor.builder().build())
                .build();

        // 构建完整的检索增强流程
        Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()
                        .similarityThreshold(0.0) // 接受所有相似度结果
                        .vectorStore(vectorStore)
                        .build())
                // 上下文增强配置
                .queryAugmenter(ContextualQueryAugmenter.builder()
                        .allowEmptyContext(false) // 禁止无上下文回答
                        .emptyContextPromptTemplate(new PromptTemplate("未找到相关知识,请重新提问"))
                        .build())
                // 查询重写转换器(去除情绪化内容)
                .queryTransformers(RewriteQueryTransformer.builder()
                        .chatClientBuilder(ChatClient.builder(dashScopeChatModel))
                        .targetSearchSystem("航空票务助手")
                        .build())
                // 查询翻译转换器(支持多语言)
                .queryTransformers(TranslationQueryTransformer.builder()
                        .chatClientBuilder(ChatClient.builder(dashScopeChatModel))
                        .targetLanguage("english")
                        .build())
                // 文档后处理器(可添加日志或过滤)
                .documentPostProcessors((query, documents) -> {
                    System.out.println("优化后查询: " + query.text());
                    System.out.println("检索到文档数: " + documents.size());
                    return documents;
                })
                .build();

        // 测试包含情绪化表达的查询
        String answer = chatClient.prompt()
                .advisors(retrievalAugmentationAdvisor)
                .user("我今天心情不好,不想去玩了,能不能告诉我退票需要多少钱?")
                .call()
                .content();

        System.out.println("高级RAG回答: " + answer);
    }

    /**
     * 测试配置类
     * 初始化内存型向量存储
     */
    @TestConfiguration
    static class TestConfig {
        @Bean
        public VectorStore vectorStore(DashScopeEmbeddingModel embeddingModel) {
            return SimpleVectorStore.builder(embeddingModel).build();
        }
    }
}

    RAG效果评估

    RetrievalAugmentationAdvisor:负责检索与用户查询相关的文档

    VectorStoreDocumentRetriever:基于向量数据库实现语义搜索

    RelevancyEvaluator:评估生成回答与问题和上下文的关联程度

    import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
    import org.junit.jupiter.api.Test;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.evaluation.RelevancyEvaluator;
    import org.springframework.ai.chat.model.ChatResponse;
    import org.springframework.ai.document.Document;
    import org.springframework.ai.evaluation.EvaluationRequest;
    import org.springframework.ai.evaluation.EvaluationResponse;
    import org.springframework.ai.ollama.OllamaEmbeddingModel;
    import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
    import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
    import org.springframework.ai.vectorstore.SimpleVectorStore;
    import org.springframework.ai.vectorstore.VectorStore;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.TestConfiguration;
    import org.springframework.context.annotation.Bean;
    
    import java.util.List;
    
    /**
     * RAG(检索增强生成)效果评估测试类
     * 演示如何评估AI回答与用户问题及检索内容的相关性
     */
    @SpringBootTest
    public class RagEvalTest {
    
        /**
         * RAG流程相关性评估测试
         * 完整流程:文档检索 -> 生成回答 -> 相关性评估
         */
        @Test
        public void testRag(@Autowired VectorStore vectorStore,
                           @Autowired DashScopeChatModel dashScopeChatModel) {
    
            // 构建测试知识库文档(航班预订相关条款)
            List<Document> documents = List.of(
                    new Document("""
                            1. 预订航班
                            - 通过我们的网站或移动应用程序预订。
                            - 预订时需要全额付款。
                            - 确保个人信息(姓名、ID 等)的准确性,因为更正可能会产生25的费用。
                            """),
                    new Document("""
                            2. 更改预订
                            - 允许在航班起飞前24小时更改。
                            - 通过在线更改或联系我们的支持人员。
                            - 改签费:经济舱50,豪华经济舱30,商务舱免费。
                            """),
                    new Document("""
                            3. 取消预订
                            - 最晚在航班起飞前48小时取消。
                            - 取消费用:经济舱75美元,豪华经济舱50美元,商务舱25美元。
                            - 退款将在7个工作日内处理。
                            """));
    
            // 将文档存入向量数据库
            vectorStore.add(documents);
    
            // 构建RAG增强顾问(负责检索相关文档)
            RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = 
                RetrievalAugmentationAdvisor.builder()
                    .documentRetriever(VectorStoreDocumentRetriever.builder()
                            .vectorStore(vectorStore)
                            .build())
                    .build();
    
            // 测试查询(与文档内容无关的问题)
            String query = "我叫什么名字";
            
            // 执行RAG流程
            ChatResponse chatResponse = ChatClient.builder(dashScopeChatModel)
                    .build()
                    .prompt(query)
                    .advisors(retrievalAugmentationAdvisor)
                    .call()
                    .chatResponse();
    
            /**
             * 构建评估请求参数:
             * 1. 原始用户问题
             * 2. RAG流程检索到的上下文文档
             * 3. AI生成的最终回答
             */
            EvaluationRequest evaluationRequest = new EvaluationRequest(
                    query,
                    chatResponse.getMetadata().get(RetrievalAugmentationAdvisor.DOCUMENT_CONTEXT),
                    chatResponse.getResult().getOutput().getText()
            );
    
            // 使用DashScope模型作为评估器
            RelevancyEvaluator evaluator = new RelevancyEvaluator(
                ChatClient.builder(dashScopeChatModel)
            );
            
            // 执行相关性评估
            EvaluationResponse evaluationResponse = evaluator.evaluate(evaluationRequest);
            
            // 输出评估结果和AI回答
            System.out.println("=== 评估结果 ===");
            System.out.println(evaluationResponse);
            System.out.println("\n=== AI回答 ===");
            System.out.println(chatResponse.getResult().getOutput().getText());
        }
    
        /**
         * 测试配置类
         * 初始化基于Ollama的向量存储
         */
        @TestConfiguration
        static class TestConfig {
            @Bean
            public VectorStore vectorStore(OllamaEmbeddingModel embeddingModel) {
                return SimpleVectorStore.builder(embeddingModel).build();
            }
        }
    }

    AI模型评估

    AI模型评估测试类专注于对AI回答进行事实检查和相关性验证,确保回答的准确性和适用性

    事实检查(Fact Checking)

    将AI回答与参考文档进行比对,验证事实一致性

    FactCheckingEvaluator使用DashScope模型评估回答的准确性

    相关性评估(Relevancy)

    原理:评估回答与上下文/问题的语义相关性

    实现:RelevancyEvaluator分析声明与上下文的逻辑关联

    import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.evaluation.FactCheckingEvaluator;
    import org.springframework.ai.chat.evaluation.RelevancyEvaluator;
    import org.springframework.ai.document.Document;
    import org.springframework.ai.evaluation.EvaluationRequest;
    import org.springframework.ai.evaluation.EvaluationResponse;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.Collections;
    import java.util.List;
    
    /**
     * AI模型评估测试类
     * 包含事实检查(Fact Checking)和相关性(Relevancy)评估测试
     */
    @SpringBootTest
    public class FactCheckingTest {
    
        /**
         * 事实检查评估测试
         * 验证AI回答是否与提供的参考文档一致
         * @param chatModel 自动注入的DashScope聊天模型实例
         */
        @Test
        void testFactChecking(@Autowired DashScopeChatModel chatModel) {
            // 创建事实检查评估器,使用DashScope模型作为评估器
            var factCheckingEvaluator = new FactCheckingEvaluator(ChatClient.builder(chatModel));
    
            // 构建参考文档(事实依据)
            Document doc = Document.builder()
                    .text("""
                            取消预订:
                            - 最晚在航班起飞前 48 小时取消。
                            - 取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。
                            - 退款将在 7 个工作日内处理。
                            """)
                    .build();
    
            // AI生成的回答(待验证)
            String response = "经济舱取消费用75 美元";
    
            // 创建评估请求,包含参考文档和AI回答
            EvaluationRequest evaluationRequest = new EvaluationRequest(List.of(doc), response);
    
            // 执行事实检查评估
            EvaluationResponse evaluationResponse = factCheckingEvaluator.evaluate(evaluationRequest);
    
            // 输出评估结果(包含通过/失败和评估详情)
            System.out.println("事实检查评估结果: " + evaluationResponse);
        }
    
        /**
         * 相关性评估测试
         * 验证AI回答是否与用户查询相关
         * @param chatModel 自动注入的DashScope聊天模型实例
         */
        @Test
        void testRelevancyEvaluator(@Autowired DashScopeChatModel chatModel) {
            // 创建相关性评估器,使用DashScope模型作为评估器
            var evaluator = new RelevancyEvaluator(ChatClient.builder(chatModel));
    
            // 测试数据
            String context = "地球是距离太阳的第三颗行星,也是已知唯一孕育生命的天文物体。";
            String claim = "地球是距离太阳的第四颗行星,也是已知唯一孕育生命的天文物体。";
    
            // 创建评估请求,包含上下文和待评估声明
            EvaluationRequest evaluationRequest = new EvaluationRequest(context, Collections.emptyList(), claim);
    
            // 执行相关性评估
            EvaluationResponse evaluationResponse = evaluator.evaluate(evaluationRequest);
    
            // 输出评估结果(包含相关度评分和评估详情)
            System.out.println("相关性评估结果: " + evaluationResponse);
        }
    }

    上一篇:【Spring AI快速上手 (三)】Tool实现业务系统对接-CSDN博客

    下一篇:【Spring AI快速上手 (五)】Agent复杂任务智能体初探-CSDN博客

    实战示例:【Spring AI实战】ChatPDF实现RAG知识库_chatpdf 关键技术及实现-CSDN博客

    该文章展示了从文档处理到智能问答的完整技术栈,特别针对中文场景进行了优化,同时支持本地和云端AI服务的灵活组合,为企业级知识库应用提供了可靠参考实现。

    有任何问题或建议欢迎评论区留言讨论! 


    网站公告

    今日签到

    点亮在社区的每一天
    去签到