基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-10-23 15:00

生产级 RAG 的难题藏在检索与文档更新里

dev.to · AI#RAG#混合检索#生产运维
Editor brief · 编辑速览

文章介绍基于 NestJS、PostgreSQL、pgvector 和 Amazon Bedrock 的文档问答系统,涉及混合检索与提示缓存。重点讨论文档更新后检索失效、答案质量下滑及缓存未降低成本等不会直接触发服务告警的问题。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

RAG 聊天机器人最麻烦的故障,往往没有报错:接口正常返回,数据库也没挂,但新文档搜不到,旧规则还在被引用,错误码 E-4021 被答成 E-4012。作者用 NestJS、PostgreSQL 和 Amazon Bedrock 搭了一套文档问答系统,重点讨论这些问题怎么沿着数据链路产生。

这篇值得读的地方,是把答案质量拆到了切块、向量化、检索、缓存和文档清理上。不过,原文展示的是一套面向生产的实现思路,没有给出线上准确率、延迟或成本对照,不能直接当成经过验证的生产方案。

答案不靠谱,先查检索拿到了什么

RAG(检索增强生成)的流程很直接:先找到相关文档片段,再把片段和问题交给模型,要求它依据材料回答并标注来源。模型能不能答对,首先取决于材料有没有找对。

作者把系统分成三段:离线索引负责读文件、切块、生成向量和入库;在线检索负责找片段;生成阶段负责组装提示词并调用 Claude。NestJS 10 用模块和依赖注入隔开这些职责,PostgreSQL 16 + pgvector 同时承担向量检索与全文检索,数据库访问使用普通 pg,没有引入 ORM。

这个结构对 Node 团队很实用。答案出错时,可以先查看检索结果,再决定是否调整提示词,不必在整条调用链里猜。

一次 POST /api/chat 请求包含 2 次 Bedrock 调用,即问题向量化和答案生成,以及并行执行的 2 条 SQL。请求体只允许 question 字段,空字符串、超过 2000 字符或携带未知字段都会返回 400。

先确认正确材料进入了上下文,再讨论模型为什么没答对。

作者把 RAG 与其他路线作了比较:全部文档塞进提示词适合很小的资料集,原文举的是几十页;RAG 更新知识只需重新入库,还能提供来源。不过,「微调只教风格、不教事实」说得过满。更稳妥的理解是,频繁变化且需要追溯的业务知识,用模型参数维护通常不如检索直接。

切块和清理旧数据,决定知识库会不会越用越乱

索引通过 npm run ingest [-- <folder>] 离线执行,使用 NestFactory.createApplicationContext(AppModule) 启动应用上下文,不开放 HTTP 端口。.md、.txt 各作为一份文档,.json 中带 text 的元素分别作为文档。

切块先按标题,再按长度。CHUNK_MAX_CHARS=1200,相邻片段重叠 CHUNK_OVERLAP_CHARS=120,长章节优先沿段落、句子边界拆分。这里的单位是字符,并非 token。

作者还把章节标题放进片段正文。保修示例中的「12 个月」因此带着「Warranty period」一起生成向量,减少脱离主题的问题。空标题章节则不生成片段。

向量模型使用 cohere.embed-multilingual-v3,输出 1024 维,每次最多提交 96 段文本。文档采用 input_type=search_document,问题采用 input_type=search_query;两端必须使用同一模型,并正确区分输入用途。配置中的 EMBEDDING_DIMENSIONS=1024 也要与数据库 vector(N) 一致,更换维度需要调整表结构并重新生成向量。

更新靠 <source>#<position> 形式的 chunkId 和 INSERT ... ON CONFLICT (chunk_id) DO UPDATE。写完后,deleteStale 删除同一文档不再存在的片段,例如从 5 块缩成 2 块时清掉剩余 3 块。

但这个实现留了几个明确缺口:每次都会重新向量化未修改内容;相对路径和绝对路径会被识别成不同来源;文件不再产生片段时,旧数据不会自动清理,彻底删除需要调用 ChunkRepository.deleteBySource。

因此,文档更新成功不能只看入库数量,还要检查旧内容是否消失。大资料集也应按作者建议保存内容哈希,跳过未变化的片段。

混合检索和缓存,为什么配置了也可能没收益

混合检索的两路各有用途:向量检索负责语义相近的表达,关键词检索补充错误码、名称等精确线索。前者使用 HNSW 索引和 embedding <=> $vec,后者使用 GIN 索引和 websearch_to_tsquery('simple', ...)。

问题在于,simple 不做词干还原,示例中的 product 匹配不到 products;普通空格分隔的词还会组合成 AND 条件。完整自然语言问题中的多个词若不同时出现在片段里,关键词分支就可能返回 0 条。保修示例确实如此,这比笼统宣称「混合检索更准」更有参考价值。

两路各取 CANDIDATE_LIMIT=50 个候选,再用倒数排名融合(RRF)合并。RRF_K=60,每路贡献 1 / (60 + rank),最终取 TOP_K=5。单路第一名得分约为 0.01639,两路都排第一则约为 0.03279。它根据排名融合,避免直接相加不同量纲的分数;关键词分支若没有结果,也不会凭空带来改善。

生成阶段的缓存,则取决于固定前缀是否真正复用。下面是原文的 InvokeModel 请求示例,用来展示固定规则与动态问题如何分开:

{
  "anthropic_version": "bedrock-2023-05-31",
  "max_tokens": 2000,
  "system": [
    { "type": "text", "text": "You are a Q&A assistant that answers from the provided documents..." },
    { "type": "text", "text": "Examples of good answers: ...",
      "cache_control": { "type": "ephemeral" } }
  ],
  "messages": [
    { "role": "user", "content": [
      { "type": "text", "text": "<context>\n[1] Warranty period\n...\n</context>" },
      { "type": "text", "text": "<question>How long is the product warranty?</question>" }
    ] }
  ]
}

关键看 cache_control 所在位置:作者希望复用系统提示词和少量示例,检索片段与问题仍按动态输入处理。ENABLE_PROMPT_CACHE=true 只是开关,实际收益要看缓存写入、读取 token 和调用条件。原文没有展开模型缓存门槛与有效期,不能据此保证第二次请求必然命中。

调用遇到 ThrottlingException 或 ServiceUnavailableException 时,分别等待 500ms、1s 重试,其他错误立即抛出。这解决短暂服务异常,不解决检索质量问题。

我的判断

我认可这套实现把数据维护和检索放在前面,也认可用 PostgreSQL 同时保存向量与全文索引:对于已有 PostgreSQL 的 Node 团队,少维护一套服务有实际价值。但作者没有提供与独立向量数据库、纯向量检索或加入重排模型后的对照数据,选型优势仍属于工程取舍。

它距离完整生产方案还有边界。当前 API 无状态,没有用历史对话改写问题;没有置信度阈值,检索不到材料也会调用模型;没有在代码中校验答案里的 [n] 是否存在。拒答示例可以引导模型,却不能替代这些检查。

返回的 citations 还包含全部送入上下文的片段,不等于全部被答案引用。前端需要依据回答实际出现的 [n] 过滤,并处理无效编号,避免把候选材料展示成已核验的证据。

原文使用 ap-northeast-1,列出生成模型示例 ID anthropic.claude-opus-5-5,并提示部分区域需要 inference profile。输入没有提供其可用性验证,不能把示例 ID 当成部署保证。提供的正文也在 loadConfig() 中途截断,没有完整代码和测试结果可供复现。

能拿来做什么

  • 做 NestJS 文档助手时,先用 npm run search -- "How long is the product warranty?" 查看候选片段和两路排名,把检索调通,再接聊天界面。
  • 为文档入库固定来源路径,保存内容哈希,并明确处理缩短、清空和删除文件三种情况,避免旧政策继续进入答案。
  • 前端将答案中的 [n] 与返回的 citations 对应,只展示实际引用且编号有效的来源,让用户能回到原始材料核对。
  • 上线前补充代表性问题集,分别检查自然语言、错误码和无答案问题;同时记录 cacheWriteTokens、cacheReadTokens,用结果判断检索与缓存是否有效。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
AI SDK 7 升级中自动迁移漏掉的坑
下一篇
Claude Code 与 Cursor 如何分工写代码

资讯速览

dev.to · AI · 2026-10-23 15:00

AI 评分 8.0 / 10

相关标签

#RAG#混合检索#生产运维

继续阅读

  • 选编程智能体,先看模型能否自由切换
  • AI SDK 7 升级中自动迁移漏掉的坑
  • Claude Code 与 Cursor 如何分工写代码
  • 物流语音Agent应先判断是否需要打电话
  • SQL只耗0.2毫秒,慢在跨区往返