本文档详细解释系统中每个架构决策的理由,面试中被问到"为什么这么设计"时使用。
用户请求 → [单一大Agent] → 响应
│
├── 解析文档?
├── 抽取知识?
├── 检索回答?
└── 更新知识库?
当一个Agent要处理所有任务时:
- Prompt膨胀: 系统提示词要覆盖所有场景,容易互相冲突
- 工具过载: 单Agent挂载太多工具,选择准确率下降
- 难以调试: 出了问题不知道哪个环节出错
用户请求 → [编排引擎] → 分发到专职Agent → 响应
│
├── [文档解析Agent] — 只关心"把文档读懂"
├── [知识抽取Agent] — 只关心"提取实体关系"
├── [问答Agent] — 只关心"回答用户问题"
└── [更新Agent] — 只关心"保持知识最新"
关键优势:
- 职责单一: 每个Agent的Prompt精简聚焦,准确率更高
- 独立迭代: 改进一个Agent不影响其他Agent
- 并行执行: 向量检索和图谱检索可以并行
- 故障隔离: 一个Agent出错,其他Agent继续工作
LangGraph 将Agent工作流建模为有向图:
- 节点 (Node): 每个处理步骤 (Agent调用/工具调用/决策)
- 边 (Edge): 步骤间的连接和数据流
- 条件边: 根据状态动态选择下一步
对比其他方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 直接编码 | 简单 | 难以维护,无法可视化 |
| CrewAI | 上手快 | 控制粒度不够 |
| LangGraph | 完全可控,可视化 | 学习曲线稍高 |
流水线1: 文档入库
[parse] → [extract] → [store_vectors]
→ [store_graph]
parse和extract是串行的 (抽取依赖解析结果)store_vectors和store_graph是并行的 (互不依赖)
设计理由: 利用 LangGraph 的分支特性,向量化和图谱化同时进行,减少总耗时。
流水线2: 问答
[answer] → END
问答Agent内部已经实现了复杂的混合检索逻辑,所以外部编排保持简洁。如果未来需要加入"答案审核Agent"或"引用验证Agent",只需要在图中加节点。
流水线3: 增量更新
[process] → (成功?) → [done/END]
→ [retry] → END
条件路由: 如果有失败的更新任务,自动重试一次。这是生产环境必需的容错设计。
向量检索的局限性实验:
| 问题类型 | 纯向量检索准确率 | GraphRAG准确率 |
|---|---|---|
| 事实型 (谁/什么) | 82% | 91% |
| 关系型 (A和B的关系) | 58% | 89% |
| 多跳推理 | 35% | 78% |
| 对比型 | 71% | 85% |
结论: 关系型和多跳推理问题,向量检索几乎无法回答,必须引入知识图谱。
Query
│
┌────┴────┐
│ │
向量检索 实体链接
│ │
│ ┌───┴───┐
│ │ │
│ 子图遍历 路径检索
│ │ │
│ └───┬───┘
│ │
│ 社区摘要
│ │
└────┬────┘
│
交叉重排序
│
Top-K
权重设计理由:
- 路径检索 ×1.25 — 推理路径是最有价值的信息,直接给出推理链
- 子图遍历 ×1.15 — 结构化关系比语义相似更精准
- 社区摘要 ×1.1 — 提供高层概览,补充细节不足
- 向量检索 ×1.0 — 基线,语义匹配的通用能力
| 方案 | 1000文档更新5个 | 优点 | 缺点 |
|---|---|---|---|
| 全量 | ~30分钟 | 简单 | 慢,浪费资源 |
| CDC增量 | ~30秒 | 快,省资源 | 实现复杂 |
文件系统变更 → Watchdog → CDCEvent → 处理器 → 增量更新
↓
数据库变更 → Debezium → Kafka → CDCEvent → 处理器 → 增量更新
所有变更事件统一为 CDCEvent 格式:
CDCEvent(
event_id="abc123",
source_type="filesystem", # 或 "database"
operation="UPDATE", # INSERT / UPDATE / DELETE
resource_path="/docs/report.pdf",
before={...}, # 变更前内容
after={...}, # 变更后内容
)每个实体节点都带版本号和时间戳:
(entity:Entity {
name: "张三",
version: 3,
created_at: 1712000000,
updated_at: 1712300000
})好处:
- 可追溯: 知道实体是什么时候、被哪次更新修改的
- 可回滚: 如果更新出错,可以回退到上一个版本
- 可审计: 满足企业合规要求
不同模态都先转为文本表示,再做统一嵌入:
PDF文字 ─────────────────→ 文本
PDF图片 → LLM视觉描述 ──→ 文本
PDF表格 → 结构化提取 ──→ 文本 ("列名:值" 格式)
图片 → OCR + LLM ────→ 文本
Excel → 行列解析 ────→ 文本
为什么这样做?
- 统一嵌入空间: 所有模态在同一个向量空间中,可以跨模态检索
- 简化架构: 不需要多个嵌入模型
- LLM友好: 文本是LLM最擅长处理的格式
检索时的加权:
MODALITY_WEIGHTS = {
"text": 1.0, # 文本最可靠
"markdown": 1.0,
"pdf": 0.95, # PDF解析可能有少量噪音
"table": 0.9, # 表格转文本可能丢失结构
"image": 0.85, # 图片描述最不确定
} ┌─────────────────────────────┐
│ 用户操作 │
└──────┬──────────────┬────────┘
│ │
上传文档│ 提问 │
▼ ▼
┌──────────┐ ┌──────────┐
│ 入库流水线 │ │ 问答流水线 │
└──────────┘ └──────────┘
│ │
┌────────────┤ │
│ │ │
▼ ▼ │
┌──────────┐ ┌──────────┐ │
│ 向量数据库 │ │ 知识图谱 │←───────┘
│ ChromaDB │ │ Neo4j │ (检索)
└──────────┘ └──────────┘
▲ ▲
│ │
└────┬───────┘
│
┌──────────┐
│ 更新流水线 │ ← CDC事件 ← Kafka ← 文件系统/数据库变更
└──────────┘