【灶台导航】 RAG系统的容错设计:从向量搜索到关键词降级,一个都不能少

news2026/5/16 7:11:09
当三个外部依赖都可能随时挂掉时如何保证用户永远有响应问题完美主义害死人做RAG系统时我们很容易陷入一种思维定势向量检索要准、LLM要强、整个链路要丝滑。但现实是——任何一个外部服务挂了用户就得不到响应。在微信小程序这种C端场景可用性比准确性重要得多。用户不在乎你用的是Qdrant还是Pinecone他只知道点进来白屏关掉再也不会用。我们系统依赖三个外部服务SiliconFlow Embedding API将用户问题转成向量Qdrant向量数据库存储和检索菜谱向量DeepSeek LLM基于检索结果生成回复任何一个挂了如果按传统思路直接报错用户就流失了。本文分享我们如何在云函数环境中用四级降级策略保证系统永远有响应——宁可推荐不够精准也不能白屏无响应。容错架构四层兜底层层递进整体架构如下用户消息 │ ▼ ┌─────────────────────┐ │ 第1级: Qdrant向量搜索 │ ──超时/失败──┐ └─────────┬───────────┘ │ │ ▼ 有高分结果? ┌─────────────────────┐ │ │ │ 第2级: TF-IDF关键词检索 │ ──超时/失败──┐ │ └────── 成功 ──────→ │ (纯JS无外部依赖) │ │ │ └─────────┬───────────┘ │ │ │ ▼ │ 有高分结果? ┌──────────────┐ │ │ │ │ 第3级: 无RAG │ │ │ └── 成功 ──→ 用TF-IDF结果 │ LLM直接回答 │ │ └──── 失败 ──→ └──────┬───────┘ │ │ └────────────────────── 统一送入DeepSeek ──────────────────────┘ │ DeepSeek也挂了? │ ▼ ┌──────────────────────┐ │ 第4级: fallbackResponse │ │ 本地关键词数据库查询 │ └──────────────────────┘核心设计原则每一级都不依赖上一级每一级都有自己的超时控制失败后静默降级。第1级→第2级向量检索失败时的TF-IDF降级向量检索失败可能的原因Embedding API 超时5秒无响应Qdrant 查询超时3秒无响应网络错误ETIMEDOUT、ECONNREFUSEDQdrant 服务完全不可用降级逻辑的核心代码// chat/index.js - retrieveContext()asyncfunctionretrieveContext(message,openid){letsystemRagRecipes[]letcontextText// ═══ 尝试向量搜索 ═══try{constvectorResultsawaitwithTimeout(qdrant.vectorSearch(message,3),TIMEOUT_CONFIG.embeddingTIMEOUT_CONFIG.qdrant,// 8秒总超时向量检索超时)constrelevantvectorResults.filter(rr.similarity0.3)if(relevant.length0){systemRagRecipesrelevant.map(rr.recipe)contextTextformatContext(relevant)}}catch(e){// Qdrant挂了或超时静默降级 — 用户完全无感知console.warn([RAG-Vector] 向量检索失败降级为 TF-IDF:,e.message)}// ═══ 仅在向量搜索无结果时走TF-IDF ═══if(systemRagRecipes.length0){const{data:recipes}awaitdb.collection(recipes).where({isPrivate:_.neq(true)}).limit(50).get()constresultssearch(message,recipes,3)// TF-IDF检索constrelevantresults.filter(rr.similarity0.05)// 阈值更宽松if(relevant.length0){systemRagRecipesrelevant.map(rr.recipe)contextTextformatContext(relevant)}}return{contextText,ragRecipes:systemRagRecipes}}关键设计细节1. 静默降级catch块中只打日志不抛异常用户永远看不到系统繁忙之类的错误提示。2. 阈值差异TF-IDF用0.05向量用0.3。因为两者的分数分布不同TF-IDF的相似度天然更低阈值需要调低。3. 只查系统菜谱降级时过滤isPrivate避免查到其他用户的私人菜谱造成隐私问题。TF-IDF的纯JS实现零依赖才能真兜底为什么TF-IDF能成为可靠的第二级因为它是纯JavaScript实现不依赖任何外部服务、不依赖C扩展、不依赖网络。云函数环境中很多npm包需要编译如jieba分词部署困难。我们实现了一个轻量级的中文TF-IDF中文分词二元组停用词// tfidf.jsconstSTOP_WORDSnewSet([的,了,是,在,和,也,都,不,就,有])functiontokenize(text){constchineseChunkstext.match(/[\u4e00-\u9fa5]/g)||[]consttokens[]for(constchunkofchineseChunks){// 双字组合匹配菜名、食材名for(leti0;ichunk.length-1;i){constbigramchunk[i]chunk[i1]if(!STOP_WORDS.has(bigram))tokens.push(bigram)}}returntokens}为什么用bigram而不是jieba因为云函数装不了C扩展而bigram对短文本菜名、食材名效果足够好。像宫保鸡丁会被切分为[“宫保”,“保鸡”,“鸡丁”]足够匹配用户查询。增强归一化TFfunctioncomputeTFIDF(tokens,idf){consttf{}for(consttoftokens)tf[t](tf[t]||0)1constmaxTfMath.max(...Object.values(tf))consttfidf{}for(const[term,freq]ofObject.entries(tf)){// 增强归一化避免长文档的单字频率被过度归一化constnormalizedTf0.50.5*(freq/maxTf)tfidf[term]normalizedTf*(idf[term]||1)}returntfidf}0.5 0.5 * (tf/maxTf)比简单的tf/maxTf更柔和避免长文档中重复词失去权重。IDF平滑// 预处理计算IDFfunctionprecomputeIdf(recipes){constNrecipes.lengthconstdf{}for(constrecipeofrecipes){consttokenstokenize(recipe.name (recipe.ingredients||[]).join( ))constuniquenewSet(tokens)for(consttermofunique){df[term](df[term]||0)1}}constidf{}for(const[term,freq]ofObject.entries(df)){// 1平滑防止除零再1偏移保证IDF不为0idf[term]Math.log((N1)/(freq1))1}returnidf}超时控制每个环节都有底线没有超时控制的降级是假降级——一个请求卡住3分钟用户早走了。constTIMEOUT_CONFIG{embedding:5000,// Embedding API: 5秒qdrant:3000,// Qdrant查询: 3秒ragQuery:3000,// TF-IDF查询: 3秒deepseekApi:30000,// DeepSeek: 30秒cloudFunction:34000// 云函数总超时: 34秒留1秒缓冲}// 用Promise.race实现functionwithTimeout(promise,ms,errorMsg){returnPromise.race([promise,newPromise((_,reject)setTimeout(()reject(newError(errorMsg)),ms))])}关键原则每个外部调用独立超时Embedding超时不影响后续降级总超时兜底云函数34秒超时保证不会无限等待超时即降级超时被当作失败进入下一级第3级LLM降级向量检索和TF-IDF都失败了怎么办走纯LLM生成无RAG上下文// chat/index.js - callDeepSeekAPI()try{constresponseawaitgot.post(deepseekUrl,{timeout:{request:TIMEOUT_CONFIG.deepseekApi}})// 解析响应...}catch(err){console.warn([DeepSeek] API调用失败走本地兜底:,err.message)returnawaitfallbackResponse(userMessage)}此时依赖就只剩下DeepSeek API了。但如果DeepSeek也挂了呢第4级本地关键词兜底最后一道防线不依赖任何外部API只依赖云数据库。asyncfunctionfallbackResponse(userMessage){constmsguserMessage.toLowerCase()// 1. 问候语直接回复不需要查库constgreetings[你好,hello,hi,嗨]if(greetings.some(gmsg.includes(g))){return{reply:你好我是灶台导航助手可以帮你推荐菜谱~,action:ask}}// 2. 关键词→分类映射constcategoryMap{牛:beef,猪:pork,鸡:chicken,鱼:fish,素:vegetarian,甜:dessert}letmatchedCategorynullfor(const[keyword,category]ofObject.entries(categoryMap)){if(msg.includes(keyword)){matchedCategorycategory;break}}// 3. 从数据库按分类或关键词查菜谱constdbcloud.database()const_db.commandletquerydb.collection(recipes)if(matchedCategory){queryquery.where({category:matchedCategory})}else{// 模糊匹配菜名constkeywordArrmsg.split(/[\s,、]/).filter(kk.length0)constorConditionskeywordArr.map(k({name:db.RegExp({regexp:k,options:i})}))queryquery.where(_.or(orConditions))}const{data:recipes}awaitquery.limit(3).orderBy(views,desc).get()if(recipes.length0){constreply为您推荐以下${matchedCategory?matchedCategory:相关}菜谱\nrecipes.map(r${r.name}${r.difficulty||中等}).join(\n)return{reply,recommendations:recipes,action:recommend}}// 连数据库都查不到——兜底的兜底return{reply:暂时没找到相关菜谱可以说说你想吃什么食材吗,action:ask}}这个兜底层有三大特点零外部依赖只调用云数据库即便Embedding/Qdrant/DeepSeek全挂也能工作规则驱动关键词映射不依赖AI100%确定渐进式降级问候语直接回复 → 分类匹配 → 模糊匹配 → 通用提示降级触发条件速查表降级级别触发条件用户感知向量→TF-IDF① Embedding API 超时(5s)② Qdrant 查询超时(3s)③ 网络错误④ 所有结果相似度 0.3无感知推荐精度略降TF-IDF→纯生成① 数据库查询超时(3s)② 所有结果相似度 0.05③ 数据库为空无感知回答无菜谱引用纯生成→兜底DeepSeek API 超时(30s) 或报错回复变简短可能答非所问兜底内部降级关键词/分类匹配失败返回通用提示继续对话为什么不合并多路结果有人会问为什么不做向量检索和TF-IDF的并行召回融合排序当前设计选择串行降级而非并行融合原因如下数据规模小菜谱数量在百级单路召回已经足够覆盖分数不可比向量相似度(01)和TF-IDF分数(0几十)尺度不同简单线性融合会引入噪声延迟更低向量检索成功时不需要等待TF-IDF执行串行反而更快代码简单逻辑清晰维护成本低如果后续扩展到万级数据可以考虑升级为多路并行召回Cross-Encoder重排序。完整的请求生命周期以一次完整对话为例追踪数据路径1. 用户输入家里有鸡肉和土豆想做点下饭的 2. 小程序调用云函数 wx.cloud.callFunction({ name: chat, data: { message: ... } }) 3. 云函数入口 exports.main() ├─ qdrant.setApiKey(sk-xxx) └─ processChat(event, openid) 4. retrieveContext() ├─ 尝试向量搜索 │ ├─ getEmbedding() → SiliconFlow API │ └─ searchQdrant() → 返回宫保鸡丁(0.72)、土豆烧鸡(0.68)、辣子鸡丁(0.51) ├─ 过滤相似度 0.3 → 3条通过 └─ 返回 RAG 上下文 5. callDeepSeekAPI() ├─ System Prompt 基础词 RAG上下文 └─ DeepSeek返回 → {reply:推荐宫保鸡丁...,action:recommend} 6. resolveRecipeIds() └─ 宫保鸡丁 → 匹配 ragRecipes 中的真实 _id 7. 保存会话到数据库 8. 返回小程序如果第4步向量检索失败会自动走TF-IDF降级后续流程完全一样用户无感知。降级效果实测场景1正常情况所有服务正常用户“想吃点下饭的”Qdrant返回宫保鸡丁(0.72)、鱼香肉丝(0.68)阈值0.3通过直接使用向量结果总耗时~2秒场景2Qdrant临时宕机Embedding API超时(5s)→抛异常自动降级TF-IDF按下饭关键词匹配找到标签含下饭的菜谱用户无感知耗时增加约1秒降级开销场景3用户说你好Qdrant返回低分结果(最高0.15)TF-IDF也无有意义匹配进入纯生成模式DeepSeek直接回复用户得到正常问候无报错场景4DeepSeek也挂了进入fallbackResponse关键词匹配回复您好请说食材名称…虽然不够智能但用户不会看到白屏关于Qdrant的安全提醒目前Qdrant直接暴露在公网且无认证存在风险风险任何人都可以查询/修改/删除你的向量数据数据泄露或被恶意清空两种加固方案方案一API Key认证推荐# docker-compose.ymlservices:qdrant:image:qdrant/qdrantenvironment:-QDRANT__SERVICE__API_KEYyour-secure-key-here方案二防火墙白名单更安全# 云厂商控制台设置入站规则# 只允许云函数所在网段访问 Qdrant 端口(6333,6334)后续扩展方向当前架构满足小规模场景后续可以迭代的方向查询改写先调LLM将模糊提问“晚上吃啥”改写为精确query“家常菜 简单 快手 30分钟”提高召回率多路召回重排序向量检索、TF-IDF、元数据过滤三路并行用Cross-Encoder重排序增量同步菜谱新增/修改后自动触发Qdrant更新而非全量重新同步缓存层对高频查询的Embedding结果做缓存减少SiliconFlow API调用混合检索利用Qdrant的filtering能力先按分类/难度过滤再做向量匹配监控告警增加各级降级的metrics上报及时发现问题小结容错不是锦上添花而是RAG系统上线的必备条件。核心思路分级降级向量搜索→关键词搜索→LLM直答→本地兜底每一级都不依赖上一级独立超时每个外部调用有自己的超时不互相阻塞超时即降级静默失败用户永远看不到技术错误只是推荐精度逐步降低零依赖兜底最后一道防线只依赖云数据库保证最低可用性在C端场景用户要的不是完美的推荐而是一个永远能用的产品。项目地址Gitee/ZaoTaiNavigation团队名称倒灶了队更新时间2026年5月

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2608562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…