OpenClaw / 03
RAG:检索增强的工程实现
RAG(Retrieval-Augmented Generation)在 Agent 系统中有两种用法:
- 知识库问答:用户提问 → 检索相关文档 → 注入上下文 → 生成回答
- 代码库导航:Agent 需要理解大型代码库时,按需检索相关代码片段
这一节覆盖从基础实现到生产级优化的完整路径。
核心管线
flowchart LR
A[文档] --> B[分块] --> C[编码 Embedding] --> D[写入向量库]
E[用户查询] --> F[编码] --> G[向量检索 Top-K] --> H[Rerank] --> I[注入 Prompt] --> J[LLM 生成]
D -.-> G
每个环节的选择直接影响最终效果。
分块策略
按语义边界分块(推荐)
// 代码文件:按函数/类边界切分
function chunkByAST(code: string, language: string): Chunk[] {
const tree = parser.parse(code, language)
return tree.rootNode.children
.filter(node => ['function', 'class', 'method'].includes(node.type))
.map(node => ({
content: node.text,
metadata: { type: node.type, name: node.name, startLine: node.startPosition.row }
}))
}
按固定窗口分块(简单场景)
function chunkByWindow(text: string, size = 512, overlap = 64): string[] {
const chunks: string[] = []
for (let i = 0; i < text.length; i += size - overlap) {
chunks.push(text.slice(i, i + size))
}
return chunks
}
面试考点:为什么需要 overlap?因为语义可能跨越切分边界,overlap 保证边界处的信息不丢失。
Embedding 选型
| 模型 | 维度 | 特点 |
|---|---|---|
text-embedding-3-small (OpenAI) |
1536 | 通用能力强,需代理 |
BGE-M3 (BAAI) |
1024 | 中文优秀,开源可部署 |
GTE-Qwen2 (阿里) |
768 | 代码理解能力强 |
Cohere embed-v3 |
1024 | 支持搜索/分类/聚类多任务 |
Coding Agent 场景推荐 GTE-Qwen2 或 BGE-M3——代码和中文都表现好,且可本地部署避免网络延迟。
向量数据库选择
| 工具 | 适用场景 | 特点 |
|---|---|---|
| ChromaDB | 本地开发、原型验证 | Python 嵌入式,零配置 |
| pgvector | 已有 PostgreSQL | 无需新增服务,事务一致性 |
| Milvus | 大规模生产(亿级) | 分布式,高吞吐 |
| Qdrant | 中等规模生产 | Rust 实现,单机性能好 |
混合检索:稠密 + 稀疏
单纯的向量检索有盲区——对精确关键词匹配(函数名、变量名)效果差。生产系统通常用混合检索:
// 伪代码:混合检索 + RRF 融合
async function hybridSearch(query: string, k: number): Promise<Chunk[]> {
// 稠密检索:语义相似
const denseResults = await vectorDB.search(embed(query), k * 2)
// 稀疏检索:关键词匹配(BM25)
const sparseResults = await bm25Index.search(query, k * 2)
// Reciprocal Rank Fusion 融合排序
return reciprocalRankFusion(denseResults, sparseResults, k)
}
function reciprocalRankFusion(lists: Result[][], k: number): Result[] {
const scores = new Map<string, number>()
const RRF_K = 60 // 常数,控制排名衰减速度
for (const list of lists) {
list.forEach((item, rank) => {
const score = 1 / (RRF_K + rank + 1)
scores.set(item.id, (scores.get(item.id) || 0) + score)
})
}
return [...scores.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, k)
.map(([id]) => getChunkById(id))
}
GBrain(OpenClaw 的生产记忆系统)就是用 Postgres + pgvector + BM25 + RRF 实现的混合检索,P@5 达到 49.1%,R@5 达到 97.9%。
Reranker:精排提升精度
粗召回 Top-K 后,用 Cross-Encoder 做精排:
async function rerankResults(query: string, chunks: Chunk[], topN: number): Promise<Chunk[]> {
const scored = await Promise.all(
chunks.map(async chunk => ({
chunk,
score: await crossEncoder.score(query, chunk.content)
}))
)
return scored.sort((a, b) => b.score - a.score).slice(0, topN)
}
常用 Reranker:bge-reranker-v2-m3、cohere-rerank-v3。
精排可以将 Top-5 精度提升 10-20%,但增加约 200ms 延迟。
在 Agent 中的集成方式
Coding Agent 中 RAG 通常不是独立的”检索步骤”,而是作为工具被模型按需调用:
const searchCodeTool = {
name: 'search_codebase',
description: '在代码库中搜索与查询语义相关的代码片段',
parameters: {
query: { type: 'string', description: '搜索查询' },
k: { type: 'number', description: '返回结果数量', default: 5 }
},
execute: async ({ query, k }) => {
const results = await hybridSearch(query, k)
return results.map(r => `${r.metadata.path}:${r.metadata.startLine}\n${r.content}`).join('\n---\n')
}
}
模型自己决定什么时候需要搜索代码库,而不是每次都强制检索。
OpenClaw 的 RAG 特点
OpenClaw 没有在核心架构中内置 RAG——它把 RAG 当作可选插件。原因:
- Coding Agent 的主要操作是读写文件(
read工具带 offset/limit),大多数情况下精确路径 + grep 就够了 - 只有在处理大规模知识库(文档库、工单库)时才需要向量检索
- RAG 质量高度依赖分块策略和 embedding 选型,不适合做通用默认方案
但 GBrain(OpenClaw 的外部记忆宿主)提供了完整的 RAG 能力:
- Postgres + pgvector 混合检索
- “Compiled Truth + Timeline” 模式——每个知识页有当前理解 + 追加式证据链
- 自动知识图谱:提取实体引用和类型化链接
- “Dream Cycle” 夜间合成:定期整理、聚合、丰富知识
面试高频题
Q:RAG 和 Fine-tuning 什么时候选哪个?
| 维度 | RAG | Fine-tuning |
|---|---|---|
| 知识更新 | 实时(改文档即生效) | 需要重新训练 |
| 幻觉控制 | 有来源可追溯 | 无法保证 |
| 成本 | 推理时增加检索开销 | 训练成本高,推理不增加 |
| 适用场景 | 知识库问答、文档检索 | 风格/格式/推理模式固化 |
Q:向量检索的 Top-K 设多少合适?
取决于 Reranker 和上下文窗口。经验值:粗召回 K=20,精排后取 Top-5 注入 Prompt。K 太大会引入噪声,太小可能漏掉相关内容。
Q:Embedding 模型和生成模型用同一个可以吗?
不推荐。Embedding 模型是专门训练的双塔/对比学习模型,生成模型的隐状态不适合做相似度检索。用专用 Embedding 模型效果显著更好。
下一篇:工具系统:MCP 协议与并行执行