1. SecurityClaw 学习笔记/

14 RAG 与上下文工程:检索证据而不是记住一切

结论先说:RAG 最容易做错的地方不是“没搜到”,而是“什么都往 prompt 里塞” #

很多人一提 RAG,就默认它应该尽可能多地把历史知识塞进模型上下文。这个想法在真实 Agent 系统里非常危险。

在 SecurityClaw 这种多步系统里,RAG 真正要解决的是:

  1. 当前 state 不足时,如何引入外部证据
  2. 检索到的证据如何被压缩进有限上下文预算
  3. 什么情况下 RAG 应该介入,什么情况下它反而会污染决策

一、RAG 在 Agent 里的角色不是“长期记忆”,而是“按需注入的证据层” #

你可以把它和 state / checkpoint 做一个最简区分:

保存什么什么时候用
State当前执行状态每一步都用
Checkpoint恢复所需结构信息恢复时用
RAG长期知识和历史证据当前信息不足时用

真正的问题不是“RAG 能不能搜到”,而是:

当前这一轮推理,是否真的需要外部证据参与?

如果不需要,RAG 介入反而会把上下文弄脏。


二、一个健康的 RAG 流程应该长这样 #

flowchart TD
    Q[当前问题] --> Need{内部 state 是否足够?}
    Need -->|是| Skip[不走 RAG]
    Need -->|否| Embed[生成 query embedding]
    Embed --> Search[向量/关键词检索]
    Search --> Rerank[重排与筛选]
    Rerank --> Budget[上下文预算裁剪]
    Budget --> Inject[注入 prompt]
    Inject --> LLM[进入下一轮规划/回答]

这里真正重要的不是检索本身,而是中间两个控制点:

  1. Rerank:不是所有检索结果都值得留下
  2. Budget:不是所有有用结果都塞得进 prompt

三、上下文预算为什么比检索精度更容易把系统搞坏 #

如果你只关注召回率,很容易忽略一个事实:

在 Agent 系统里,过多的“相关内容”也会成为噪音。

最典型的错误做法 #

docs = retriever.retrieve(query, top_k=20)
context = "\n".join(doc.text for doc in docs)
messages.append(context)

这段逻辑的问题不是“搜得不准”,而是:

  • 20 条文档对当前问题可能太多
  • 每条文档长度不受控
  • 模型会被旧证据拖偏注意力
  • token 被历史文本吞掉,真正规划空间反而不够

更合理的模型 #

docs = retriever.retrieve(query, top_k=20)
ranked = rerank(docs, query)
selected = take_until_token_budget(ranked, max_tokens=1200)
context = format_as_evidence(selected)

这里最重要的是 take_until_token_budget。这一步决定了系统是在“做知识注入”,还是“做信息污染”。


四、RAG 什么时候不该参与 #

这是最容易被忽略,但其实最重要的边界。

4.1 当前 state 已经足够 #

如果问题是:

  • 当前 step 结果是什么
  • 当前 plan 为什么失败
  • 当前预算还剩多少

这些都属于 state / runtime,不该走 RAG。

4.2 问题本身是确定性校验 #

例如:

  • 工具名是否合法
  • 参数 schema 是否匹配
  • 权限是否足够

这些应该由运行时代码直接判断,不该交给检索层。

4.3 历史知识会干扰当前判断 #

安全场景尤其明显:

  • 当前告警和历史某次攻击长得像
  • 但这一次实际并不是同类攻击

如果 RAG 把“相似历史”强行注入,模型很可能过度类比。

所以正确理解应该是:

RAG 不是默认参与者,而是一个条件性介入层。


五、为什么需要 rerank,而不是只用向量相似度 #

向量检索能解决“语义相似”,但不一定解决“当前任务最有用”。

比如同样都和“异常外联”相关:

  • 一条是背景知识
  • 一条是这个资产 3 小时前的真实行为

从语义上看两者都相关,但从当前决策价值看,显然后者更重要。

这就是 rerank 的意义:

# 伪代码:相关 ≠ 最有决策价值
ranked = reranker.score(query, docs)
selected = sorted(ranked, key=lambda x: x.decision_value, reverse=True)

你可以把 rerank 理解成:

  • 检索负责“找得到”
  • 重排负责“找得对”

六、Failure path:RAG 最常见的三种失败 #

6.1 召回到相似但无用的文本 #

问题不在 embedding,而在缺少重排和预算裁剪。

6.2 检索本身工作正常,但 prompt 被污染 #

这时系统看起来“召回成功”,但实际推理效果变差。

6.3 把 RAG 当成状态记忆替代品 #

这会导致每一步都重新检索同样的信息,系统不断重复喂旧证据,最后既浪费 token 又污染上下文。


七、这一篇真正要记住的框架 #

RAG 在 Agent 系统里可以压成这条链:

需要外部证据吗?
  -> 检索
  -> 重排
  -> 预算裁剪
  -> 格式化注入

真正值钱的不是“有检索”,而是:

  • 知道何时该检索
  • 知道检索后该保留多少
  • 知道哪些知识不该参与当前回合

八、验证命令 #

pytest tests/ -k "rag or retrieval or context" -v