Command Palette

Search for a command to run...

0

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

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

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,可能让整个任务方向跑偏。

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