RAG与Agent工程 · AI Agent
RAG 与 Agent 工程
核心定位
RAG 的本质是:
代码块收起展开
先检索可信资料,再让模型基于资料回答。它解决的是“模型不知道你的私有知识”的问题,不解决所有 Agent 问题。RAG 负责知识进入上下文;Agent 负责什么时候检索、是否调用工具、如何多步执行、如何记忆和评测。
标准链路
后端工程视角
| RAG 模块 | 后端类比 | 关键问题 | 必留字段 |
|---|---|---|---|
| Loader | 文件导入 / ETL | 编码、增量扫描、失败重试 | source_path, mtime, hash |
| Parser | DTO 转换 | Markdown/PDF/HTML 结构保留 | title, headings, raw_text |
| Chunker | 数据切分 | 块大小、标题路径、上下文丢失 | chunk_id, title_path, start_line |
| Embedder | 外部 API 调用 | 批处理、限流、缓存、成本 | embedding_model, vector_dim |
| Vector Store | 索引库 | 过滤、更新、删除、重建 | chunk_id, embedding |
| Retriever | 查询服务 | TopK、混合检索、rerank | score, rank, query |
| Generator | 业务服务 | Prompt、引用、拒答 | used_sources, answer |
| Evaluator | 测试系统 | 回归集、指标、错误归因 | eval_case_id, pass/fail |
Obsidian 切块策略
Markdown 不适合按固定字符硬切。建议按标题结构切:
代码块收起展开
final class NoteChunk {
String chunkId; // 稳定 ID,建议 sourcePath + headingPath + hash
String sourcePath; // 原始 Markdown 路径,用于引用和回跳
String titlePath; // 标题层级,例如 AI Agent > MCP > Tool
int startLine; // 起始行,便于定位
int endLine; // 结束行,便于定位
String content; // chunk 正文
String hash; // 判断内容是否变化
List<String> tags; // frontmatter 或正文标签
}切块样例与边界判断
同一篇 Markdown 里,标题、列表、代码块和表格都不能随便切碎。切块的目标是让证据“刚好能回答问题”,不是让 chunk 数量看起来很多。
代码块收起展开
# MCP 专题学习笔记
## Tool 设计原则
坏工具:run_command(command: string)
问题:权限过大、参数无边界。
## 安全边界
路径越权:resolve 后必须在白名单目录内。| 切法 | chunk 内容 | 结果 |
|---|---|---|
| 按 200 字硬切 | 可能把“坏工具”和“问题”切开 | 证据碎,模型看不懂原因 |
| 按 H2 切 | Tool 设计原则 成为完整块 | 能回答工具为什么要收敛 |
| H2 太长再按段落切 | 保留父标题路径 | 既控 token,又不丢语义 |
| 表格整体保留 | 不拆表头和行 | 引用时字段含义完整 |
代码块收起展开
final class ChunkBoundaryRule {
int maxChars; // 单个 chunk 最大字符数,防止上下文膨胀
int overlapChars; // 段落二次切分时保留少量重叠
boolean keepTableTogether; // 表格不拆开,否则列含义会丢失
boolean keepCodeTogether; // 代码块不拆开,否则字段注释会丢失
boolean includeParentPath; // 每个 chunk 附带 H1/H2/H3 路径
}检索策略
| 策略 | 解决什么 | 适合场景 | 风险 |
|---|---|---|---|
| Vector Search | 语义相似 | 问法和笔记措辞不同 | 关键词精确命中弱 |
| BM25 / Keyword | 精确词匹配 | 类名、术语、文件名 | 同义表达召回弱 |
| Metadata Filter | 限定范围 | 只搜 AI Agent 或 Java | 过滤过严会漏 |
| Rerank | 重排候选 | TopK 噪声多 | 成本增加 |
| Parent Context | 补父标题/相邻段 | chunk 太碎 | 上下文变长 |
| Citation Check | 强制引用证据 | 防编造 | 答案可能变保守 |
推荐最小链路:
代码块收起展开
BM25 top20 + Vector top20 -> merge -> metadata filter -> rerank top5 -> context builderRAG 与 Agent 的关系
| 能力 | RAG 负责 | Agent 负责 |
|---|---|---|
| 找资料 | chunk、embedding、召回、重排 | 决定是否需要找 |
| 控制上下文 | 提供候选证据 | 分配 token 预算 |
| 生成答案 | 不负责最终语言 | 要求引用和拒答 |
| 多步任务 | 不负责 | 规划、工具、状态机 |
| 记忆更新 | 不一定负责 | 决定是否写入长期记忆 |
典型失败模式
| 失败 | 表现 | 根因 | 修复 |
|---|---|---|---|
| 召回错 | 找到相似但无关片段 | embedding 只看语义近 | 加 BM25、metadata、rerank |
| 证据碎 | 片段看似相关但无法回答 | chunk 太小或丢标题 | 保留标题路径和父块 |
| 证据噪 | topK 太多,答案跑偏 | 上下文污染 | rerank + topK 限制 |
| 编造 | 没资料也回答 | prompt 没拒答策略 | 强制“资料不足则拒答” |
| 引用假 | 引用不支持结论 | citation 未校验 | answer-source consistency check |
| 索引旧 | 改笔记后答旧内容 | 无增量更新 | hash + mtime 重建 |
| 难复盘 | 不知道错在哪 | 没 trace | 记录 query、chunks、scores |
评测体系
quadrantChart
title RAG 评测象限
x-axis 低召回 --> 高召回
y-axis 低忠实 --> 高忠实
quadrant-1 好系统
quadrant-2 资料找得到但会乱写
quadrant-3 完全不可用
quadrant-4 回答谨慎但找不到资料
"理想 RAG": [0.85, 0.9]
"只向量检索": [0.55, 0.65]
"无引用生成": [0.75, 0.35]
"过度拒答": [0.3, 0.85]
| 指标 | 问题 | 采集方式 |
|---|---|---|
| Recall@K | 正确资料是否进 topK | 标注 expected source |
| MRR | 正确资料排第几 | 看 rank |
| Faithfulness | 答案是否被证据支持 | 人工/规则/Judge |
| Citation Accuracy | 引用是否真实支持句子 | 抽查引用 |
| Refusal Quality | 无资料时是否拒答 | 负例问题 |
| Latency | 用户等多久 | trace |
| Cost | embedding + rerank + LLM 成本 | usage log |
最小数据表
代码块收起展开
final class RagTrace {
String traceId; // 一次问答链路 ID
String originalQuestion; // 用户原问题
String rewrittenQuery; // 检索改写后的 query
List<String> recalledIds; // 初召回 chunk
List<String> rerankedIds; // 重排后 chunk
List<String> usedSources; // 最终答案引用来源
boolean refused; // 是否拒答
long latencyMs; // 总耗时
}检索调试手册
RAG 出错时不要直接改 prompt。先看 trace,判断错误发生在资料、切块、召回、重排、上下文还是生成阶段。
| 症状 | 先看字段 | 常见根因 | 优先修复 |
|---|---|---|---|
| 完全找不到资料 | recalledIds, score | query 改写差、索引旧 | 加 BM25、重建索引 |
| 找到相邻但没答案 | titlePath, startLine | chunk 太碎或丢父标题 | parent context |
| topK 有答案但没用 | rerankedIds | rerank 排错 | 规则重排或 rerank 模型 |
| 答案混入无关内容 | usedSources | context 太噪 | 限 topK,去重,压缩 |
| 引用不支持结论 | answer, usedSources | 生成阶段偷换概念 | citation check |
| 没资料却回答 | refused | prompt 缺拒答约束 | 增加负例 eval |
评测数据集设计
20 个问题只是起步,关键是覆盖失败模式。每个 eval case 都要有期望来源、拒答标记和评价规则。
代码块收起展开
{
"id": "rag-mcp-001", // 稳定用例 ID,方便回归对比
"question": "MCP 的 Tool 和 Resource 区别是什么?",
"expectedSources": ["01-MCP专题学习笔记.md"], // 至少应命中的笔记或 chunk
"mustCite": true, // 是否要求回答带来源
"shouldRefuse": false, // 负例问题应设为 true
"checks": ["source_hit", "faithful", "clear"]
}| 用例类型 | 目标 | 通过标准 |
|---|---|---|
| 事实定位 | 找到明确段落 | expected source 进 top5 |
| 跨文档综合 | 合并多篇笔记 | 每个关键结论有来源 |
| 操作建议 | 给出下一步 | 建议能追溯到项目路线 |
| 负例拒答 | 防编造 | 说明资料不足,不硬答 |
| 引用一致性 | 防假引用 | 引用片段支持对应句子 |
引用一致性检查
引用不是装饰。答案中的关键句必须能被某个 source 支持,否则就是“带引用的编造”。
| 检查项 | 合格 | 不合格 |
|---|---|---|
| 来源存在 | 路径和标题可打开 | 引用不存在的文件 |
| 句子支持 | source 能证明结论 | source 只主题相近 |
| 范围合适 | 引用到具体标题或行 | 只引用整个目录 |
| 冲突处理 | 多来源冲突时说明 | 只选一个有利来源 |
| 资料不足 | 明确拒答或追问 | 用常识补全私有事实 |
RAG 调参顺序
RAG 失败时不要同时改 chunk、embedding、prompt、topK。每次只改一个变量,否则你不知道哪一步真的生效。推荐按下面顺序排查:
| 顺序 | 先问什么 | 看什么证据 | 常见修复 |
|---|---|---|---|
| 1 | 正确资料有没有进索引 | sourcePath、hash、updatedAt | 重建索引、修正 ignore 规则 |
| 2 | query 能不能召回正确文档 | recalledIds、BM25/vector score | query rewrite、BM25 + vector 混合 |
| 3 | chunk 是否保留必要上下文 | titlePath、父标题、相邻段落 | parent context、按标题切块 |
| 4 | rerank 是否把正确资料排前 | rerankedIds、rerank reason | 规则 rerank、标题权重、来源权重 |
| 5 | prompt 是否把证据放清楚 | prompt snapshot、source 编号 | 统一 source 格式,要求引用 |
| 6 | 模型是否忠实使用证据 | answer-check、citation-check | 增加拒答约束和负例 eval |
| 7 | 输出是否可验收 | final answer、citations、trace | schema 校验、引用一致性检查 |
一条高质量 RetrievedChunk 不只是正文片段,至少要带这些字段:
| 字段 | 为什么必须有 |
|---|---|
chunkId | eval 和 trace 能定位同一片段 |
sourcePath | 用户能打开原文 |
titlePath | 模型知道片段在文档结构里的位置 |
startLine/endLine | Debug 时能精确回到笔记 |
score | 判断召回是否可信 |
retriever | 区分 BM25、vector、hybrid、manual |
contentHash | 判断索引是否过期 |
RAG 的调参记录也要进 trace。至少记录:
代码块收起展开
{
"query": "MCP 和 RAG 的区别", // 用户原始问题。
"rewrite": ["MCP tool protocol", "RAG retrieval evidence"], // 查询改写结果,用于扩大召回。
"topK": 8, // 初召回数量,太小容易漏证据,太大增加噪声。
"retriever": "hybrid", // 检索器版本:向量、关键词或混合检索。
"rerank": "title+score", // 重排策略,说明最终证据如何排序。
"selectedChunkIds": ["01-mcp-309", "02-rag-199"], // 进入最终上下文的 chunk。
"refused": false // 是否因为证据不足而拒答。
}个人知识库 Agent MVP
| 模块 | 功能 | 验收 |
|---|---|---|
| Indexer | 索引 技术栈/AI Agent Markdown | 修改文件后能增量更新 |
| Retriever | 混合检索 + rerank | 正确笔记进入 top5 |
| Answerer | 带引用回答 | 每段关键结论有来源 |
| Refuser | 资料不足拒答 | 不存在的问题不编 |
| Trace | 保存检索链路 | 能看到 query、score、source |
| Eval | 固定问题集 | 改 prompt 后可对比 |
20 个评测问题怎么设计
| 类型 | 数量 | 例子 |
|---|---|---|
| 明确事实 | 5 | MCP 的 tool/resource/prompt 区别是什么 |
| 跨笔记综合 | 5 | Java 后端怎么迁移到 Agent 工程 |
| 操作建议 | 4 | 现在应该先做哪个 Agent 项目 |
| 负例拒答 | 3 | 笔记里没有的具体产品参数 |
| 引用检查 | 3 | 要求回答附来源路径和标题 |
最小实现顺序
- Markdown parser:按标题切 chunk。
- Metadata DB:保存路径、标题、hash、更新时间。
- Embedding:批量生成向量,支持缓存。
- Retriever:BM25 + vector 初召回。
- Rerank:先用规则重排,再接模型重排。
- Prompt:强制引用和资料不足拒答。
- Trace:记录 query、chunks、scores、answer。
- Eval:固定 20 个问题回归。
RAG 验收门槛
| 门槛 | 最低标准 | 失败就先别进入下一步 |
|---|---|---|
| 索引新鲜度 | 修改笔记后能增量更新 | 回答旧内容 |
| 召回命中 | 事实类问题 expected source 进 top5 | topK 全无关 |
| 引用忠实 | 关键结论有可打开来源 | 引用只主题相近 |
| 拒答能力 | 负例问题不编造 | 用常识硬答 |
| Trace 可复盘 | 保存 query、chunk、score、answer | 只保存最终答案 |
| Eval 可回归 | 改策略后能比较 pass/fail | 没有固定数据集 |
核心判断
RAG 的难点不在“把资料塞给模型”,而在:
代码块收起展开
资料能更新,检索能命中,证据能引用,答案能拒答,错误能复盘。