跳到主要内容
返回写作

Agent 的 RAG 不只是向量搜索:混合检索架构实践

Skill 检索本身就是一个 RAG 系统——用检索到的 skill 增强 agent 的生成能力。本文拆解双路混合检索的实现与取舍。

本文索引07
  1. 01问题
  2. 02双路检索架构
  3. 03Embedding 方案
  4. 04为什么是"混合"而非"纯向量"
  5. 05融合策略
  6. 06生产级别的考量
  7. 07与通用 RAG 的区别

Skill 检索本身就是一个 RAG 系统——用检索到的 skill 增强 agent 的生成能力。

1. 问题

在我们 56 个 skill 的技能系统中,每次任务开始前需要从技能库里找到最相关的 skill 加载到 prompt 里。这本质上就是一个 RAG 问题:

Query = 当前任务描述
Corpus = 56 条 SKILL.md
Goal  = 召回最相关的 3-5 个 skill

大多数 RAG 教程只讲向量搜索。但在生产环境中,纯向量搜索不够。

2. 双路检索架构

我们不走纯向量路线,而是用结构化召回 + 向量召回的混合方案:

Query
  │
  ├─→ Structural Recall (结构化召回)
  │   ├─ 实体匹配:query 中的关键词 vs skill 的 provides/requires/tags
  │   ├─ 实体扩展:seed entity → cosine 近邻 → 候选集
  │   └─ 评分:base_score + overlap_weight × min(entity_overlap, cap)
  │
  └─→ Vector Recall (向量召回)
      ├─ Query embedding (384d) vs 所有 skill embeddings
      ├─ Cosine similarity > threshold → 候选集
      └─ 评分:similarity × vector_weight
  │
  └─→ Fusion
      ├─ Both paths hit → structural + vector + both_boost
      ├─ Only structural → structural score
      └─ Only vector → vector score
      └─ Sort by score → top-k

3. Embedding 方案

# 模型:paraphrase-multilingual-MiniLM-L12-v2 (384维,CPU 可跑)
# 文本构建:name + description + tags + triggers + body_preview
text = f"{skill['name']} {skill['description']} {' '.join(skill['tags'])} ..."
embedding = model.encode(text, normalize_embeddings=True)
 
# 降级策略:sentence-transformers 不可用 → hash-based embedding
# MD5 分桶 + 符号位 → 384 维稀疏向量 → cosine 仍可用

为什么选这个模型?

  • 384 维,CPU 推理 2ms/条
  • 多语言支持(技能名称有中英文混杂)
  • sentence-transformers 生态成熟,pip install 即用

降级策略是生产环境必备——如果 embed 模型加载失败(OOM、版本冲突),系统不能挂。hash-based embedding 虽然精度差,但保证检索不中断。

4. 为什么是"混合"而非"纯向量"

场景纯向量混合
"部署服务器" → server-operations✅ 语义匹配✅ + tag 精确匹配
"code-review" → code-review✅ 但可能匹配 code-style✅ provides 精确命中
"查错 debug" → debugging-toolkit✅ + when_to_apply 匹配
拼写错误 "dubug" → debugging❌ embedding 偏移✅ 结构化召回兜底

结论:结构化召回处理精确匹配和容错,向量召回处理语义泛化,两者互补。

5. 融合策略

双路召回后需要融合排序。我们试过三种策略:

策略效果问题
加权平均简单可控阈值难调,一路高分可能盖过另一路
RRF (Reciprocal Rank Fusion)对排名敏感,鲁棒性好忽略分数绝对值
条件融合 (目前在用)双命中加分,单路降权需要调 both_boost 系数

最终选条件融合,因为它允许我们把"双路都命中的 skill"推到顶部——经验上这些往往是最相关的。

6. 生产级别的考量

  • 缓存:embedding 构建时预计算所有 skill,运行时只有 query 需要 encode
  • 延迟:一次检索 ≈ 5ms (embed) + 1ms (cosine) + 0.5ms (结构化)
  • 更新:skill 新增/修改 → 增量更新 embedding 索引,不需要重建全部
  • 降级链:正常 → 退化到纯向量 → 退化到关键词 LIKE 查询 → 退化到随机返回

7. 与通用 RAG 的区别

我们这里 RAG 的"G"不是外部文档,而是技能库。这意味着:

  • 召回的不是"信息",而是"行为模式"
  • 相关性不是语义相似度,而是"这个 skill 能不能处理当前任务"
  • 召回错误的影响不是"答非所问",而是"Agent 用错了方法"

所以召回质量比通用 RAG 更关键——召回一个不相关的 skill,可能让整个任务方向跑偏。

结论:混合检索 + 条件融合 + 三阶降级,是我们在生产环境验证过的可靠方案。