项目全景与架构设计#
一句话介绍#
DocMind 是一个面向研发团队内部技术文档的 Agentic RAG + MCP 知识问答系统,解决”文档分散、格式混杂、查找低效”的研发效能痛点。
业务场景#
目标用户:中大型研发团队(50-200 人),文档分散在 Confluence、飞书、Git 仓库、本地 PDF 等多个平台。
核心痛点:
- 新人 onboarding 慢——需要翻遍几十篇文档才能找到一个 API 参数配置,老员工反复被打断
- 精确查找难——“Spring Boot 3.4 的
server.shutdown默认值是什么?“纯向量检索容易被语义相近内容干扰 - 文档格式碎片化——架构设计是 PDF,API 文档是 Markdown,运维手册是 Word,会议纪要是网页
- 知识孤岛——文档只在 Web UI 能搜,开发者不想离开 IDE
DocMind 的解决方式:
| 痛点 | 方案 |
|---|---|
| 精确匹配 API 名/参数/版本号 | BM25 关键词检索(评测:事实类 Recall +0.20) |
| 语义性问题(“微服务拆分原则”) | 向量检索 + RRF 融合 |
| 复杂问题跨多篇文档 | agentic 工具调用循环运行时自驱拆解 / 多跳 / web 补充 |
| 多格式文档 | MinerU 统一解析为 Markdown → Markdown-Aware Chunker |
| IDE 内直接查知识库 | MCP 标准协议暴露给 Cursor / VS Code |
| 答案不能编造技术细节 | 结构化引用 + 覆盖率置信度(生成即终态,CitationParser 解析 [n] 算句子级覆盖率,越界引用计入 invalidRefs;仅无证据才兜底,CRAG 低分但有证据时带证据作答不丢弃,#28) |
项目定位#
不同于传统 RAG 的 “query → 向量检索 → 拼接 → 生成” 固定管线,DocMind 让 LLM 作为 Agent 通过真正的工具调用循环动态决定调用哪些检索工具、调用几次、何时停止,并由模型在运行时自驱拆解 / 多跳 / web 补充。生成即终态,最终答案通过结构化引用([n] 标记解析为可点击来源 + 覆盖率置信度)保证可溯源、不编造。同时通过 MCP 协议对外暴露工具能力,开发者在 IDE 中可直接调用团队知识库而无需切换到 Web UI。
技术选型与理由#
| 技术 | 选型 | 为什么选它(面试话术) |
|---|---|---|
| 后端框架 | Spring Boot 3.4 + Spring AI 1.1.4 | Spring AI 原生支持 Function Calling,和 MCP Server 集成零额外代码 |
| 向量数据库 | Milvus 2.3 | 支持 COSINE 相似度 + 标量过滤联合查询,适合多知识库场景 |
| 全文检索 | Lucene 8.11 (SmartCN) | 内嵌式 BM25,不需要额外部署 Elasticsearch,对中文有原生分词支持 |
| 重排序 | DashScope gte-rerank | Cross-Encoder 级别精排,HTTP API 调用,免部署 Python 推理服务 |
| 嵌入模型 | text-embedding-v3 (1024维) | 阿里云原生,和 DashScope 统一计费,中文效果好 |
| 缓存 | Redis 7 | 同时承担热点缓存 + 跨会话用户记忆两个角色,减少组件数量 |
| 对象存储 | MinIO | S3 兼容,私有化部署,文档原件持久化 |
| 文档解析 | MinerU 云端 API(PDF / PPT / 图片 / 网页 URL 统一入口)+ Apache PDFBox(PDF 兜底)+ Apache POI(DOCX) | layout-aware 多栏 / 表格 / 公式 / OCR / 网页正文抽取;统一输出 Markdown 后进入 RAG,下游链路无需感知格式差异 |
| 前端 | Vue 3 + TypeScript + Element Plus | 类型安全 + 成熟 UI 组件库,开发效率高 |
整体架构#
用户提问
│
▼
┌────────────────────────────────────────────────────────────────┐
│ DocMindAgent (主编排器) │
│ │
│ ① Query Understanding (LLM 一次调用:改写 + 分类,不拆解) │
│ ② Scope Routing (Tier-0 规则 + Tier-1 LLM 范畴判定,六范畴短路) │
│ ③ Path Decision (DocMindAgent.routePath,三模式) │
│ ├─ SELECTED_DOC : 选中文档 + 文档/摘要意图 → 直读 │
│ ├─ RULE_PLANNER : SIMPLE 且非歧义 → 一次性检索 │
│ └─ AGENTIC : 复杂/中等/歧义/多焦点 → 工具调用循环 │
│ ④ Retrieval (两条路径) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ AGENTIC: AgenticSearchOrchestrator │ │
│ │ 真·LLM 工具调用循环 (ToolCallingManager 手动控环)│ │
│ │ 模型自驱:拆解 / 多跳 / web 补充 / 何时停 │ │
│ │ 白名单 4 读 + executeCode 计算(默认关) │ │
│ │ 异常或空 → 回退 one-shot │ │
│ ├──────────────────────────────────────────────────┤ │
│ │ one-shot: SupervisorAgent.oneShotRetrieval │ │
│ │ 并行 Worker 派发 │ │
│ │ ├─ RetrievalWorker (Vector+BM25+RRF) │ │
│ │ ├─ WebWorker (Tavily) │ │
│ │ └─ MemoryWorker (Redis) │ │
│ └──────────────────────────────────────────────────┘ │
│ 两路收束 → Cross-Encoder rerank → MMR → 压缩 → CRAG grade │
│ ⑤ Safety Check + Prompt Assembly │
│ ⑥ LLM Streaming + 结构化引用 (CitationParser) │
│ 生成即终态,无自反思;[n] 解析为可点击来源 + 覆盖率置信度 │
└────────────────────────────────────────────────────────────────┘
│
▼
SSE 流式响应 → 前端逐步渲染plaintext核心设计决策#
1. 为什么用 Agent 而不是固定流水线?#
问题:技术文档问答场景中查询类型差异极大——“Spring Boot 3.4 的 server.shutdown 默认值”需要精确 BM25 匹配,“微服务拆分的最佳实践”需要语义检索 + 大召回量,“昨天故障的根因分析”需要联网查最新信息。固定管线无法兼顾。
方案:路径决策三模式(PathDecision.Mode,DocMindAgent.routePath)。根据查询分类结果选择执行路径——
- AGENTIC(复杂 / 中等 / 歧义 / 多焦点):
AgenticSearchOrchestrator真·LLM 工具调用循环(Spring AIToolCallingManager手动控环,internalToolExecutionEnabled(false),上限agentic.max_iterations默认 4)。白名单暴露 4 个读工具(searchDocs / keywordSearch / webSearch / recall_memory)+ executeCode 沙箱计算工具(sandbox.enabled默认关),模型自驱拆解 / 多跳 / web 补充 / 计算 / 何时停,不再有手写分支。 - RULE_PLANNER(SIMPLE 且非歧义):
SupervisorAgent.oneShotRetrieval一次性检索,工具由RetrievalPlanner规则引擎选。 - SELECTED_DOC(用户选中文档 + 文档/摘要意图):
DocumentDirectReader直读 DB chunks + 均匀采样,跳过检索。
降级:多层降级链——AGENTIC 异常或召回为空 → 回退一次性检索 oneShotRetrieval(agentic.enabled 灰度总开关,默认 on);召回分数低 → HyDE 增强;Query Understanding 失败 → QueryClassification.fallback() 保守工具集兜底。
2. 为什么同时用向量检索和 BM25?#
问题:技术文档中充满精确标识符——API 名、参数名、版本号、错误码。纯向量检索对”text-embedding-v3”可能匹配到所有 embedding 相关内容,无法精确定位。纯 BM25 对”怎么优化检索效果”这种概念性问题又无能为力。
方案:混合检索 + RRF 融合。评测数据:事实类(含 API 名/参数)Recall@5 从 0.73(纯向量)提升到 0.93(+BM25 融合),BM25 贡献了 +0.20。
3. 为什么用结构化引用 + 覆盖率置信度而不是自反思?#
问题:技术文档场景下 LLM 编造不存在的 API 参数或错误的配置值是致命的——开发者如果照着错误配置部署,可能引发线上事故。早期方案是生成后再做自反思重写(二次审查 + 条件重写),但这条路径成本高、时延翻倍、且与流式生成体验冲突。
方案:生成即终态,无生成后审查。改用 CitationParser(纯 Java,无 LLM)做结构化引用与置信度量化——
- 解析最终答案里的
[n]标记 → 结构化 citations(含 index / id / name);越界编号丢弃并计入invalidRefs。 - 计算句子级 coverage = 含 ≥1 个有效
[n]的实质陈述句数 / 实质陈述句总数。 confidenceScore = clamp(coverage) × rerank-top1;coverage 不可算时退回 rerank-top1。- 兜底语义(#28):只有无任何证据(
compressed空)才走assembleFallback兜底模板;CRAG 判 LOW 但有证据时走assembleLowConfidence把 KB/Web 证据带进 prompt 据实作答,不丢弃;零覆盖 + CRAG LOW 仅记ungrounded观测信号,不再触发兜底。 - 前端把
[n]渲染为可点击的.citation-ref,点击滚动高亮对应来源卡,让”答案的每句话从哪来”对开发者透明可验证。
这样把”防编造”从昂贵的事后 LLM 审查,转成确定性、可单测、零额外 LLM 调用的解析 + 量化机制。
4. MCP 协议的价值是什么?#
问题:开发者日常工作在 IDE 中,切换到 Web UI 查文档打断心流。知识库能力被锁在一个入口里。
方案:通过 Spring AI MCP Server 将 doc_search、keyword_search 等工具标准化暴露。实际验证:用 Cursor IDE 作为 MCP 客户端对接,开发者在编辑器内直接用自然语言查询团队知识库,检索结果作为编程辅助的上下文来源。同一套工具代码既服务内部 Agent 循环,也对外暴露标准接口,零代码重复。
5. 为什么 Scope Routing 用两层设计?#
问题:Phase 2 的意图分类(factoid / procedural / comparison 等)默认每种都要查知识库,导致”总结上面的对话”这类元对话被错误送入检索流程,召回完全无关切片。
方案:两层范畴判定——Tier-0 用纯正则(MetaIntentDetector)零 LLM 成本识别高确定性模式(问候/致谢/显式对话引用),Tier-1 复用 QueryUnderstanding 的同一次 LLM 调用输出 scope 字段。任何一层失败默认走 KNOWLEDGE_QUERY,保证向后兼容。
为什么不只用 LLM? Tier-0 正则能处理约 20% 的非知识查询(“你好/谢谢/总结上面”),这些模式确定性极高,用 LLM 是浪费。两层叠加:高确定性的零成本处理,模糊边界的才调 LLM。
6. 为什么 Path Decision 用规则引擎不用 LLM?#
问题:工具选择(doc_search / keyword_search / web_search / recall_memory)本质是 4 个布尔信号(是否模糊、是否精确、是否时效、是否需要记忆)到 N 个工具的确定性映射。
方案:RetrievalPlanner 纯规则引擎,4 个 if 判断 <1ms。比 LLM Function Calling(~300ms、结果不稳定、不可单元测试)更适合这个规则空间极小的场景。PathDecision record 统一输出路径决策,reason 字段记录机器可读的判定原因,写入 trace 可追溯。
项目规模#
| 维度 | 数值 |
|---|---|
| 后端 Java 文件 | 139 个(~16,300 行) |
| 前端 Vue/TS 文件 | 35 个(~6,900 行) |
| 核心 RAG 组件 | 28 个文件 |
| 数据库表 | 11 张(含 RBAC 4 表) |
| 动态配置参数 | 49 个(热配,无需重启) |
| Prompt 模板 | 7 个 |
| MCP 工具 | 5 个 / 6 端点(内外双路径) |
| 优化迭代 | 20 次,每次有评测数据 |
| 评测数据集 | 52 条 × 7 类 × 4 档 × 3 轮 |
完整文件级地图见 14-项目工程全景.md
部署架构#
┌─── docker-compose.dev.yml ──────────────────────┐
│ Redis 7 :6379 缓存 + 用户长期记忆 │
│ MinIO :9000 S3 兼容文档原件存储 │
│ etcd :2379 Milvus 元数据 │
│ Milvus :19530 向量数据库(COSINE, HNSW) │
└─────────────────────────────────────────────────┘
MySQL 8 :3306 业务数据 + BM25 FULLTEXT(独立运行)
Spring Boot :8080 后端 API + MCP Server
Vue 3 + Vite :5173 前端(proxy /api → 8080)
外部 API:DashScope(LLM + Embedding + Rerank)/ MinerU(文档解析)/ Tavily(搜索)/ Langfuse(OTel 协议全链路 trace)plaintext数据库设计概要#
| 表 | 核心设计决策 |
|---|---|
kb_chunk | content_hash(SHA-256 增量索引)+ parent_chunk_id(Parent Document Retrieval)+ vector_id(MySQL↔Milvus 1:1)+ FULLTEXT INDEX(BM25 预召回) |
kb_knowledge_base | version(chunk 变更自增,语义缓存失效依据)+ status 异步处理状态机 |
qa_message | JSON 字段结构化存储推理过程:sources / agent_trace / mcp_calls + confidence_score / confidence_band(覆盖率派生)。reflection_log 列因自反思删除已不再写入,仅为兼容旧数据保留 |
sys_ai_config | 49 个热配参数(Phase 5 新增 10 个 agent 配置),admin 面板实时调整,ConcurrentHashMap + AtomicReference 无锁更新 |
sys_user | RBAC + BCrypt 密码 + JWT 24h/7d 双 token |
sys_role / sys_permission / sys_user_role / sys_role_permission | Phase 4 新增 RBAC 四表:3 角色(SUPER_ADMIN/KB_ADMIN/USER)× 15 权限(kb:upload/mcp:execute 等) |
mcp_tool_registry | 工具调用统计(call_count + avg_latency_ms) |
完整 schema 设计见 14-项目工程全景.md
面试 Q&A#
Q: 这个项目的业务场景是什么?解决什么问题?
A: 面向研发团队的内部技术文档问答。痛点是文档分散在 PDF/Markdown/Word/网页多种格式中,新人 onboarding 需要翻遍几十篇文档找一个配置参数,老员工反复被打断。核心目标是让任何人用自然语言提问,系统精确定位到相关文档段落,并给出有来源引用的答案——在 IDE 里也能直接查。
Q: 项目最大的技术挑战是什么?
A: 两个层面。第一是精确性与可溯源要求高——技术文档场景下,API 参数、版本号、配置值必须精确,不能”大意对”。所以我做了混合检索(BM25 精确匹配 + 向量语义),并在生成端用结构化引用 + 覆盖率置信度保证可溯源:
CitationParser纯解析答案里的[n]标记算句子级覆盖率,confidenceScore = clamp(coverage) × rerank-top1;兜底只在真的一条证据都没有时触发,CRAG 低分但有证据时带证据走 low-confidence 作答而非丢弃(#28,“rerank 分低≠不相关”),避免给出无依据答案。第二是LLM 不稳定性——方案是”双轨设计”,agentic 工具调用循环异常或召回为空时自动回退到一次性检索(规则引擎选工具)。
Q: 和市面上的 RAG 方案(如 Dify、RAGFlow)有什么区别?
A: 三个维度:①检索不是固定管线而是路径决策三模式——简单查询走规则一次性检索,复杂/多焦点查询走真·LLM 工具调用的 agentic 循环(模型自驱拆解 / 多跳 / web 补充 / 何时停);②答案是结构化引用 + 覆盖率置信度——
[n]标记解析为可点击来源、句子级覆盖率算置信度、仅无证据才兜底(低分有证据则带证据作答),让”每句话从哪来”可验证;③通过 MCP 标准协议暴露给 IDE——开发者不需要离开编辑器就能查知识库,这是 Web-only 方案做不到的。
Q: 为什么不直接用搜索引擎 / Confluence 搜索?
A: 传统搜索返回的是文档链接列表,开发者还要自己点开、定位段落、理解上下文。DocMind 直接返回结构化答案 + 具体段落引用 + 页码,并且支持跨文档综合——比如”对比 IVF 和 HNSW 哪个更适合我们的场景”需要综合两篇不同文档的内容,agentic 工具调用循环会自主做多焦点检索(模型运行时决定查几次、查什么)再综合回答。
Q: 项目规模多大?是一个人做的吗?
A: 全栈独立开发。后端 139 个 Java 文件(~16,300 行),前端 35 个 Vue/TypeScript 文件(~6,900 行)。11 张数据库表,49 个动态配置参数,7 个 Prompt 模板。经历了 6 个 Phase、20 次评测驱动的迭代优化,每次都有量化对比数据。
Q: 部署架构是什么?
A: 本地开发用 docker-compose 拉起 5 个服务(Redis、MinIO、etcd、Milvus standalone、Milvus attu),MySQL 8 独立运行。后端 Spring Boot 8080 端口,前端 Vite dev server 5173 端口 proxy 到后端。外部依赖是阿里云 DashScope(LLM + Embedding + Rerank 统一计费)、MinerU(可选,多模态文档解析)、Tavily(可选,联网搜索)、Langfuse(可选,可观测性)。所有外部依赖都有降级方案——关掉也能跑。
Q: 数据库怎么设计的?
A: 11 张表,核心是
kb_chunk。几个设计亮点:第一是content_hash(SHA-256),100 页文档改 1 字只需 ~1 次 embedding 而非 200-400 次。第二是parent_chunk_id外键支撑 Parent Document Retrieval——400 字子块进 Milvus 保精度,1500 字父块只存 MySQL 保上下文。第三是qa_message的 JSON 字段把推理过程结构化存储(agent_trace / mcp_calls / sources),前端可完整回放每步推理(reflection_log列因自反思删除已不再写入,仅为兼容旧数据保留)。第四是kb_knowledge_base.version字段——chunk 变更自增,语义缓存通过比对 version 判断失效。