☰
RAG信息充分性评估:用LangGraph实现联网搜索与结构化输出
2026/10/29 7:48:22 网站建设 项目流程

1. 从“缺料就出门买”说起:RAG 的信息充分性判断到底在解决什么

做过 RAG 项目的人大概都有过这种体验:用户问了一个问题,检索模块老老实实返回了 Top-K 文档,生成模块也老老实实基于这些文档编了一段话,看起来有模有样,但仔细一读——答非所问,或者干脆是模型在“硬编”。更尴尬的是,知识库里明明没有相关内容,系统却依然一本正经地输出,用户被误导了还不自知。

这个问题的根源,其实不在检索算法,也不在生成模型,而在于整个链路里缺了一个关键角色:信息充分性评估。换句话说,系统没有能力判断“我手上这些资料,到底够不够回答这个问题”。

我给这个机制起了个很形象的名字——“缺料就出门买”。就像做饭一样,厨房里食材齐不齐,得先看一眼。如果缺了主料,硬炒出来的菜肯定不对味,这时候正确的做法是出门去菜市场补货,而不是硬着头皮端上一盘四不像。对应到 RAG 系统里,“出门买”就是触发联网搜索或者调用外部工具去补充信息。

这篇文章要聊的,就是怎么给 RAG 装上这套“判断力”。核心关键词包括RAG、信息充分评估、联网搜索、LangGraph、结构化输出。我会从整体设计思路讲到具体实现,包括怎么用 LangGraph 编排这个判断流程、怎么用结构化输出让模型给出可靠的评估结论、什么情况下该触发联网搜索、以及实际落地时会踩哪些坑。适合已经做过基础 RAG、想进一步提升系统可靠性的朋友,也适合正在选型 Agent 框架的开发者参考。

先说结论:这套机制的价值不在于让 RAG “更聪明”,而在于让它“更诚实”。知道自己不知道,比假装什么都知道要重要得多。

2. 整体设计思路:为什么要在 RAG 里加一道“验货”环节

2.1 传统 RAG 链路的三个断点

大部分入门级 RAG 的实现是这样的:用户提问 → 向量检索 Top-K → 拼接 Prompt → LLM 生成。这条链路看起来简洁,但实际跑起来有三个明显的断点。

第一个断点是检索结果与问题的相关性没有校验。向量相似度高不等于语义相关,尤其是当知识库里存在大量主题相近但细节不同的文档时,检索出来的内容可能只是“看起来像”,实际答不了问题。我见过一个案例,用户问的是“退款流程第三步是什么”,检索返回的全是“退款政策总览”,相似度很高,但根本没有第三步的具体描述。

第二个断点是知识覆盖度没有评估。就算检索到的文档确实相关,也可能只覆盖了问题的一部分。比如用户问“这个 API 支持哪些参数,分别是什么类型”,检索只返回了参数列表但没有类型说明,这时候直接生成就会导致模型编造类型信息。

第三个断点是没有兜底策略。当检索结果确实不够时,系统要么硬答,要么直接说“我不知道”,前者会误导用户,后者体验很差。理想的做法是:先判断信息够不够,不够就去外部找,找到再答,找不到再老实说不知道。

2.2 信息充分性评估的定位

把信息充分性评估加进链路之后,整个流程变成了:检索 → 评估 → 分支决策。评估环节要回答的核心问题是:当前检索到的上下文,是否足以支撑一个准确、完整的回答?

这个判断需要从两个维度来看。一是相关性:检索到的内容是否真的和问题有关。二是完整性:这些内容是否覆盖了回答所需的全部关键信息。两个维度都满足,才叫“信息充分”。

这里有个容易混淆的点:信息充分性评估和传统的 Rerank 不是一回事。Rerank 解决的是“排序”问题,把最相关的文档排到前面;信息充分性评估解决的是“有无”问题,判断现有资料能不能支撑回答。前者是优化检索质量,后者是决定要不要走下一步。

2.3 为什么选 LangGraph 来编排

实现这套逻辑,可以用普通的 if-else 写,也可以用 LangChain 的 Chain 串起来。但我最终选了LangGraph,原因有三个。

第一,这个流程天然是有状态的分支图。检索是一个节点,评估是一个节点,评估结果决定走“直接生成”还是“联网搜索”分支,联网搜索之后可能还要再评估一次。这种带条件分支和循环的结构,用 LangGraph 的 StateGraph 表达最自然。

第二,LangGraph 的状态管理很适合这种多步骤场景。整个流程中需要传递的信息包括:原始问题、检索到的文档、评估结论、联网搜索的结果、最终答案等。用 TypedDict 定义好 State,每个节点读写自己需要的字段,逻辑清晰。

第三,可观测性和可调试性。LangGraph 的每个节点执行都可以单独追踪,出问题的时候能快速定位是检索的问题、评估的问题还是生成的问题。这在生产环境里太重要了。

2.4 结构化输出在评估环节的关键作用

评估环节最怕的是什么?是模型输出一段模棱两可的自然语言,比如“这些信息基本可以回答问题,但可能还需要补充一些细节”。这种输出没法用代码判断,等于白评估。

所以评估节点的输出必须结构化。我通常定义一个 Pydantic 模型,包含这几个字段:is_sufficient(布尔值,信息是否充分)、missing_aspects(列表,缺失的信息点)、reason(字符串,判断理由)、search_query(字符串,如果需要联网搜索,建议的搜索词)。

用结构化输出有两个好处。一是程序可判断,is_sufficient为 True 就走生成分支,为 False 就走搜索分支,逻辑干净。二是可解释,missing_aspects和reason字段让整个判断过程透明,出问题的时候能回溯模型为什么这么判断。

提示:结构化输出一定要用模型原生的 function calling 或 JSON mode,不要靠 Prompt 里写“请输出 JSON”然后自己解析字符串。后者在模型输出不稳定的时候会频繁解析失败,生产环境里是灾难。

3. 核心细节拆解:评估节点的 Prompt 设计与参数选择

3.1 评估 Prompt 的四个必备要素

评估节点的 Prompt 写得好不好,直接决定整个机制的可靠性。我踩过不少坑之后,总结出一个合格的评估 Prompt 必须包含四个要素。

要素一:明确的评估标准。不能只说“判断信息是否充分”,要给出具体的判断维度。我通常这样写:“请从以下两个维度评估:1)相关性——检索内容是否直接回应了用户问题;2)完整性——检索内容是否覆盖了回答所需的全部关键信息点。两个维度都满足才算充分。”

要素二:原始问题和检索内容都要完整传入。有些实现为了省 token,只传检索内容的摘要,这是大忌。评估需要看到完整的问题和完整的上下文,摘要会丢失关键细节,导致误判。

要素三:输出格式的严格约束。用 Pydantic 模型定义好 schema,通过 function calling 强制模型按格式输出。字段名要语义清晰,比如is_sufficient比flag好,missing_aspects比gaps好。

要素四:few-shot 示例。这是提升评估准确率最有效的手段。给两三个示例,一个“充分”的案例,一个“不充分”的案例,一个“边界情况”的案例。示例不用长,但要覆盖典型场景。

3.2 评估模型的选型考量

评估节点用什么模型,是个需要权衡的问题。我试过三种方案,各有优劣。

方案优势劣势适用场景
大模型(如 GPT-4 级别)判断准确率高,边界情况处理好成本高,延迟大对准确性要求极高的场景
小模型(如 7B 级别)成本低,速度快复杂判断容易出错问题类型单一、预算有限的场景
规则 + 小模型混合简单情况用规则,复杂情况用模型实现复杂度高有明确规则可循的垂直领域

我个人的选择是:评估用中等规模的模型,生成用大模型。因为评估任务本质是分类 + 信息抽取,比生成任务简单,中等模型足够。而生成任务需要语言组织和推理能力,值得用大模型。这样在成本和效果之间取得平衡。

3.3 检索参数对评估结果的影响

评估的准确性不只取决于评估节点本身,还和上游检索的参数密切相关。这里有两个参数需要特别注意。

Top-K 的选择。K 太小,检索内容不全,评估容易误判为“不充分”;K 太大,引入噪声文档,评估容易被无关信息干扰。我的经验值是 K=5 到 K=8 之间。如果知识库文档粒度较细(比如按段落切分),可以取大一点;如果文档粒度较粗(比如按章节切分),取小一点。

相似度阈值。设置一个最低相似度阈值,低于阈值的文档直接丢弃,不进入评估环节。这个阈值需要根据 embedding 模型的特性来调。我通常先用一批标注数据跑一遍,看正样本和负样本的相似度分布,取一个能分开两者的值。一般余弦相似度在 0.7 到 0.75 之间是个合理的起点。

注意:相似度阈值不要设得太高。设太高会导致召回率下降,本来能回答的问题因为检索不到足够文档而被判定为“不充分”,触发不必要的联网搜索,浪费资源。

3.4 状态字段的设计

LangGraph 的 State 设计是个容易被忽视但很重要的细节。我建议至少包含以下字段:

  • question:用户原始问题,字符串
  • retrieved_docs:检索到的文档列表
  • is_sufficient:评估结论,布尔值
  • missing_aspects:缺失信息点,字符串列表
  • search_query:联网搜索用的查询词,字符串
  • web_results:联网搜索结果,字符串
  • final_answer:最终答案,字符串
  • retry_count:重试次数,整数

retry_count这个字段很关键。联网搜索之后如果评估仍然不充分,不能无限循环下去,要设一个最大重试次数(通常 1 到 2 次),超过就直接走“诚实回答”分支,告诉用户当前信息不足。

4. 实操过程:用 LangGraph 搭建带信息评估的 RAG 流程

4.1 环境准备与依赖安装

先把基础环境搭起来。我用的 Python 3.10+,核心依赖是 langgraph、langchain、langchain-openai 和 pydantic。

pip install langgraph langchain langchain-openai pydantic

如果你用的是其他模型提供商,把 langchain-openai 换成对应的包就行。向量库我用的 Chroma,本地开发方便,生产环境可以换 Milvus 或 Qdrant。

pip install chromadb

联网搜索这块,我用的是一个通用的搜索 API 封装。具体选哪家看你的预算和需求,核心是要有一个能返回结构化搜索结果的接口。这里我用一个抽象的函数来表示,实际替换成你自己的搜索实现即可。

4.2 定义状态和结构化输出模型

先定义 State 和评估输出的 Pydantic 模型。这两个是整个流程的基础。

from typing import TypedDict, List from pydantic import BaseModel, Field class RAGState(TypedDict): question: str retrieved_docs: List[str] is_sufficient: bool missing_aspects: List[str] search_query: str web_results: str final_answer: str retry_count: int class SufficiencyEval(BaseModel): is_sufficient: bool = Field( description="检索内容是否足以完整回答用户问题" ) missing_aspects: List[str] = Field( description="如果信息不充分,列出缺失的关键信息点" ) reason: str = Field( description="做出该判断的简要理由" ) search_query: str = Field( description="如果需要补充信息,建议的搜索查询词" )

这里missing_aspects和search_query的设计有个小心机:当评估为“不充分”时,模型不仅要指出缺什么,还要直接给出搜索词。这样联网搜索节点就不用再额外做一次查询改写,省了一个步骤,也减少了信息损失。

4.3 检索节点的实现

检索节点负责从向量库拉取相关文档。这里的关键是控制好 Top-K 和相似度阈值。

def retrieve_node(state: RAGState) -> dict: question = state["question"] # 向量检索,返回 Top-6 docs = vectorstore.similarity_search_with_score( question, k=6 ) # 过滤低相似度文档 filtered = [ doc.page_content for doc, score in docs if score >= 0.72 ] return {"retrieved_docs": filtered}

相似度阈值 0.72 是我在几个项目里调出来的经验值,但你要根据自己的 embedding 模型和数据集去调。调的方法很简单:准备 50 条左右的标注数据,一半是知识库能回答的,一半是不能回答的,跑一遍看分数分布,找一个能分开两类的阈值。

4.4 评估节点的实现

评估节点是整个流程的核心。Prompt 的设计直接决定判断质量。

from langchain_openai import ChatOpenAI eval_llm = ChatOpenAI( model="gpt-4o-mini", temperature=0 ).with_structured_output(SufficiencyEval) EVAL_PROMPT = """你是一个信息充分性评估专家。请判断以下检索内容是否足以完整回答用户问题。 评估标准: 1. 相关性:检索内容是否直接回应了用户问题 2. 完整性:检索内容是否覆盖了回答所需的全部关键信息点 两个标准都满足才算充分。 用户问题:{question} 检索内容: {docs} 请给出你的评估结论。如果信息不充分,请列出缺失的关键信息点,并给出一个适合用于联网搜索的查询词。""" def evaluate_node(state: RAGState) -> dict: docs_text = "\n---\n".join(state["retrieved_docs"]) prompt = EVAL_PROMPT.format( question=state["question"], docs=docs_text ) result = eval_llm.invoke(prompt) return { "is_sufficient": result.is_sufficient, "missing_aspects": result.missing_aspects, "search_query": result.search_query }

temperature=0是必须的,评估任务要的是稳定和一致,不需要创造性。with_structured_output会自动处理 function calling 的细节,把模型输出解析成 Pydantic 对象。

4.5 联网搜索节点的实现

当评估判定信息不充分时,走联网搜索分支。搜索节点用评估节点给出的search_query去查。

def web_search_node(state: RAGState) -> dict: query = state["search_query"] # 调用搜索 API,这里用抽象函数表示 results = search_api.search(query, max_results=5) # 把搜索结果格式化成文本 formatted = "\n---\n".join([ f"标题:{r['title']}\n内容:{r['snippet']}" for r in results ]) return { "web_results": formatted, "retry_count": state.get("retry_count", 0) + 1 }

搜索结果的格式化要注意保留来源信息,方便后续生成时引用。另外retry_count每次搜索都加一,用于控制循环次数。

4.6 生成节点的实现

生成节点要处理两种情况:一种是检索内容充分,直接基于检索内容生成;另一种是联网搜索之后,基于检索内容 + 搜索结果生成。

GEN_PROMPT = """请基于以下信息回答用户问题。如果信息中有不确定的内容,请明确说明。 用户问题:{question} 参考资料: {docs} {web_section} 请给出准确、完整的回答。""" def generate_node(state: RAGState) -> dict: docs_text = "\n---\n".join(state["retrieved_docs"]) web_section = "" if state.get("web_results"): web_section = f"补充网络资料:\n{state['web_results']}" prompt = GEN_PROMPT.format( question=state["question"], docs=docs_text, web_section=web_section ) answer = gen_llm.invoke(prompt).content return {"final_answer": answer}

4.7 用条件边把流程串起来

最后用 LangGraph 的 StateGraph 把所有节点串起来,关键是条件边的设计。

from langgraph.graph import StateGraph, END def route_after_eval(state: RAGState) -> str: if state["is_sufficient"]: return "generate" if state.get("retry_count", 0) >= 2: return "generate" # 重试超限,直接生成(诚实回答) return "web_search" workflow = StateGraph(RAGState) workflow.add_node("retrieve", retrieve_node) workflow.add_node("evaluate", evaluate_node) workflow.add_node("web_search", web_search_node) workflow.add_node("generate", generate_node) workflow.set_entry_point("retrieve") workflow.add_edge("retrieve", "evaluate") workflow.add_conditional_edges( "evaluate", route_after_eval, { "generate": "generate", "web_search": "web_search" } ) workflow.add_edge("web_search", "evaluate") workflow.add_edge("generate", END) app = workflow.compile()

注意web_search之后又回到了evaluate,形成一个小循环。这样搜索完之后会重新评估一次,如果补充的信息够了就生成,不够且没超过重试次数就再搜,超过就直接生成。这个设计比“搜完直接生成”要稳妥得多。

4.8 完整流程的调用示例

result = app.invoke({ "question": "LangGraph 的条件边怎么用?", "retrieved_docs": [], "is_sufficient": False, "missing_aspects": [], "search_query": "", "web_results": "", "final_answer": "", "retry_count": 0 }) print(result["final_answer"])

跑起来之后,你可以通过打印中间状态来观察整个流程。我调试的时候习惯在每个节点加日志,记录is_sufficient的值和retry_count,这样能清楚看到流程走了哪条分支。

5. 常见问题与排查技巧实录

5.1 评估总是判定“不充分”怎么办

这是最常见的问题。系统频繁触发联网搜索,成本和延迟都上去了。原因通常有三个。

原因一:评估 Prompt 太严格。有些 Prompt 写的是“必须完全覆盖所有细节才算充分”,这个标准太高了。实际场景里,回答一个问题不需要覆盖所有细节,抓住核心信息点就行。把标准调整为“覆盖回答所需的关键信息点”会好很多。

原因二:检索质量差。检索出来的文档本身就不相关,评估当然说不充分。这时候要回头检查切分策略和 embedding 模型。我遇到过按固定长度切分导致语义断裂的情况,换成按语义切分之后,检索质量明显提升。

原因三:相似度阈值设太高。前面提过,阈值太高会过滤掉本来有用的文档。把阈值降 0.05 试试,往往能解决。

5.2 评估判定“充分”但生成质量差

反过来,评估说够了,但生成出来的答案还是不行。这种情况通常是评估模型被“表面相关”骗了。

比如用户问“A 和 B 的区别是什么”,检索返回了一堆分别介绍 A 和 B 的文档,评估模型看到 A 和 B 都出现了,就判定充分。但实际上这些文档没有做对比,生成模型只能自己编对比内容。

解决办法是在评估 Prompt 里加一条:“如果问题涉及比较、推理或多步计算,请确认检索内容是否包含支撑这些操作的直接依据。”这一条能拦住大部分“表面相关”的误判。

5.3 联网搜索结果质量不稳定

联网搜索的结果质量取决于搜索 API 和查询词。查询词的质量又取决于评估节点给出的search_query。如果搜索词太宽泛,返回的结果会很杂;太窄,又搜不到东西。

我的经验是,在评估 Prompt 里明确要求搜索词要“具体、包含关键实体、避免过于宽泛的表述”。另外可以在搜索节点加一个查询改写步骤,用一个小模型把search_query优化一下再搜。但这会增加延迟,看你的场景能不能接受。

5.4 循环次数控制与死循环防范

LangGraph 的条件边如果设计不当,可能出现死循环。防范措施有两个。

一是设置最大重试次数。我在route_after_eval里判断retry_count >= 2就强制走生成分支,不管评估结果如何。这样最多搜两次,不会无限循环。

二是在搜索节点做去重。如果两次搜索返回的结果高度相似,说明搜索词没起到补充作用,这时候可以直接跳过第二次评估,走生成分支。实现方式是在 State 里记录上一次的搜索结果,比较相似度。

5.5 常见问题速查表

问题现象可能原因排查方向解决建议
频繁触发联网搜索评估标准过严 / 检索质量差检查评估 Prompt 和检索结果放宽评估标准,优化切分策略
评估充分但答案差表面相关误判检查是否涉及比较推理类问题评估 Prompt 增加推理依据检查
搜索结果不相关搜索词质量差查看 search_query 内容优化评估 Prompt 中的搜索词要求
流程死循环条件边设计缺陷检查 retry_count 逻辑设置最大重试次数,搜索去重
结构化输出解析失败模型不支持 function calling确认模型能力换支持 function calling 的模型
评估延迟高评估模型太大检查模型选型评估换中等模型,生成用大模型

5.6 几个我踩过的坑

坑一:评估节点和生成节点用同一个模型实例。看起来省事,但两个节点的 temperature 需求不同。评估要 temperature=0,生成可能要 0.3 到 0.7。用同一个实例会导致其中一个效果打折。一定要分开配置。

坑二:检索内容直接拼接不做分隔。多个文档拼在一起,模型分不清边界,评估和生成都会受影响。用明确的分隔符(比如---)隔开,并在 Prompt 里说明“以下内容由多个文档组成,用 --- 分隔”。

坑三:忽略 token 长度限制。检索内容 + 搜索结果 + Prompt 模板,很容易超过模型的上下文窗口。要在拼接前做 token 计数,超了就截断或摘要。我一般留 20% 的余量给模型输出。

坑四:没有记录评估日志。上线之后出问题,没有日志根本没法排查。每个评估节点的输入输出都要落盘,包括问题、检索内容、评估结论、理由。这些数据后续还能用来微调评估模型。

6. 进阶优化:让信息充分性评估更可靠

6.1 用少量标注数据校准评估模型

评估节点的准确率不能靠感觉,要靠数据。我的做法是:从真实用户问题里采样 100 条,人工标注每条问题的“信息充分性”标签,然后跑一遍评估节点,看准确率和混淆矩阵。

如果发现“不充分”被误判为“充分”的比例高,说明评估标准太松,要收紧;反之则要放宽。这个过程通常迭代两三轮就能把准确率调到 90% 以上。

标注数据不用多,100 条足够看出问题。关键是标注要一致,最好由同一个人标,或者先对齐标注标准再多人标。

6.2 评估结果的缓存策略

同样的检索内容 + 同样的问题,评估结果应该是一样的。所以可以做缓存,避免重复调用模型。

缓存的 key 用question + hash(retrieved_docs)生成。缓存有效期看你的知识库更新频率,更新频繁就设短一点,比如 1 小时;更新不频繁可以设 24 小时。

这个优化在高并发场景下效果很明显,能省不少模型调用成本。但要注意,缓存的是评估结果,不是最终答案,因为最终答案还依赖生成模型的状态。

6.3 多轮对话场景下的评估调整

单轮问答的评估逻辑比较直接,但多轮对话就复杂了。用户的问题可能依赖上文,比如“那它的参数呢”这种指代性问题。

处理方式是:在评估之前先做一次查询改写,把多轮对话中的指代消解掉,还原成完整问题,再走评估流程。查询改写可以用一个小模型做,成本不高但效果显著。

另外,多轮对话里可以把历史轮次的检索结果也纳入评估范围。如果上一轮已经检索到了相关文档,这一轮可以复用,不必重新检索。这需要在 State 里维护一个历史文档池。

6.4 评估阈值的动态调整

固定的评估标准在不同类型的问题上表现不一样。事实型问题(“X 是什么”)标准可以严一点,因为答案明确;开放型问题(“如何看待 X”)标准可以松一点,因为答案本身就有多种可能。

实现方式是在评估 Prompt 里先让模型判断问题类型,然后根据类型应用不同的评估标准。这增加了 Prompt 的复杂度,但能提升评估的适应性。我一般只在问题类型差异很大的场景下才这么做,否则得不偿失。

6.5 与知识库更新机制的联动

信息充分性评估的结果,其实反映了知识库的覆盖情况。如果某类问题频繁被判定为“不充分”,说明知识库在这块有缺口,应该补充相关文档。

我习惯定期导出评估日志,统计“不充分”问题的分布,按主题聚类,然后反馈给知识库维护团队。这个闭环跑起来之后,知识库的质量会持续提升,联网搜索的触发频率也会逐渐下降。

这个机制的价值在于,它把 RAG 系统从“被动检索”变成了“主动发现知识缺口”。长期来看,这比单纯优化检索算法更有价值。

提示:评估日志的统计不要只看“不充分”的比例,还要看“不充分”问题的具体缺失点。有时候不是知识库缺文档,而是切分策略导致信息被割裂了,这种情况补充文档没用,要调整切分逻辑。

6.6 性能与成本的平衡

最后聊聊成本和性能。加了评估节点和联网搜索之后,系统的延迟和成本都会上升。评估节点增加一次模型调用,联网搜索增加一次 API 调用和一次额外的评估调用。

优化方向有三个。一是评估模型用小模型,前面提过,评估任务比生成任务简单,小模型够用。二是缓存评估结果,减少重复调用。三是控制联网搜索的触发频率,通过校准评估标准,让真正需要搜索的场景才触发。

我实测下来,在评估准确率调到 90% 以上的情况下,联网搜索的触发比例大概在 15% 到 25% 之间。这个比例是合理的,既保证了回答质量,又没有让成本失控。如果你的触发比例超过 40%,大概率是评估标准太严或者检索质量有问题,要回头排查。

这套机制上线之后,最直观的变化是用户投诉“答非所问”的比例明显下降。系统不再硬答自己不知道的问题,而是要么补充信息后给出准确回答,要么诚实地告诉用户当前信息不足。这种“知道自己不知道”的能力,恰恰是 RAG 系统从 demo 走向生产的关键一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询