1. 这不是“加个检索框”那么简单RAG 在 AI Agent 架构里到底承担什么角色你可能已经看过十几篇讲 RAG 的文章标题都差不多“三步搞定 RAG”、“手把手教你搭建知识库”。但如果你真在一线做过 AI Agent 项目就会发现——这些教程教的只是“检索拼接”而实际落地时90% 的问题根本不在那几行代码里。我去年带团队给一家制造业客户做设备故障诊断 Agent初期方案就是照搬 LangChain 官方 RAG 示例PDF 切块 → 向量入库 → query 检索 → prompt 注入。结果上线后 hit rate检索命中率只有 37%用户问“主轴过热报警怎么处理”系统返回的却是三年前某次轴承润滑记录完全不相关。后来我们花了整整六周重拆整个知识获取管道才把有效响应率拉到 82%。这六周干了什么不是调 embedding 模型不是换 LLM而是重新定义了 RAG 在 Agent 中的定位它不是“知识搬运工”而是 Agent 的长期记忆调度中枢。RAGRetrieval-Augmented Generation这个词本身就有误导性。“Retrieval”听起来像数据库查询“Augmented”像给 prompt 做加法。但放在 AI Agent 场景下它本质是解决一个更底层的问题如何让 Agent 在不重训模型的前提下动态接入、理解、验证并调用外部知识源且这个过程必须可追溯、可干预、可降级。你看热搜词里反复出现的 “agentic RAG”、“RAG as Service”、“Ontology RAG”背后全是这个诉求——RAG 不再是静态知识库的附属功能而是 Agent 的核心能力模块和 Planning、Tool Calling、Memory Management 并列。比如 “agentscope 2.0 rag as service” 这个热词指的就是把 RAG 能力抽象成独立服务Agent 在执行 Plan 时可以按需调用不同 RAG 实例查产品手册用结构化知识库查维修日志用时序向量库查安全规范用规则引擎增强的 RAG。这种解耦才是 RAG 真正进入 Agent 架构的关键分水岭。所以当你看到标题“知识获取管道——RAG 基础”别只盯着“基础”俩字。这里的“基础”指的是 Agent 架构中知识流动的基础设施层就像水电管道之于建筑——看不见但断了就全楼停摆。它要解决的不是“能不能查到”而是“查到的是否可信”、“查到的能否被 Agent 理解”、“查不到时怎么办”、“查到多个冲突结果时如何仲裁”。这些细节恰恰是所有“三步教程”里绝口不提的。接下来我会从设计思路、核心环节、实操陷阱三个维度带你真正看清这条管道是怎么铺的为什么这么铺以及踩坑时怎么自救。2. 管道设计为什么不能直接用 LangChain 的默认 RAG 流程很多开发者一上来就跑通 LangChain 的RetrievalQA链觉得“能返回结果就算成功”。但我在给金融、医疗、工业三个领域客户做 Agent 时发现LangChain 默认流程在真实场景中会暴露出四个结构性缺陷每个都足以让整个 Agent 失效。这不是框架不好而是它的设计初衷是“演示 RAG 原理”而非支撑生产级 Agent。下面我用制造业设备诊断 Agent 的真实案例逐条拆解这些缺陷及其改造逻辑。2.1 缺陷一检索与生成强耦合导致 Agent 失去决策权LangChain 默认的RetrievalQA是把检索结果硬塞进 prompt然后丢给 LLM 生成答案。问题在于Agent 无法对检索结果做任何判断。比如用户问“FANUC 0i-MD 系统报 SV0432当前主轴温度 85℃冷却液压力 0.3MPa怎么处理” 默认流程会检索所有含“SV0432”的文档片段可能返回① 通用报警代码表说这是“伺服电机过载”② 某次维修报告提到“冷却不足导致过载”③ 一份新发布的固件补丁说明指出该报警在 v3.2.1 版本存在误报。这三个结果逻辑冲突但 LLM 只会把它们当平等文本拼接大概率生成“请检查冷却系统”的错误建议而忽略最关键的固件版本信息。改造方案引入检索后置处理Post-Retrieval Processing层我们把 pipeline 拆成三段Retrieval Layer只负责召回原始 chunk不做排序保留 top-k20Filter Rank Layer用轻量级规则 小模型对 chunk 做二次过滤。例如规则过滤剔除发布日期早于当前设备固件版本的文档语义重排用bge-reranker-base对剩余 chunk 按 query 相关性重打分冲突检测若 top3 chunk 中有互斥结论如“需更换电机” vs “属软件误报”标记为 high-risk触发人工审核流。Generation LayerLLM 输入不再是 raw chunks而是带元数据的 structured context包含chunk 内容、来源文档 ID、置信度分数、冲突标记。Agent 的 Planner 可据此决定是直接生成答案、还是调用 Tool 查当前固件版本、或是转交工程师。提示这个改造的核心思想是——把“检索”从生成环节剥离变成 Agent 可编程的中间态。就像汽车的变速箱不是简单传递动力而是根据路况query context调整输出retrieved knowledge。2.2 缺陷二知识源异构性被粗暴忽略导致“查得到却看不懂”热搜词里频繁出现的 “net rag本地知识库”、“本地erp rag llm”暴露了一个现实企业知识从来不是整齐的 PDF 或 Markdown。它可能是ERP 系统里的结构化工单数据MySQL 表repair_records字段含fault_code,machine_id,solution_text,status设备传感器实时流Kafka topicmachine_telemetry含温度、振动、电流等时序数据工程师写的非结构化维修笔记Confluence 页面混杂文字、截图、表格供应商提供的 PDF 手册含复杂图表和页眉页脚噪声。LangChain 默认的DirectoryLoader或PyPDFLoader只能处理文件对数据库、API、流数据束手无策。更糟的是它把所有 source 都切块向量化导致 ERP 里的精确数值如“冷却液压力阈值0.4MPa”被淹没在长文本中检索时根本无法精准匹配。改造方案构建多模态知识接入适配器Adapter我们为每类知识源开发专用 AdapterDB Adapter不导出为文本而是将 SQL 查询能力封装为 Tool。Agent Planner 可生成 SQL“SELECT solution_text FROM repair_records WHERE fault_codeSV0432 AND machine_idFANUC-0iMD-2023 ORDER BY created_at DESC LIMIT 3”。返回结果是结构化 JSON直接喂给 LLMTelemetry Adapter将 Kafka 流接入向量库时不存原始数值而是存“特征摘要”如对过去 1 小时振动频谱做 FFT提取主频能量比向量化后标注为vibration_anomaly_profile。检索时query “主轴异常振动” 会匹配到该 profile而非一堆原始数字PDF Adapter放弃全文切块改用 LayoutParser 识别文档结构分离标题、正文、表格、图注。表格单独存为 CSV 向量图注与对应图片哈希绑定确保“图中显示冷却管路堵塞”能准确关联到图像内容。这个方案让 RAG 从“文本搬运工”升级为“知识翻译官”——它不改变知识源形态而是为每种形态提供恰如其分的接入方式。2.3 缺陷三缺乏知识新鲜度管控Agent 成为“活化石”客户常抱怨“你们的 Agent 总推荐过时方案。” 根源在于 LangChain 默认 RAG 没有知识生命周期管理。我们曾发现某客户知识库中 63% 的维修指南发布于 2021 年前而他们最新设备是 2023 年上线的。更隐蔽的问题是知识源本身在变。ERP 里的工单状态会更新“已解决”→“复发”Confluence 笔记会被编辑PDF 手册会发布新版。但向量库不会自动同步。改造方案实施知识源健康度监控Health Monitoring我们在每个 Adapter 上增加健康探针时效性探针对每个知识源记录最后更新时间戳。当 Agent 检索到某 chunk 时若其来源文档距今 180 天自动在 context 中添加警示“此信息发布于 {date}可能不适用于当前设备版本”一致性探针对数据库类源定期运行校验 SQL“SELECT COUNT(*) FROM repair_records WHERE statusresolved AND updated_at 2023-01-01”若数量突增触发告警版本探针对 PDF 手册用 PDF metadata 提取Producer和ModDate比对官网最新版哈希值不一致时标记为 outdated。这些探针不修改知识库只在检索时注入元数据。Agent 的 Memory Manager 可据此决定是否优先调用新版知识、是否需要向用户确认信息时效性。2.4 缺陷四错误无回退机制一次失败全链路崩塌默认 RAG 是“all-or-nothing”检索失败 → 返回空 → LLM 胡编乱造。但在工业场景这很危险。比如用户问“如何校准激光测距仪”若 RAG 检索无果Agent 不该说“我不知道”而应启动降级策略调用设备内置 help command、查询通用计量标准NIST 文档、或引导用户拍摄屏幕截图上传。改造方案设计多级知识获取回退树Fallback Tree我们将 RAG 管道扩展为树状结构Level 0主 RAG企业知识库Level 1公共知识 RAG预加载 NIST、ISO 标准向量库Level 2工具调用 RAG调用设备 API 获取实时 helpLevel 3用户交互 RAG引导上传截图用多模态模型解析。每个 Level 有独立超时和成功率阈值。若 Level 0 在 800ms 内未返回置信度 0.7 的结果则自动触发 Level 1。这种设计让 Agent 具备“韧性”而不是脆弱的单点依赖。这四点改造不是炫技而是把 RAG 从 demo 级工具变成 Agent 可信赖的“知识神经系统”。它不再被动响应 query而是主动参与 Agent 的决策闭环——这才是“知识获取管道”的真实含义。3. 核心环节拆解从数据接入到答案生成的七道工序很多人以为 RAG 就是“切块→向量化→检索→拼接”但在我经手的 12 个生产级 Agent 项目中真正决定成败的是这七个被严重低估的中间环节。每个环节都藏着影响 hit rate 和 answer quality 的关键参数而这些参数绝不是“调个 embedding 模型”就能解决的。下面我以制造业设备诊断 Agent 的真实配置为例逐条详解。3.1 知识源解析不是“读文件”而是“理解语境”第一步永远不是加载 PDF而是判断这份知识在什么语境下产生、为谁而写、解决什么问题。比如一份《FANUC 维修手册》PDF表面看是技术文档但深入解析会发现读者语境面向现场工程师而非研发人员因此省略原理推导强调操作步骤问题域语境聚焦“故障现象→原因→解决方案”闭环而非设备设计参数时效语境手册末尾注明“适用于 v3.0-v3.2 固件”暗示 v3.3 后内容可能失效。LangChain 的PyPDFLoader只做 OCR 和文本提取丢失了所有语境信号。我们的做法是用pdfplumber提取文本时同步捕获字体大小、加粗、列表符号等格式特征识别标题层级H1章节名H2故障代码H3处理步骤用正则匹配手册中的“适用版本”、“警告”、“注意”等关键词块将其作为 chunk 的 metadata 存储对每个 chunk用小模型如distilroberta-base-finetuned-squad2抽取出隐含的“问题-原因-方案”三元组存为结构化字段。这样当 query 是“SV0432 报警”检索时不仅匹配文本相似度还强制要求chunk.metadata.version_compatibility CONTAINS v3.2且chunk.triple.reason cooling_insufficient。这比单纯向量检索精准得多。实操心得我试过直接用all-MiniLM-L6-v2向量化整页 PDFhit rate 仅 41%加入语境解析后同一 query 的 hit rate 升至 79%。关键不是模型更强而是让知识有了“身份标签”。3.2 分块策略切多细不是越细越好而是“够用即止”分块chunking是 RAG 最被滥用的环节。新手常以为“切得越细检索越准”结果把一段完整的故障处理流程切成 5 个碎片LLM 拼不回去。我们的原则是chunk 必须是语义完整单元Semantic Unit。判断标准只有一个脱离上下文它能否独立回答一个具体问题以维修手册中一段为例【SV0432】伺服电机过载现象主轴运行中突然停机HMI 显示 SV0432。可能原因1. 冷却液不足2. 电机绕组短路3. 驱动器参数设置错误。处理步骤a) 检查冷却液箱液位应 ≥ 80%b) 用万用表测电机 U-V 间电阻正常值 12±2Ωc) 进入驱动器菜单核对 P123 参数是否为 0.85。如果按固定长度切如 200 字符会把“处理步骤”切成两半。我们的分块逻辑是标题块【SV0432】...单独成 chunk含故障代码、现象原因块*可能原因*...单独成 chunk含所有原因因原因间是并列关系步骤块*处理步骤*...单独成 chunk因步骤是顺序执行不可分割。chunk size 不是固定值而是由语义边界决定。我们用 spaCy 的句子分割 规则引擎识别列表项、标题符号动态确定切点。实测表明这种语义分块比固定长度分块在 multi-hop query如“SV0432 的处理步骤中哪一步需要测电阻”上准确率提升 52%。3.3 向量化选模型不是看 benchmark而是看你的 query 长什么样Embedding 模型选择是玄学不是数学。LangChain 教程总推荐text-embedding-ada-002或bge-base-zh但它们在工业场景表现平平。原因在于benchmark 用的是 Wikipedia 或新闻语料而你的 query 是“SV0432 报警 主轴温度85℃ 冷却液压力0.3MPa”充满缩写、数值、单位和通用语料分布差异巨大。我们的选型方法论Step 1分析 query 分布。收集 1000 条真实用户 query统计缩写占比如 SV, HMI, MPa38%数值单位组合如 “85℃”, “0.3MPa”22%动词名词短语如 “检查液位”, “核对参数”40%。Step 2针对性微调。用上述 query 数据对bge-small-zh做继续预训练Continual Pretraining目标是让模型学会将 “SV0432” 和 “伺服过载” 映射到相近向量空间将 “85℃” 和 “高温报警” 关联而非和 “85号汽油” 关联理解 “检查液位” 和 “查看液位计读数” 是同义动作。微调只需 2 小时A10 GPU但 embedding 质量跃升在自建的工业 query benchmark 上cosine similarity 准确率从 51% 提升到 83%。这比换更大模型如bge-large效果更显著因为解决了 domain mismatch 问题。3.4 检索增强不只是“重排序”而是“意图矫正”检索后重排Rerank常被当作锦上添花但在 Agent 场景它是防止 LLM 被误导的关键防线。默认向量检索返回 top-5 chunk但其中可能混入高相似度低相关性的噪声。比如 query “SV0432”向量检索可能返回正确SV0432 故障处理相似度 0.82噪声SV0422 故障处理相似度 0.79因代码接近噪声SV0432 在另一型号设备上的误报记录相似度 0.75。我们的增强策略是第一层规则过滤。剔除所有chunk.metadata.fault_code ! SV0432的结果利用前面解析出的结构化字段第二层意图重排。用 query 和每个 chunk 构造 pair输入微调后的bge-reranker但它不只算相似度而是预测relevance_score是否回答 queryactionability_score是否含可执行步骤confidence_score信息是否来自权威源如手册 vs 论坛帖。最终排序依据是加权分0.5*relevance 0.3*actionability 0.2*confidence。这确保返回的不仅是“像”的内容而是“能用”的内容。3.5 上下文注入不是“塞满 prompt”而是“精准供给”LangChain 的stuff、map_reduce等 chain本质是把所有 chunk 塞进 prompt。但 LLM 的 context window 有限如 GPT-4-turbo 128K而工业知识 chunk 往往很长。更糟的是LLM 会优先关注 prompt 开头导致关键步骤被淹没。我们的解法是Context Distillation上下文蒸馏。对每个 query不注入全部 top-k chunk而是用 LLM小模型如Qwen2-1.5B做一次轻量级摘要输入 query top-5 chunk输出 3 句核心事实如“SV0432 表示伺服电机过载主因是冷却不足处理步骤a) 查液位 ≥80%b) 测电阻 12±2Ωc) 核对 P1230.85。”将摘要 原始 chunk 的引用 ID如manual_p123_s45注入主 prompt。这样LLM 的注意力集中在 distilled facts 上而需要溯源时可通过 ID 调取原始 chunk。实测在 8K context 下answer accuracy 比 full-stuff 提升 35%且 token 消耗减少 60%。3.6 答案生成LLM 不是“写答案”而是“执行指令”很多人把 RAG 的终点设为 LLM 生成自然语言答案。但在 Agent 场景答案常需结构化输出以便下游 Tool 调用。比如 query “当前主轴温度多少”理想输出不是“主轴温度是 85 摄氏度”而是{ intent: get_sensor_value, sensor: spindle_temperature, unit: ℃, value: 85 }我们的做法是在 prompt 中明确定义 output schema并给出 2 个严格格式的示例用JSON mode如 OpenAI 的response_format: { type: json_object }强制输出对 LLM 输出做 schema validation失败则重试或 fallback。这使 Agent 能无缝衔接 Tool Calling无需额外的 parsing 模块。一次成功的温度查询从 RAG 检索到 Tool 执行全程 1.2 秒。3.7 效果评估不用 accuracy用 operational hit rate最后如何评估 RAG 是否有效别用学术界的 accuracy 或 F1-score。在生产环境我们只看Operational Hit RateOHR分子Agent 在本次对话中RAG 返回的结果被最终 answer 直接引用的次数分母Agent 发起 RAG 请求的总次数。OHR 1 表示每次检索都物尽其用OHR 0.6 表示大量检索结果被 LLM 忽略说明 pipeline 存在冗余或噪声。我们要求 OHR ≥ 0.85否则重构 pipeline。这个指标直指 RAG 的“业务价值”而非“技术正确性”。这七道工序环环相扣。少一道RAG 就只是个 fancy 的 search box全打通它才成为 Agent 的知识心脏。4. 实操避坑指南那些没人告诉你的“死亡陷阱”纸上谈兵终觉浅我来分享几个在真实项目中差点让整个 Agent 项目夭折的坑。这些坑不会出现在任何 tutorial 里因为它们源于真实世界的混乱——数据脏、需求变、人难控。每个坑后面都跟着一条血泪换来的经验。4.1 陷阱一PDF 表格识别失真导致“查得到用错”客户给的维修手册 PDF第 47 页有个关键表格故障代码现象解决方案SV0432主轴过热停机检查冷却液液位用PyPDFLoader加载后表格变成乱码“SV0432 主轴过热停机 检查冷却液液位”三列挤成一行。向量化后“SV0432” 和 “检查冷却液液位” 的关联被破坏。用户问“SV0432 的解决方案”检索返回的 chunk 只含“SV0432 主轴过热停机”LLM 只能胡猜。破解方法弃用通用 loader上 LayoutParser Table Transformer用layoutparser检测 PDF 中的表格区域用table-transformer基于 DETR精准识别表格结构输出标准 HTML 表格将表格转为 Markdown 表格再用unstructured的HtmlPartitioner解析保留行列关系。这样“SV0432” 和 “检查冷却液液位” 在同一个 table cell 中向量化时天然关联。我们测试了 200 份工业手册表格识别准确率从 31% 提升到 94%。注意别信“PDF 转 Word 再处理”的方案。Word 会丢失原始 PDF 的字体嵌入和坐标信息表格更易错乱。4.2 陷阱二向量库“假阳性”泛滥检索像开盲盒用ChromaDB做向量库query “SV0432” 返回 10 个结果前 3 个全是无关内容。查原因发现是all-MiniLM-L6-v2对缩写编码太弱“SV” 被映射到 “service”、“save”、“savings” 等向量簇而非 “servo”。更糟的是ChromaDB 的默认相似度算法cosine对短 query 敏感一个字符差异就导致结果天差地别。破解方法双路检索Dual-Path Retrieval语义路用微调后的 embedding 模型做向量检索覆盖模糊匹配关键词路用Elasticsearch建立倒排索引对故障代码、型号、参数名等关键字段做 exact match覆盖精确匹配融合路对两路结果按权重合并。例如若 query 含明确代码如 “SV0432”关键词路权重 0.7若 query 是自然语言如 “主轴突然停机”语义路权重 0.8。我们用rank_fusion算法如 Reciprocal Rank Fusion加权hit rate 稳定在 89% 以上且无假阳性。4.3 陷阱三LLM “幻觉”接管 RAG把检索结果当草稿涂改最危险的不是检索失败而是检索成功后 LLM 自作主张。比如 RAG 返回“SV0432 的解决方案是检查冷却液液位”但 LLM 生成“SV0432 是伺服过载需先重启驱动器再检查冷却液”。它把“检查液位”篡改为“重启驱动器”而后者根本不在知识库中。破解方法Constrained Decoding Verification Prompt在 prompt 中加入硬约束“你只能使用以下知识生成答案禁止添加任何知识库未提及的操作步骤。若知识库未提供某步骤请说‘未找到相关信息’。”对 LLM 输出做 post-hoc verification用小模型如deberta-v3-base-mnli判断生成答案与检索 chunk 的 entailment 关系。若 confidence 0.9触发 fallback。这招让我们把幻觉率从 22% 压到 3.7%代价是增加 150ms 延迟但值得。4.4 陷阱四知识库更新“静默失效”Agent 在用旧地图导航客户说“知识库每周更新”但没告诉我们更新脚本只增量插入新文档从不删除过期文档。半年后知识库中存着 12 个版本的同一份手册RAG 检索时随机返回一个Agent 给出的方案可能对应已停产的设备型号。破解方法实施知识库版本快照Snapshot每次更新不是直接写入而是生成新知识库快照snapshot_v20240601更新元数据表标记current_snapshot snapshot_v20240601RAG 检索时强制指定 snapshot ID而非访问“最新”库。Agent 的 Planner 在发起 RAG 请求时会带上设备型号和固件版本后端据此选择匹配的 snapshot如FANUC-0iMD-v3.2→snapshot_v20240315。这确保 Agent 永远用“当时当地”的知识而不是“最新但错配”的知识。4.5 陷阱五跨源知识“语义鸿沟”Agent 理解不了自己的知识ERP 数据说“工单 #12345故障代码 SV0432已解决”而维修笔记写“SV0432 是冷却不足换了水泵”。RAG 能分别检索到这两条但 LLM 无法自动关联“工单已解决”和“换了水泵”是同一事件因为它不懂“工单 ID”和“维修动作”的映射关系。破解方法构建轻量级本体Lightweight Ontology不搞复杂 OWL而是用 YAML 定义核心实体关系entities: - name: fault_code aliases: [报警代码, 故障码] relations: - to: repair_action via: caused_by - name: repair_action aliases: [处理措施, 解决方案] relations: - to: work_order via: recorded_in在检索时若 query 含 fault_codepipeline 自动追加 relation-aware query“查找与 SV0432 相关的 repair_action 和 work_order”。这相当于给 RAG 装了个“知识翻译器”让异构数据在语义层面联通。我们在客户项目中跨源问答准确率从 48% 提升到 76%。这些坑每一个都曾让我连续加班 48 小时。但填平它们的过程恰恰是把 RAG 从玩具变成武器的关键。记住在 AI Agent 世界没有银弹只有针对具体场景的、带着泥土味的解决方案。5. 从基础到实战一个可立即复用的 RAG 管道模板说了这么多理论和陷阱现在给你一个“开箱即用”的 RAG 管道模板。它不是 toy example而是我们正在客户现场跑的最小可行版本MVP代码量 300 行支持 PDF/DB/Text 多源已在 3 个项目中验证。你可以直接 clone替换你的知识源5 分钟内跑通。5.1 核心架构三层解耦各司其职[User Query] ↓ [Router Layer] → 判断 query 类型代码查询 / 自然语言 / 数值查询 ↓ [Adapter Layer] → 调用对应 AdapterPDFAdapter / DBAdapter / TextAdapter ↓ [Processor Layer] → 语义分块 → 微调 embedding → 双路检索 → Context Distillation ↓ [Generator Layer] → 结构化 prompt → LLM 生成 → Schema validation ↓ [Answer]这个架构的妙处在于Router 和 Adapter 是插件式新增知识源只需写一个 Adapter 类不碰核心 pipeline。5.2 关键代码实现Python# config.py - 配置中心所有可调参数在此 EMBEDDING_MODEL_NAME your-finetuned-bge-small-zh # 微调后的模型 CHROMA_DB_PATH ./chroma_db ES_HOST http://localhost:9200 # 知识源配置 SOURCES { manuals: {type: pdf, path: ./docs/manuals/, adapter: PDFAdapter}, repair_db: {type: db, uri: mysql://user:passhost/db, adapter: DBAdapter}, } # adapter/base.py - Adapter 基类 class BaseAdapter(ABC): abstractmethod def load(self, query: str) - List[Chunk]: 根据 query 加载相关知识 chunk pass # adapter/pdf_adapter.py - PDF Adapter 实现 class PDFAdapter(BaseAdapter): def __init__(self): self.layout_parser lp.Detectron2LayoutModel(lp://PubLayNet/faster_rcnn_R_50_FPN_3x/config.yaml) self.table_transformer TableTransformer() def load(self, query: str) - List[Chunk]: # 1. 用 layoutparser 定位表格和文本块 doc fitz.open(pdf_path) for page in doc: layout self.layout_parser.detect(page.to_pil()) tables [self.table_transformer.extract_table(t) for t in layout if t.type Table] # 2. 语义分块标题块、原因块、步骤块 chunks self.semantic_chunking(page.get_text()) # 3. 过滤只返回含 query 中故障代码的 chunk return [c for c in chunks if query.split()[0] in c.metadata.get(fault_codes, [])] # pipeline/core.py - 核心 pipeline class RAGPipeline: def __init__(self): self.embedding_model SentenceTransformer(EMBEDDING_MODEL_NAME) self.chroma_client chromadb.PersistentClient(pathCHROMA_DB_PATH) self.es_client Elasticsearch(ES_HOST) def retrieve(self, query: str, source: str) - List[Chunk]: # 双路检索 semantic_results self._vector_retrieve(query, source) keyword_results self._es_retrieve(query, source) # RRF 融合 return self._rrf_fusion(semantic_results, keyword_results) def _vector_retrieve(self, query: str, source: str) - List[Chunk]: query_embedding self.embedding_model.encode([query])[0] collection self.chroma_client.get_collection(source) results collection.query(query_embeddings[query_embedding], n_results10) return [Chunk(contentr, metadatam) for r, m in zip(results[documents][0], results[metadatas][0])] def _es_retrieve(self, query: str, source: str) - List[Chunk]: #