跳到主要内容

11 篇博文 含有标签「RAG」

Retrieval-Augmented Generation

查看所有标签

多模态 RAG 的检索层工程化:视觉后期交互(ColQwen)的账本、剪枝与踩坑

· 阅读需 15 分钟

做 RAG 的后端工程师大多有过这样的经历:一套 PDF 解析(PyMuPDF + pdfplumber)、分块、embedding、rerank 的管线在技术文档上效果不错,一换到财报、合同、产品手册就开始掉分。检索出来的段落看着相关,但答案在表格的某个交叉格里、在某张流程图的标注里、或者依赖两栏排版的位置关系——这些信息在"解析成纯文本"的那一步就已经丢了。

VisRAG 这篇工作把这个损失量化过:不做文本解析、直接把文档页当图片用 VLM 嵌入,端到端比传统文本 RAG 管线高 20%~40%。这个数字不必照搬(它建立在自建的检索/生成数据上),但方向是明确的:多模态文档的检索单元应该是"页",而不是"段落"。

本文不讨论 VLM 生成侧,只讲检索层怎么落地:三条技术路线的取舍、多向量的存储与算力账本、把成本压下来的四种手段,以及我自己在 ColQwen 上趟过的坑。前置阅读可以看 RAG 进阶检索 和 向量索引 HNSW/IVF-PQ 两篇。

RAG 进阶实战:切块策略、混合检索与 Re-ranking 的量化收益与工程取舍

· 阅读需 9 分钟

RAG 的链路谁都能搭:文档切块、embedding 进库、相似度召回、拼 prompt。但同一套链路,有人线上答对率八成,有人五成——差距不在「会不会搭」,而在「每一步的效果账算没算清」。基础链路见 RAG 全链路解析,这篇讲进阶:切块、混合检索、Re-ranking 各值多少分、花多少钱,Graph RAG 什么时候才值得上。

Embedding 进阶:Matryoshka 表示学习、量化向量与检索调优,把 RAG 的存储和延迟砍一个数量级

· 阅读需 10 分钟

前两篇分别聊了向量数据库选型(向量数据库选型对比)和检索链路的分块/重排序(RAG 检索优化深度拆解),但都把 Embedding 模型当成了黑盒——喂进去文本,吐出来一串浮点向量。这篇把盒子拆开,聚焦一个高级后端工程师做规模化 RAG 时迟早会撞上的问题:向量又大又多,存储和检索延迟怎么砍?

先算笔账。一个 100 万 chunk 的语料库,用 1536 维 float32 的 Embedding(比如 text-embedding-3-small),单是向量本体就是:

1,000,000 × 1536 × 4 字节 ≈ 6.1 GB

这还没算 HNSW 图索引的开销(通常再乘 1.2 到 1.5 倍)。规模涨到 1000 万 chunk,向量本体就 61GB,单机内存放不下,得上分布式。但真相是:绝大多数检索场景根本用不到 1536 维的全精度浮点。今天聊的三件事——对比学习怎么训出 Embedding、Matryoshka 怎么一个模型任意维度截断、量化怎么把向量压 8 到 32 倍——就是把这笔账砍下来的三条路。

RAG 检索优化深度拆解:分块策略到 Cross-Encoder 重排序,召回率提升的完整链路

· 阅读需 13 分钟

先抛一个反直觉的结论:RAG 效果不好的时候,九成问题出在检索,而不是生成。生成模型再强,检索漏掉的 chunk 永远不会出现在 context 里——"垃圾进,垃圾出"在 RAG 里是一条铁律。业界普遍共识是「检索质量决定 RAG 上限」:生成只能在上限之内修修补补,把答案写得通顺,但补不回没检索到的事实。

本文不聊"如何搭一个 hello-world RAG",而是拆解检索链路里真正决定召回率的三个环节——分块(Chunking)、检索融合(Hybrid Search)、重排序(Re-ranking)——每一环给出可运行代码、性能量级和生产踩坑记录。这是我在多个生产 RAG 系统里反复调过的地方。

Graph RAG 深度解析:当知识图谱遇上 RAG

· 阅读需 10 分钟

把一份 50 页的技术报告丢给 RAG 系统,问它 "这份报告的核心结论是什么?"——传统的 chunk-embed-retrieve-generate 链路直接跪了。不是检索不准,是这个问题本身就无法被任何单个 chunk 回答。

这是 2024 年 Microsoft Research 提出 Graph RAG 的出发点。一年过去,这个方向已经从论文变成了实际可跑的工程方案。本文从架构层面拆解它到底在做什么、怎么做的、以及什么时候该用。


问题:Vector RAG 的盲区​

标准 RAG 流程的前提假设是:答案分布在少数几个 chunk 中。所以策略是 embed → top-k 检索 → 塞进 context → 生成。

一旦问题变成全局性的("总结全文"、"对比三个方案的优劣"、"找出贯穿始终的主题"),top-k 检索就失效了。一个直觉的做法是先把全文喂给 LLM——但 50 页报告远超 context window。就算模型支持 1M token,"大海捞针"的准确率也随 context 增长而骤降。

Graph RAG 的解法:先离线建图,再在线查询。把文档转化成知识图谱,用图算法提取结构,把全局问题转化成对图结构的查询。


架构总览​

Microsoft 的 Graph RAG 分两个阶段:

离线索引(Indexing)​

文档 → 切分 chunk → 实体/关系抽取 → 知识图谱 → 社区检测 → 社区摘要

每个步骤都跑 LLM,完全离线,不占查询延迟。

在线查询(Query)​

查询分两种模式:

  • Local Search:从图中提取与查询相关的实体邻居子图,适合事实型问题
  • Global Search:用社区摘要做 map-reduce,适合全局总结型问题

Global Search 是 Graph RAG 的核心差异化能力。下面重点拆它。


离线阶段:从文本到图谱​

Step 1:实体与关系抽取​

对每个 chunk 调 LLM,提取 (subject, predicate, object) 三元组:

# 伪代码:Graph RAG 的实体抽取 prompt 模式
EXTRACT_PROMPT = """从文本中提取所有实体和关系,返回 JSON:

{
"entities": [
{"name": "Microsoft", "type": "ORGANIZATION", "desc": "科技公司"}
],
"relationships": [
{"source": "Microsoft", "target": "Graph RAG", "desc": "提出者"}
]
}

文本:
{chunk_text}
"""

这一步是整个 pipeline 最贵的环节。一篇 100 页文档,按 300 token/chunk 切,会产生几百次 LLM 调用。Microsoft 的做法是并行跑,用 gpt-4o-mini 压低成本。

关键细节:同一个实体会在不同 chunk 中被提取多次("Microsoft"、"MSFT"、"微软"),所以下一步要做实体消歧。

Step 2:实体消歧(Entity Resolution)​

Graph RAG 没有用复杂的 entity linking,而是再调一次 LLM:

def resolve_entities(entity_groups: list[list[dict]]) -> dict:
"""同一类型的候选实体分组后,让 LLM 判断哪些是同一实体"""
prompt = f"""
以下实体中,哪些指向同一个真实世界实体?
返回分组结果。

候选实体:
{json.dumps(entity_groups, ensure_ascii=False)}
"""
# 返回合并后的实体映射

为什么用 LLM 而不是传统的字符串匹配?因为 "GPT-4" 和 "GPT4"、"gpt-4-turbo" 需要语义理解,简单的编辑距离不够。

消歧后,图里约 5-15% 的节点被合并,边也相应聚合。

Step 3:社区检测(Community Detection)​

这是 Graph RAG 最巧妙的一步。构建好实体-关系图后,用 Leiden 算法做社区发现:

import networkx as nx
import leidenalg
import igraph as ig

def detect_communities(G: nx.Graph) -> list[set[str]]:
"""Leiden 社区检测,返回多层社区结构"""
g_ig = ig.Graph.from_networkx(G)

# 多层社区检测——这是精髓
partition = leidenalg.find_partition(
g_ig,
leidenalg.ModularityVertexPartition,
n_iterations=-1 # 跑到收敛
)

# Leiden 天然支持层次化社区
# Level 0: 最细粒度的社区(几十个实体)
# Level 1: 合并后的中等社区
# Level 2: 最粗粒度的大主题社区
return partition

Leiden 优于 Louvain 的地方是它保证连通性——每个社区内部是连通的,不会出现碎片化社区。对于知识图谱这种天然聚团的结构,Leiden 跑出来的社区往往对应真实世界的主题:比如"模型架构"社区包含 Transformer、Attention、MLP 等实体,"训练方法"社区包含 SGD、AdamW、Learning Rate 等。

Step 4:社区摘要(Community Summarization)​

对每个社区,把其成员实体和关系序列化成文本,让 LLM 生成摘要:

def summarize_community(
community: list[str],
entities: dict,
relationships: list
) -> str:
"""为一个社区生成摘要"""
# 收集社区内的实体和关系
community_entities = [entities[e] for e in community]
community_rels = [
r for r in relationships
if r.source in community and r.target in community
]

prompt = f"""
以下是一个知识图谱社区的信息。请生成一个简洁的摘要,
描述这个社区的核心主题、关键实体和它们之间的关系。

社区实体:
{format_entities(community_entities)}

社区关系:
{format_relationships(community_rels)}

摘要:
"""
return call_llm(prompt)

这个摘要会在 Global Search 时被用到。摘要的质量直接决定最终回答的质量——摘要太泛,查询结果就是废话;摘要太细,又回到了 chunk-level 的问题。


在线查询:Map-Reduce 模式​

当用户问 "这份文档主要讨论了哪些技术方向?",Graph RAG 的 Global Search 流程:

1. 把用户 query 嵌入向量
2. 对所有社区摘要做向量检索,选出 top-K 个相关社区
3. 对每个社区的摘要,让 LLM 生成一个"局部答案"(map)
4. 把所有局部答案汇总,让 LLM 生成最终回答(reduce)
async def global_search(query: str, community_summaries: list[str]) -> str:
# Step 1: 检索相关社区
query_emb = embed(query)
relevant = top_k_by_similarity(query_emb, community_summaries, k=10)

# Step 2: Map — 每个社区生成局部答案
map_tasks = [
map_prompt(query, summary)
for summary in relevant
]
partial_answers = await asyncio.gather(*[call_llm(t) for t in map_tasks])

# Step 3: Reduce — 汇总成最终答案
final_answer = call_llm(reduce_prompt(query, partial_answers))
return final_answer

这和传统的 map-reduce summarization 思路一样,区别在于:输入不是随机采样的 chunk,而是按语义聚类好的社区摘要。每个社区摘要本身已经是一个凝练的信息单元,所以 map 阶段产生的噪音远小于直接对 chunk 做 map。

对于事实型问题("GPT-4 的 context window 是多少?"),走 Local Search:

1. 用 query 找到相关实体
2. 提取这些实体的邻居子图(1-2 hop)
3. 把子图信息 + 实体的原始文本属性 + 关系描述塞进 context
4. 生成回答

Local Search 的召回率显著高于纯向量检索,因为它利用了图的结构信息——"GPT-4" 和 "context window" 之间有一条 has_context_window 边,即使它们的文本描述中从来没有出现在同一个 chunk 里。


性能数据​

Microsoft 的论文在两个数据集上做了评测:

指标Naive RAGGraph RAG (Global)提升
Comprehensiveness2.873.97+38%
Diversity2.533.54+40%
Directness2.953.76+27%
LLM 调用次数1~12 (10 map + 1 reduce + 1 embed)—

数据来源:Microsoft Research, "From Local to Global: A Graph RAG Approach to Query-Focused Summarization", 2024

代价也很明显:

  • 索引成本:一篇 100 页文档,约 $4-8(gpt-4o-mini),$50-100(gpt-4o)
  • 索引时间:串行跑需要 30-60 分钟,并行可以压到 5-10 分钟
  • 查询延迟:Global Search 需要 10+ 次 LLM 调用,单次查询 10-30 秒

什么时候该用 Graph RAG?

  • ✅ 需要对大文档集做全局总结、主题分析、趋势发现
  • ✅ 文档之间有密集的实体关联(科研文献、法律文书、技术报告)
  • ✅ 索引可以离线跑,查询延迟可以接受 10-30 秒
  • ❌ 简单的 FAQ 或事实型问答——标准 RAG 更快更便宜
  • ❌ 需要毫秒级响应的实时场景
  • ❌ 文档实体关系稀疏(小说、散文)

工程落地要点​

1. 实体抽取的 Prompt 设计​

实体类型的定义直接影响图的质量。太少,图就稀疏;太多,消歧压力大。建议:

# 实际可用的实体类型(不是越多越好)
ENTITY_TYPES = [
"PERSON", # 人物
"ORGANIZATION", # 组织/公司
"TECHNOLOGY", # 技术/框架/模型
"CONCEPT", # 抽象概念
"METRIC", # 指标/数值
"EVENT", # 事件
]

控制在 5-8 个类型,每个类型有明确的边界定义。

2. Chunk 大小不是越小越好​

实体抽取需要一个 chunk 内包含足够的上下文才能准确识别关系。Graph RAG 的推荐是 600-1200 token/chunk,比传统 RAG 的 256-512 更大。

3. 增量更新是个坑​

图结构让增量更新变得复杂——新增一个实体可能改变社区结构,导致需要重新计算社区检测和摘要。目前 Graph RAG 的设计假设是"全量重建",对于频繁更新的文档集不友好。一个折中方案是分区索引(按文档或时间段分区),只重建变更的分区。

4. 社区层级的选择​

Leiden 产出的是多层社区结构。Global Search 用哪一层?

  • 层级太高(Level 0):摘要太多,map-reduce 成本高
  • 层级太低(Level 2/3):摘要太粗糙,信息丢失

经验法则是选 实体数中位数在 20-100 的那一层。太小没聚合效果,太大每个摘要就失去主题聚焦性了。


与 LightRAG 等变体的对比​

2024 年底出现了几个 Graph RAG 的变体,最值得注意的是 LightRAG:

Graph RAG (Microsoft)LightRAG
社区检测Leiden(层级化)不显式做,用图遍历替代
查询模式Global + LocalNaive / Local / Global / Hybrid 四种
索引时间慢(多次 LLM 调社区摘要)快(跳过社区摘要)
实体消歧LLM-based向量相似度 + LLM 辅助
适用场景大规模静态文档集快速迭代、中小规模

LightRAG 本质上是用检索时的图遍历(BFS/DFS)替代了离线社区摘要,牺牲了一些全局理解能力换来了更高的索引速度。


总结​

Graph RAG 解决的不是检索精度问题,而是检索范式问题——从 "找到包含答案的 chunk" 变成 "理解文档的全局结构"。这个思路的价值远超 RAG 本身:它证明了 LLM + 图结构 是一种比 LLM + 向量更丰富的知识表示方式。

对于后端工程师来说,Graph RAG 的技术栈其实很熟悉:图数据库(Neo4j/Neptune)、图算法(Leiden/Louvain)、Map-Reduce 模式——加上 LLM 作为"智能提取器"。你需要关注的不是这些组件本身,而是它们组合后产生的协同效应。

下一步可以深挖的方向:

  • 用 Graph RAG 做代码库理解(实体 = 函数/类/模块,关系 = 调用/继承/依赖)
  • 多跳推理:不只查 1-2 hop 邻居,而是让 LLM 沿图做多步推理
  • 图 + 向量双索引:局部事实走图,语义相似走向量,路由分发

参考​

  • Edge, D., et al. "From Local to Global: A Graph RAG Approach to Query-Focused Summarization." arXiv:2404.16130, 2024.
  • Traag, V.A., et al. "From Louvain to Leiden: guaranteeing well-connected communities." Scientific Reports, 2019.
  • LightRAG: https://github.com/HKUDS/LightRAG

上下文工程:把 Context 当作一等资源来管理

· 阅读需 9 分钟

过去两年我们花了很多精力在「输入端」——prompt engineering、few-shot、结构化输出。但 Agent 规模化之后,真正的瓶颈转移到了上下文(context)本身:怎么把有限、昂贵、会腐烂的上下文,像内存一样管理起来。Anthropic 在 2025 年 9 月发表的《Effective context engineering for AI agents》把这件事正式命名为 context engineering——一门关于「上下文生命周期」的工程学科。

本文不重复「怎么写好 prompt」,而是把上下文当成一种可度量、可压缩、可缓存、可分层存储的资源,讨论四个核心问题:它为什么会烂(context rot)、怎么主动丢弃(compaction)、怎么变便宜(caching)、以及怎么用外部存储补足它(memory hierarchy)。

扩展 LLM 的能力边界:联网搜索、文件读写与记忆功能全景指南

· 阅读需 10 分钟

引言​

上一篇我们聊了 Function Calling 诞生的背景——LLM 为什么必须学会调用函数。这篇更进一步,把问题摊开:

一个只会输出文本的模型,如何长出"眼睛"、"手"和"记忆"?

  • 联网搜索给它眼睛,让它看到训练数据截止之后的世界;
  • 文件读写给它手,让它能在真实工作环境中留下成果;
  • 记忆功能让它跨越会话,记住你是谁、上次做到哪一步。

这三项能力,加上代码执行和 MCP 协议的标准化,构成了今天所有 AI Agent 的地基。本文逐项拆解它们的原理、实现路径和工程上的坑。