尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

AI Agent知识获取管道:RAG检索增强生成从切片到检索的TypeScript实战

发布时间:2026/9/28 21:05:47

资讯中心
01
ARTICLE

AI Agent知识获取管道:RAG检索增强生成从切片到检索的TypeScript实战

AI Agent知识获取管道:RAG检索增强生成从切片到检索的TypeScript实战
1. 为什么知识获取管道是 AI Agent 的分水岭很多人搭 AI Agent 的时候第一反应是去调模型、写提示词、接工具觉得只要模型够强、工具够多Agent 就能干活。但真正跑过几个项目之后你会发现决定一个 Agent 好不好用的往往不是模型本身而是它能不能拿到对的信息。这就是知识获取管道要解决的问题。我在实际项目里踩过最典型的一个坑做一个内部技术文档问答的 Agent模型用的是当时能力很强的那一档提示词也反复调了好几轮但回答质量始终不稳定。后来排查了半天才发现问题根本不在模型而在于我喂给它的文档切片方式太粗暴——按固定字数硬切把一段完整的配置说明从中间截断前半段在切片 A后半段在切片 B。模型检索到切片 A 的时候看到的是一个没有结尾的句子自然答不对。这个经历让我彻底改变了对 RAG 的认知。RAG 全称是 Retrieval-Augmented Generation检索增强生成说白了就是让模型在回答问题之前先去一个知识库里把相关资料捞出来再基于这些资料组织答案。它不是一个可选项而是 AI Agent 从能聊天进化到能干活的关键基础设施。这一篇是 AI Agent 系列的第四篇前面几篇聊了 Agent 的基本架构、工具调用和任务规划这一篇专门讲知识获取管道也就是 RAG 的基础部分。我会用 TypeScript 作为主要示例语言因为它在类型安全性和工程化方面对 Agent 开发非常友好。内容会覆盖从文档处理、切片策略、向量化、检索到最终拼装上下文的完整链路适合已经了解 Agent 基本概念、想动手搭建知识管道的开发者。如果你之前只听说过 RAG 这个词但没真正落地过这篇可以当作一份从零开始的实操参考。需要先说明一点RAG 的基础版本并不复杂核心链路就那么几步。真正难的是每一步的细节决策——切片切多大、用什么模型做向量化、检索召回多少条、怎么处理多轮对话里的上下文。这些决策没有标准答案只有适合你场景的答案。我下面会把每个环节的取舍逻辑讲清楚你可以根据自己的实际情况调整。2. 知识获取管道的完整链路拆解2.1 从原始文档到可检索知识库的五个阶段一条完整的知识获取管道从原始文档到最终被 Agent 使用大致会经过五个阶段。理解这五个阶段的边界和职责是后面做技术选型和问题排查的基础。第一个阶段是文档加载。你的知识可能散落在各种地方Markdown 文件、PDF、网页、数据库、甚至飞书或 Notion 的页面。加载阶段要做的事情就是把这些异构来源统一读成纯文本或结构化文本。这一步看起来简单但坑不少比如 PDF 里的表格和公式、网页里的导航栏噪音都会影响后续质量。第二个阶段是文本切片。模型和向量化模型都有输入长度限制你不可能把一整本书塞进去。切片就是把长文档拆成一段段语义相对完整的小块。这是整个管道里最影响效果的一步后面我会专门展开讲。第三个阶段是向量化。把每个文本切片通过一个嵌入模型转成一串数字向量这串向量在数学上代表了这段文本的语义。语义相近的文本向量距离就近。这一步是让检索变得可能的核心。第四个阶段是存储与索引。把向量和原始文本一起存进向量数据库并建立索引让后续的相似度查询能快速返回结果。小规模场景用内存数组也能凑合但一旦上量就必须用专业的向量库。第五个阶段是检索与上下文拼装。用户提问时把问题也向量化去向量库里找最相近的若干切片再把这些切片和原始问题一起拼成提示词交给模型生成答案。这五个阶段串起来就是 RAG 的基础形态。下面这张表可以帮你快速对照每个阶段的核心任务和常见工具阶段核心任务常见方案最容易出问题的地方文档加载异构来源统一为文本各类解析库、爬取脚本表格、公式、噪音处理文本切片拆成语义完整的小块固定长度、递归、语义切片切断语义、块过大过小向量化文本转向量各类嵌入模型模型选型、维度、成本存储索引向量入库建索引向量数据库、内存数组索引类型、过滤条件检索拼装召回并组装上下文相似度检索、重排序召回数量、上下文超长2.2 为什么检索比生成更值得投入很多团队在 RAG 项目上的资源分配是反的花大量时间调提示词、换更强的生成模型却在检索环节草草了事。我的经验是检索质量决定了 RAG 效果的上限生成模型只是逼近这个上限。打个比方RAG 就像开卷考试。检索环节相当于你翻书找答案的那一步生成环节相当于你根据找到的内容组织语言写答案。如果书翻错了页找到的是完全不相关的内容那你语言组织能力再强也写不出正确答案。反过来只要你翻对了页哪怕表达能力一般也能把要点答出来。这个道理在实际项目里体现得特别明显。我做过一个对比测试同一套知识库同一批问题只换生成模型从能力中等换到能力很强答案准确率的提升大概在几个百分点但把检索策略从固定长度切片单路召回换成递归切片多路召回重排序准确率提升能到二十个百分点以上。这个差距说明检索才是性价比最高的优化点。所以在这一篇里我会把重心放在切片、向量化和检索这三个环节生成环节只讲怎么把上下文拼好。这也是知识获取管道这个说法的由来——重点在获取不在生成。2.3 TypeScript 在 Agent 知识管道里的角色为什么用 TypeScript 来讲这套东西因为 Agent 开发本质上是工程问题而工程问题最怕的就是类型混乱。知识管道里数据在多个阶段之间流转每个阶段的数据结构都不一样加载阶段是文档对象切片阶段是切片对象向量化后是带向量的切片检索后是带分数的结果。如果用动态类型语言这些结构很容易在传递过程中被改得面目全非排查起来非常痛苦。TypeScript 的接口和类型别名可以把每个阶段的数据契约固定下来。比如你定义一个Chunk接口规定它必须有id、content、metadata三个字段那么任何试图传一个缺字段的对象进来的代码在编译阶段就会报错而不是等到运行时才炸。这在多人协作的项目里价值巨大。另外现在主流的 Agent 框架和向量数据库基本都提供了 TypeScript 或 JavaScript 的 SDK生态是完整的。你完全可以用一套 TypeScript 代码把加载、切片、向量化、检索、拼装全部串起来不需要在多种语言之间来回切换。下面我就按这条链路一步步讲怎么落地。3. 文档加载与切片决定 RAG 效果的第一道关3.1 加载阶段要处理的三种典型脏数据文档加载听起来就是把文件读成字符串但实际项目里脏数据的处理才是重头戏。我总结了三类最常见的脏数据几乎每个项目都会遇到。第一类是格式噪音。从网页抓下来的内容往往带着导航栏、页脚、广告、版权声明这些和正文无关的东西。如果不清理这些噪音会被切进切片向量化之后污染检索结果。处理办法是在加载阶段就做一轮清洗用正则或 DOM 解析把正文区域提取出来。如果是用爬虫抓的尽量在抓取时就定位到正文容器而不是抓整个页面。第二类是结构丢失。PDF 和 Word 文档里的标题层级、列表、表格转成纯文本后往往就没了。标题变成普通一行字列表变成一串没有标记的句子表格变成一堆错位的文字。这会严重影响切片时的语义判断。我的做法是尽量用能保留结构的解析库把标题标记成 Markdown 的#列表保留-表格转成 Markdown 表格。这样后续切片时就能根据结构做更聪明的切分。第三类是编码和空白问题。中文文档里常见的全角空格、不间断空格、各种换行符混用都会影响后续处理。加载后统一做一次规范化全角转半角、连续空白合并、统一换行符。这一步花不了多少代码但能省掉后面很多莫名其妙的 bug。下面是一个加载和清洗的 TypeScript 示例展示了基本的处理思路interface RawDocument { id: string; source: string; content: string; metadata: Recordstring, unknown; } function normalizeText(text: string): string { return text .replace(/\u00a0/g, ) // 不间断空格转普通空格 .replace(/[\u3000]/g, ) // 全角空格转半角 .replace(/\r\n/g, \n) // 统一换行符 .replace(/\n{3,}/g, \n\n) // 连续空行合并 .replace(/[ \t]{2,}/g, ) // 连续空格合并 .trim(); } function loadMarkdown(source: string, raw: string): RawDocument { return { id: source, source, content: normalizeText(raw), metadata: { type: markdown, loadedAt: Date.now() }, }; }注意清洗不要过度。有些空白和换行在代码块、表格里是有意义的一刀切地全部合并会破坏这些结构。建议在清洗前先识别出代码块和表格区域跳过这些区域的空白处理。3.2 切片策略的取舍固定长度、递归还是语义切片是 RAG 里最需要动脑子的一步。切得太碎每个切片信息量不足检索出来答不完整切得太大一个切片里混了好几个主题向量化后语义被稀释检索精度下降。而且切片边界如果切断了完整的句子或段落模型拿到的就是残缺信息。我按从简单到复杂介绍三种主流策略以及它们各自适合的场景。固定长度切片是最简单的做法设定一个字符数上限比如 500 字从头到尾硬切。实现容易但问题很明显——它完全不考虑语义边界经常把一句话从中间切断。这种策略只适合对效果要求不高、或者文档本身结构很规整的场景。如果非要用至少要加一个重叠窗口比如每片 500 字、相邻片重叠 50 字这样被切断的信息有机会在相邻片里补全。递归切片是我最推荐的默认策略。它的思路是优先按最大的语义单位切如果切完还超长再往下一级单位切。具体来说先按段落双换行切段落还超长就按句子切句子还超长才按字符硬切。这样能最大程度保证每个切片是语义完整的。LangChain 等框架里都有现成的递归切片器但自己实现也不难核心就是一个递归函数。语义切片是更进阶的做法用嵌入模型计算相邻句子的语义相似度在相似度骤降的地方切开因为那通常意味着话题转换了。效果最好但成本也最高因为要对每个句子做向量化。适合知识库规模不大、但对精度要求极高的场景。下面这张表对比了三种策略策略实现难度效果成本适用场景固定长度低一般低结构规整、要求不高递归切片中好低大多数场景的默认选择语义切片高最好高小规模、高精度要求3.3 切片大小和重叠窗口的经验值切片大小到底设多少这个问题我被问过无数次。网上流传的说法是500 到 1000 个 token但这个范围太宽实际用起来还是没底。我分享几个从项目里总结的经验值。首先要区分字符数和token 数。中文里一个汉字大约对应一到两个 token英文一个单词大约对应一到一点三个 token。很多嵌入模型的限制是按 token 算的所以切片时要按 token 估算不能只看字符数。粗略换算的话中文场景下 500 个 token 大约对应 350 到 500 个汉字。对于技术文档和知识库问答我一般把切片控制在 300 到 500 个 token。这个大小能容纳一个完整的知识点又不会混入太多无关内容。对于长篇文章和书籍可以放宽到 800 到 1000 个 token因为这类内容段落本身就长切太小反而破坏完整性。重叠窗口的作用是防止边界信息丢失。经验值是切片大小的 10% 到 20%。比如切片 500 token重叠就设 50 到 100 token。重叠太小起不到作用太大则会让知识库膨胀、检索时返回大量重复内容。我一般从 15% 起步根据实际效果微调。还有一个容易被忽略的点切片要带上足够的元数据。至少要有来源文档 ID、切片在文档中的位置、所属的标题层级。这些元数据在检索时可以用来做过滤比如只在某个产品的文档里搜或者在拼装上下文时告诉模型这段内容来自第三章第二节帮助模型理解上下文。interface Chunk { id: string; docId: string; content: string; position: number; heading: string; tokenCount: number; } function recursiveSplit( text: string, docId: string, maxTokens 500, overlap 75 ): Chunk[] { const separators [\n\n, \n, 。, , , . , ]; const chunks: Chunk[] []; let position 0; function split(content: string, sepIndex: number): string[] { if (estimateTokens(content) maxTokens || sepIndex separators.length) { return [content]; } const sep separators[sepIndex]; const parts content.split(sep); const result: string[] []; let buffer ; for (const part of parts) { const candidate buffer ? buffer sep part : part; if (estimateTokens(candidate) maxTokens buffer) { result.push(buffer); buffer part; } else { buffer candidate; } } if (buffer) result.push(buffer); return result.flatMap((p) estimateTokens(p) maxTokens ? split(p, sepIndex 1) : [p] ); } for (const piece of split(text, 0)) { chunks.push({ id: ${docId}-${position}, docId, content: piece, position, heading: , tokenCount: estimateTokens(piece), }); position 1; } return chunks; } function estimateTokens(text: string): number { // 粗略估算中文按字符数英文按空格分词 const cjk (text.match(/[\u4e00-\u9fa5]/g) || []).length; const others text.length - cjk; return Math.ceil(cjk * 1.5 others / 4); }提示estimateTokens只是粗略估算生产环境建议用对应嵌入模型的官方分词器来精确计算避免切片超长导致向量化失败。4. 向量化与存储让知识变得可检索4.1 嵌入模型选型要看哪几个维度向量化的核心是嵌入模型它决定了文本被映射到什么样的语义空间。选型时我主要看四个维度。语义质量是第一位的。不同模型对中文、英文、代码的理解能力差异很大。有些模型英文很强但中文一般有些专门针对中文优化过。选型时最靠谱的办法是拿你自己的数据做一轮小规模测试准备几十个问题和对应的正确文档看哪个模型能把正确文档排到前面。别只看榜单榜单和你的实际数据往往对不上。向量维度影响存储和检索成本。维度越高表达能力越强但存储和计算开销也越大。常见的有 768 维、1024 维、1536 维。对于大多数知识库场景768 到 1024 维已经够用没必要盲目追求高维。输入长度限制决定了单个切片能有多长。有些模型只支持 512 token有些支持 8192。如果你的切片比较大就要选支持长输入的模型否则会被截断。成本和部署方式也很关键。有的模型只能调云端接口按调用量计费有的可以本地部署一次性投入。如果知识库很大、更新频繁云端接口的费用会累积得很快这时候本地部署可能更划算。如果知识库小、更新少云端接口省事。维度关注点建议语义质量中英文、代码理解用自有数据实测别只看榜单向量维度存储与计算成本768-1024 维通常够用输入长度是否匹配切片大小至少覆盖最大切片长度成本部署调用费用与本地化大规模考虑本地部署4.2 向量数据库的选型与索引类型向量化之后要把向量存起来并且能快速做相似度查询。小规模场景几千条以内用内存数组加暴力计算也能跑但一旦上万条就必须用向量数据库。选向量数据库时我关注三点索引类型、过滤能力、运维成本。索引类型决定了查询速度和召回率的平衡。常见的索引有扁平索引暴力计算最准但最慢、倒排文件索引速度快召回率略降、分层可导航小世界图速度快召回率高内存占用大。对于大多数场景分层可导航小世界图是默认选择。过滤能力指的是能不能在向量检索的同时按元数据过滤。比如只在 2024 年之后的文档里搜如果数据库不支持过滤你就得先全量检索再手动筛效率很低。这个能力在实际项目里非常重要选型时一定要确认。运维成本包括部署难度、是否需要额外服务、数据持久化方式等。有些向量库是嵌入式的直接跑在应用进程里适合小项目有些是独立服务需要单独部署和维护适合大规模场景。interface VectorRecord { id: string; vector: number[]; content: string; metadata: Recordstring, unknown; } class InMemoryVectorStore { private records: VectorRecord[] []; add(records: VectorRecord[]): void { this.records.push(...records); } search( queryVector: number[], topK 5, filter?: (meta: Recordstring, unknown) boolean ): ArrayVectorRecord { score: number } { const candidates filter ? this.records.filter((r) filter(r.metadata)) : this.records; return candidates .map((r) ({ ...r, score: cosineSimilarity(queryVector, r.vector) })) .sort((a, b) b.score - a.score) .slice(0, topK); } } function cosineSimilarity(a: number[], b: number[]): number { let dot 0, normA 0, normB 0; for (let i 0; i a.length; i) { dot a[i] * b[i]; normA a[i] * a[i]; normB b[i] * b[i]; } return dot / (Math.sqrt(normA) * Math.sqrt(normB) 1e-8); }注意上面的内存实现只适合演示和小规模数据。生产环境请用专业向量数据库它们对索引、持久化、并发都有专门优化。4.3 批量向量化时的并发与限流处理实际项目里知识库动辄几千上万条切片逐条调嵌入接口会非常慢。必须做批量处理但批量又会遇到接口的速率限制。我的做法是控制并发数加失败重试。并发数不能太高否则容易触发限流也不能太低否则跑得慢。我一般从 5 到 10 并发起步根据接口的响应情况调整。同时要处理失败重试网络抖动或临时限流导致的失败退避几秒后重试通常就能成功。重试要有次数上限避免死循环。async function embedBatch( chunks: Chunk[], embedFn: (text: string) Promisenumber[], concurrency 5, maxRetries 3 ): PromiseVectorRecord[] { const results: VectorRecord[] []; const queue [...chunks]; async function worker(): Promisevoid { while (queue.length 0) { const chunk queue.shift(); if (!chunk) break; let attempt 0; while (attempt maxRetries) { try { const vector await embedFn(chunk.content); results.push({ id: chunk.id, vector, content: chunk.content, metadata: { docId: chunk.docId, position: chunk.position }, }); break; } catch (err) { attempt 1; if (attempt maxRetries) { console.error(切片 ${chunk.id} 向量化失败, err); } else { await new Promise((r) setTimeout(r, 1000 * attempt)); } } } } } await Promise.all(Array.from({ length: concurrency }, () worker())); return results; }这段代码的核心是工作池模式启动固定数量的 worker每个 worker 从队列里不断取任务处理直到队列空。这样并发数可控又不会浪费等待时间。重试用了简单的线性退避实际项目里可以换成指数退避效果更好。5. 检索与上下文拼装把知识喂给模型5.1 相似度检索的召回数量怎么定检索阶段第一个要定的参数是召回数量 topK也就是每次返回多少个最相似的切片。这个值太小可能漏掉关键信息太大会引入无关内容还会撑爆模型的上下文窗口。我的经验是 topK 从 3 到 5 起步。对于问题比较聚焦、知识库切片质量高的场景3 条往往就够。如果问题比较复杂、需要综合多处信息可以提到 8 到 10 条。但不要盲目加大因为相似度排序靠后的切片相关性下降得很快加进来多半是噪音。一个更聪明的做法是先多召回再重排序。第一轮用向量检索召回比如 20 条然后用一个重排序模型对这 20 条做精排取前 5 条。重排序模型比向量检索更准因为它能同时看到问题和文档做更精细的相关性判断。这样既保证了召回率又保证了精度。5.2 多路召回与重排序的实战价值单一向量检索有个天然短板它擅长语义相似但对关键词精确匹配不敏感。比如用户问一个具体的错误码向量检索可能返回一堆语义相关但不含这个错误码的文档。这时候就需要多路召回一路走向量检索一路走关键词检索比如 BM25两路结果合并后再重排序。我在一个技术文档项目里加了这个策略效果提升很明显。之前用户搜具体 API 名称时经常搜不到加了关键词召回之后这类问题的命中率大幅提升。原因是向量检索把 API 名称这种专有名词的语义泛化了而关键词检索能精确匹配。多路召回的合并策略有两种一种是简单去重后合并另一种是按分数加权融合。加权融合需要把两路的分数归一化到同一量纲稍微复杂一点但效果更好。如果嫌麻烦简单合并加重排序也能拿到大部分收益。interface RetrievedChunk { id: string; content: string; score: number; source: vector | keyword; } async function hybridRetrieve( query: string, vectorStore: InMemoryVectorStore, keywordIndex: Mapstring, string[], topK 5 ): PromiseRetrievedChunk[] { const queryVector await embedQuery(query); const vectorResults vectorStore.search(queryVector, topK * 4); const keywords extractKeywords(query); const keywordIds new Setstring(); for (const kw of keywords) { for (const id of keywordIndex.get(kw) || []) { keywordIds.add(id); } } const merged new Mapstring, RetrievedChunk(); for (const r of vectorResults) { merged.set(r.id, { id: r.id, content: r.content, score: r.score, source: vector, }); } for (const id of keywordIds) { if (!merged.has(id)) { merged.set(id, { id, content: , score: 0.5, source: keyword, }); } } return Array.from(merged.values()) .sort((a, b) b.score - a.score) .slice(0, topK); } function extractKeywords(query: string): string[] { return query .split(/[\s,。?!]/) .filter((w) w.length 2); }5.3 上下文拼装的顺序与格式技巧检索到相关切片后最后一步是把它们和用户问题拼成提示词。这一步看似简单但格式和顺序会影响模型的理解。顺序上我习惯把最相关的切片放在最前面和最后面中间放次相关的。这是利用了模型对首尾内容注意力更强的特点。如果切片数量少直接按相关度降序排也行。格式上每个切片要标清楚来源和边界。我一般用这样的格式[文档1] 来源xxx.md 第三章 内容... [文档2] 来源yyy.md 第一节 内容...这样模型能区分不同切片回答时也能引用来源。切片之间用明确的分隔符隔开避免模型把它们当成连续文本。提示词里要明确告诉模型只根据提供的资料回答资料里没有的信息不要编造。这句话能显著降低幻觉。同时可以要求模型在回答里标注引用了哪个文档方便用户核实。function buildPrompt( question: string, chunks: RetrievedChunk[] ): string { const context chunks .map( (c, i) [文档${i 1}] 来源${c.id}\n内容${c.content} ) .join(\n\n); return 你是一个严谨的知识助手。请只根据下面提供的资料回答问题。 如果资料中没有相关信息请直接说明资料中未找到相关内容不要编造。 资料 ${context} 问题${question} 回答; }提示上下文不是越长越好。如果拼装后的提示词超过了模型的上下文窗口要么截断切片要么减少召回数量。截断时优先保留相关度高的切片。6. 从能跑到好用几个必须知道的实战细节6.1 知识库更新时怎么避免全量重跑知识库不是一次性的文档会更新、会新增。如果每次更新都全量重新向量化成本高、耗时长。我的做法是给每个切片记录来源文档的哈希值更新时只处理哈希变化的文档。具体来说加载阶段先算每个文档内容的哈希和上次存储的哈希对比。没变的文档直接跳过变的文档删掉旧切片、重新切片向量化。这样增量更新能把处理量降到最低。对于频繁更新的知识库这个优化能省下大量时间和费用。6.2 检索结果不理想时的排查顺序RAG 效果不好时很多人第一反应是换模型但往往问题不在模型。我总结了一个排查顺序从最可能的原因开始查。先看切片质量把检索到的切片打印出来看内容是否完整、是否包含答案。如果切片本身就是残缺的或无关的那问题在切片环节换模型没用。再看向量化确认切片和查询用的是同一个嵌入模型。用不同模型向量化语义空间对不上检索必然失败。这个错误很隐蔽因为代码不会报错只是效果差。然后看检索参数topK 是不是太小相似度阈值是不是设得太高。可以临时把 topK 调大看正确切片有没有出现在结果里。如果出现了但排名靠后说明是排序问题考虑加重排序。最后才看生成环节如果检索到的切片明明包含答案但模型答错了那才是提示词或模型的问题。这时候再调提示词、换模型。6.3 评估 RAG 效果的一套简易方法没有评估就没法优化。我一般会建一个小规模的评估集准备 30 到 50 个真实问题每个问题标注出应该被检索到的文档 ID以及期望答案的要点。评估时看两个指标检索命中率即正确文档有没有出现在召回结果里答案准确率即最终回答是否覆盖了期望要点。前者反映检索质量后者反映端到端效果。每次调整切片策略或检索参数后跑一遍评估集对比指标变化就能知道改动是变好还是变坏。这个评估集不需要很大但一定要用真实问题不要自己编。真实问题里的表述方式、用词习惯和你拍脑袋想出来的问题差别很大只有真实问题才能反映实际效果。6.4 一个容易忽略的坑查询改写用户的问题往往很短、很口语化直接拿去检索效果不一定好。比如用户问那个报错怎么解决那个指什么完全不知道。这时候需要查询改写用模型把用户问题改写成更适合检索的形式或者结合对话历史补全指代。在多轮对话场景里查询改写尤其重要。用户第二轮问那它支持吗如果不结合上一轮补全它指什么检索必然失败。我的做法是在检索前加一步把对话历史和当前问题一起交给模型让它输出一个独立的、完整的检索查询。这一步能显著提升多轮场景的检索质量。async function rewriteQuery( history: Array{ role: string; content: string }, current: string, llm: (prompt: string) Promisestring ): Promisestring { if (history.length 0) return current; const historyText history .map((m) ${m.role}: ${m.content}) .join(\n); const prompt 根据下面的对话历史把用户的最新问题改写成一个独立、完整、适合检索的问题。 只输出改写后的问题不要解释。 对话历史 ${historyText} 最新问题${current} 改写后的问题; return (await llm(prompt)).trim(); }这个改写步骤会增加一次模型调用带来一点延迟和成本但在多轮场景里非常值得。单轮场景可以省略或者只在问题明显有指代时才触发。7. 我对知识获取管道的一点个人体会搭了几套 RAG 系统之后我最大的体会是别追求一步到位先跑通再优化。很多人卡在选型阶段纠结用哪个嵌入模型、哪个向量库结果迟迟不动手。其实基础版本用最简单的方案就能跑起来跑起来之后你才知道瓶颈在哪才知道该优化什么。我自己的习惯是先搭一个最小可用版本内存向量库、固定长度切片、单路检索能回答简单问题就行。然后拿真实问题去测看哪里不行。往往是切片问题最突出那就先优化切片切片好了还不行再看检索检索好了还不行才轮到生成。这个顺序能保证你把精力花在刀刃上。另外知识获取管道不是搭完就完事的它需要持续维护。文档会变、用户问题会变、模型会更新定期跑评估集、看失败案例才能让它一直好用。我一般每个月会抽时间看一批失败案例往往能发现新的优化点。最后说一句RAG 的基础部分真的不难难的是细节和耐心。把切片、向量化、检索这三步的细节抠到位效果自然就上来了。下一篇我会讲 Agentic RAG也就是让 Agent 自己决定怎么检索、检索几轮那是在这个基础之上的进阶玩法。但前提是你得先把这一篇的基础打牢。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。