GraphRAG Question Generation 全面解析:基于实体的候选问题生成机制与实战指南
本篇技术指南聚焦 GraphRAG 项目中基于实体的问题生成(Entity-based Question Generation)功能,系统讲解其从知识图谱结构化数据与输入文档非结构化数据中提炼候选追问问题的核心思路、代码级方法流程、参数配置与 Notebook 实战用法。读完你将掌握 LocalQuestionGen 的构造与调用方式、其与 Local Search 共用的上下文构建机制,以及如何结合问题历史批量生成可供对话系统推荐的高质量候选问题。
一、什么是 GraphRAG 的 Question Generation
Question Generation 是 GraphRAG 查询层中的一项能力,用于结合知识图谱中的结构化数据与输入文档中的非结构化数据,针对特定实体生成候选问题。其核心价值在于:在图问答、多轮对话或检索增强生成的交互场景中,系统不仅需要回答当前问题,还需要主动推荐"下一步该问什么",从而引导用户逐步深入探索某个主题。
与其关联的两个概念必须区分清楚:
- 本功能针对的是"基于实体"(entity-based)的问题生成,即围绕知识图谱中的具体实体(人、地点、事件等)组织候选问题,而不是泛泛地生成任意开放域问题。
- 它复用 Local Search(本地搜索)的上下文构建策略,因此对已构建好图索引的 GraphRAG 项目可直接开箱使用,相关依赖项(实体/关系/文本单元的 embedding 存储等)与本地搜索保持一致。
从仓库结构看,本功能的相关实现集中在 question_gen 目录下,由 base.py(抽象基类与结果数据结构)、local_gen.py(本地问题生成实现类)共同组成。
二、方法论:如何从"已有问题"生成"下一个问题"
官方文档将 Question Generation 的方法论概括为:给定一串先前用户提问的历史列表,该方法沿用 local search 的上下文构建思路,抽取并优先排序相关的结构化与非结构化数据——包括实体(entities)、关系(relationships)、协变量(covariates)、社区报告(community reports)与原始文本切片(text chunks)——随后将这些数据记录拼装进单个 LLM prompt,从而生成候选追问问题。
这些候选问题应当代表当前数据中最重要或最紧迫的信息内容与主题。
从源码看核心调用链
LocalQuestionGen.generate(同步)与 LocalQuestionGen.agenerate(异步)共同实现了这一流程,见 local_gen.py。其内部逻辑可分为四个阶段:
- 问题历史拆分:若
question_history非空,则取最后一条作为"当前问题"(question_text = question_history[-1]),其余条目全部作为历史对话放入ConversationHistory,供上下文构建时使用;若历史为空,则当前问题文本为空串、对话历史为None。 - 上下文构建:若调用方未显式传入
context_data,则调用self.context_builder.build_context(...)基于当前问题与对话历史实时检索并组装上下文文本与记录(context_chunks、context_records)。 - Prompt 拼装:将
context_data与question_count格式化进system_prompt,再用CompletionMessagesBuilder构造出"系统消息 + 用户消息"的消息结构。 - 流式生成与结果封装:通过
self.model.completion_async(..., stream=True)流式获取响应,将逐 token 回调广播给各 callback,最终按换行符切分得到候选问题列表response,并封装为QuestionResult。
从 base.py 可以看出,抽象基类 BaseQuestionGen 预先完成了 tokenizer(缺省回退为 model.tokenizer)、model_params、context_builder_params 的默认化处理,并在其上声明了 generate 与 agenerate 两个抽象方法,QuestionResult 数据结构则承载最终输出:
response: list[str]——按行切分的候选问题列表;context_data——本次生成所用的上下文数据(当前问题文本 + 上下文记录);completion_time——单次生成的耗时;llm_calls——本次生成的 LLM 调用次数(当前实现恒为 1);prompt_tokens——由 tokenizer 统计出的 prompt 消耗 token 数。
与 Local Search 共用的上下文构建器
LocalQuestionGen 构造时的 context_builder 参数类型为 LocalContextBuilder(见 local_gen.py 的类型注解,以及 builders.py 中 LocalContextBuilder 的定义)。这意味着本地搜索所依赖的实体描述向量检索、社区报告检索、文本单元检索、关系与协变量关联等全部逻辑在这里原样复用,确保"生成问题"与"回答问题"面对的是同一份数据语义与检索口径,生成的追问问题天然可被 Local Search 后续检索并回答。
三、配置与参数详解
LocalQuestionGen 的核心配置参数如下(依据源码构造签名 local_gen.py 与官方文档整理):
| 参数 | 说明 |
|---|---|
model | LLM 对话补全对象(LLMCompletion),用于生成候选问题,必须显式提供 |
context_builder | LocalContextBuilder 实例,负责从知识模型对象的集合中准备上下文数据,与 Local Search 使用同一上下文构建类 |
tokenizer | 可选。用于统计 prompt token 消耗,缺省时回退为 model.tokenizer |
system_prompt | 用于生成候选问题的提示模板,默认模板见 question_gen_system_prompt.py 中的 QUESTION_SYSTEM_PROMPT |
callbacks | 可选的回调函数列表,用于为 LLM 的流式补全事件提供自定义处理器(每个新 token 到达时触发 on_llm_new_token) |
model_params | 传递给 LLM 调用的额外参数字典(如 temperature、max_tokens)。注意:官方文档描述中该字段写作 llm_params,而源码中的实际参数名为 model_params,以源码为准 |
context_builder_params | 传递给 context_builder 的额外参数字典,用于控制上下文构建行为(如各类型数据在上下文窗口中的占比、检索 top-k 等) |
需要留意一个易混淆点:仓库文档原表述与源码在 model_params/llm_params 命名上存在差异,实际编码与 Notebook 均使用 model_params,同时为兼容 build_context 的扩展参数,generate/agenerate 中的 **kwargs 也会与 context_builder_params 一并展开传入 build_context。
默认 System Prompt 的结构
默认模板 question_gen_system_prompt.py 内部结构相当考究,它通过 {question_count} 与 {context_data} 两个占位符动态注入参数,并在 role/goal 描述中固化了三条生成约束:
- 候选问题必须能由给定的数据表(data tables)回答,但问题文本不得直接提及具体数据字段或数据表名;
- 候选问题应体现数据表中最重要或最紧迫的信息内容与主题;
- 若用户的历史问题引用多个具名实体,则每个候选问题都应引用全部具名实体——这正是"基于实体"语义在 prompt 层面的落点。
此外,该模板会随 graphrag init 初始化流程被一并写出到用户的提示词目录中。在 initialize.py 中可以看到 "question_gen_system_prompt": QUESTION_SYSTEM_PROMPT 的映射,用户可据此在本地配置文件层面覆盖默认模板(从源码结构看,提示词文件支持按需个性化替换)。
四、实战:在本地搜索 Notebook 中使用 Question Generation
官方提供了一个完整的可运行示例,位于本地搜索 Notebook local_search.ipynb。整个 Notebook 以 Operation Dulce 数据集为基础,先构造出 context_builder 与 embedding/LLM 等基础设施,随后在 "Question Generation" 一节演示了从构造到调用的完整三步。
第 1 步:构造 context_builder
在 Notebook 中,context_builder 通过 LocalContextBuilder 及相关组件(entity/relationship/covariate 描述向量存储、文本单元向量存储、embedder 等)组合而成,其目标是让"检索出的上下文"既有知识图谱的结构化记录、又有原文文本的非结构化切片。
第 2 步:实例化 LocalQuestionGen
核心代码片段如下:
question_generator = LocalQuestionGen(
model=chat_model,
context_builder=context_builder,
tokenizer=tokenizer,
model_params=model_params,
context_builder_params=local_context_params,
)
其中 chat_model 为 LLM 对话补全模型,tokenizer 用于统计 token,model_params 与 local_context_params 则分别控制生成行为与上下文窗口的分配。
第 3 步:传入问题历史并生成候选问题
question_history = [
"Tell me about Agent Mercer",
"What happens in Dulce military base?",
]
candidate_questions = await question_generator.agenerate(
question_history=question_history, context_data=None, question_count=5
)
print(candidate_questions.response)
注意 context_data=None 的传法:它会触发 LocalQuestionGen 内部自动调用 context_builder.build_context(),根据问题历史动态构建上下文;调用结束后,candidate_questions.response 即为按行切分后的候选问题列表。
Notebook 中建议的上下文与生成参数参考
Notebook 为 context_builder_params 与 model_params 提供了完整的注释与推荐取值(local_search.ipynb),与问题生成直接相关、值得关注的关键项如下:
- 上下文窗口预算:
max_tokens(上下文窗口最大 token 数,示例为12_000,需按模型上限调整,如 8k 上限模型可设为约 5k); - 数据占比:
text_unit_prop(分配给相关文本单元的比例,示例0.5)、community_prop(分配给社区报告的比例,示例0.1),两者之和应<= 1,剩余比例留给实体与关系; - 检索广度:
top_k_mapped_entities(从实体描述向量存储检索的相关实体数,示例10)、top_k_relationships(拉入上下文的网络外关系数,示例10); - 对话历史:
conversation_history_max_turns(最多纳入的历史轮数,示例5)、conversation_history_user_turns_only(是否只纳入用户消息); - 是否注入元信息:
include_entity_rank、include_relationship_weight、include_community_rank决定是否在上下文中附带实体的 rank/度数、关系权重与社区 rank; - 向量键类型:
embedding_vectorstore_key(示例使用EntityVectorStoreKey.ID,若向量存储以实体标题作为 id 则应改为EntityVectorStoreKey.TITLE)。
生成侧,model_params 的 temperature 示例取 0.0,max_tokens 示例取 2_000,以保证输出稳定且不超限——在按问题历史生成候选问题时,较低温度往往能获得更聚焦、更可复现的结果。
五、设计细节与易错点
结合源码实现,有几点值得开发者注意:
- 同步与异步双接口:
generate与agenerate在 local_gen.py 中实现逻辑完全一致,均采用流式响应聚合;在 Notebook / Jupyter 异步环境或基于 asyncio 的 FastAPI 等服务中,应优先使用agenerate。 - 历史为空时的行为:当
question_history为空时,当前问题文本为空串、对话历史为None,此时上下文构建将退化为一次无引导检索。实际使用时建议至少给出一条"种子问题"。 - 异常兜底:源码中整个生成过程被
try/except包裹,异常时返回空的response列表并记录日志(Exception in generating questions),不会中断上层对话流程——集成方应意识到"候选问题为空"也是一种合法返回,需要自行降级处理。 - 问题文本与数据字段解耦:prompt 明确要求候选问题可被数据表回答、但不提及字段/表名,这意味着下游在使用这些候选问题时不需要做任何字段层面的解析或过滤。
六、典型应用场景
从能力形态看,Question Generation 主要服务于以下场景:
- 图问答/知识库对话的"追问建议":多轮会话中,在回答完当前问题后自动给出 3~5 条可点击的候选追问,引导用户沿实体关系逐步下钻;
- 查询列表预生成:批量给定一个实体或主题,为运营、评测或构建 Question-Answer 数据集预先枚举高质量问题;
- Local Search 体验闭环:与 local_search.md、drift_search.md 等方法文档配套,使检索增强问答不再是一次性问答,而具备可持续的探索路径。
结语
Question Generation 是 GraphRAG 查询层中容易被忽略却颇具实用价值的一环:它复用 Local Search 的上下文构建机制,将知识图谱与原始文档统一喂入单个 prompt,围绕具体实体生成最值得追问的候选问题。本文结合 question_generation.md、local_gen.py 与 local_search.ipynb,从方法论、源码调用链、参数语义到 Notebook 级实战给出了完整梳理。理解它的关键在于把握两点:其一,"基于实体 + 复用本地搜索上下文"是它的设计骨架;其二,model_params 与 context_builder_params 的预算分配直接决定生成质量。在此基础上,你完全可以在自己的 GraphRAG 应用中快速集成一套"回答 + 追问"的交互闭环。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



