面试知识库

项目全景与架构设计#

一句话介绍#

DocMind 是一个面向研发团队内部技术文档Agentic RAG + MCP 知识问答系统,解决”文档分散、格式混杂、查找低效”的研发效能痛点。

业务场景#

目标用户:中大型研发团队(50-200 人),文档分散在 Confluence、飞书、Git 仓库、本地 PDF 等多个平台。

核心痛点

  1. 新人 onboarding 慢——需要翻遍几十篇文档才能找到一个 API 参数配置,老员工反复被打断
  2. 精确查找难——“Spring Boot 3.4 的 server.shutdown 默认值是什么?“纯向量检索容易被语义相近内容干扰
  3. 文档格式碎片化——架构设计是 PDF,API 文档是 Markdown,运维手册是 Word,会议纪要是网页
  4. 知识孤岛——文档只在 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.4Spring AI 原生支持 Function Calling,和 MCP Server 集成零额外代码
向量数据库Milvus 2.3支持 COSINE 相似度 + 标量过滤联合查询,适合多知识库场景
全文检索Lucene 8.11 (SmartCN)内嵌式 BM25,不需要额外部署 Elasticsearch,对中文有原生分词支持
重排序DashScope gte-rerankCross-Encoder 级别精排,HTTP API 调用,免部署 Python 推理服务
嵌入模型text-embedding-v3 (1024维)阿里云原生,和 DashScope 统一计费,中文效果好
缓存Redis 7同时承担热点缓存 + 跨会话用户记忆两个角色,减少组件数量
对象存储MinIOS3 兼容,私有化部署,文档原件持久化
文档解析MinerU 云端 API(PDF / PPT / 图片 / 网页 URL 统一入口)+ Apache PDFBox(PDF 兜底)+ Apache POI(DOCX)layout-aware 多栏 / 表格 / 公式 / OCR / 网页正文抽取;统一输出 Markdown 后进入 RAG,下游链路无需感知格式差异
前端Vue 3 + TypeScript + Element Plus类型安全 + 成熟 UI 组件库,开发效率高

整体架构#

核心设计决策#

1. 为什么用 Agent 而不是固定流水线?#

问题:技术文档问答场景中查询类型差异极大——“Spring Boot 3.4 的 server.shutdown 默认值”需要精确 BM25 匹配,“微服务拆分的最佳实践”需要语义检索 + 大召回量,“昨天故障的根因分析”需要联网查最新信息。固定管线无法兼顾。

方案:路径决策三模式(PathDecision.ModeDocMindAgent.routePath)。根据查询分类结果选择执行路径——

  • AGENTIC(复杂 / 中等 / 歧义 / 多焦点):AgenticSearchOrchestrator 真·LLM 工具调用循环(Spring AI ToolCallingManager 手动控环,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 异常或召回为空 → 回退一次性检索 oneShotRetrievalagentic.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_chunkcontent_hash(SHA-256 增量索引)+ parent_chunk_id(Parent Document Retrieval)+ vector_id(MySQL↔Milvus 1:1)+ FULLTEXT INDEX(BM25 预召回)
kb_knowledge_baseversion(chunk 变更自增,语义缓存失效依据)+ status 异步处理状态机
qa_messageJSON 字段结构化存储推理过程:sources / agent_trace / mcp_calls + confidence_score / confidence_band(覆盖率派生)。reflection_log 列因自反思删除已不再写入,仅为兼容旧数据保留
sys_ai_config49 个热配参数(Phase 5 新增 10 个 agent 配置),admin 面板实时调整,ConcurrentHashMap + AtomicReference 无锁更新
sys_userRBAC + BCrypt 密码 + JWT 24h/7d 双 token
sys_role / sys_permission / sys_user_role / sys_role_permissionPhase 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 判断失效。