LLM - 从 RAG 到 Context Engine:2025 实战总结与 2026 落地指南

2025-12-24 19:14:36
文章摘要
过去两年,围绕 RAG 的声音两极分化:一边是“RAG 只是临时方案,很快会被长上下文、KV Cache 干掉”;另一边是越来越多严肃做 AI 中台的团队,把 RAG 当作企业非结构化数据的底座来建设。1. 如何搭一个现代 RAG 系统,而不是“向量库 + Embedding”拼凑品。 2. 如何把 RAG 升级为支撑 Agent 的 Context Engine,接管知识库、Memory

引言

过去两年,围绕 RAG 的声音两极分化:一边是“RAG 只是临时方案,很快会被长上下文、KV Cache 干掉”;另一边是越来越多严肃做 AI 中台的团队,把 RAG 当作企业非结构化数据的底座来建设。

从 2025 年的实践看,后者正在成为共识:

  • 长上下文、KV Cache、简单 Grep 各有适用场景,但都无法在成本、灵活性、可治理性上全面替代 RAG。
  • 真正发生的变化是:RAG 从“检索增强生成”组件,升级为面向 Agent 的“上下文引擎(Context Engine)”,成为知识、记忆、工具三类上下文的统一入口。

接下来我们以“实战向”视角,聚焦三个问题:

  1. 如何搭一个现代 RAG 系统,而不是“向量库 + Embedding”拼凑品。
  2. 如何把 RAG 升级为支撑 Agent 的 Context Engine,接管知识库、Memory 和 Tool Retrieval。
  3. 2026 年要不要上多模态 RAG,上了怎么活下来(成本与工程挑战)。

一、RAG vs 长上下文:先别吵,先算账

1.1 四种主流“喂知识”方式

给 LLM 塞外部知识,现在大致有四种套路:

  • 方案 A:长上下文

    • 直接把一堆文档丢进上下文窗口,模型自己“看着办”。
    • 优点:实现简单;易于 PoC。
    • 缺点:Lost in the Middle、注意力分散、上下文成本随长度非线性上涨。
  • 方案 B:KV Cache / AlayaDB 类方案

    • 把文档预先过一遍 LLM 前向,存成张量;推理时做“边生成边检索张量”。
    • 优点:推理时可以复用中间表示,理论上能高效重用上下文。
    • 缺点:
      • 预处理成本高,存储张量很烧钱。
      • 超过显存就要走二级存储 + 检索链路,I/O 与架构变复杂。
  • 方案 C:无索引 RAG / Grep

    • 不建向量索引,直接靠关键词 / 正则搜索文件内容。
    • 优点:简单、无索引维护成本;对日志、单一代码库这类“格式规整+术语固定”数据挺好用。
    • 缺点:对自然语言、多模态、复杂结构文档基本无能为力。
  • 方案 D:RAG(检索增强生成)

    • 建立向量索引 + 语义增强,检索后把结果喂给 LLM。
    • 优点:
      • 成本相对可控,比“全量长上下文”便宜 1–2 个数量级。
      • 能结合结构化过滤、多索引、多级召回。
    • 缺点:需要打磨数据注入、切片策略、召回与重排,否则效果不稳定。

如果把“单次推理成本”与“可以容纳的知识规模”画成一个简单示意表:

方案 单次成本量级 易用性 长期可扩展性 适用典型场景
长上下文 最高 静态少量文档深阅读
KV Cache 小规模高频问答
Grep / 无索引 日志、代码局部搜索
现代 RAG 企业知识、Agent 中台

结论很直接:

  • 长上下文与 KV Cache 更像“局部场景优化器”;
  • RAG 更像“通用检索中台”,尤其适合企业要把一大堆私有知识资产稳定接入 LLM 的场景。

1.2 实战建议:如何混用

工程上推荐的组合模式是:“检索前置 + 长上下文容纳”:

用户 Query
   ↓
(1) 语义检索:先用 RAG 找到可能相关的 10~50 个片段
   ↓
(2) 上下文组装:按 TreeRAG / PageIndex 之类策略,拼成若干大段
   ↓
(3) 长上下文模型:把组装好的上下文 + Query 一起喂入模型推理

这样做有几个好处:

  • 长上下文窗口被“高浓度信息”占满,而不是一堆噪声。
  • 检索逻辑与模型版本解耦,后续换模型不用重做数据处理与索引。

二、现代 RAG:别再只想“切块 + 向量库”

2.1 经典 RAG 的结构性矛盾

传统“分块–嵌入–检索”流水线里有个天然矛盾:

  • 为了检索准:块要小,语义纯净(100–256 Token)。
  • 为了上下文好用:块要大,语义完整(≥1024 Token)。

结果就是:

  • 小块:召回准,但上下文碎,模型缺乏整体语境。
  • 大块:上下文连贯,但召回易偏;向量表达过于“平均化”。

要想兼得,最直接的路线是:Search / Retrieve 解耦 + 多粒度表示

2.2 TreeRAG:Search / Retrieve 解耦的典型实现

TreeRAG 可以看作“树结构版现代 RAG”的代表方案。

架构示意(注入阶段)

原始文档
  ↓  Parsing(DeepDoc / PDF Parser)
规整文本 + 结构(标题/段落/表格) [page:1]
  ↓  切片(Chunking,简单重叠切分)
基础切片序列
  ↓  LLM 语义增强(离线)
为每个切片/章节生成:
- 多级摘要(章/节/段)
- 关键词 / 实体 / 问题
- 目录树节点关系[page:1]
  ↓
写入索引:
- 向量索引(小粒度)
- 树结构索引(大粒度)[page:1]

伪代码示意(注入):

def ingest_document(doc_id: str, raw_bytes: bytes):
    # 1. 解析
    parsed = parse_pdf_or_docx(raw_bytes)  # 返回带标题/段落结构文本[page:1]
# 2. 切片
chunks = simple_overlap_chunk(parsed.text, size=256, overlap=64)

# 3. 调用 LLM 做语义增强与树结构构建
outline = llm_generate_outline(parsed.text)  # 章/节/小节结构[page:1]
node_summaries = llm_summarize_by_outline(parsed.text, outline)

# 4. 建小粒度向量
for chunk in chunks:
    emb = embed(chunk.text)
    vec_index.add(doc_id=doc_id, chunk_id=chunk.id, vector=emb,
                  metadata={"page": chunk.page, "section": chunk.section})

# 5. 建树索引
tree_index.add_document(doc_id, outline, node_summaries)

检索阶段:先找点,再“展开阅读”

def tree_rag_retrieve(query: str, top_k_chunks: int = 20):
    # 1. 小粒度 Search:在向量索引里找若干高相关 chunk[page:1]
    q_vec = embed(query)
    candidates = vec_index.search(q_vec, top_k=top_k_chunks)
# 2. 利用树结构向上 & 横向扩展
expanded_passages = []
for c in candidates:
    # 找到所在节点(如“第 3 章 第 2 节”)
    node = tree_index.locate_node(c.doc_id, c.metadata)
    # 把父节点 / 邻居节点的摘要 + 原文拉进来[page:1]
    ctx = tree_index.expand_context(node, window=1)
    expanded_passages.append(ctx)

# 3. 去重 & 截断,形成最终上下文
final_context = merge_and_truncate(expanded_passages, max_tokens=4096)
return final_context

这种做法本质上是在做两件事:

  • 用“小块”保证召回精度。
  • 用“树结构 + 摘要”保证上下文连贯性与覆盖度。

2.3 GraphRAG:补 TreeRAG 不擅长的“跨文档推理”

TreeRAG 主要沿着文档内物理结构(章 / 节 / 段 / 页)扩展上下文,对解决“切块导致的上下文断裂”很有效,但对以下场景不够:

  • 答案分散在多个不相邻章节。
  • 涉及多个文档间的实体、事件关系推理。

GraphRAG 的思路是:

  • 离线抽取实体 & 关系,构成知识图谱;
  • 检索时先根据 Query 定位若干“种子实体 / 主题”,在图上做邻域遍历,再聚合对应文本片段。

伪代码示意(极简版):

def build_kg_from_doc(doc_id: str, text: str):
    triples = llm_extract_triples(text)  # [(head, rel, tail, span_id), ...][page:1]
    for h, r, t, span_id in triples:
        kg.add_edge(h, t, relation=r, metadata={"doc_id": doc_id, "span_id": span_id})

def graph_rag_retrieve(query: str, top_k_entities: int = 10):
# 1. 先做一个语义检索,得到若干候选片段与实体[page:1]
seeds = entity_index.search(query, top_k=top_k_entities)

# 2. 在图上做个 Personalized PageRank / k-hop BFS
subgraph = kg.expand_from(seeds, hops=2)

# 3. 收集相关实体所属的文本 span
spans = collect_text_spans(subgraph)

# 4. 做摘要 / 去重,组装上下文
ctx = summarize_and_merge(spans, max_tokens=4096)
return ctx

在实战中,更推荐:TreeRAG + GraphRAG 混合

  • TreeRAG 解决“局部语义连贯”;
  • GraphRAG 解决“跨章节 / 跨文档关联”。

三、从“知识库 RAG”到“Context Engine”:三类数据统一建模

3.1 PTI:把非结构化数据当成“ETL 同级公民”

企业已经习惯了 ETL/ELT 做结构化数仓,现在要做的是给非结构化数据搭一个 PTI(Parse–Transform–Index)流水线。

对比一下两者的主流程:

环节 ETL/ELT(结构化) PTI(RAG / Context)
解析 读 DB / CSV / 日志 PDF/Word 解析、OCR、VLM 解析、layout 结构化
转换 SQL 清洗、聚合、业务逻辑 LLM 切片、摘要、目录树、实体 / 关系、问题生成等语义增强
加载 / 索引 写数据仓库或湖 建立向量索引、倒排索引、树索引、图索引、多模态张量索引

PTI 伪代码骨架:

def pti_pipeline(doc_id: str, raw_input: bytes, mime_type: str):
    # P: Parse
    parsed = parse_by_mime(raw_input, mime_type)  # 带 layout 的结构化文本[page:1]
# T: Transform(关键价值点)
chunks = simple_overlap_chunk(parsed.text)
semantic_meta = llm_batch_enhance(chunks)  # 摘要、实体、关键词、QA、目录节点等[page:1]

# I: Index
for chunk, meta in zip(chunks, semantic_meta):
    vec_index.add(vector=embed(chunk.text),
                  metadata={**meta, "doc_id": doc_id})
tree_index.update(doc_id, meta["outline"])
# 若开启 GraphRAG
kg_index.ingest_triples(meta["triples"])

工程实战里,PTI 是否设计好,决定了你做的是:

  • “能跑的 Demo 知识库”,还是
  • “能撑住全公司 Agent 的非结构化数据中台”。

3.2 三类数据:知识库、记忆、工具

Context Engine 要接管三类核心数据源:

  • 知识库(RAG):

    • 文档、手册、报告、FAQ 等“静态领域知识”。
    • 目标:提供事实、背景、规范。
  • 记忆(Memory):

    • 用户 / Agent 的历史对话、系统状态、LLM 生成的总结与反思。
    • 目标:保持长程上下文、个性化、从经验中“学习”。
  • 工具(Tool + Playbook):

    • 工具描述(名称、参数、功能)、使用示例、Playbook、Guideline。
    • 目标:帮助 Agent 选择合适工具、以正确顺序 / 参数调用。

三者在底层都满足一个统一抽象:

@dataclass
class ContextItem:
    id: str
    type: Literal["knowledge", "memory", "tool"]
    content: str               # 自然语言描述
    embedding: np.ndarray
    metadata: dict             # 时间、用户、会话、标签、权限等[page:1]

Context Engine 的统一检索接口大致可以长这样:

class ContextEngine:
    def __init__(self, knowledge_store, memory_store, tool_store):
        self.k_store = knowledge_store
        self.m_store = memory_store
        self.t_store = tool_store
def query(self, query: str, need_knowledge=True,
          need_memory=True, need_tools=True, k=5):
    q_vec = embed(query)
    results = []

    if need_knowledge:
        results += self.k_store.search(q_vec, top_k=k)
    if need_memory:
        results += self.m_store.search(q_vec, top_k=k)
    if need_tools:
        results += self.t_store.search(q_vec, top_k=k)

    return self._rank_and_group(results)

Agent 框架只需要调用 ContextEngine.query(),拿到一包“已组装好的上下文切片”:

  • 部分来自知识库。
  • 部分来自记忆。
  • 部分是“本轮任务最可能用到的工具介绍 + 使用步骤 / Playbook”。

3.3 实战:如何做 Tool Retrieval

简单把所有 MCP 工具的描述塞进 system prompt,在几十个工具时还能撑一下,上百个之后立刻崩溃:

  • 上下文成本爆炸。
  • 模型选择工具容易“晕”,常出现幻觉调用。

Tool Retrieval 的做法是:

  1. 针对每个工具建一条 ContextItem(type="tool"),内容包含:
    • 工具用途说明、典型参数、失败案例说明。
  2. 工具 Playbook / Guideline 也建成独立条目。
  3. 在对话中:
    • 先对 Query 做一次“工具域”的语义检索,只召回 Top-k 工具 + Playbook。
    • 把这些内容拼接进上下文,让模型在“小工具集合”里选择。

伪代码示意:

def select_tools_for_turn(query: str, max_tools: int = 3):
    q_vec = embed(query)
    # 在工具库中检索(可混合向量 + BM25)
    candidates = tool_store.search(q_vec, top_k=20)
    # 规则:优先带有高评分 Playbook 的工具
    scored = rerank_by_playbook(candidates)
    return scored[:max_tools]

Agent 执行循环时,引入一层“工具选择”中间件:

def agent_step(user_query: str, history: list):
    # 1. 决定是否需要工具(fast heuristic or LLM call)[page:1]
    if should_use_tool(user_query):
        tools = select_tools_for_turn(user_query)
    else:
        tools = []
# 2. 向 Context Engine 请求知识 + 记忆
ctx = context_engine.query(user_query,
                           need_knowledge=True,
                           need_memory=True,
                           need_tools=False)

# 3. 把工具说明一起塞入上下文
full_ctx = assemble_llm_context(history, ctx, tools)
return call_llm(full_ctx)


四、多模态 RAG:什么时候值得上,怎么不上“死”

4.1 什么时候值得用多模态 RAG

从当前公开基准(如医学场景 M3Retrieve)与业界实践看:

  • 图文都重要的场景:
    • 医学影像 + 报告、图表密集的财报 / 设计图纸、工艺流程图。
    • 多模态 RAG 明显强于“纯文本 + OCR”。
  • 文本主导的场景:
    • 普通 FAQ、产品手册、普通合同。
    • 目前成熟的文本 RAG 足够用,多模态未必带来显著增益。

因此可以简单归纳为:

  • 你的关键问题是否“看图说话”?是 → 值得考虑多模态 RAG;否则先把文本 RAG 打磨好。

4.2 张量索引的工程地狱:存储与计算

以一页 PDF 转图片、用多模态模型生成 1024 个 Token、每个 128 维 float32 为例:

  • 单页张量大小约 512 KB。
  • 百万页文档 → 原始索引体积 TB 级。
  • 再叠加图像 + 文本双通道,成本更高。

常见工程解法有三条路径:

  1. 多比特 / 二值量化

    • 把向量量化为低比特表示(甚至 1 bit);
    • 存储降到原来的 1/32,代价是精度损失,需要模型侧配合做鲁棒训练。
  2. 减少 Token 数量

    • Token 聚类:对 1024 个 Token 做聚类,用几十个中心代替。
    • 随机投影 / MUVERA:把多 Token 压到一个大维度向量。
    • 模型端 Token 剪枝:训练模型只吐出“关键 Token”。
  3. 召回+重排两级结构

    • 召回阶段用轻量索引(全文 / 单向量);
    • 只对 Top-k 结果用张量重排器做精排。

实战建议:

  • 没有明确的图像强需求时,不要提前 All-in 多模态。
  • 真要上,优先“文本 + 页级张量重排”的折中方案,而不是一上来就做“全量张量检索”。

五、2026 年落地策略:从“拼模型”到“拼上下文”

从 2025 的演进可以看出,一条很清晰的路线:

  • 模型层在不断内卷,而真正决定体验与 ROI 的,是上下文层的质量。
  • RAG 不再是“问答小模块”,而是承载知识、记忆、工具三类数据的 Context Engine。

如果要在 2026 年系统性推进这条线,建议按下面的优先级落地:

  1. 先把 PTI 流水线搭起来

    • 目标:稳定 ingest 企业的 PDF/Word/网页/代码等,抽出结构化文本 + 基础语义增强。
    • 指标:日常增量文档自动入库、失败可观测、切片与索引可灰度调整。
  2. 再升级“问答知识库”为“统一检索中台”

    • 同一套检索服务,支持 FAQ、文档问答、内部搜索等多应用接入。
    • 指标:索引、Rerank、日志分析等都集中在一套 Infra 上运维。
  3. 引入 Memory 与 Tool Retrieval,把 Agent 的“外脑”收回到中台

    • Memory:把 Agent 历史对话与状态接到同一检索内核;
    • 工具:给 MCP / 内部 API 建工具索引与 Playbook 检索。
    • 指标:新 Agent 项目不再自己维护工具清单与记忆逻辑,而是声明式配置使用统一 Context Engine。
  4. 视业务需求再考虑多模态 RAG 与张量检索

    • 优先在强需求垂直场景试点,如医疗、设计、工业文档。
    • 逐步演进索引与模型,而不是一次性改造全站。
声明:该内容由作者自行发布,观点内容仅供参考,不代表平台立场;如有侵权,请联系平台删除。
标签:
语义检索(RAG)
智能体(Agent)
任务规划 Agent
工具调用 Agent
上下文