<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>向量检索 on XEDCZQ的博客</title><link>https://xedczq.cn/tags/%E5%90%91%E9%87%8F%E6%A3%80%E7%B4%A2/</link><description>Recent content in 向量检索 on XEDCZQ的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Wed, 27 May 2026 15:46:26 +0800</lastBuildDate><atom:link href="https://xedczq.cn/tags/%E5%90%91%E9%87%8F%E6%A3%80%E7%B4%A2/index.xml" rel="self" type="application/rss+xml"/><item><title>MapAI：RAG 1.0 系统架构与痛点复盘</title><link>https://xedczq.cn/post/mapai_ragconstruction/</link><pubDate>Wed, 27 May 2026 15:46:26 +0800</pubDate><guid>https://xedczq.cn/post/mapai_ragconstruction/</guid><description>&lt;h1 id="mapairag-10-系统架构与痛点复盘"&gt;&lt;a href="#mapairag-10-%e7%b3%bb%e7%bb%9f%e6%9e%b6%e6%9e%84%e4%b8%8e%e7%97%9b%e7%82%b9%e5%a4%8d%e7%9b%98" class="header-anchor"&gt;&lt;/a&gt;MapAI：RAG 1.0 系统架构与痛点复盘
&lt;/h1&gt;&lt;p&gt;这篇笔记整理我在 &lt;code&gt;MapAI&lt;/code&gt; 早期版本里实现的一套 &lt;code&gt;RAG 1.0&lt;/code&gt; 架构。它已经具备了完整的上传、清洗、切片、向量化、检索和问答闭环，但从今天回头看，很多设计还是明显带着“先跑起来”的 1.0 痕迹。&lt;/p&gt;
&lt;p&gt;这份复盘的重点不是讲一个完美方案，而是把老版本真实的构造流程、运行方式和后续暴露出来的问题讲清楚，方便后面继续演进到更稳的 &lt;code&gt;RAG 2.0&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="rag-10-总览"&gt;&lt;a href="#rag-10-%e6%80%bb%e8%a7%88" class="header-anchor"&gt;&lt;/a&gt;RAG 1.0 总览
&lt;/h2&gt;&lt;p&gt;整个系统可以分成两条主链路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据入库链路：负责把用户上传的原始文件清洗、切片并写入向量库。&lt;/li&gt;
&lt;li&gt;检索推理链路：负责在用户提问时召回相关 Chunk，并交给大模型做最终回答。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对应到接口层，分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /api/rag/upload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/rag/chat&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="数据流管道"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e6%b5%81%e7%ae%a1%e9%81%93" class="header-anchor"&gt;&lt;/a&gt;数据流管道
&lt;/h2&gt;&lt;p&gt;当用户调用 &lt;code&gt;POST /api/rag/upload&lt;/code&gt; 上传文件时，系统会依次经过下面 5 步完成处理：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[文件上传]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 1. SHA-256 去重
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 2. Apache Tika 文本提取
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 3. 动态语义分片
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 4. DashScope 向量化
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 5. Redis 持久化
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="sha-256-去重"&gt;&lt;a href="#sha-256-%e5%8e%bb%e9%87%8d" class="header-anchor"&gt;&lt;/a&gt;SHA-256 去重
&lt;/h3&gt;&lt;p&gt;系统会先计算文件哈希值，在同一个知识库 &lt;code&gt;KB&lt;/code&gt; 内实现秒传与去重。&lt;/p&gt;
&lt;p&gt;这样做的目标很直接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;避免同一份文件被重复写入。&lt;/li&gt;
&lt;li&gt;节省 Redis 存储空间和向量化成本。&lt;/li&gt;
&lt;li&gt;降低重复 Chunk 对检索排序造成的噪声。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一步本质上是“知识库内去重”，而不是全局跨库去重，所以它能保证当前 KB 里的数据相对干净，但也意味着多个 KB 之间仍可能保留相同内容。&lt;/p&gt;
&lt;h3 id="文本提取"&gt;&lt;a href="#%e6%96%87%e6%9c%ac%e6%8f%90%e5%8f%96" class="header-anchor"&gt;&lt;/a&gt;文本提取
&lt;/h3&gt;&lt;p&gt;原始文件上传后，系统使用 &lt;code&gt;Apache Tika&lt;/code&gt; 做统一文本解析，把不同格式的文件都转成纯文本 &lt;code&gt;String&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;当时支持的主要格式包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PDF&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DOCX&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TXT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CSV&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XLSX&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSON&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个设计的好处是前面统一输入，后面的切片和 Embedding 都只面对字符串，不需要对每种文档格式分别写检索逻辑。&lt;/p&gt;
&lt;p&gt;代价也很明显：一旦进入纯文本阶段，很多原始结构信息就丢了，比如表格、标题层级、字段列关系和单元格边界。&lt;/p&gt;
&lt;h3 id="动态语义分片"&gt;&lt;a href="#%e5%8a%a8%e6%80%81%e8%af%ad%e4%b9%89%e5%88%86%e7%89%87" class="header-anchor"&gt;&lt;/a&gt;动态语义分片
&lt;/h3&gt;&lt;p&gt;RAG 1.0 的切片策略已经不是最原始的固定长度截断，但本质仍然属于“规则驱动的半语义切片”。&lt;/p&gt;
&lt;p&gt;核心参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;minSize = 180&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;targetSize = 700&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxSize = 1000&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;切分优先级：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;优先按双换行拆段落：&lt;code&gt;\n\s*\n+&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果段落仍然过长，再按结束标点拆句子：&lt;code&gt;。！？；;.!?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果还拆不开，就按 &lt;code&gt;maxSize&lt;/code&gt; 硬切&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;组装逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;累积文本达到 &lt;code&gt;targetSize&lt;/code&gt; 后，输出为一个独立 Chunk。&lt;/li&gt;
&lt;li&gt;如果尾部碎片小于 &lt;code&gt;minSize&lt;/code&gt;，则强行并到上一个 Chunk，尽量保证语义完整。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套方案在 1.0 阶段已经比“纯 500 字一刀切”强不少，因为它至少知道先看段落和句子边界，但它仍然不是严格意义上的语义切片。&lt;/p&gt;
&lt;h3 id="向量化与写入"&gt;&lt;a href="#%e5%90%91%e9%87%8f%e5%8c%96%e4%b8%8e%e5%86%99%e5%85%a5" class="header-anchor"&gt;&lt;/a&gt;向量化与写入
&lt;/h3&gt;&lt;p&gt;每个切好的文本块最终会被封装为统一的 &lt;code&gt;Document&lt;/code&gt; 结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kb_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chunk_index&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Embedding 模型使用的是阿里云 &lt;code&gt;DashScope&lt;/code&gt;，把文本转换成稠密向量后写入 Redis 向量索引：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rag_vector_index_v1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这一层本质是一次 &lt;code&gt;Vector Upsert&lt;/code&gt;，完成后系统就具备了按向量相似度检索 Chunk 的能力。&lt;/p&gt;
&lt;h3 id="redis-持久化设计"&gt;&lt;a href="#redis-%e6%8c%81%e4%b9%85%e5%8c%96%e8%ae%be%e8%ae%a1" class="header-anchor"&gt;&lt;/a&gt;Redis 持久化设计
&lt;/h3&gt;&lt;p&gt;除了向量索引本身，RAG 1.0 还在 Redis 中额外保留了一套辅助元数据，方便文件管理和关键词兜底。&lt;/p&gt;
&lt;p&gt;文件列表：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rag:kb:{kbName}:files
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里通常用 &lt;code&gt;Hash&lt;/code&gt; 结构，保存文件名和基础属性映射。&lt;/p&gt;
&lt;p&gt;原始全文：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rag:kb:{kbName}:file:{base64}:text
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里通常用 &lt;code&gt;String&lt;/code&gt; 结构，保留该文件的完整纯文本内容，用于后续兜底搜索。&lt;/p&gt;
&lt;p&gt;Chunk 索引：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rag:kb:{kbName}:file:{base64}:chunks
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里通常用 &lt;code&gt;Set&lt;/code&gt; 结构，保存该文件下所有 Chunk ID。&lt;/p&gt;
&lt;p&gt;这种设计的好处是简单、直接、落地快。缺点是索引和元数据结构开始变多以后，后期维护成本会上升，而且 Redis 同时承担“向量库 + 文件目录 + 原文仓库”三种角色，边界不够清晰。&lt;/p&gt;
&lt;h2 id="检索与推理管道"&gt;&lt;a href="#%e6%a3%80%e7%b4%a2%e4%b8%8e%e6%8e%a8%e7%90%86%e7%ae%a1%e9%81%93" class="header-anchor"&gt;&lt;/a&gt;检索与推理管道
&lt;/h2&gt;&lt;p&gt;当用户调用 &lt;code&gt;POST /api/rag/chat&lt;/code&gt; 进行对话时，请求会先经过检索层，再进入生成层。&lt;/p&gt;
&lt;p&gt;可以粗略拆成两段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阶段 A：检索与过滤，主要由 &lt;code&gt;KnowledgeBaseService&lt;/code&gt; 负责&lt;/li&gt;
&lt;li&gt;阶段 B：上下文组装与推理，主要由 &lt;code&gt;RagChatService&lt;/code&gt; 负责&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="阶段-a检索与过滤"&gt;&lt;a href="#%e9%98%b6%e6%ae%b5-a%e6%a3%80%e7%b4%a2%e4%b8%8e%e8%bf%87%e6%bb%a4" class="header-anchor"&gt;&lt;/a&gt;阶段 A：检索与过滤
&lt;/h2&gt;&lt;h3 id="过采样计算"&gt;&lt;a href="#%e8%bf%87%e9%87%87%e6%a0%b7%e8%ae%a1%e7%ae%97" class="header-anchor"&gt;&lt;/a&gt;过采样计算
&lt;/h3&gt;&lt;p&gt;为了降低多知识库混查时目标数据被稀释的风险，系统不会直接只取 &lt;code&gt;topK&lt;/code&gt; 条结果，而是先放大召回范围：&lt;/p&gt;
$$
\text{fetchK} = \text{clamp}(\text{topK} \times 30, 60, 200)
$$&lt;p&gt;也就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最少取 &lt;code&gt;60&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;最多取 &lt;code&gt;200&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;中间按 &lt;code&gt;topK * 30&lt;/code&gt; 放大&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个思路本身是对的，它相当于承认“第一次粗召回不够准”，所以先多捞一点数据回来再做过滤。&lt;/p&gt;
&lt;h3 id="多租户隔离过滤"&gt;&lt;a href="#%e5%a4%9a%e7%a7%9f%e6%88%b7%e9%9a%94%e7%a6%bb%e8%bf%87%e6%bb%a4" class="header-anchor"&gt;&lt;/a&gt;多租户隔离过滤
&lt;/h3&gt;&lt;p&gt;RAG 1.0 的多知识库隔离不是在向量搜索阶段完成的，而是在向量搜索之后做“后置过滤”。&lt;/p&gt;
&lt;p&gt;流程大致是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先从 Redis &lt;code&gt;Set&lt;/code&gt; 里取当前 KB 的合法 &lt;code&gt;allowedIds&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;再执行向量库 &lt;code&gt;similaritySearch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;最后对结果做双重保险过滤，并截断到 &lt;code&gt;topK&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;过滤逻辑可以写成：&lt;/p&gt;
$$
\text{Filter: } \text{id} \in \text{allowedIds} \lor \text{meta.kb\_name} == \text{kbName}
$$&lt;p&gt;最后再执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;limit(topK)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这一步保证了逻辑上的多租户隔离，但因为它是“检索后过滤”，所以当全局数据规模变大时，会出现明显的稀疏化问题。&lt;/p&gt;
&lt;h3 id="关键词兜底"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e8%af%8d%e5%85%9c%e5%ba%95" class="header-anchor"&gt;&lt;/a&gt;关键词兜底
&lt;/h3&gt;&lt;p&gt;如果向量检索结果为空，系统不会直接返回“查不到”，而是退化成原文扫描。&lt;/p&gt;
&lt;p&gt;具体做法是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 Redis 里的原始全文读取文本&lt;/li&gt;
&lt;li&gt;做大小写不敏感的子串匹配&lt;/li&gt;
&lt;li&gt;命中后返回相关内容作为兜底结果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一步的价值主要在于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;防止冷启动时 Embedding 质量不稳定&lt;/li&gt;
&lt;li&gt;防止专有名词、缩写或新词没有被向量很好表示&lt;/li&gt;
&lt;li&gt;至少让系统在“完全搜不到”的情况下还能给一个兜底结果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但它本质上还是非常弱的字符串匹配，能力上和真正的信息检索差得很远。&lt;/p&gt;
&lt;h2 id="阶段-b上下文组装与推理"&gt;&lt;a href="#%e9%98%b6%e6%ae%b5-b%e4%b8%8a%e4%b8%8b%e6%96%87%e7%bb%84%e8%a3%85%e4%b8%8e%e6%8e%a8%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;阶段 B：上下文组装与推理
&lt;/h2&gt;&lt;h3 id="上下文拼接"&gt;&lt;a href="#%e4%b8%8a%e4%b8%8b%e6%96%87%e6%8b%bc%e6%8e%a5" class="header-anchor"&gt;&lt;/a&gt;上下文拼接
&lt;/h3&gt;&lt;p&gt;在检索阶段拿到 &lt;code&gt;docs[]&lt;/code&gt; 后，系统会把这些召回文本拼接成 &lt;code&gt;context&lt;/code&gt;，再注入最终提示词。&lt;/p&gt;
&lt;p&gt;这一步是典型的 RAG prompt 组装逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检索层负责找到候选证据&lt;/li&gt;
&lt;li&gt;Prompt 层负责把证据组织给大模型&lt;/li&gt;
&lt;li&gt;大模型在此基础上完成回答&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="llm-裁判任务"&gt;&lt;a href="#llm-%e8%a3%81%e5%88%a4%e4%bb%bb%e5%8a%a1" class="header-anchor"&gt;&lt;/a&gt;LLM 裁判任务
&lt;/h3&gt;&lt;p&gt;RAG 1.0 的提示词里，大模型不只是回答问题，还被要求扮演一个“情报查询问答节点”。&lt;/p&gt;
&lt;p&gt;它需要同时承担三件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;做相关性校验&lt;/li&gt;
&lt;li&gt;基于已给出的知识库内容作答&lt;/li&gt;
&lt;li&gt;拦截幻觉和无依据扩写&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着同一次调用里，LLM 既是生成器，又承担了一部分检索判断职责。&lt;/p&gt;
&lt;h3 id="结构化输出"&gt;&lt;a href="#%e7%bb%93%e6%9e%84%e5%8c%96%e8%be%93%e5%87%ba" class="header-anchor"&gt;&lt;/a&gt;结构化输出
&lt;/h3&gt;&lt;p&gt;为了让后端更稳定地消费结果，系统会约束大模型输出固定 JSON，再由后端解析后返回给用户：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;related&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;reply&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;大模型基于知识库给出的回答...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;reason&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;判断相关或不相关的逻辑解释&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个设计在工程上很实用，因为后端不需要再从一大段自然语言中猜测模型到底是“拒答”还是“回答成功”。&lt;/p&gt;
&lt;h2 id="rag-10-的核心痛点"&gt;&lt;a href="#rag-10-%e7%9a%84%e6%a0%b8%e5%bf%83%e7%97%9b%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;RAG 1.0 的核心痛点
&lt;/h2&gt;&lt;p&gt;RAG 1.0 最大的问题不是“完全不能用”，而是它虽然把流程跑通了，但很多关键环节都还是 1.0 的折中方案。一旦文档规模、知识库数量或问题复杂度上去，问题就会集中暴露。&lt;/p&gt;
&lt;h3 id="痛点-1分片策略仍然机械化"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-1%e5%88%86%e7%89%87%e7%ad%96%e7%95%a5%e4%bb%8d%e7%84%b6%e6%9c%ba%e6%a2%b0%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;痛点 1：分片策略仍然机械化
&lt;/h3&gt;&lt;p&gt;现状：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虽然切片时已经考虑段落和标点&lt;/li&gt;
&lt;li&gt;但本质仍然是围绕 &lt;code&gt;700 / 1000&lt;/code&gt; 字目标做规则凑字数&lt;/li&gt;
&lt;li&gt;Chunk 之间没有 &lt;code&gt;Overlap&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;物理边界上的上下句容易被切断&lt;/li&gt;
&lt;li&gt;参数表、说明表、列表型内容可能被一分为二&lt;/li&gt;
&lt;li&gt;上下文连续性不够，影响后续召回和回答质量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后续方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;改成真正的 &lt;code&gt;Semantic Chunking&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;或至少引入 &lt;code&gt;10% ~ 15%&lt;/code&gt; 的滚动窗口重叠区&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="痛点-2全局向量搜索--后置过滤"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-2%e5%85%a8%e5%b1%80%e5%90%91%e9%87%8f%e6%90%9c%e7%b4%a2--%e5%90%8e%e7%bd%ae%e8%bf%87%e6%bb%a4" class="header-anchor"&gt;&lt;/a&gt;痛点 2：全局向量搜索 + 后置过滤
&lt;/h3&gt;&lt;p&gt;现状：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统先在全量索引里搜索&lt;/li&gt;
&lt;li&gt;再用当前 KB 的合法 ID 集合做后置过滤&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当总知识库规模变大时，&lt;code&gt;fetchK&lt;/code&gt; 即使放大到 &lt;code&gt;200&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;也可能先被其他知识库的高相似内容占满&lt;/li&gt;
&lt;li&gt;最后过滤完之后，当前 KB 的有效结果几乎没有，甚至直接搜空&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这本质上就是“目标知识被全局噪声稀释”。&lt;/p&gt;
&lt;p&gt;后续方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;改成 &lt;code&gt;Pre-Filtering&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在向量检索阶段就基于 &lt;code&gt;kb_name&lt;/code&gt; 或其他元数据做过滤&lt;/li&gt;
&lt;li&gt;把相似度计算范围直接收缩到当前知识库内部&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="痛点-3关键词兜底过弱"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-3%e5%85%b3%e9%94%ae%e8%af%8d%e5%85%9c%e5%ba%95%e8%bf%87%e5%bc%b1" class="header-anchor"&gt;&lt;/a&gt;痛点 3：关键词兜底过弱
&lt;/h3&gt;&lt;p&gt;现状：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;向量检索失败后，只能退化成大小写不敏感的原文子串匹配&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不能处理同义词&lt;/li&gt;
&lt;li&gt;不能处理缩写&lt;/li&gt;
&lt;li&gt;不能处理近义表达&lt;/li&gt;
&lt;li&gt;专有术语换一种说法后就直接未命中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文档里写的是 &lt;code&gt;DDG&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用户问的是“驱逐舰”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这类场景下，子串匹配基本没有任何容错能力。&lt;/p&gt;
&lt;p&gt;后续方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;引入 &lt;code&gt;BM25&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;或构建倒排索引 &lt;code&gt;Inverted Index&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;配合中文分词器，做真正的 &lt;code&gt;Hybrid Search&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="痛点-4缺少重排机制"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-4%e7%bc%ba%e5%b0%91%e9%87%8d%e6%8e%92%e6%9c%ba%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;痛点 4：缺少重排机制
&lt;/h3&gt;&lt;p&gt;现状：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;向量库返回结果后，只依据 Embedding 相似度粗排&lt;/li&gt;
&lt;li&gt;粗排结果直接塞给 LLM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前几个 Chunk 可能带有较多低相关噪声&lt;/li&gt;
&lt;li&gt;真正包含标准答案的 Chunk 可能被挤到后面&lt;/li&gt;
&lt;li&gt;一旦上下文窗口有限，LLM 很容易“迷失在中间”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后续方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在检索后增加 &lt;code&gt;Reranker&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;比如 &lt;code&gt;BGE-Reranker&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;对候选结果做二次精排&lt;/li&gt;
&lt;li&gt;再结合分数阈值做动态截断&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="痛点-5llm-既当运动员又当裁判"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-5llm-%e6%97%a2%e5%bd%93%e8%bf%90%e5%8a%a8%e5%91%98%e5%8f%88%e5%bd%93%e8%a3%81%e5%88%a4" class="header-anchor"&gt;&lt;/a&gt;痛点 5：LLM 既当运动员又当裁判
&lt;/h3&gt;&lt;p&gt;现状：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单次调用里，LLM 同时做“相关性判断”和“答案生成”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只要检索结果“不够完美但有点相关”&lt;/li&gt;
&lt;li&gt;LLM 就可能直接判定 &lt;code&gt;related: false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;结果是部分可用信息被浪费，系统容错率很低&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后续方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把相关性判断前置到检索阶段&lt;/li&gt;
&lt;li&gt;由 &lt;code&gt;Reranker&lt;/code&gt; 或轻量分类模型做相关性过滤&lt;/li&gt;
&lt;li&gt;让大模型专注于信息整合和自然语言生成&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="痛点-6固定切片参数--缺乏评估体系"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-6%e5%9b%ba%e5%ae%9a%e5%88%87%e7%89%87%e5%8f%82%e6%95%b0--%e7%bc%ba%e4%b9%8f%e8%af%84%e4%bc%b0%e4%bd%93%e7%b3%bb" class="header-anchor"&gt;&lt;/a&gt;痛点 6：固定切片参数 + 缺乏评估体系
&lt;/h3&gt;&lt;p&gt;现状：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不区分文档类型，统一用一套切片参数&lt;/li&gt;
&lt;li&gt;参数密集的表格、叙事文档、结构化 JSON 被同样处理&lt;/li&gt;
&lt;li&gt;同时没有标准召回评估&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;短文档或高密度文档容易被切坏&lt;/li&gt;
&lt;li&gt;系统效果无法量化&lt;/li&gt;
&lt;li&gt;后续优化变成“靠感觉调参”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后续方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;针对不同文件类型使用差异化切片策略&lt;/li&gt;
&lt;li&gt;表格类文档采用结构化切片&lt;/li&gt;
&lt;li&gt;建立标准问题对 &lt;code&gt;Ground Truth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;评估 &lt;code&gt;Recall@10&lt;/code&gt;、&lt;code&gt;MRR&lt;/code&gt; 等指标&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="从今天回看-rag-10"&gt;&lt;a href="#%e4%bb%8e%e4%bb%8a%e5%a4%a9%e5%9b%9e%e7%9c%8b-rag-10" class="header-anchor"&gt;&lt;/a&gt;从今天回看 RAG 1.0
&lt;/h2&gt;&lt;p&gt;如果用一句话概括这套老版架构，我会说：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;它已经完成了“能上传、能检索、能回答”的最小闭环，但很多关键模块仍停留在规则驱动、后置修补和弱评估阶段。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;RAG 1.0 最宝贵的价值，不是它有多先进，而是它把问题暴露得足够具体：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;切片为什么会影响召回&lt;/li&gt;
&lt;li&gt;全局搜索为什么会造成稀疏化&lt;/li&gt;
&lt;li&gt;为什么不能只靠向量检索兜底一切&lt;/li&gt;
&lt;li&gt;为什么 LLM 不能既做检索判断又做最终回答&lt;/li&gt;
&lt;li&gt;为什么没有评估体系的优化几乎等于盲调&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些坑踩出来之后，后面的 RAG 2.0 才会更有方向。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;MapAI&lt;/code&gt; 的 &lt;code&gt;RAG 1.0&lt;/code&gt; 已经具备了完整的数据入库和问答链路：上传文件后完成去重、解析、切片、向量化和 Redis 持久化；用户提问时完成召回、过滤、上下文组装和 JSON 化回答。&lt;/p&gt;
&lt;p&gt;但它的核心问题也很清楚：切片不够语义化、检索过滤顺序不合理、兜底检索太弱、缺少重排、LLM 职责过重、效果无法量化。对一个 1.0 系统来说，这其实很正常。真正重要的是把问题讲清楚，然后让下一版架构不再重复这些老坑。&lt;/p&gt;</description></item></channel></rss>