简历素材与 STAR 故事#
本文档为简历编写和面试叙事提供结构化素材。 所有数据均来自项目真实评测和代码(详见 04、10),非估算。
〇、项目背景与简介#
项目是什么#
DocMind 是一个面向研发团队的企业级知识问答系统,核心能力是把 PDF、Word、Markdown 等文档转变为可对话的知识库——用自然语言提问,系统定位相关段落、生成答案、标注来源,并在发现幻觉时自动修正。
为什么做这个项目#
研发团队日常积累大量技术文档、设计规范、操作手册,但现有的文档检索停留在关键词匹配层面,跨文档推理、对比类问题基本无解。同时随着 LLM 普及,RAG(检索增强生成)成为 AI 落地的主流范式,但开箱即用的 RAG 普遍存在检索精度不足、幻觉无法自纠、无法量化效果三个痛点。
该项目的出发点是:以可量化的方式逐步解决这三个痛点,同时将整个优化过程作为可面试展示的工程实践。
项目规模与形态#
| 维度 | 信息 |
|---|---|
| 性质 | 个人独立设计 + 全栈实现(非团队协作) |
| 周期 | 约 6 周(2026-04 至 2026-05) |
| 代码规模 | Java 后端 139 文件 + Vue 3 前端 35 文件 |
| 迭代轮次 | 22 次迭代,每次有评测数据支撑 |
| 评测规模 | 52 条 × 7 类问题 × 4 对照档 × 3 轮,共 624 行评测报告 |
| 技术栈 | Spring Boot 3.4 / Spring AI / Milvus / Redis / Lucene / Vue 3 / TypeScript |
| LLM 接入 | 阿里云 DashScope(qwen-plus/turbo + text-embedding-v3 + gte-rerank) |
核心架构演进脉络#
朴素 RAG(向量检索 + 生成)
→ 混合检索(Vector + BM25 + RRF 融合)
→ 重排序(Cross-Encoder gte-rerank)
→ 自反思条件重写(Self-Reflection,后于四刀改造移除)
→ Supervisor-Worker 编排架构
→ 双层范畴路由 + 规则引擎工具选择(PathDecision)
→ CRAG 三档检索质量评估
→ Langfuse OTel 全链路追踪
→ 对标主流四刀改造:三条手写检索分支(拆解 / 多跳 / CRAG-web)+ 自反思
统一收敛为「真·LLM 工具调用 agentic 循环 + 结构化引用(覆盖率置信度)」plaintext「加了又删」是这条脉络的关键判断力:自研拆解 / 串行多跳 / 自反思都真实落地过、有评测数据, 最终对标 Anthropic / Glean 后基于「简单可组合胜过复杂抽象」把它们收敛掉——见 04 #22、20-改造实现设计。
每一步均有离线评测量化增量价值,而非”感觉变好了”。
一、项目一句话描述#
30 字版#
设计并实现企业级 Agentic RAG + MCP 知识问答系统,支持多格式文档智能检索、真·LLM 工具调用检索循环与结构化引用溯源。
50 字版#
独立设计并实现 Agentic RAG + MCP 知识问答系统(Java 全栈),涵盖真·LLM 工具调用 agentic 检索循环、混合检索、Cross-Encoder 重排、结构化引用(覆盖率置信度),经 52 条离线评测验证。
100 字版#
独立设计并实现面向研发团队的 Agentic RAG + MCP 知识问答系统。后端 Spring Boot 3.4 + Spring AI(139 文件),前端 Vue 3 + TypeScript(35 文件)。核心为路径决策三模式(SELECTED_DOC / RULE_PLANNER / AGENTIC):AGENTIC 走真·LLM 工具调用 agentic 循环(Spring AI ToolCallingManager 手动控环,模型自驱拆解 / 多跳 / 补检索 / 停),RULE_PLANNER 走一次性检索(Supervisor-Worker 并行派发)。实现双层范畴路由(规则+LLM)、规则引擎工具选择(<1ms)、CRAG 三档检索评分、混合检索 RRF 融合、Cross-Encoder 重排、HyDE、结构化引用(CitationParser 解析
[n]→覆盖率置信度,生成即终态)。Langfuse OTel 全链路追踪。52 条离线评测量化每层增量价值。MCP 协议暴露 5 工具给 IDE。
English (50 words)#
Designed and built an enterprise Agentic RAG + MCP knowledge Q&A system (Java full-stack). Features a real LLM tool-calling agentic retrieval loop (Spring AI ToolCallingManager), hybrid retrieval (Vector + BM25 + RRF), Cross-Encoder reranking, structured clickable citations with coverage-based confidence, and a 52-query offline evaluation framework. Exposed tools via MCP protocol for IDE integration.
一·五、简历项目描述(三个版本)#
适用于简历”项目经历”栏,格式与趣逛项目对齐。根据目标岗位选用不同版本。
版本 A:侧重后端架构(适合 Java 后端 / AI 应用岗)#
DocMind — 企业级 Agentic RAG 知识问答系统 | Spring Boot / Spring AI / Milvus / Redis / Vue 3
设计并实现路径决策三模式(SELECTED_DOC / RULE_PLANNER / AGENTIC)+ Supervisor-Worker 编排:AGENTIC 模式用 Spring AI ToolCallingManager 手动控环(internalToolExecutionEnabled(false) + max_iterations 兜底)跑真·LLM 工具调用循环,模型自驱拆解 / 多跳 / 补检索 / 停(白名单只给 3 个读工具,硬排除写工具);RULE_PLANNER 模式走一次性检索 oneShotRetrieval(多路 Worker 用 CompletableFuture 并行派发、每路加超时部分降级);新增 Worker 仅需实现接口 + 在调度处注册 1 处
设计双层范畴路由(Tier-0 正则规则快路径 + Tier-1 LLM 结构化判定),区分六种范畴(知识检索 / 元对话 / 闲聊 / 超范围 / 知识库元查询 / 任务执行),元对话路径完全跳过 Milvus + BM25 + Reranker 调用;路由判定合并进 QueryUnderstanding 同一次模型调用,知识查询路径路由从 2 次 LLM 降为 1 次(省 ~250ms)
实现 RetrievalPlanner 纯规则引擎替代 LLM Function Calling 做工具选择,基于 QueryClassification 四维信号(isAmbiguous / specificity / timeAware / memoryAware)映射工具子集,路径决策延迟从 ~300ms 降至 <1ms,100% 确定性且可单元测试断言
设计 MCP 工具双执行路径:5 个 @Tool 类同时服务内部 Agent 循环(Worker 编程调用,AgentToolContext ThreadLocal 侧通道传递结果)和外部 IDE 插件(MCP 协议暴露 6 端点),零代码重复;AgentToolContext.isActive() 区分内外调用,内部路径强制覆盖 kbIds 防权限穿透
设计结构化引用与覆盖率置信度:CitationParser(纯 Java 无 LLM)解析答案 [n]→结构化引用、越界编号计入 invalidRefs、句子级覆盖率,confidenceScore = clamp(coverage) × rerank-top1,前端 [n] 可点击滚动定位来源卡;曾实现 Self-Reflection 条件重写(忠实度 +0.037),后经评测发现其在流式生成下收益有限、对标主流后移除——体现「加了又删」的数据驱动工程判断
版本 B:侧重检索算法与评测(适合 AI 工程 / 搜索算法岗)#
DocMind — 企业级 Agentic RAG 知识问答系统 | Spring Boot / Spring AI / Milvus / Redis / Vue 3
实现 Vector + BM25 混合检索 + RRF 加权融合(score = weight / (k + rank)),Recall@5 从 0.673 提升至 0.788(+0.115),事实类查询 BM25 贡献 +0.200(0.733→0.933);集成 Cross-Encoder 语义重排(DashScope gte-rerank),MRR 从 0.594 提升至 0.731(+0.137),全链路 MRR 累计 +0.310(0.421→0.731),核心价值为将相关文档推至 top-1 利用 LLM 注意力分布优势
参考 CRAG 论文设计三档检索质量评估(HIGH ≥0.60 / AMBIGUOUS / LOW ≤0.25),灰区仲裁利用 rerank 阶段已产出的 per-chunk 分数做分布分析(0ms 延迟),AMBIGUOUS + 未尝试 web 时触发 WebWorker 补偿检索;LOW 质量直接走降级 prompt 避免基于不相关切片生成幻觉
设计结构化引用与覆盖率置信度:CitationParser(纯 Java)解析 [n]→句子级覆盖率,confidenceScore = clamp(coverage) × rerank-top1,越界编号计入 invalidRefs,generation 即终态;曾设计 Self-Reflection 条件重写(注入审查 issues 触发重写,忠实度 +0.037 / 0.812→0.849,8 条被重写查询平均 +0.230),评测显示其在流式生成下收益有限,对标 Anthropic / Glean 后移除
搭建 52 条 × 4 档对照 × 3 轮离线评测框架,覆盖 7 类查询(事实/操作/对比/推理/对抗/超范围/多跳),产出 Recall@5、MRR、忠实度、相关性等 11 维指标;评测过程暴露 BM25 索引未刷新的生产级 bug(文档入库调了 Milvus 但遗漏 Lucene),修复后事实类 Recall 直接跳升 +0.200
实现 Markdown-Aware 双层切块(表格/代码块原子保留不切碎,heading 切换强制 flush 防跨章节合并)+ SHA-256 增量索引(同 hash 复用 vector_id 跳过 embedding),100 页文档改 1 字 embedding 次数从 200-400 降至 ~1;集成 MinerU 统一 PDF/PPT/图片/网页为 Markdown,下游检索链路零行改动
版本 C:精简版(适合简历空间有限 / 实习岗)#
DocMind — 企业级 Agentic RAG 知识问答系统 | Spring Boot / Spring AI / Milvus / Redis / Vue 3
设计路径决策三模式 + Supervisor-Worker 编排(AGENTIC 真·LLM 工具调用循环 / RULE_PLANNER 一次性检索 / SELECTED_DOC 直读),双层范畴路由短路非检索场景,规则引擎替代 LLM Function Calling 做工具选择(延迟 300ms→<1ms)
实现 Vector + BM25 混合检索 + RRF 融合 + Cross-Encoder 重排,Recall@5 +0.115、全链路 MRR +0.310(其中 RRF +0.173、Reranking +0.137);CRAG 三档评估拦截低质量检索,结构化引用按覆盖率给出置信度(曾用 Self-Reflection 条件重写、忠实度 +0.037,后移除)
搭建 52 条离线评测框架量化全链路增量价值,双模型策略 + 语义缓存 + 生成即终态(省去自反思往返)将单查询成本降低 ~50%
MCP 协议暴露 5 工具 / 6 端点,双执行路径同时服务内部 Agent 和外部 IDE 插件;Langfuse OTel 全链路追踪
二、技术亮点 Bullet Points(简历用)#
每条格式:动词 + 技术 + 量化结果,可直接复制到简历。
-
设计 Supervisor-Worker 编排架构,将 1400 行单体 Agent 拆分为 20 个职责单一的文件(Supervisor + 4 Worker + 共享上下文 + 辅助类),新增 Worker 仅需实现接口 + 注册,改动文件数从 ≥3 降为 1
-
实现 Vector + BM25 混合检索 + RRF 加权融合,Recall@5 从 0.673 提升至 0.788(+0.115),事实类查询 Recall@5 从 0.733 提升至 0.933(+0.200,BM25 贡献)
-
集成 Cross-Encoder 语义重排(DashScope gte-rerank),MRR 从 0.594 提升至 0.731(+0.137),核心价值为将相关文档推至 top-1 位置,利用 LLM 注意力分布优势
-
设计结构化引用 + 覆盖率置信度(CitationParser),纯 Java 解析答案
[n]→结构化引用、越界编号计入invalidRefs、句子级覆盖率,confidenceScore = clamp(coverage) × rerank-top1,前端[n]可点击定位来源卡;曾实现 Self-Reflection 条件重写(忠实度 +0.037 / 0.812→0.849,8 条幻觉/遗漏被修正),评测显示流式生成下收益有限、对标主流后移除——「加了又删」的数据驱动工程判断 -
搭建 52 条 × 4 档对照的离线评测框架,覆盖 7 类查询(事实/操作/对比/推理/对抗/超范围/多跳),产出 Recall@5、MRR、忠实度、相关性等 11 维指标报告,单次评测成本 ¥18.4
-
实现 Markdown-Aware 双层切块 + 增量索引,表格/代码块原子保留不切碎,SHA-256 content_hash 实现增量更新(100 页文档改 1 字仅需 ~1 次 embedding,原需 200-400 次)
-
集成 MinerU 多模态文档解析,统一 PDF/PPT/图片/网页为 Markdown 后入 RAG,下游检索链路零行改动;按格式分级降级(PDF 降级 PDFBox,PPT/图片 fail-fast 不静默)
-
实现自适应检索参数(QueryProfiler),Complexity × Specificity 二维矩阵映射 8 套检索预设,精确查询 BM25 权重 0.7、语义查询向量权重 0.6,纯规则零 LLM 成本
-
通过 MCP 标准协议暴露 5 个工具 / 6 端点(doc_search / keyword_search / web_search / recall_memory / store_memory / kb_meta),同一套工具代码同时服务内部 Agent 循环和外部 IDE 插件(Cursor),零代码重复
-
实现链路成本优化,双模型策略(主回答 qwen-plus / 决策类 qwen-turbo)+ 语义缓存 + 生成即终态(四刀改造移除自反思往返;早期亦做过反思高分短路),单查询成本从 ¥0.012 降至 ¥0.005-0.007(-50%)
-
基于真实 bug 调研业界做法补上路由分层(Phase 5),起点是用户输入”总结上面的对话”被错误送入向量检索召回 12 条无关切片。调研 12 个生产级框架(LangGraph / Bedrock / Perplexity / LinkedIn 等)+ 3 篇论文(CRAG / Self-RAG / Adaptive-RAG)后,加三道闸:规则快路径 + 模型范畴判定 + CRAG 风格三档评估(当时还有第四道自纠错切题度检查,已随四刀改造移除,跑题兜底改由零覆盖率 + CRAG LOW)。元对话路径完全跳过检索,灰区仲裁用 reranker 单对打分(50ms)替代小模型生成(300ms)
-
实现 PathDecision 规则引擎替代 LLM 路由(Phase 5),工具选择从 LLM Function Calling(~300ms、不确定、不可测试)改为 RetrievalPlanner 纯规则引擎(<1ms、100% 确定性、单元测试可断言),PathDecision record 统一路径决策输出 + reason 字段写入 trace 可追溯
-
集成并标准化 Langfuse OTel 全链路追踪(Phase 6 接入 → 观测重构 Part 1),Agent 执行(understanding → routing → agentic 循环每轮工具调用 → retrieval → reranking → grading → generation)通过 OpenTelemetry 协议上报 Langfuse,TracedOp 消除 span 样板代码,ChatModelObservationFilter 采集 LLM prompt/completion/token 用量。Part 1 重构:先质疑「Spring Boot 3.4 OTel 与 spring-ai 版本冲突」前提(依赖树实测 OTel 统一 1.43.0、为伪),删手写 OTel SDK(228→90 行)改 Spring Boot 原生 OTLP autoconfig,RootNameFilteringSpanProcessor 事后过滤换成自定义 head-based
Sampler,热切换模型接ObservationRegistry启用原生 gen_ai 埋点(删手动gen_ai.*setAttribute) -
对标主流(Anthropic / Glean / RAGFlow)实施四刀改造,把三条手写检索分支(自研拆解 / 串行多跳 / CRAG-web 回溯)+ 自反思子系统统一收敛为真·LLM 工具调用 agentic 循环(Spring AI
ToolCallingManager手动控环、max_iterations=4兜底、白名单 4 个读工具 + executeCode 沙箱计算工具(默认关)硬排除写工具,模型运行时自驱拆解 / 多跳 / 补检索 / 计算 / 停)+ 结构化引用;SupervisorAgent从 ~1683 行瘦身到 ~595 行,mvn test126 通过并经真 qwen-plus 端到端冒烟验证
三、量化成果总表#
| 指标 | 数值 | 对比基线 | 来源 |
|---|---|---|---|
| Recall@5 提升 | +0.115(0.673→0.788) | V1 朴素 → V2 RRF 融合 | 评测 52 条 × 3 轮 |
| 事实类 Recall@5 | +0.200(0.733→0.933) | BM25 对事实类的贡献 | 评测分类切片 |
| MRR 提升(全链路) | +0.310(0.421→0.731) | V1→V2 RRF +0.173 + V2→V3 Reranking +0.137 | 评测全链路 |
| 忠实度提升(历史) | +0.037(0.812→0.849) | V3 → V4 条件重写 | 评测复测(Self-Reflection,四刀改造后已移除) |
| 相关性提升(历史) | +0.027(0.834→0.861) | V3 → V4 条件重写 | 评测复测(Self-Reflection,四刀改造后已移除) |
| 被重写查询忠实度(历史) | +0.230(0.510→0.740) | 8 条被修正查询平均 | 评测明细(Self-Reflection,已移除) |
| 对抗查询拒答率 | 100%(5/5) | 提示词注入变体 | 评测 |
| 单查询成本降低 | -50%(¥0.012→¥0.006) | 双模型+短路+缓存 | 成本估算 |
| 缓存命中率提升 | +50%(30%→45%) | 阈值 3→2 | 线上观测 |
| 增量索引效率 | -99%(200次→1次 embedding) | 100 页改 1 字场景 | 设计分析 |
| 路径决策延迟 | <1ms(vs LLM ~300ms) | RetrievalPlanner 规则引擎替代 LLM | Phase 5 |
| 多焦点查询复杂度升级触发率 | 100%(vs 60%) | 确定性后处理把多焦点信号升级为 ≥MEDIUM,路由进 agentic 循环 | Phase 5 / 四刀改造 |
| 检索分支收敛 | 3 手写分支 → 1 agentic 循环 + 1 一次性兜底 | 拆解 / 多跳 / CRAG-web → 真·LLM 工具调用 | 四刀改造 |
| SupervisorAgent 瘦身 | ~1683 → ~595 行 | 删拆解 / 多跳 / CRAG-web 分支 | 四刀改造 |
| 代码规模 | 139 Java + 35 前端文件 | - | 源码统计 |
| 核心 RAG 组件 | 28 个文件 | - | wc -l |
| 优化迭代数 | 22 次,每次有评测数据 | - | 迭代记录 |
| 评测数据集 | 52 条 × 7 类 × 4 档 × 3 轮 | - | 评测框架 |
| 评测报告 | 624 行 Markdown | - | 评测产物 |
四、STAR 故事(8 则核心 + 3 则补充)#
STAR-1:Self-Reflection 的完整生命周期——从 noop 到条件重写,再到数据驱动的移除(最推荐讲述)#
这是「加了又删」的代表故事:一个组件被建好、被证明有效、最终又被有依据地删掉。完整呈现工程判断力,而非只讲「我加了个很厉害的东西」。
Situation:搭建离线评测框架后,跑出一个反直觉结论——V3(+重排)和 V4(+反思)在忠实度、相关性上数值完全相同(保留 3 位小数也一样)。Self-Reflection 贡献了 956ms 延迟(占总耗时 29%),对答案质量的贡献是零。
Task:必须解决这个问题——面试中讲”Self-Reflection 提升答案质量”却拿不出数据,会被直接击穿。
Action(第一幕:从 noop 到真正有效的条件重写):
- 抽取 5 个样例的完整执行记录,确认反思模块确实只输出了置信度评分,不返回修改后的答案
- 定位根因:流式模式下 token 已经逐个 flush 给前端,反思在流式完成后执行,“发现问题但改不了”
- 设计 post-stream conditional rewrite 方案:流式完成后,小模型做四维评分;不通过时,将审查发现的具体 issues(如”编造了不存在的三阶段降级策略""Milvus HNSW 参数描述与来源不一致”)注入 rewrite prompt,主模型重新流式生成
- 成本控制设计三层过滤:高分短路跳过 65%、评分通过跳过 50%、仅 15% 查询触发重写
- 前端新增 3 个 SSE 事件(reflection_start / reflection_token / reflection_done),实现答案无缝替换
Result(第一幕成效):
- 忠实度 +0.037(0.812→0.849),V4 首次与 V3 拉开可量化差距
- 8 条被重写查询的忠实度平均从 0.510 提升到 0.740(+0.230)
- 修正了具体问题:Milvus HNSW 参数错误、编造不存在策略、遗漏关键步骤、幻觉修正(“运行 11 个月”→“知识库中未找到”)
- 成本增量仅 ¥0.004/查询,因为三层过滤使 ~83% 查询零额外成本
终局(第二幕:四刀改造中整类移除):
- 对标 Anthropic《Building Effective Agents》/ Glean / RAGFlow 后重新评估自反思的定位:流式生成下「生成后再审查重写」本质是事后补救,而且 +0.037 的收益高度集中在少数中等置信度查询,对整体质量是边际改善
- 拆解自反思真正不可替代的部分,发现其实只有「引用编号校验 + 来源匹配」是刚需——而这些可以用纯 Java、零 LLM 成本的
CitationParser在生成后直接完成 - 于是四刀改造中整类删除 Self-Reflection(含
reflection.*配置、3 个reflection_*SSE 事件、跑题度检查):生成即终态,置信度改由confidenceScore = clamp(coverage) × rerank-top1给出,越界引用计入invalidRefs,跑题 / 低质兜底改由结构化引用覆盖率接管(后 #28 收紧:CRAG 低分但有证据不再兜底,而是带证据走 low-confidence 作答;仅真·无证据才 fallback) - 收益:省掉一整条生成后 LLM 往返与 956ms 延迟,置信度反而更可解释(句子级覆盖率 + 可点击溯源),架构组件更少
面试追问预判:
- “为什么不在生成前做反思?” → 流式体验和质量的权衡;前置反思需要先完整生成再评估,延迟更高
- “15% 的重写率够高吗?” → 这恰好说明系统在大多数情况下首次生成质量已经足够好;重写集中在中等置信度区间(rerank 0.5-0.7),正好是最需要修正的场景
- “你既然做出来了 +0.037,为什么最后又删了?” → 这正是我想讲的判断力:+0.037 是真实的,但它是「边际收益 + 一整条 LLM 往返 + 956ms 延迟」换来的;当我发现自反思不可替代的那部分(编号校验 / 来源匹配)能用纯 Java 零成本覆盖时,留着它就违背了「简单可组合胜过复杂抽象」。能加是能力,知道何时该删、并用更轻的方案承接,是更稀缺的判断力。
STAR-2:BM25 索引未刷新导致 V2 < V1(评测暴露运维缺口)#
Situation:首轮评测跑出反常数据——加入 BM25 混合检索的 V2 在事实类上 Recall@5 = 0.667,低于纯向量检索 V1 的 0.733。加了组件反而变差。
Task:定位这个反常数据的根因,确认是算法问题还是工程问题。
Action:
- 第一步怀疑 RRF 公式实现有误,写单元测试用教科书例子验证 → 公式正确
- 第二步单独调用 BM25Retriever 检索 “text-embedding-v3 维度” → 返回 0 条结果
- 第三步检查 Lucene 索引文件时间戳 → 最后修改 2026-04-12,比知识库最近一次入库(2026-04-29)早了 17 天
- 定位根因:迭代 #9 通过 MinerU 重新入库时,
processDocument()调用了MilvusService.insertChunks()但遗漏了BM25Retriever.rebuildIndex(),切片进了 Milvus 但没进 Lucene - 修复后全量重建索引,重跑评测 → V2 事实类 Recall@5 = 0.933
Result:
- 发现并修复了一个真实的生产级 bug(文档入库后 BM25 不可用)
- 如果没有评测,这个问题要等用户反馈”最近上传的内容搜不到”才能暴露
- 最终 RRF 融合在事实类上贡献了 +0.200 的 Recall 提升(0.733→0.933)
核心叙事:评测的价值不只是产出数字,更是暴露运维缺口——光为了让评测正确运行就发现了一个真实 bug。
STAR-3:从单体 Agent 到 Supervisor-Worker 重构#
Situation:DocMindAgent 膨胀至 ~1400 行,检索编排决策和执行逻辑耦合在一个类中。新增检索策略(HyDE、多文档对比)需要修改核心类。
Task:重构为可扩展的 Supervisor-Worker 编排架构,不破坏现有 SSE 事件协议和 API 签名。
Action:
- 设计 Supervisor-Worker 两层拓扑:SupervisorAgent 做规则编排(当时 3 路策略:标准检索 / 子问题拆解 / 串行多跳——后于四刀改造统一收敛为「真·LLM 工具调用 agentic 循环 + 一次性检索兜底」),Worker 做执行
- 定义统一 Worker 接口(execute + name),Worker 实现(Retrieval / Web / Memory)
- CRAG 三档评分在检索后评估质量,AMBIGUOUS 时条件触发 WebWorker 补偿检索
- 多路 Worker 用 CompletableFuture 并行派发、每路 orTimeout 部分降级(Phase 2 另实现了 Plan-and-Execute 的依赖拓扑并行执行,后在链路精简中移除——见 04 #21)
Result:
- DocMindAgent 从 ~1400 行重构(当前 ~1473 行,含 Phase 5/6 新增的范畴路由 + 路径决策)
- 新增 20 个文件,每个 80-990 行,职责单一
- 新增 Worker 仅需 1 个文件(实现接口 + 注册),原来需要改 ≥3 个核心文件
- 新增多种执行策略与置信度反馈机制(其中计划 / 拆解 / 多跳分支后于四刀改造收敛为真·LLM 工具调用 agentic 循环,置信度改由引用覆盖率给出)
- SSE 事件协议、API 签名完全不变,前端无感知
STAR-4:fail-silent → fail-fast(随机向量 bug)#
Situation:Review 代码时发现 VectorRetriever.embed() 在 Embedding API 失败时返回 1024 维随机向量,用这个随机向量去 Milvus 检索会返回无意义的垃圾结果,但下游完全不知道——它们带着正常的相似度分数混入 RRF 融合和最终 Prompt。
Task:消除这个 fail-silent 反模式,建立正确的降级语义。
Action:将 catch 块从”返回随机向量”改为”抛出 RuntimeException”,异常向上传播到 retrieve() 被外层 catch 捕获,返回空列表。空列表进入 RRF 融合不污染结果,系统自然降级为只用 BM25。
Result:
- Embedding 正常时行为不变
- Embedding 异常时向量检索被跳过,只用 BM25 结果,答案质量下降但不会出现幻觉来源
- 建立了项目的核心原则:宁可少返回结果,不返回错误的结果
STAR-5:双模型策略 + 热切换#
Situation:链路成本估算显示单查询 ¥0.012,主回答占 50%、Self-Reflection 占 30%、工具选择占 15%。但反思和工具选择都是轻决策类任务,用主模型是浪费。
Task:在不引入额外运维复杂度的前提下降低成本。
Action:
- 不维护两套 ChatModel 实例,复用主模型的 OpenAiApi 连接
- 通过
OpenAiChatOptions.builder().model("qwen-turbo").build()实现 per-call 模型覆盖 - AtomicReference 实现 LLM 模型热替换,旧引用被正在进行的 SSE 流持有,自然完成后 GC 回收
- 反思高分短路(rerank top-1 ≥ 0.85 跳过),覆盖 70% 查询(四刀改造后自反思整类移除,这条成本项直接归零)
- 缓存阈值从 3 调到 2,命中率从 30% 提升到 45%
Result:
- 单查询成本从 ¥0.012 降至 ¥0.005-0.007(-50%)
- 每项优化有独立的 admin 开关,可运行时回退
- 小模型 function calling 失败时自动降级到规则路由
STAR-6:Markdown-Aware 切块 + 增量索引#
Situation:MinerU 输出了结构化 Markdown(带 heading、表格、代码块),但 TextChunker 还在按 \n\n 朴素分段——表格被切碎、代码块被英文句号 . 切碎、heading 层级丢失(DB 里 chapter 字段一直是空字符串)。同时文档更新只能整库重建,100 页 PDF 改 1 字要重新 embedding 200-400 次。
Task:重写切块器保留结构化语义,同时实现增量索引。
Action:
- 重写为 block-aware 装配器:逐行扫描识别 5 种 block(HEADING/TABLE/CODE/IMAGE/PARAGRAPH)
- 表格和代码块原子保留不切碎,即使超过 600 字目标值——结构完整性优先于大小均匀性
- heading 切换强制 flush buffer 防止跨章节误合并(这个 bug 被自己写的单测抓出来)
- SHA-256 content_hash 做增量 diff:同 hash → 复用 vector_id 跳过 embedding,新 hash → 才 embedding
- 打通 MySQL ↔ Milvus 的 vector_id 对齐(原来两端 ID 断开,只能整库 wipe)
Result:
- 表格完整保留、代码不被切碎、chapter 字段有值(“第一章 > 第一节 > 概念定义”)
- 100 页文档改 1 字:embedding 从 200-400 次降到 ~1 次
- 9 个单测覆盖各种结构化场景
补充 STAR-7:离线评测框架搭建#
核心叙事:面试中”数据呢?“这个问题的根本解法。搭建 52 条 × 4 档对照 × 3 轮的评测框架,不复用 DocMindAgent(避免缓存/早停/短路污染对照),组件拆开按 variant 重新组合。最大发现不是数字,而是推翻了 3 个”我以为有用”的假设。
补充 STAR-8:MinerU 多模态文档统一入库#
核心叙事:用一个外部依赖解决整个”多模态文档入库”问题。关键洞察是 MinerU 把 PDF/PPT/图片/HTML 统一输出 Markdown,下游 RAG 链路零行改动。降级按格式分级:PDF 降级 PDFBox(次优可用),PPT/图片 fail-fast(Java 生态无等价替代,静默降级比报错更危险)。
补充 STAR-9:对抗查询 100% 拒答的”虚假安全感”#
核心叙事:5/5 拒答看起来完美,但查执行记录发现功劳属于 qwen-plus 的安全对齐训练,不是 SafetyGuard 拦截的。换安全对齐弱的模型可能 0/5。评测的价值不只是展示好指标,更是对漂亮数字提出质疑。
STAR-10:规则引擎替代 LLM 路由——确定性 vs 智能的取舍#
Situation:Phase 2 的 SupervisorAgent 使用 LLM Function Calling 决定调用哪些工具(doc_search / keyword_search / web_search / recall_memory),延迟 ~300ms,且结果不稳定——同一个查询两次可能选出不同的工具组合。路径判定逻辑散落在 DocMindAgent 和 SupervisorAgent 中,用隐式布尔开关推断路径,出了问题不知道走了哪条路。
Task:用确定性方案替代 LLM 工具选择,同时保证覆盖度——不能因为去掉 LLM 就漏选工具。
Action:
- 分析工具选择的决策空间:本质是 4 个信号(isAmbiguous / specificity / timeAware / memoryAware)到 4 个工具子集的映射,规则空间只有十几种组合
- 设计 PathDecision record 统一封装路径决策输出:3 种模式(SELECTED_DOC / DECOMPOSED / RULE_PLANNER),
reason字段记录机器可读的判定原因 - 实现 RetrievalPlanner 纯规则引擎:4 个 if 判断 <1ms,双源时效性校验(原始+改写问题同时检查时效关键词,防止改写丢失时间词)
- ambiguous 保守策略:QueryClassification 降级时 isAmbiguous=true → 强制加 keyword_search 防向量漂移
Result:
- 路径决策延迟从 ~300ms 降到 <1ms
- 100% 确定性:同一输入永远产出相同工具组合
- 可单元测试:
assertEquals(expected, planner.plan(classification)) - 可追溯:PathDecision.reason 写入 agent trace + SSE routing 事件
核心叙事:不是所有决策都适合交给 LLM。工具选择的规则空间小且变化慢,LLM 在这上面的表现不比 if-else 好,但贵 300ms + 不确定 + 不可测试。
STAR-11:CRAG 评分机制——检索质量的最后一道闸#
Situation:检索召回低质量切片(rerank 顶分 0.2-0.3)时,LLM 仍然基于这些切片生成答案——因为 LLM 不知道切片质量差,它只会”尽力回答”,结果大概率是基于不相关内容编造的幻觉。(当时的 Self-Reflection 能事后检查事实一致性,但检查不了”检索到的内容跟问题不是同一主题”这种根源问题——四刀改造后自反思已移除,跑题 / 低质改由 CRAG LOW + 零覆盖率兜底承接,CRAG 这道生成前的质量闸保留。)
Task:在 generation 前加一道检索质量评估,LOW 质量时主动降级避免幻觉,AMBIGUOUS 时触发补偿检索。
Action:
- 参考 CRAG 论文(Yan et al. 2024)设计三档评分:HIGH(≥0.60 直接生成)/ AMBIGUOUS(灰区仲裁)/ LOW(≤0.25 降级 prompt)
- 灰区仲裁选 heuristic 模式(默认):利用 rerank 阶段已产出的 per-chunk 分数做分布分析——top1 强且与 top2 差距 ≥0.15 → HIGH;avg ≤0.30 → LOW;其余 AMBIGUOUS。0ms 延迟,零额外调用
- cross_encoder 模式作为可选:把 top-3 切片拼成上下文让 reranker 单对打分,~50ms,比论文里的小模型仲裁(~300ms)快 6 倍
- AMBIGUOUS + 未尝试 web → 触发 WebWorker 补偿 → 补充后重新评分
- 全部阈值放入 sys_ai_config 热配表(grader.high_threshold / grader.low_threshold / grader.arbitration_mode)
Result:
- LOW 质量切片不再进入 generation,直接走降级 prompt 明确告知”证据不足”
- AMBIGUOUS 触发 Web Search 补偿,覆盖知识库盲区
- 灰区阈值(0.60/0.25)是在 Langfuse trace 里看 rerank 分数分布后调出来的——Phase 6 可观测性的直接收益
- 前端 grader 事件展示三色徽章,用户一眼可见检索质量
核心叙事:CRAG 解决的是”检索到了但不相关”的问题——它是生成前的检索质量闸,四刀改造后作为唯一保留的检索质量门。答案侧的置信度则改由结构化引用覆盖率给出(原 Self-Reflection 已移除);CRAG 查检索质量、CitationParser 查答案对来源的覆盖,二者位置不同、职责不同。
STAR-12:从一个真实 bug 到调研业界做法补上路由分层(推荐讲述)#
Situation:一次正常使用中遇到 bug。先做了一段普通的知识问答,再问”总结上面的对话”。系统的实际处理是:
- 把”总结”、“上面”、“对话”当成检索关键词去 Milvus 查询,召回 12 条与对话内容毫无关系的切片
- 自纠错判断置信度 65% 不达标,触发重写
- 重写仍然基于同一批跑题切片,置信度降到 0%
- 0% 置信度的回答还是被推送到前端
Task:定位根因后修掉这个 bug。但更重要的是判断这是个孤立 bug,还是设计上的结构性缺陷——后续是否还有类似问题(比如”你刚才说了什么”、“翻译你的回答”等)。
Action:
第一步是确认结构性缺陷。读代码发现意图分类只有一层(factoid / procedural / comparison / opinion / chitchat),五个取值都默认要去查知识库。“总结上面的对话”被归入 factoid 或 chitchat,但下游不区分意图,照常调用 RetrievalWorker。自纠错只检查事实一致性,无法识别”检索到的内容跟问题完全不是一个主题”。所以这不是孤立 bug。
第二步是调研业界做法。看了 12 个生产级框架/产品和 3 篇核心论文:
- LangGraph Adaptive RAG 教程:用结构化输出做条件路由 + 检索后再加一道闸
- AWS Bedrock Agents:6 个内置提示词模板,Pre-processing 是独立阶段
- Perplexity 工程博客:把追问先重写为独立查询,引用历史对话的请求在重写阶段就识别
- LinkedIn 工程博客:路由 → 检索 → 生成三段式,路由用小模型决定是否在范围内
- CRAG 论文(Yan 2024):检索后用独立评估器分三档
- Self-RAG 论文(Asai 2023):在词表加反思 token 让模型自己决定是否检索(需要微调,未采用)
被弃用的方案也很明确:纯靠模型函数调用决定是否检索,LinkedIn / Airbnb v1→v2 / Microsoft Semantic Kernel 都从这个路径迁出。
第三步是设计与实现:
- 加一层范畴判定区分六种情况:知识检索、元对话、闲聊、超范围、知识库元查询、任务执行
- 判定走两级——先用正则识别高置信度模式(命中即跳过模型调用),不命中再走模型
- 元对话路径完全不进检索流程,只基于历史对话生成回答
- 检索完成后加 CRAG 风格三档评估:rerank 顶分 ≥ 0.60 直接 HIGH,≤ 0.25 LOW,灰区做仲裁
- 仲裁的实现选择上没用论文里的小模型评估器,而是把 top-3 切片拼成上下文让 reranker 重新单对打分。一次调用 50ms,是模型生成的 1/5 时延
第四步是工程优化。范畴判定原本设计成独立组件方便排错,跑通后做了第二轮——把它合并进 QueryUnderstanding 的同一次模型调用,只在提示词里加了一个 scope 字段。知识查询路径的路由调用次数从 2 次降到 1 次,省 ~250ms。要排错时一个开关切回拆分模式即可。
整套改造严格遵循一个原则:每一道闸都做了降级路径,任何一道判错或调用失败都会回到原来的流程,不会比改造前更糟。
Result:
- 检索越界 bug 修复,复现率从 100% 降到 0
- 元对话路径调用 Milvus / BM25 / reranker 次数从各 1 次降到 0
- 知识查询路径路由调用次数从 2 次降到 1 次
- 灰区仲裁延迟从 ~300ms 降到 ~50ms
- 新增 23 条单元测试覆盖(11 条规则路径 + 12 条三档评估各种模式)
- 整个改动 ~825 行,不动 SupervisorAgent / Worker 现有逻辑、前端零改动可向后兼容
为什么推荐讲这个 STAR:
- 真实:起点是一个能复现的 bug,不是”我想做这个”
- 方法论:bug → 判断是否结构性 → 调研业界 → 决策 → 实现 → 二次优化,链条完整
- 取舍:能讲清楚为什么没用 Self-RAG(要微调)、为什么没用纯函数调用路由(业界已弃)、为什么仲裁不用小模型而用 reranker(成本/延迟权衡)
- 避坑意识:先做独立组件再合并,便于排错;每一道闸都做降级,不比改造前更糟
五、项目时间线#
2026-04 初 项目启动,基础 RAG + Web UI(Spring Boot + Vue 3)
│
├── 迭代 #1 fail-silent → fail-fast(随机向量 bug)
├── 迭代 #2 Langfuse OTel 可观测性集成
├── 迭代 #3 Self-Reflection 置信度标记
├── 迭代 #4 Query Decomposition 多跳检索
├── 迭代 #5 QueryProfiler 自适应检索参数(8 套预设)
├── 迭代 #6 双执行路径合并(删 1100 行 dead code)
├── 迭代 #7 成本优化 Tier 1(双模型+短路+缓存,-50%)
├── 迭代 #8 MinerU 多模态文档统一入库
├── 迭代 #9 Markdown-Aware Chunker + 增量索引
│
2026-05-02 离线评测框架搭建 + 首轮评测
│
├── 迭代 #10 离线评测框架(52 条 × 4 档 × 3 轮)
├── 迭代 #11 Self-Reflection 条件重写(忠实度 +0.037)
├── 迭代 #12 Phase 1: 扩大候选池 + MMR + 元数据丰富化
├── 迭代 #13 Phase 2: Supervisor-Worker 编排架构
├── 迭代 #14 Phase 3: HyDE 假设文档生成
├── 迭代 #15 Phase 3: Parent Document Retrieval 双层切块
│
2026-05-06 Phase 4 完成,文档整理
│
├── 迭代 #16 Phase 5: 范畴判定层 + CRAG 三档检索置信度评估
│ (起点:用户输入"总结上面的对话"被错误送入向量检索)
├── 迭代 #17 Phase 5: PathDecision 规则引擎替代 LLM 路由
├── 迭代 #18 Phase 5: QueryUnderstanding 确定性后处理
│
2026-05-09 Phase 6 完成
│
├── 迭代 #19 Phase 6: Langfuse OTel 全链路追踪增强
├── 迭代 #20 Phase 6: 前端思考时间线重构
│
2026-06-06 编排链路精简
│
├── 迭代 #21 删冗余路径(Plan-and-Execute)+ 收敛重复管道 + 条件反思
│
2026-06-07 对标主流四刀改造(A/C/B/E)
│
└── 迭代 #22 真·LLM 工具调用 agentic 循环 + 结构化引用 + 删自反思 + 分类器瘦身plaintext六、技术选型对照表#
| 领域 | 选了 | 放弃了 | 为什么 |
|---|---|---|---|
| 后端框架 | Spring Boot 3.4 + Spring AI 1.1.4 | LangChain4j / LangChain (Python) | Spring AI 原生支持 Function Calling + MCP Server,Java 生态成熟 |
| 向量数据库 | Milvus 2.3.4 | PgVector, Qdrant, Pinecone | Spring AI 原生支持,COSINE + 标量过滤联合查询,可扩展到亿级 |
| 全文检索 | Lucene 8.11 (SmartCN) | Elasticsearch | 内嵌式零额外部署,BM25 + 中文原生分词,项目规模不需要分布式 |
| 重排序 | DashScope gte-rerank (API) | 本地 BGE-Reranker-v2-m3 | 无 GPU 环境,API 调用简单免运维;后续可切本地部署 |
| 嵌入模型 | text-embedding-v3 (1024 维) | OpenAI ada-002, BGE-M3 | 阿里云原生 + DashScope 统一计费,中文效果好 |
| LLM | qwen-plus / qwen-turbo | GPT-4o, Claude | DashScope 统一生态 + 成本可控(双模型策略) |
| 文档解析 | MinerU 云端 API | Tabula+POI+Tess4J 拼凑 / 自部署 MinerU | 一个依赖解决 PDF/PPT/图片/网页,免 GPU,统一输出 Markdown |
| Agent 架构 | 真·LLM 工具调用 agentic 循环(Supervisor-Worker 作一次性兜底) | LangChain Plan-and-Execute / 自研拆解·多跳分支 | 模型运行时自驱拆 / 跳 / 补 / 停,比手写分支更具适应性;白名单约束 + max_iterations 兜底保可控 |
| 多焦点 / 多跳 | agentic 循环运行时自驱 | 自研 Query Decomposition / 串行多跳(均已移除) | 拆不拆、跳不跳交给模型按观察自决,免手写分支与阈值硬判 |
| 缓存 | Redis 7(答案缓存+用户记忆) | Caffeine / Memcached | 同时承担两个角色减少组件数,支持 TTL + 跨实例共享 |
| 对象存储 | MinIO | 本地磁盘 / 阿里 OSS | S3 兼容 + 私有化部署,原件持久化 |
| 可观测性 | Langfuse + OpenTelemetry | Prometheus + Grafana | Langfuse 原生理解 LLM 概念(prompt/completion/token),Spring AI 内置 OTel 支持 |
| 前端 | Vue 3.5 + TypeScript + Element Plus | React / Ant Design | 类型安全 + 成熟 UI 组件库 + SSE 流式渲染支持好 |
七、核心技术深度(简历扩展用)#
算法与公式#
| 算法 | 公式 | 应用场景 |
|---|---|---|
| RRF 加权融合 | score = weight / (k + rank), k=60 | 向量+BM25 结果合并 |
| BM25 | Σ[IDF × TF(k1+1) / (TF + k1(1-b+b×dl/avgdl))], k1=1.5, b=0.75 | 关键词全文检索 |
| MMR 多样性 | MMR = λ×relevance - (1-λ)×max_sim(d, selected), λ=0.7 | 重排后多样性保证 |
| COSINE 相似度 | Milvus HNSW 索引, ef=64 | 向量近邻检索 |
| SHA-256 | content_hash 增量索引 | 文档更新 diff |
并发与线程模型#
| 组件 | 并发模型 | 要点 |
|---|---|---|
| AgentToolContext | ThreadLocal + synchronizedList | 工具执行侧通道,finally 清理防泄漏 |
| SSE 流式 | DelegatingSecurityContextExecutorService | 子线程继承 Spring Security 认证信息 |
| Worker 并行派发 | CompletableFuture.allOf + orTimeout | 多路 Worker / 拆解子问题并行,单路超时部分降级 |
| 一次性检索多路 Worker | 专用线程池(core=8, max=16, CallerRunsPolicy) | IO 密集独立于 commonPool |
| ChatModel 热切换 | AtomicReference CAS | 旧引用被 SSE 流自然持有,GC 回收 |
降级策略体系#
| 正常路径 | 降级路径 | 触发条件 |
|---|---|---|
| LLM Function Calling | QueryRouter 规则路由 | LLM 调用异常 |
| Cross-Encoder API | 关键词覆盖度打分 | rerank API 超时/429 |
| MinerU 云端解析 | PDFBox 文本提取 (PDF only) | MinerU 不可用 |
| 向量检索 | 跳过,只用 BM25 | embedding API 异常 |
| BM25 MySQL FULLTEXT | 应用内分词+模糊匹配 | FULLTEXT 无结果 |
| agentic 循环异常 / 空结果 | 回退一次性检索 oneShotRetrieval | LLM 工具调用循环失败 |
| CitationParser 解析失败 / 零覆盖 | 置信度退回 rerank-top1 | 引用解析异常 |
| Redis 缓存 | 跳过缓存正常执行 | Redis 连接异常 |