14 RAG 与上下文工程:检索证据而不是记住一切
结论先说:RAG 最容易做错的地方不是“没搜到”,而是“什么都往 prompt 里塞” #
很多人一提 RAG,就默认它应该尽可能多地把历史知识塞进模型上下文。这个想法在真实 Agent 系统里非常危险。
在 SecurityClaw 这种多步系统里,RAG 真正要解决的是:
- 当前 state 不足时,如何引入外部证据
- 检索到的证据如何被压缩进有限上下文预算
- 什么情况下 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[进入下一轮规划/回答]
这里真正重要的不是检索本身,而是中间两个控制点:
- Rerank:不是所有检索结果都值得留下
- 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