news 2026/6/16 12:38:07

Spring AI RAG实战:Java企业级智能问答系统搭建指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI RAG实战:Java企业级智能问答系统搭建指南

1. 项目概述:这不是一个“玩具Demo”,而是一套可直接进生产环境的RAG问答系统

2026年春天,Spring AI 已经不是概念验证阶段的实验框架,而是Java生态中真正扛得起高并发、稳得住知识精度、接得上企业级运维体系的RAG基础设施。我从去年底开始在三个不同行业的客户现场落地 Spring AI + RAG 方案——电商客服知识库、制造业设备维修手册问答、金融合规政策检索系统。所有项目都跑在 Spring Boot 3.5.x + JDK 17 的标准生产栈上,没有用任何“胶水层”或临时脚手架。今天这篇内容,就是把我们踩过坑、调过参、压过测、上线后稳定运行超180天的真实项目经验,原样拆解给你看。核心关键词就四个:Spring AI、RAG、知识库、智能问答系统——但请注意,这里说的“知识库”不是上传个PDF点几下就完事的SaaS界面,而是你能在自己服务器上完全掌控文档解析逻辑、切分策略、向量入库流程、检索阈值、提示词工程、结果溯源机制的完整闭环。它解决的也不是“能不能问出答案”,而是“答案从哪来、为什么是这个答案、有没有可能错、错了怎么快速定位”。适合谁?如果你是Java后端工程师,正在评估是否要把RAG集成进现有客服系统;如果你是技术负责人,需要给老板讲清楚“为什么选Spring AI而不是LangChain+Python微服务”;如果你是刚学完Spring Boot想动手做点真东西的开发者,这篇内容会告诉你:哪些配置必须写死、哪些参数不能信默认值、哪些日志要看、哪些测试用例不跑等于没上线。它不教你怎么安装IDEA,但会告诉你TokenTextSplitterchunkSize=600在电商条款场景下为什么比512更稳,以及similarityThreshold=0.07这个数字是怎么从Milvus的cosine距离分布直方图里抠出来的。

2. 整体架构设计与技术选型逻辑:为什么是这套组合,而不是别的

2.1 不是“能跑就行”,而是每一环都承担明确职责

很多初学者一上来就猛堆组件:LangChain+LlamaIndex+Chroma+FastAPI,结果本地跑通了,一上K8s就OOM,查日志发现90%时间耗在PDF解析线程阻塞上。我们的架构设计原则就一条:让每个模块只干一件它最擅长的事,且这件事必须可监控、可替换、可降级。整个系统分四层:

  • 接入层:Spring Web REST Controller,只负责HTTP协议转换、参数校验、基础限流(用@RateLimiter注解),不做任何业务逻辑;
  • 编排层:Spring AI 的ChatClient+Advisor机制,这是整套方案的灵魂。它把“检索”和“生成”彻底解耦,不像传统RAG那样把retriever.retrieve()硬塞进prompt模板里。QuestionAnswerAdvisor专注做“条款级精准匹配”,RetrievalAugmentationAdvisor专注做“上下文感知的语义增强”,两者可以独立开关、独立压测、独立告警;
  • 向量层:Milvus 2.4.0,不是因为它是“国产之光”,而是因为它在topK=4similarityThreshold=0.07这种低相似度阈值下的召回稳定性,远超FAISS(内存暴涨)和Qdrant(冷启动慢)。我们做过对比测试:同样10万条电商条款文本,Milvus在P99检索延迟<80ms,FAISS在批量向量化时GC停顿达1.2秒,Qdrant首次查询要预热3秒;
  • 知识层TikaDocumentReader+PdfDocumentReader双引擎。Tika处理DOC/DOCX/XLSX/PPTX,PdfDocumentReader处理PDF。关键点在于:我们禁用了Tika的自动OCR(太耗CPU),对扫描版PDF强制走spring-ai-pdf-document-readerPdfBox后端,并在application.properties里加了spring.ai.pdf.document.reader.pdfbox.parse.strategy=STANDARD——这个配置项官网文档根本没提,但不加的话,带表格的PDF解析出来全是乱码。

提示:不要迷信“向量数据库选型排行榜”。Milvus在小规模(<50万向量)场景下,initialize-schema=true会自动建collection,但线上环境必须关掉,改用手动SQL建表+索引优化。我们在线上集群里,对guide_exam_storecollection执行了CREATE INDEX ON guide_exam_store (vector) USING IVF_FLAT WITH (nlist = 1024),把10万向量的topK=4查询P95从120ms压到45ms。

2.2 Spring AI 1.0.0-SNAPSHOT:为什么必须用快照版,而不是Maven中央仓的1.0.0.RELEASE

这是最容易被忽略的致命细节。2026年1月发布的Spring AI 1.0.0.RELEASE,其spring-ai-rag模块里的RetrievalAugmentationAdvisor存在一个硬编码bug:当ContextualQueryAugmenterallowEmptyContext=true时,如果检索返回空列表,它会抛NullPointerException而非优雅返回空响应。这个bug在快照版里已修复,但官方没发补丁包。我们验证过:用RELEASE版跑测试4,question=怎么办理信用卡?会直接500报错,而快照版能正确走fallback逻辑,返回预设话术。所以pom.xml里必须这样写:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-rag</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency>

同时在settings.xml里配好Spring Snapshot仓库:

<repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository>

注意:快照版不支持mvn clean install -DskipTests跳过测试,因为它的集成测试依赖真实Milvus实例。我们CI流水线里,专门起了一个Docker Compose服务,包含Milvus standalone和Zhipu AI mock server,确保每次构建都过全链路测试。

2.3 大模型选型:为什么是智普AI GLM-4-Flash,而不是OpenAI或千问

选模型不是看谁参数多,而是看谁在“规则类问答”场景下幻觉率最低、token成本最可控、中文长文本理解最稳。我们用同一组200个电商问题(含歧义句、省略句、口语化表达)做了AB测试:

模型幻觉率平均响应TokenP95延迟知识库引用准确率
GLM-4-Flash2.3%1871.2s98.6%
Qwen2-72B-Instruct5.7%3212.8s94.1%
GPT-4-turbo1.8%2953.5s97.2%

GPT-4虽然幻觉率最低,但延迟和成本不可接受;Qwen2在“偏远地区包邮”这类问题上,会把“新疆”错误泛化成“西藏”“青海”,属于地理实体识别偏差;GLM-4-Flash在保持低幻觉的同时,对中文条款的标点、括号、序号理解极准,比如能正确区分“(一)通用退换货规则”和“1. 7天无理由退换”,这对后续做条款溯源至关重要。更重要的是,智普AI的embedding-2模型,在电商文本上的向量聚类效果最好——我们用t-SNE可视化10万条条款向量,GLM-4-Flash的embedding在“退换货”“物流”“促销”三个簇上的分离度,比其他模型高37%。

3. 核心细节解析:从文档解析到向量入库,每一步都是经验之谈

3.1 文档解析:别让PDF毁了你的RAG系统

PDF解析是RAG项目失败的第一大雷区。我们遇到过三种典型故障:

  • 扫描版PDF:Tika直接返回空字符串,PdfDocumentReader也报IOException: Cannot read a null stream。解决方案:在KnowledgeBaseConfig里加预检逻辑:

    private boolean isScannedPdf(Resource resource) { try (InputStream is = resource.getInputStream()) { PDDocument doc = PDDocument.load(is); for (PDPage page : doc.getPages()) { if (page.getResources().getXObjectNames().stream() .anyMatch(name -> name.startsWith("Im"))) { return true; // 含图片对象,大概率是扫描版 } } } catch (Exception e) { // 解析失败也按扫描版处理 return true; } return false; }

    对扫描版,走OCR流程(我们用Tesseract 5.3,不是PaddleOCR,因为后者Java调用太重);

  • 带密码PDFPdfDocumentReader默认不处理密码,会静默失败。必须在application.properties里加:

    spring.ai.pdf.document.reader.pdfbox.password=your_password

    且密码必须是明文,Spring AI不支持密钥管理;

  • 表格错乱PDF:电商条款常有“退换货时效对比表”,Tika解析后变成一堆\n\n\n。解决方案:不用TikaDocumentReader,改用PdfBoxDocumentReader并开启表格提取:

    PdfBoxDocumentReader reader = new PdfBoxDocumentReader(resource); reader.setExtractTables(true); // 关键! reader.setTableExtractionStrategy(new SimpleTableExtractionStrategy());

实操心得:所有PDF文档在放入src/main/resources前,必须用pdfinfo命令检查PagesEncryptedTagged字段。我们CI流水线里加了Shell脚本,对每个PDF执行pdfinfo $file | grep -E "(Pages|Encrypted|Tagged)",不满足条件的直接打回。

3.2 文本切分:600 Token不是玄学,是电商条款的物理长度

TokenTextSplitterchunkSize设多少?网上教程全说“512”或“1024”,但我们实测发现,电商条款有强结构特征:每条规则平均长度320~680字符,含标题、编号、分号、括号。设chunkSize=512会导致大量规则被硬切在“7天无理由退换;美妆、个护类商品”这种半截位置,检索时召回片段缺失关键约束条件。设chunkSize=1024又会让单个chunk混入多条无关规则,增大噪声。最终我们用真实数据统计:取1000条电商条款,计算每条的tokenizer.encode().length,得到分布峰值在587,所以定chunkSize=600

但光设大小不够,还得保结构。withKeepSeparator(true)是必须的,否则(一)通用退换货规则和下面的1. 7天无理由退换会被切开。更关键的是withMinChunkSizeChars(200)——我们发现,小于200字符的chunk,92%是页眉页脚或孤立标点,必须过滤。代码里这样写:

TokenTextSplitter splitter = TokenTextSplitter.builder() .withChunkSize(600) .withMinChunkSizeChars(200) .withKeepSeparator(true) .withOverlap(0) // 电商条款不重叠,避免重复计费 .build();

注意:withOverlap(0)不是偷懒。RAG里重叠切分(overlap)是为了缓解边界丢失,但电商条款是原子化规则,overlap=50会导致同一条规则出现在两个chunk里,Milvus向量库里存两份,检索时topK=4可能召回3个重复片段,浪费算力。我们宁可牺牲一点边界鲁棒性,也要保证知识单元的唯一性。

3.3 向量入库:批量操作不是性能优化,而是避免OOM的生存法则

vectorStore.add(allSplitDocs)这行代码看着简单,但allSplitDocs如果超过5000条,JVM直接OOM。原因:Spring AI的MilvusVectorStore默认用InsertParam逐条插入,每条都要建List<List<Float>>向量矩阵,内存爆炸。解决方案:必须分批+异步。

我们封装了BatchVectorStore工具类:

public class BatchVectorStore { private final VectorStore vectorStore; private final int batchSize = 1000; // Milvus单次insert上限 public void addBatch(List<Document> documents) { List<List<Document>> batches = Lists.partition(documents, batchSize); batches.parallelStream().forEach(batch -> { try { vectorStore.add(batch); // Spring AI 1.0.0-SNAPSHOT已支持批量 log.info("Batch insert success: {} docs", batch.size()); } catch (Exception e) { log.error("Batch insert failed", e); throw new RuntimeException(e); } }); } }

并在KnowledgeBaseConfig里调用:

// 5. 批量向量入库(分批+异步) int total = allSplitDocs.size(); log.info("Start batch insert: {} documents", total); new BatchVectorStore(vectorStore).addBatch(allSplitDocs); log.info("Batch insert completed");

实操心得:Milvus的insert操作不是原子的,一批1000条里某条失败,整批回滚。所以batchSize不能设太大。我们压测过:batchSize=2000时失败率12%,batchSize=1000时失败率0.3%,batchSize=500时失败率0.01%但吞吐下降40%。1000是性价比拐点。

4. RAG核心实现:Advisor机制如何让“检索”和“生成”各司其职

4.1 QuestionAnswerAdvisor:精准条款查询的底层逻辑

QuestionAnswerAdvisor不是简单的“检索+拼接”,它内置了三重过滤:

  1. 语义过滤:基于similarityThreshold=0.07,这个值怎么来的?我们导出Milvus里所有条款向量,用Python计算任意两条规则间的余弦相似度,画直方图。发现“退换货”类规则内部相似度集中在0.05~0.12,跨类(如退换货vs物流)相似度<0.03。所以0.07是类内召回和跨类误召的平衡点;

  2. 结构过滤QuestionAnswerAdvisor会自动解析用户问题,提取实体。比如question=新疆地区订单多少金额包邮?,它会识别出[地域:新疆, 业务:包邮, 属性:金额],然后只检索含“新疆”和“包邮”的chunk,跳过“物流服务标准”里关于“江浙沪”的描述;

  3. 溯源过滤QuestionAnswerAdvisor强制要求每个回答末尾带信息来源:[文档名称 - 相关条款类别]。这个不是前端拼的,是ChatClientdefaultSystem提示词里硬编码的规则,大模型必须遵守,否则重试。我们测试过,GLM-4-Flash在该提示下溯源准确率99.2%,Qwen2只有87.6%。

配置代码里这个细节很重要:

@Bean public QuestionAnswerAdvisor questionAnswerAdvisor() { return QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(SearchRequest.builder() .similarityThreshold(0.07) .topK(4) .build()) .documentContentKey("content") // 必须指定,否则取不到原文 .build(); }

documentContentKey="content"是关键。Spring AI默认用Document.getMetadata().get("content"),但TikaDocumentReader存的是Document.getContent(),不设这个key会拿空字符串。

4.2 RetrievalAugmentationAdvisor:复杂场景增强的“上下文手术刀”

RetrievalAugmentationAdvisor是处理“双11买的口红拆封了能退吗?我是VIP用户?”这种问题的核心。它分三步:

  • 第一步:原始检索:用VectorStoreDocumentRetrievertopK=4,但similarityThreshold放宽到0.05,召回更多潜在相关片段;
  • 第二步:查询增强ContextualQueryAugmenter拿到这4个片段,分析它们的共性关键词(如“口红”“拆封”“VIP”“退换货”),生成新查询"美妆类商品拆封后VIP用户退换货政策",再检一次;
  • 第三步:结果融合:把两次检索结果去重合并,按相关性重排序,喂给大模型。

这个过程在RAGConfig里体现为:

@Bean public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor() { VectorStoreDocumentRetriever retriever = VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.05) // 放宽阈值 .topK(4) .build(); ContextualQueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder() .allowEmptyContext(true) // 允许第一次检索为空,避免中断 .build(); return RetrievalAugmentationAdvisor.builder() .documentRetriever(retriever) .queryAugmenter(queryAugmenter) .build(); }

注意:allowEmptyContext=true不是摆设。当用户问“怎么办理信用卡?”,第一次检索必然为空,若设为falseContextualQueryAugmenter会直接抛异常。设为true后,它会跳过增强步骤,直接走fallback逻辑,返回预设话术。

4.3 ChatClient系统提示词:不是“写得漂亮”,而是“让模型不敢胡说”

defaultSystem提示词是RAG系统的“宪法”,必须满足三个条件:指令明确、边界清晰、容错可靠。我们最终版是:

.defaultSystem(""" 你是友好的电商客服顾问,仅基于提供的知识库内容回答用户问题,规则如下: 1. 回答需亲切、简洁、准确,符合电商客服沟通语气,避免生硬表述; 2. 涉及政策规则时,分点说明关键信息,让用户一目了然; 3. 回答末尾必须标注信息来源(格式:信息来源:[文档名称 - 相关条款类别]); 4. 若未查询到相关信息,回复"非常抱歉,暂未查询到该问题的相关规则,建议联系人工客服咨询~"; 5. 仅回应与电商购物(退换货、促销、物流等)相关的问题,无关问题直接回复上述统一话术。 """)

重点在第4、5条。我们测试过,不加第4条,模型在检索为空时会自由发挥,编造“请联系400-XXX”;不加第5条,它会对“怎么办理信用卡?”回答“信用卡办理需携带身份证到银行网点”,完全脱离知识库。这个提示词经过20轮A/B测试才定稿,每轮用50个边界case验证。

5. 实操全流程:从零搭建可运行的电商客服RAG系统

5.1 环境准备:JDK 17 + Spring Boot 3.5.3 + Milvus 2.4.0

Milvus安装(Docker方式,生产环境用K8s)

# docker-compose.yml version: '3.8' services: milvus-standalone: container_name: milvus-standalone image: milvusdb/milvus:v2.4.0 command: ["milvus", "run", "-t", "standalone"] environment: ETCD_ENDPOINTS: etcd:2379 MINIO_ADDRESS: minio:9000 ports: - "19530:19530" - "9091:9091" volumes: - ./milvus-data:/var/lib/milvus depends_on: - etcd - minio

启动后,访问http://localhost:9091确认Milvus UI正常。

Spring Boot项目创建

  • 用 start.spring.io 选Spring Boot 3.5.3、Java 17;
  • 依赖勾选:Spring Web、Lombok、Spring Boot DevTools;
  • 手动添加Spring AI依赖(见前文pom.xml)。

application.properties完整配置

# 应用基础配置 spring.application.name=Weiz-SpringAI-RAG-EcommerceCustomer server.port=8080 spring.profiles.active=dev # 智普 AI 配置 spring.ai.zhipuai.api-key=sk-xxx # 从智普AI控制台获取 spring.ai.zhipuai.base-url=https://open.bigmodel.cn/api/paas spring.ai.zhipuai.embedding.options.model=embedding-2 spring.ai.zhipuai.chat.options.model=GLM-4-Flash spring.ai.zhipuai.chat.options.temperature=0.1 # 降低随机性,规则类问答要确定性 # Milvus 向量数据库配置 spring.ai.vectorstore.milvus.client.host=localhost spring.ai.vectorstore.milvus.client.port=19530 spring.ai.vectorstore.milvus.client.token=root:Milvus spring.ai.vectorstore.milvus.database-name=default spring.ai.vectorstore.milvus.collection-name=guide_exam_store spring.ai.vectorstore.milvus.initialize-schema=false # 生产环境必须false spring.ai.vectorstore.milvus.embedding-dimension=1024 # PDF解析配置 spring.ai.pdf.document.reader.pdfbox.parse.strategy=STANDARD spring.ai.pdf.document.reader.pdfbox.password= # 如有密码填此处 # 日志配置(便于调试) logging.level.org.springframework.ai=INFO logging.level.com.example=DEBUG logging.level.io.milvus=INFO

5.2 知识库文档准备:电商知识库标准条款.docx

文档必须满足:

  • 格式:DOCX(非WPS私有格式);
  • 结构:用Word样式定义标题(标题1=章节名,标题2=条款名),不要用纯字体加粗;
  • 内容:每条规则独立成段,避免长段落。例如:

    一、退换货政策(核心条款)
    (一)通用退换货规则
    1. 7 天无理由退换:用户签收商品后 7 天内,商品完好(吊牌未拆、包装完整、无使用痕迹),支持无理由退换;美妆、个护类商品拆封后仍支持 7 天无理由退换,但需保留赠品及原包装配件。
    2. 质量问题退换:商品存在破损、功能故障、材质不符等质量问题,支持签收后 30 天内免费退换...

将此文件放入src/main/resources/电商知识库标准条款.docx

5.3 核心代码实现:四步走,缺一不可

Step 1:KnowledgeBaseConfig.java(知识库初始化)

@Component public class KnowledgeBaseConfig { private final VectorStore vectorStore; private final Logger log = LoggerFactory.getLogger(KnowledgeBaseConfig.class); public KnowledgeBaseConfig(VectorStore vectorStore) { this.vectorStore = vectorStore; } @PostConstruct public void initKnowledgeBase() { try { log.info("开始初始化电商客服知识库..."); List<String> docFiles = List.of("电商知识库标准条款.docx"); List<Document> allSplitDocs = new ArrayList<>(); for (String fileName : docFiles) { Resource resource = new ClassPathResource(fileName); // 检查是否为扫描版PDF(此处为DOCX,跳过) TikaDocumentReader reader = new TikaDocumentReader(resource); List<Document> rawDocs = reader.read(); log.info("已读取文档:{},原始段落数:{}", fileName, rawDocs.size()); // 切分策略 TokenTextSplitter splitter = TokenTextSplitter.builder() .withChunkSize(600) .withMinChunkSizeChars(200) .withKeepSeparator(true) .withOverlap(0) .build(); List<Document> splitDocs = splitter.apply(rawDocs); log.info("文档:{},切分后段落数:{}", fileName, splitDocs.size()); allSplitDocs.addAll(splitDocs); } // 批量入库 int total = allSplitDocs.size(); log.info("准备向量入库:{} 个文本片段", total); new BatchVectorStore(vectorStore).addBatch(allSplitDocs); log.info("知识库初始化完成,共导入 {} 个文本片段", total); } catch (Exception e) { log.error("知识库初始化失败", e); throw new RuntimeException("KnowledgeBase init failed", e); } } }

Step 2:RAGConfig.java(Advisor配置)

@Configuration public class RAGConfig { private final VectorStore vectorStore; public RAGConfig(VectorStore vectorStore) { this.vectorStore = vectorStore; } @Bean public QuestionAnswerAdvisor questionAnswerAdvisor() { return QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(SearchRequest.builder() .similarityThreshold(0.07) .topK(4) .build()) .documentContentKey("content") .build(); } @Bean public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor() { VectorStoreDocumentRetriever retriever = VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.05) .topK(4) .build(); ContextualQueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder() .allowEmptyContext(true) .build(); return RetrievalAugmentationAdvisor.builder() .documentRetriever(retriever) .queryAugmenter(queryAugmenter) .build(); } @Bean public ChatClient chatClient(ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem(""" 你是友好的电商客服顾问,仅基于提供的知识库内容回答用户问题,规则如下: 1. 回答需亲切、简洁、准确,符合电商客服沟通语气,避免生硬表述; 2. 涉及政策规则时,分点说明关键信息,让用户一目了然; 3. 回答末尾必须标注信息来源(格式:信息来源:[文档名称 - 相关条款类别]); 4. 若未查询到相关信息,回复"非常抱歉,暂未查询到该问题的相关规则,建议联系人工客服咨询~"; 5. 仅回应与电商购物(退换货、促销、物流等)相关的问题,无关问题直接回复上述统一话术。 """) .build(); } }

Step 3:CustomerServiceController.java(问答接口)

@RestController @RequestMapping("/ecommerce/service") public class CustomerServiceController { private final ChatClient chatClient; private final QuestionAnswerAdvisor questionAnswerAdvisor; private final RetrievalAugmentationAdvisor retrievalAugmentationAdvisor; public CustomerServiceController( ChatClient chatClient, QuestionAnswerAdvisor questionAnswerAdvisor, RetrievalAugmentationAdvisor retrievalAugmentationAdvisor) { this.chatClient = chatClient; this.questionAnswerAdvisor = questionAnswerAdvisor; this.retrievalAugmentationAdvisor = retrievalAugmentationAdvisor; } @GetMapping("/chat/precise") public Map<String, String> preciseChat(@RequestParam("question") String question) { String answer = chatClient.prompt() .user(question) .advisors(List.of(questionAnswerAdvisor)) .call() .content(); return Map.of( "question", question, "answer", answer, "mode", "precise(精准条款查询)" ); } @GetMapping("/chat/enhanced") public Map<String, String> enhancedChat(@RequestParam("question") String question) { String answer = chatClient.prompt() .user(question) .advisors(List.of(retrievalAugmentationAdvisor)) .call() .content(); return Map.of( "question", question, "answer", answer, "mode", "enhanced(复杂场景增强)" ); } }

Step 4:启动与验证

  • mvn spring-boot:run启动应用;
  • 观察日志,确认知识库初始化完成
  • 访问http://localhost:8080/ecommerce/service/chat/precise?question=新疆地区订单多少金额包邮?
  • 预期响应:
    { "question": "新疆地区订单多少金额包邮?", "answer": "新疆属于偏远地区,订单金额满199元可享受包邮服务,不满199元需收取20元运费哦~ 信息来源:[电商知识库标准条款文档模板 - 物流服务标准]", "mode": "precise(精准条款查询)" }

6. 常见问题与排查技巧实录:那些文档里不会写的坑

6.1 问题速查表

问题现象可能原因排查命令/方法解决方案
启动时报No qualifying bean of type 'VectorStore'spring-ai-starter-vector-store-milvus依赖未生效mvn dependency:tree | grep milvus检查pom.xmlspring-ai-starter-vector-store-milvus版本是否匹配Spring AI快照版
/chat/precise接口返回500,日志NullPointerExceptionQuestionAnswerAdvisordocumentContentKey未设RAGConfig里加.documentContentKey("content")见4.1节配置
返回答案里没有信息来源:[...]defaultSystem提示词未生效或模型忽略curl -X POST http://localhost:8080/actuator/health检查ChatClientBean是否被正确注入,用@Autowired(required = false)测试
Milvus查询延迟>1scollection未建索引或nlist参数不合理milvus_cli连上后执行describe collection guide_exam_store执行create index on guide_exam_store (vector) using IVF_FLAT with (nlist=1024)
中文乱码(如æ–°ç–†application.properties文件编码不是UTF-8file -i src/main/resources/application.properties用IDEA右下角转编码为UTF-8,或iconv -f GBK -t UTF-8 application.properties > tmp && mv tmp application.properties

6.2 独家避坑技巧

技巧1:用/actuator/health暴露Milvus连接状态
pom.xmlspring-boot-starter-actuatorapplication.properties加:

management.endpoints.web.exposure.include=health,info,metrics,prometheus management.endpoint.health.show-details=always

启动后访问http://localhost:8080/actuator/health,能看到milvus健康状态。这是线上巡检第一道防线。

技巧2:知识库更新不重启服务
@PostConstruct只在启动时执行。要支持热更新,加一个@RestController

@PostMapping("/knowledge/reload") public String reloadKnowledge(@RequestParam String fileName) { // 重新读取fileName,切分,addBatch return "Reloaded: " + fileName; }

配合前端上传按钮,实现运营人员自助更新。

技巧3:测试用例必须覆盖“坏问题”
除了正常问题,必须写JUnit测试:

@Test void testIrrelevantQuestion() { String question = "怎么办理信用卡?"; String answer = chatClient.prompt() .user(question) .advisors(List.of(questionAnswerAdvisor)) .call() .content(); assertTrue(answer.contains("非常抱歉,暂未查询到该问题的相关规则")); }

我们规定:RAG项目上线前,坏问题测试覆盖率必须100%。

7. 系统扩展与生产就绪:从Demo到企业级应用的最后一步

7.1 多格式文档支持:不只是PDF和DOCX

要支持Excel(价格表)、PPTX(培训材料)、TXT(日志规则),只需加依赖和Reader:

<!-- Excel支持 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-apache-poi-document-reader</artifactId> </dependency> <!-- PPTX支持 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-apache-poi-document-reader</artifactId> </dependency>

然后在KnowledgeBaseConfig里:

if (fileName.endsWith(".xlsx")) { XlsxDocumentReader reader = new XlsxDocumentReader(resource); rawDocs = reader.read(); } else if (fileName.endsWith(".pptx")) { PptxDocumentReader reader = new PptxDocumentReader(resource); rawDocs = reader.read(); }

7.2 权限控制:RBAC不是可选项,是必选项

电商知识库可能含敏感政策。用Spring Security加:

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/16 12:32:50

Topit:让你的Mac窗口永远置顶的终极解决方案

Topit&#xff1a;让你的Mac窗口永远置顶的终极解决方案 【免费下载链接】Topit Pin any window to the top of your screen / 在Mac上将你的任何窗口强制置顶 项目地址: https://gitcode.com/gh_mirrors/to/Topit 你是否曾在视频会议时频繁切换窗口查看文档&#xff1f…

作者头像 李华
网站建设 2026/6/16 12:32:49

计算机Java毕设实战-集成人脸识别实名认证的校园互动论坛平台研发与实践 SpringBoot 架构下带实名人脸核验的校园论坛系统设计【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/16 12:29:53

硬盘低级格式化工具深度解析:原理、风险与实战指南

1. 项目概述&#xff1a;硬盘低级格式化工具的本质与边界聊到硬盘低级格式化&#xff0c;很多朋友的第一反应可能是“这玩意儿能把坏硬盘修好”&#xff0c;或者“这是彻底删除数据的终极手段”。作为一个在IT运维和数据安全领域摸爬滚打了十多年的老手&#xff0c;我必须得说&…

作者头像 李华
网站建设 2026/6/16 12:28:55

大模型可解释性:为什么给出这个答案

大模型可解释性&#xff1a;为什么给出这个答案&#x1f4dd; 本章学习目标&#xff1a;通过本章学习&#xff0c;你将全面掌握"大模型可解释性&#xff1a;为什么给出这个答案"这一核心主题&#xff0c;建立系统性认知。一、引言&#xff1a;为什么这个话题如此重要…

作者头像 李华