公司动态

EduAgent 项目全解析(三):RAG 智能问答 Agent——混合检索、HyDE 与 Multi-Query

📅 2026/8/22 16:45:14
EduAgent 项目全解析(三):RAG 智能问答 Agent——混合检索、HyDE 与 Multi-Query
本篇是系列的核心篇。我们拆解 EduAgent 里最复杂的QA 智能问答 Agent10 个节点、3 层条件路由的 LangGraph 状态图背后是意图分类 → 混合检索 → 精排 → 生成的完整 RAG 流水线还引入了 HyDE、Multi-Query 两个高级检索策略。读完你会理解一个生产级 RAG 系统是怎么把成本、延迟、准确率平衡到极致的。一、先看整体QA Agent 的状态图GENERAL_WEBGENERALPRECISEVAGUEBROAD来自GENERALhigh置信度low_weblow_direct来自低置信度STARTclassify_query意图分类web_searchMCP联网搜索generate_generalLLM直答retrieve混合检索精排hyde_generate假设文档生成multi_query_rewrite子问题改写generate_ragRAG回答generate_directLLM直答save_memoryenqueue_pending低分问题入队END图中的条件路由在graph.py里用add_conditional_edges实现三个路由函数分别处理Query 类型分流、置信度分流、web_search 出口分流。二、三层意图分类把该不该检索判断到极致分类节点classify_query_node的核心目标判断用户问题是通用闲聊还是课程专业问题以及该用哪种检索策略。它采用规则 → MiniLM → LLM三层结构逐层递进能省则省2.1 Layer 0规则快判零成本# 命中即确认为专业问题跳过 MiniLM直接进策略判定_SPECIALIZED_KEYWORDS(课程,实战,项目,案例,老师,章节,作业,课堂,培训,我们学的,课程项目,第几章,第几节,训练营,)def_rule_classify_specialized(query:str)-bool:规则层是否含课程/项目信号词→ 专业qquery.lower()returnany(kwinqforkwin_SPECIALIZED_KEYWORDS)2.2 Layer 1MiniLM 二分类器CPU 推理毫秒级课程信号词没命中时用本地微调过的 MiniLM 模型做general / specialized二分类。这里有个很讲究的阈值设计# general 侧置信度阈值设为 0.85# 专业问题被误判为通用问题的代价更高——LLM 会用自身知识回答# 可能与课程内容矛盾。宁可多走一次 RAG不要漏掉课程相关问题。GENERAL_CONFIDENCE_THRESHOLD0.85defclassify(self,text:str)-tuple[str,float]:raw_outputsself._pipeline(text)[0]general_scorenext(i[score]foriinraw_outputsifi[label]general)ifgeneral_scoreGENERAL_CONFIDENCE_THRESHOLD:returngeneral,general_scorereturnspecialized,1.0-general_score# 保守偏向专业偏向 specialized 的原因专业问题被误判成通用问题的代价更高LLM 用通用知识回答可能与课程内容矛盾所以宁可多走一次 RAG。分类器本身也是用tmp/training_data.jsonl微调出来的query_classifier.py里包含完整的 Trainer 训练代码分层切分、早停、F1 评估这是自训练小模型 大模型的经典组合。2.3 Layer 2LLM 精判检索策略PRECISE / VAGUE / BROAD对问题做 RAG 策略判定时先走规则快判_fast_rag_strategy1ms只有规则判为模糊VAGUE/BROAD**且问题较长≥18字**时才调 LLM 校正避免误判asyncdef_determine_rag_strategy_fast(query:str)-str:strategy_fast_rag_strategy(query)ifstrategyPRECISE:returnstrategy# 规则已明确不调 LLMiflen(query.strip())18:returnawait_determine_rag_strategy(query)# 长问题才校正returnstrategy# 极短问题直接信规则三种策略的含义PRECISE问题明确直接向量检索“Redis ZSet 的底层数据结构是什么”VAGUE问题模糊“没太懂这块”先 HyDE 生成假设文档扩充语义BROAD问题过宽“讲讲微服务”拆成多个子问题并行检索三、混合检索Dense Sparse 双路召回3.1 BGE-M3一次推理双输出knowledge_base.py的BGEMEmbedder用 BGE-M3 模型一次推理同时输出两种向量dense_vecsoutput[dense_vecs].tolist()# 1024维语义向量sparse_vecs[{int(k):float(v)fork,vind.items()}# {token_id: weight} 关键词向量fordinoutput[lexical_weights]]为什么需要混合检索稠密向量擅长语义相似“Java 里对象怎么比较↔equals 方法”但可能漏掉精确术语稀疏向量擅长关键词精确命中“HashMap” 就是 “HashMap”但不懂语义。两者互补。细节稀疏向量必须做{int(k): float(v)}类型转换——LangGraph MemorySaver 用 msgpack 序列化 Statemsgpack 不支持 numpy 类型不转会在运行时抛 TypeError。这是本地模型 LangGraph 组合的经典坑。3.2 Milvus Hybrid 检索 WeightedRanker 融合dense_reqAnnSearchRequest(data[query_embedding],anns_fieldembedding,param{metric_type:COSINE,params:{ef:64}},# HNSWlimittop_k,exprfilters)sparse_reqAnnSearchRequest(data[query_sparse],anns_fieldsparse_embedding,param{metric_type:IP},# 稀疏向量用内积limittop_k,exprfilters)resultsself._client.hybrid_search(collection_nameCOLLECTION_NAME,reqs[dense_req,sparse_req],rankerWeightedRanker(0.7,0.3),# 稠密权重0.7稀疏权重0.3limittop_k,output_fieldsoutput_fields)两个 ANN 请求在 Milvus 服务端并行执行用WeightedRanker(0.7, 0.3)加权融合排序。语义匹配为主0.7、关键词匹配为辅0.3召回 Top-10 候选。多租户隔离检索时用_build_filter()生成tenant_id xxx的布尔表达式并做了引号转义防注入def_build_filter(tenant_id:str,course_id:Optional[str]None)-str:safe_tenanttenant_id.replace(,\\)exprftenant_id {safe_tenant}ifcourse_id:exprf and course_id {safe_course}returnexpr四、BGE-Reranker 精排用 CrossEncoder 把质量再筛一轮Hybrid 检索召回的 Top-10 是排序信号而非相关性概率直接喂给 LLM 浪费 token 且可能引入噪声。reranker.py用 BGE-RerankerCrossEncoder做第二道精排defrerank_with_confidence(self,query,documents,top_k3):# 截断长文档防止超出 CrossEncoder max_length512pairs[(query,(doc.get(content)or)[:1200])fordocindocuments]scoresself._model.predict(pairs).tolist()# 输出 [0,1] 概率rankedsorted([...],keylambdax:x.score,reverseTrue)top_resultsranked[:top_k]confidencetop_results[0].scoreiftop_resultselse0.0returntop_results,confidence置信度阈值0.75是整条 RAG 管线的决策点def_route_by_confidence(state:QAState)-str:ifstate.get(is_high_confidence,False):returnhigh# ≥0.75RAG 高质量回答ifstate.get(enable_web_search,False):returnlow_web# 低置信 允许联网先搜索再回答returnlow_direct# 否则LLM 直答兜底reranker.py最后提供一个一体化入口retrieve()调用方只需传query tenant_id三步闭环向量化 → Hybrid 检索 → 精排内部完成——这是把复杂封装给调用方的好设计。五、两个高级检索策略HyDE 与 Multi-Query5.1 HyDEVAGUE 分支模糊问题先生成假设文档用户说我没太懂这块直接拿这句话去检索效果很差。HyDE 的思路先用 LLM 根据对话上下文推断用户想问的具体问题并生成一段专业文档作为假设性回答再用这段文档的向量去检索HYDE_PROMPT你是一位 IT 培训助教。请根据以下对话上下文推断学员想问的具体技术问题 并生成一段高质量的技术文档片段作为假设性回答。 【对话上下文最近几轮】 {history} 【学员当前输入】 {query} 请直接输出一段专业的技术文档内容150-300字不要包含假设或可能等不确定语气。 输出格式纯文本可包含代码块。生成的假设文档与真实知识库文档在向量空间里更接近检索命中率显著提升。实现上就是hyde_generate_node调一次 LLM把结果写进state[hyde_document]retrieve_node用它替代原始 query 编码。5.2 Multi-QueryBROAD 分支宽泛问题拆成子问题讲讲微服务范围太宽一次检索召回可能不聚焦。Multi-Query 把宽泛问题改写成 3-5 个具体子问题是什么/为什么/怎么用/和什么区别每个子问题独立检索、合并去重asyncdefmulti_query_rewrite_node(state:QAState)-dict:promptMULTI_QUERY_REWRITE_PROMPT.format(last_answer...,querystate[original_query])respawaitllm.ainvoke([...])queries[line.strip()forlineinresp.splitlines()ifline.strip()][:MAX_BROAD_QUERIES]return{rewritten_queries:queries}retrieve 时对每个子问题召回 Top-4最后合并去重、统一送精排。注意改写时把上一轮 AI 回答内容也传了进去——用户说没懂往往是指上一条回答这个细节很贴心。六、低置信度问题入队 记忆管理6.1 enqueue_pending把没答好的问题留给教师asyncdefenqueue_pending_node(state:QAState)-dict:低置信度/直答的问题写入 knowledge_pending_queue 表 供教师后续审查补充知识库。# 把 question answer confidence 写入 pending 队列这是知识库闭环AI 没检索到的课程问题不会消失而是沉淀下来让教师审查甚至可以回灌训练数据——RAG 系统越用越准。6.2 save_memory滑动窗口 摘要压缩save_memory_node处理两类记忆窗口裁剪保留最近 10 轮对话SystemMessage 永远保留摘要压缩对话超 10 轮时用 LLM 把历史压成学员画像摘要保留薄弱点、项目背景丢弃已解决内容并做增量压缩防止重复记录摘要会写进数据库跨会话持久化下一轮对话加载进 System Prompt——学员画像在长对话中不断累积。七、本篇小结RAG 管线的完整链路用户输入 │ ├─ 联网指令提取不知道可以联网搜 → enable_web_searchTrue │ ▼ classify_query规则→MiniLM→LLM 三层分类 ├─ GENERAL ────────→ LLM 直答跳过检索 ├─ PRECISE ────────→ retrieve ├─ VAGUE ──────────→ HyDE 假设文档 → retrieve └─ BROAD ──────────→ Multi-Query 子问题 → retrieve │ ▼ BGE-M3 向量化 → Milvus Hybrid 检索Dense 0.7 Sparse 0.3 → BGE-Reranker 精排 Top-3 │ ▼ 置信度 ≥ 0.75 ──是──→ generate_ragRAG 回答来源 │否 ├─允许联网 → web_search → generate_direct └─否则 ───→ generate_directLLM 直答 │ ▼ enqueue_pending低分入队→ save_memory记忆更新贯穿全程的三个工程思想能省则省规则能判的绝不调模型短问题不调 LLM 校正分类阈值偏向保守。多级兜底检索失败走直答直答失败走 fallback永远有响应。闭环进化低置信度问题入队给教师沉淀成更好的知识库。下篇预告下一篇《EduAgent 项目全解析四简历审查 Agent——PDF 解析与六维并行评分》看一个直线图 无 checkpointer的简单 Agent 怎么做PyMuPDF 文本提取、LLM Function Calling 结构化提取、asyncio.gather六维并行评分、Think Tool 宏观分析再诊断。这是理解 LangGraph 和并行 LLM 调用的最佳入门案例。项目源码仅供参考欢迎评论区交流讨论。