外观
项目工程全景(代码级地图)
面试中回答"项目规模多大""代码结构是什么""数据库怎么设计的"时的参考。 所有数据来自源码实际统计,非估算。
一、仓库统计
| 维度 | 数值 |
|---|---|
| 后端 Java 源文件 | 143 个(~18,200 行) |
| 前端 Vue/TS 源文件 | 35 个(~6,900 行) |
| 核心 RAG 组件 | 29 个文件 |
| 数据库表 | 14 张(7 核心业务 + 7 离线评测) |
| 动态配置参数 | 39 个(sys_ai_config,6 组:rag/llm/cache/safety/功能开关/agent) |
| Prompt 模板 | 8 个 |
| MCP 工具 | 5 个 / 6 端点(双路径) |
| Docker 服务 | 5 个(Redis + MinIO + etcd + Milvus standalone + Milvus attu) |
二、后端包结构(com.simon.DocMind)
agent/ — Agent 核心(21 文件)
| 文件 | 行数 | 职责 |
|---|---|---|
DocMindAgent.java | 1510 | ReAct 主编排器:SSE 流式、语义缓存、紧急短路、范畴路由、路径决策、6 阶段推理循环 |
MetaIntentDetector.java | — | Phase 5: Tier-0 纯正则范畴判定(6 条规则,零 LLM) |
ScopeDecision.java | — | Phase 5: 范畴判定结果 record(6 种 Scope + confidence + fastPath) |
PathDecision.java | — | Phase 5: 路径决策结果 record(SELECTED_DOC / DECOMPOSED / RULE_PLANNER + reason) |
AgentToolContext.java | — | ThreadLocal 侧通道:工具执行结果汇总,synchronizedList + finally 清理 |
SelectedDocumentScopeDecider.java | — | 用户选定文档时绕过搜索,直接读 chunk 均匀采样 |
SelfReflection.java | 254 | 四维评分(事实一致/完整/来源/表达)+ 条件重写 + 切题度检查(Phase 5) |
agent/supervisor/ (7 文件)
| 文件 | 行数 | 职责 |
|---|---|---|
SupervisorAgent.java | 586 | 编排核心:3 路策略(迭代 ReAct / Plan-and-Execute / 子问题拆解) |
IterationDecider.java | — | 终止决策:置信度阈值 + 轮次上限 + 停滞检测 → CONTINUE/SWITCH/TERMINATE |
ObservationEvaluator.java | — | 质量评估 + 5 种降级动作推荐(空召回→Web, 低分→HyDE, 冲突→Analysis) |
PlanGenerator.java | — | LLM 生成结构化 JSON 执行计划 |
PlanExecutor.java | — | 按 dependsOn 拓扑排序 + 同层 CompletableFuture 并行 |
ExecutionPlan.java | — | 计划数据载体(record)+ fallback 工厂方法 |
SupervisorResult.java | — | 编排输出(record) |
agent/worker/ (7 文件)
| 文件 | 行数 | 职责 |
|---|---|---|
Worker.java | — | 统一接口(execute + name) |
RetrievalWorker.java | — | 封装 Vector + BM25 + RRF + Rerank + MMR + ParentChunk |
WebWorker.java | — | 封装 Tavily 搜索 |
MemoryWorker.java | — | 封装 Redis 长期记忆读取 |
AnalysisWorker.java | — | LLM 多文档对比分析 |
WorkerRequest.java | — | Worker 入参(record) |
WorkerResult.java | — | Worker 出参 + 工厂方法(record) |
agent/state/ (2 文件)
| 文件 | 职责 |
|---|---|
AgentState.java | 推理状态机:Evidence 累积、置信度轨迹、策略记录、查询分类 |
Evidence.java | 标准证据结构(record) |
service/rag/ — RAG 检索管线(29 文件)
| 文件 | 行数 | 职责 |
|---|---|---|
QueryUnderstandingService.java | 498 | 合并 5 个旧组件的统一分类器:意图/复杂度/专指度/时效/记忆 + 确定性后处理 |
BM25Retriever.java | 265 | MySQL FULLTEXT 预召回 + Java BM25 评分(K1=1.5, B=0.75)+ 短语覆盖加分 |
PromptAssembler.java | 249 | 3 模式 Prompt 组装(标准/拆解/降级)+ 对话历史裁剪(6 条) |
CrossEncoderReranker.java | 233 | DashScope gte-rerank API + 降级关键词覆盖度打分 + 去重截断 |
SemanticCacheService.java | 209 | query embedding cosine 近邻 + KB version 校验 + Redis 存取 |
VectorRetriever.java | 154 | Milvus COSINE 检索(HNSW ef=64)+ 标量过滤 |
MMRDiversifier.java | 127 | MMR 多样性重排(λ=0.7, bigram Jaccard 相似度) |
ParentChunkResolver.java | 128 | 子块→父块展开 + 同父去重 |
SafetyGuard.java | 111 | 紧急词检测 + 置信度阈值 + LLM 答案安全审查 |
RRFFusion.java | 92 | RRF 等权/加权融合(k=60)+ HYBRID 标记 + 50-char 去重 |
HyDEGenerator.java | 70 | 假设文档生成(小模型 200-400ms)+ 失败回退原 query |
RetrievalPlanner.java | 100 | Phase 5: 规则引擎(零 LLM):4 信号 → 工具选择,双源时效性校验 |
RetrievalGrader.java | — | Phase 5: CRAG 三档评分(HIGH/AMBIGUOUS/LOW)+ 灰区仲裁(heuristic/cross_encoder/disabled) |
GradeResult.java | — | Phase 5: 评分结果 record(tier + topScore + avgScore + reason) |
RecommendationGenerator.java | — | 推荐阅读:同 category + tags 共现 → 最多 3 条 |
EarlyStopGate.java | — | 双关检索质量门:vector/BM25 低分 + rerank 低分 |
KbVersionService.java | — | KB 版本号管理(缓存失效用) |
SourcePayloadFactory.java | — | 来源 JSON 构建(文档名+章节+页码+分数) |
SubQueryMerger.java | — | 保底分配 + 全局补齐 + 跨子问题去重 |
ChineseTextTokenizer.java | — | Lucene SmartCN 中文分词包装 |
QueryClassification.java | — | 分类输出 record(9 字段) |
QueryUnderstandingResult.java | — | 理解结果 record |
DecompositionResult.java | — | 拆解结果 record |
SubQuery.java | — | 子问题 record |
SubQueryRetrievalResult.java | — | 子问题检索结果 record |
RetrievalPlan.java | — | 检索计划 record |
RetrievedChunk.java | — | 检索 chunk 载体(score + metadata + source 枚举) |
SemanticCacheEntry.java | — | 缓存条目 record |
service/knowledge/ — 文档处理(5 文件)
| 文件 | 行数 | 职责 |
|---|---|---|
TextChunker.java | 542 | Markdown-Aware 双层切块:5 种 block 识别 + heading 栈 + content_hash |
DocumentExtractor.java | — | 多格式路由:PDF→MinerU+PDFBox / DOCX→POI / PPT+图片→MinerU / URL→MinerU-HTML |
MilvusService.java | — | Milvus CRUD:insertVectors(带外部 ID) / deleteByVectorIds / search |
MinerUClient.java | — | MinerU 云端 API 双流程客户端(文件+URL)+ 合并轮询 |
MinioService.java | — | MinIO S3 兼容存储:上传/下载/删除原件 |
mcp/ — MCP 工具(5 文件,双路径)
| 文件 | 内部调用 | 外部暴露 | 职责 |
|---|---|---|---|
DocSearchTool.java | Spring AI Function Calling | MCP Server endpoint | 语义向量检索 |
KeywordSearchTool.java | Spring AI Function Calling | MCP Server endpoint | BM25 关键词检索 |
WebSearchTool.java | Spring AI Function Calling | MCP Server endpoint | Tavily 联网搜索 |
MemoryTool.java | Spring AI Function Calling | MCP Server endpoint | Redis 长期记忆(recall + store) |
KbMetaTool.java | Agent KB_META 短路路径直接查 DB | MCP Server endpoint | 知识库元信息查询 |
同一套 @Tool 注解代码既被内部 ChatClient 调用,也通过 spring-ai-starter-mcp-server-webmvc 对外暴露。工具执行结果通过 AgentToolContext ThreadLocal 写入,Agent 读取后清理。
MCP 安全边界(MVP → 生产):当前 MCP Server 无独立鉴权层,内部调用由 AgentToolContext + 用户会话上下文保证 kbIds 隔离和 userId 注入。外部调用存在 4 个加固点:①工具端点需接入 JWT/API-Key 校验;②kbIds 需校验用户归属;③Memory userId 需从 auth context 注入,禁止外部指定;④Web Search 需接入 rate-limiter 防配额滥用。
config/ — 配置(21 文件)
| 文件 | 职责 |
|---|---|
AiConfigHolder.java | ConcurrentHashMap 配置 + AtomicReference 热替换 ChatModel + 双模型 callSmallModel |
AiConfigInitializer.java | 启动时按 key 增量插入 sys_ai_config 默认值 |
LlmConfig.java | Spring AI OpenAI-compatible ChatModel / EmbeddingModel Bean |
MilvusConfig.java | Milvus 连接 + Collection 初始化(COSINE, HNSW) |
RedisConfig.java | RedisTemplate<String, Object> 序列化 |
MinioConfig.java | MinIO S3 客户端 Bean |
SecurityConfig.java | Spring Security 过滤链 + JWT + CORS + 路径白名单 |
WebSearchConfig.java | Tavily RestTemplate Bean |
McpToolsConfig.java | ToolCallbackProvider 注册 MCP 工具 |
MinerUConfig.java | MinerU 专用 RestTemplate(独立超时策略) |
MinerUProperties.java | @ConfigurationProperties MinerU 配置封装 |
LangfuseOtelConfig.java | Phase 6 增强: OTel SDK + BatchSpanProcessor + RootNameFilteringSpanProcessor 白名单过滤 |
LangfuseProperties.java | Langfuse 连接参数(enabled/baseUrl/publicKey/secretKey) |
ChatModelObservationFilter.java | Phase 6: Spring AI Observation → OTel span 桥接(prompt/completion 自动采集,截断 10000 字) |
RagExecutorConfig.java | Query Decomposition 专用线程池(core=8, max=16) |
DocmindWebSearchProperties.java | Web 搜索配置属性 |
WebSocketConfig.java | WebSocket 端点配置 |
WebMvcConfig.java | 静态资源映射 |
MybatisPlusConfig.java | MyBatis Plus 分页插件 |
AppInitConfig.java | 应用启动初始化 |
KbChunkIndexInitializer.java | 启动时初始化 BM25 Lucene 索引 |
controller/ — REST API(9 文件)
| 文件 | 端点前缀 | 职责 |
|---|---|---|
DocMindChatController.java | /api/v2/chat | SSE 流式问答 + 会话管理 |
KnowledgeBaseController.java | /api/knowledge | 文档上传/列表/删除/更新/URL 入库 |
KbChunkController.java | /api/chunks | chunk 明细查看 |
McpConsoleController.java | /api/mcp | MCP 工具注册表 + 调用统计 |
AiConfigController.java | /api/ai-config | 27 个动态参数 CRUD |
UserController.java | /api/users | 用户注册/登录/JWT 刷新/RBAC |
StatsController.java | /api/stats | 统计面板数据 |
DocMindStatsController.java | /api/v2/stats | 增强统计 |
SpeechWebSocketServer.java | /ws/speech | 语音输入 WebSocket |
support/ — 工具类
| 文件 | 职责 |
|---|---|
TracedOp.java | Phase 6: OTel span 样板代码消除工具(run/exec 方法封装 span 生命周期) |
entity/ — 数据模型(15 文件)
实体 + DTO + VO,对应 14 张数据库表(7 核心业务 + 7 离线评测)。
mapper/ — MyBatis Plus
每个实体一个 Mapper 接口。XML 配置在 src/main/resources/mapper/。
security/ — 安全(2 文件)
| 文件 | 职责 |
|---|---|
JwtAuthenticationFilter.java | 拦截请求,解析 JWT,设置 SecurityContext |
UserDetailsServiceImpl.java | 从 sys_user 表加载用户信息 |
三、前端结构(DocMind-frontend/src/)
views/ — 页面(10 Vue 文件)
| 文件 | 职责 |
|---|---|
chat/ChatView.vue | SSE 事件状态机 + 流式 Markdown 渲染 + 来源卡片 + Agent trace + 置信度徽章 + Phase 6: 思考时间线(11 步类型)+ 路由/评分三色徽章 |
knowledge/KnowledgeView.vue | 文件上传 + URL 入库 + 处理状态轮询 + 文档管理 |
mcp/McpView.vue | MCP 工具注册表 + 调用统计图表 |
admin/AiConfigView.vue | 49 参数分组编辑面板 |
dashboard/DashboardView.vue | ECharts 统计面板 |
auth/LoginView.vue | 登录页 |
auth/RegisterView.vue | 注册页 |
auth/ForgotPasswordView.vue | 密码重置 |
user/ProfileView.vue | 用户信息 |
user/UserManageView.vue | 用户管理(admin) |
api/ — HTTP 客户端(6 TS 文件)
chat.ts / knowledge.ts / mcp.ts / aiConfig.ts / stats.ts / user.ts
其他
stores/user.ts— Pinia 用户状态(JWT token 管理)utils/request.ts— Axios 实例 + JWT 拦截器 + 401 自动跳转router/index.ts— Vue Router + 路由守卫layout/MainLayout.vue— 侧边栏导航布局
四、数据库表设计(14 张表)
kb_chunk — 文档切片表(核心表,8 个设计决策)
| 字段 | 类型 | 设计决策 |
|---|---|---|
content | TEXT | 切片文本,FULLTEXT INDEX (ngram) 支持 BM25 |
content_hash | VARCHAR(64) | SHA-256 hex,增量索引核心——按 (kb_id, content_hash) diff |
vector_id | VARCHAR(100) | 与 Milvus 1:1 映射(VarChar PK),精准删除依赖此字段 |
metadata | JSON | {chapter, contentType, pageNumber, docVersion, effectiveDate} |
tags | VARCHAR(512) | JSON 数组,从 heading 栈自动提取,支持 LIKE 过滤 |
doc_version | VARCHAR(32) | 文档版本号 |
parent_chunk_id | BIGINT | 外键→父块 ID,Parent Document Retrieval 的基础 |
source_file_name | VARCHAR(255) | 原始文件名 |
索引:idx_kb_id + idx_kb_id_content_hash(增量索引)+ idx_parent_chunk_id + FULLTEXT ft_kb_chunk_content_ngram
kb_knowledge_base — 知识库文档表
| 字段 | 设计决策 |
|---|---|
version | BIGINT,chunk 任何变更后自增,语义缓存通过此字段判断失效 |
status | uploading → processing → ready / error,异步处理状态机 |
file_type | pdf/docx/md/txt/ppt/pptx/jpg/png/url,MinerU 扩展后新增多种 |
file_url | MinIO 路径或原始 URL(url 类型) |
qa_message — 对话消息表(4 类 JSON 结构化存储)
| JSON 字段 | 存储内容 |
|---|---|
sources | 来源引用:文档名 + 章节 + 页码 + 相关性分数 |
agent_trace | ReAct 推理链:每步的 thought → action → observation |
mcp_calls | MCP 工具调用记录:工具名 + 入参 + 出参 + 耗时 |
reflection_log | 自纠错日志:4 维评分 + 通过/不通过 + issues |
额外字段:confidence_score (FLOAT) + confidence_band (VARCHAR) 直接存储置信度。
qa_conversation — 对话会话表
kb_ids (JSON) 存储关联知识库 ID 列表,message_count 计数,last_active 排序。
sys_ai_config — 动态配置表(详见下节)
sys_user — 用户表
role (admin/user) RBAC,password BCrypt 加密,preference (JSON) 用户偏好。
mcp_tool_registry — MCP 工具注册表
call_count + avg_latency_ms 统计,mode (embedded/remote)。
五、动态配置参数全景(39 个 sys_ai_config)
RAG 组(19 个)
| Key | 默认值 | 说明 |
|---|---|---|
rag.vector_top_k | 10 | 基础向量召回 Top-K |
rag.bm25_top_k | 10 | 基础 BM25 召回 Top-K |
rag.rerank_top_n | 5 | 基础重排 Top-N |
rag.chunk_size | 512 | 切片大小 |
rag.chunk_overlap | 64 | 切片重叠 |
rag.rrf_top_n | 30 | RRF 融合后候选数 |
rag.rerank_top_k | 6 | 重排最终保留数 |
rag.rrf_k_constant | 60 | RRF 平滑常数 |
retrieval.main.vector_top_k | 50 | 主查询向量候选池 |
retrieval.main.bm25_top_k | 50 | 主查询 BM25 候选池 |
retrieval.sub.vector_top_k | 15 | 子查询向量 Top-K |
retrieval.rerank.top_k | 8 | 精排输出条数 |
retrieval.early_stop.vector_threshold | 0.55 | 向量低质量阈值 |
retrieval.early_stop.bm25_threshold | 5.0 | BM25 低质量阈值 |
retrieval.early_stop.require_both_low | true | 双路低分才早停 |
retrieval.early_stop.rerank_threshold | 0.30 | 精排 fallback 阈值 |
generation.high_confidence_threshold | 0.85 | 高置信度阈值 |
generation.medium_confidence_threshold | 0.60 | 中等置信度阈值 |
reflection.skip_threshold | 0.85 | 反思高分短路阈值 |
LLM 组(6 个)
| Key | 默认值 | 说明 |
|---|---|---|
llm.model | qwen-plus | 主回答模型 |
llm.temperature | 0.7 | 通用温度 |
llm.chat_temperature | 0.7 | 对话温度 |
llm.streaming_temperature | 0.7 | 流式温度 |
llm.timeout_seconds | 60 | 请求超时 |
llm.max_tokens | 2048 | 最大输出 Token |
Cache 组(6 个)
| Key | 默认值 | 说明 |
|---|---|---|
cache.enable | true | 缓存总开关 |
cache.ttl_seconds | 3600 | 缓存 TTL(秒) |
cache.ttl_hours | 1 | 缓存 TTL(小时) |
cache.freq_threshold | 2 | 频次阈值(2 次即缓存) |
cache.semantic.distance_threshold | 0.92 | 语义缓存近邻阈值 |
cache.semantic.ttl_hours | 24 | 语义缓存兜底 TTL |
Safety 组(4 个)
| Key | 默认值 | 说明 |
|---|---|---|
safety.enable_guard | true | 安全过滤开关 |
safety.max_retries | 2 | 最大自纠错轮数 |
safety.confidence_threshold | 0.6 | 兜底触发阈值 |
safety.fallback_msg | 抱歉... | 兜底话术 |
功能开关(3 个)
| Key | 默认值 | 说明 |
|---|---|---|
mmr.enabled | true | MMR 多样性开关 |
mmr.lambda | 0.7 | MMR λ 参数 |
hyde.enabled | true | HyDE 开关 |
Agent 组(1 个)
| Key | 默认值 | 说明 |
|---|---|---|
agent.observation.low_score_threshold | 0.4 | 低分触发 HyDE 阈值 |
六、Prompt 模板清单
| 文件 | 用途 | 注入变量 |
|---|---|---|
knowledge_qa.txt | 标准 QA 回答生成 | |
knowledge_qa_decomposed.txt | 拆解模式回答 | 同上 + + chunk 标注 (子问题 #1, #2) |
query_understanding.txt | Phase 1a 意图/复杂度/专指度分类 | |
query_decompose.txt | Phase 1b 子问题拆解 | |
hyde_generation.txt | HyDE 假设文档生成 | ,输出约 200 字假设回答 |
knowledge_qa_low_confidence.txt | 低置信度降级回答 | 同 knowledge_qa + 显式声明"检索结果不足" |
memory_extract.txt | 用户记忆提取 | → 输出结构化记忆点 |
safety_check.txt | LLM 答案安全审查 | |
PromptAssembler 提供 3 种组装模式:
assemble()— 标准 QA,带 context + history + memoryassembleDecomposed()— 每条 chunk 标注子问题归属assembleFallback()— 低置信度降级,显式声明"未找到充分依据"
七、部署架构
docker-compose.dev.yml
┌──────────────────────────────────────────────┐
│ Redis 7 :6379 缓存 + 用户记忆 │
│ MinIO :9000 S3 兼容对象存储 │
│ etcd :2379 Milvus 元数据存储 │
│ Milvus standalone :19530 向量数据库 │
│ Milvus attu :8000 向量库管理 UI │
└──────────────────────────────────────────────┘
↑ 连接
┌──────────────────────────────────────────────┐
│ MySQL 8 :3306 业务数据 + BM25 │
│ (需独立运行,非 docker-compose 管理) │
└──────────────────────────────────────────────┘
↑ 连接
┌──────────────────────────────────────────────┐
│ Spring Boot 3.4 :8080 后端 API + MCP │
│ Vue 3 + Vite :5173 前端 (proxy→8080) │
└──────────────────────────────────────────────┘
↑ 外部 API
┌──────────────────────────────────────────────┐
│ DashScope API — LLM (qwen-plus/turbo) │
│ DashScope API — Embedding (text-emb-v3) │
│ DashScope API — Rerank (gte-rerank) │
│ MinerU API — 文档解析(可选) │
│ Tavily API — 联网搜索(可选) │
│ Langfuse — LLM 可观测性(可选) │
└──────────────────────────────────────────────┘八、面试 Q&A
Q: 项目代码规模多大?
A: 后端 143 个 Java 文件(~18,200 行),核心 RAG 组件 29 个文件。前端 35 个 Vue/TypeScript 文件(~6,900 行)。14 张数据库表,39 个动态配置参数,8 个 Prompt 模板。全栈独立开发,经历 6 个 Phase、20 次评测驱动迭代。
Q: 最复杂的文件是哪个?
A:
DocMindAgent.java(1510 行),是 ReAct 编排器的入口——管理 SSE 流式、语义缓存、紧急短路、范畴路由、路径决策、6 阶段推理循环、Self-Reflection 条件重写。Phase 2 重构时从 1400 行降到 ~900 行,Phase 5/6 加回范畴路由和路径决策后升到 1510 行。核心检索逻辑委托给了 SupervisorAgent(586 行)。
Q: 数据库设计有什么亮点?
A: 三个设计值得讲:第一是
kb_chunk.content_hash(SHA-256),支撑增量索引——100 页 PDF 改 1 字只重新 embedding ~1 次。第二是kb_chunk.parent_chunk_id,支撑 Parent Document Retrieval 双层切块。第三是qa_message的 4 个 JSON 字段(sources / agent_trace / mcp_calls / reflection_log),把推理过程结构化存储,前端可回放展示。