数据截至 (上游 commit d8cdaa66c074)
三种查询:纯文本 / 多模态注入 / VLM 增强
本章讲读路径:
QueryMixin(query.py)提供的三种问答方式,以及最有意思的「VLM 增强」是怎么把图片真的喂回给模型看的。
这一节讲什么
先分清三种查询各解决什么场景,再深入 VLM 增强的关键三步(检索→找回图片→喂 VLM),最后看其中的防 prompt 注入安全设计。
5.1 三种查询一览
| 方法 | 场景 | 图片怎么用 |
|---|---|---|
aquery(纯文本) | 问已入库内容 | 不直接用图;但有 VLM 时默认转去 VLM 增强 |
aquery_with_multimodal | 提问时自己带一张图/表/公式 | 把带来的 内容先翻译成文字拼进 query |
aquery_vlm_enhanced | 问的内容涉及库里的图 | 检索出相关图,把真实图片喂给 VLM 看 |
关键的自动切换:aquery(query.py:128)里,只要配了 vision_model_func 且没显式关掉,vlm_enhanced 默认为真,于是普通文本查询会自动转去 aquery_vlm_enhanced。用户不用关心这个细节——「问涉及图的问题,系统自动会看图」。
5.2 aquery_with_multimodal:提问时自带内容
aquery_with_multimodal(query.py:221)用于「我有一张图/一个表,想结合知识库问问题」。它不直接检索图,而是先把带来的多模态内容翻译成文字描述,拼进 query 文本,再走普通 aquery:
# 示意,逻辑见 query.py:448-499 _process_multimodal_query_content
enhanced_parts = [f"User query: {base_query}"]
for content in multimodal_content:
processor = get_processor_for_type(self.modal_processors, content["type"])
description = await self._generate_query_content_description(processor, content, type)
enhanced_parts.append(f"Related {type} content: {description}")
enhanced_query = "\n".join(enhanced_parts) + PROMPTS["QUERY_ENHANCEMENT_SUFFIX"]
结果有缓存:基于「query + 模态内容 + mode」算 key(_generate_multimodal_cache_key,query.py:59),命中就直接返回,省掉重复的模型调用。注意算 key 时图片路径只取 basename、大表体取 hash,让缓存更可移植。
5.3 VLM 增强:把图片塞回检索结果
这是本章的精华。aquery_vlm_enhanced(query.py:375)分三步:
怎么读这张图
从上到下是先后顺序。关键洞察在第 2 步:LightRAG 检索出的 prompt 里,因为 02-modal-processors.md 提到的 chunk 模板保留了 Image Path: 行,所以图片路径还在文本里,能被正则捞回来。
① only_need_prompt=True 让 LightRAG 只检索、不作答
│ 拿回 raw_prompt(含命中 chunk 的全文,里面有 "Image Path: /xxx.jpg")
▼
② 正则扫 raw_prompt 里的 Image Path 行
│ 对每个路径:安全校验 → 读文件转 base64 → 在原位插入 [VLM_IMAGE_N] 标记
▼
③ 按 [VLM_IMAGE_N] 标记切分文本,把对应图片 base64 插回相应位置
│ 组装成 VLM 的 messages(图文交错)
▼
调 vision_model_func 看图作答
第 1 步:只检索不作答
# 示意,见 query.py:417-418
query_param = QueryParam(mode=mode, only_need_prompt=True, **kwargs)
raw_prompt = await self.lightrag.aquery(query, param=query_param)
only_need_prompt 是 LightRAG 的参数,让它把「检索 + 组装好的上下文 prompt」原样吐回来,而不去调 LLM 生成最终答案。RAG-Anything 要的就是这段含上下文的 prompt。
第 2 步:正则找回图片
_process_image_paths_for_vlm(query.py:615)用这个正则匹配路径:
# 示意,见 query.py:636
image_path_pattern = r"Image Path:\s*([^\r\n]*?\.(?:jpg|jpeg|png|gif|bmp|webp|tiff|tif))"
匹配到后,不删除原路径,而是在它后面追加一个 [VLM_IMAGE_N] 标记,同时把图片读成 base64 存进 self._current_images_base64。保留路径 + 加标记,是为了第 3 步能按标记把图片 插回正确位置。
第 3 步:图文交错组装
_build_vlm_messages_with_images(query.py:734)按 [VLM_IMAGE_N] 标记切分文本,在每个标记处插入对应的 base64 图片,拼成 OpenAI 风格的图文交错 messages({type:text} 和 {type:image_url} 交替),最后调 VLM(_call_vlm_with_multimodal_content,query.py:818)。
如果没找到任何有效图片,直接退回普通文本查询(query.py:427)——VLM 增强对没图的问题不会平白多花一次视觉调用。
5.4 安全:防间接 prompt 注入
第 2 步有个不显眼但重要的安全门(query.py:658-697)。威胁模型是:知识库里的某段文本可能被恶意构造,塞进 Image Path: /etc/passwd 之类的路径,诱导系统去读任意系统文件再喂给模型。
防御:只允许加载安全目录内的图片——
# 示意,逻辑见 query.py:660-696
abs_image_path = Path(image_path).resolve()
is_in_safe_dir = (
abs_image_path.is_relative_to(Path.cwd()) # 当前工作目录
or abs_image_path.is_relative_to(config.working_dir) # RAG 存储目录
or abs_image_path.is_relative_to(config.parser_output_dir) # 解析输出目录
or any(abs_image_path.is_relative_to(d) for d in extra_safe_dirs) # 显式额外目录
)
if not is_in_safe_dir:
is_valid = False # 拒绝,保留原文不读图
配套的 validate_image_file(utils.py:248)还会拒绝符号链接(防 symlink 绕过目录限制)、校验扩展名、限制文件大小(默认 50MB)。这套检查是该项目少有的、明确针对安全的工程化设计。
关键细节 / 坑
- VLM messages 的两种形态:
vision_model_func既可能收到image_data(单图,旧式),也可能收到messages(图文交错,VLM 增强用)。README 的示例函数就同时处理 这两种分支——用户的vision_model_func必须兼容messages参数,否则 VLM 增强用不了。 - 同步包装:
query/query_with_multimodal(query.py:852/870)是异步版的同步封装,用always_get_an_event_loop跑事件循环。 - mode 默认
mix:三种查询默认都用 LightRAG 的mix模式(向量 + 图谱混合检索),也可传local/global/hybrid/naive/bypass。