Mnemo 是本人独立设计与实现的本地知识库问答系统,后端采用 FastAPI 构建,代码规模约 3.8 万行 Python(不含前端)。项目的核心目标并非实现一个基础的检索增强生成(RAG)演示系统,而是系统性地解决生产级 RAG 系统中的若干关键工程问题:检索质量如何量化评估与提升、长对话上下文如何在信息损耗可控的前提下压缩、多家大模型供应商的工具调用(Function Calling)协议差异如何统一,以及 Agent 应在何种条件下自主决定进行二次检索。
其中核心是设计并实现了 Plan-Reasoning-Act-Observe-Reflect(PRAOR)的 Agentic RAG 自适应检索闭环,详见下文第二节。
技术栈:FastAPI + Uvicorn(后端服务)、MongoDB(业务数据)、Qdrant(向量检索)、Neo4j(知识图谱)、Redis(缓存与全文检索,RediSearch)、Ollama(本地模型与嵌入模型部署)、jieba / sentence-transformers(分词与重排序)、PyMuPDF / Unstructured / PaddleOCR(文档解析与 OCR)。前端提供 Next.js 16 与 Vite + TanStack 两套实现。
以下按功能模块对系统设计进行详细说明。
多数 RAG 系统对所有查询使用统一的检索参数,未考虑查询本身的复杂度差异。Mnemo 在检索前引入了一层独立的查询规划模块 QueryPlanner,负责意图识别、子查询分解与查询改写,产出结构化的检索参数(final_k 返回证据数、prefetch_k 预取候选数、fusion_strategy 融合策略等)。这一步的输出是确定性的参数,不涉及模型的自主推理决策——这一点将在第二节说明其与后续 Reasoning 阶段的职责边界。
规划过程支持三种模式,默认使用自适应(auto)模式:
规划完成后,意图为 compare / summary / clause / verification 等复杂类型的查询会先执行一轮预检索,按 Plan 产物提前拉取初始证据并注入生成上下文,使模型在第一轮生成时即可看到相关证据;意图为 general 的简单查询则跳过预检索,直接进入下一阶段由模型自主判断是否需要检索,避免为简单查询增加不必要的首轮延迟。
这是本项目中投入最多设计与验证工作的模块。传统 RAG 系统的检索通常是一次性的:执行一次检索、拼接上下文、生成答案,缺乏根据证据质量动态调整检索策略的能力。Mnemo 将检索过程设计为一个五阶段闭环,支持在单轮对话内进行多轮自主检索:
Plan(规划):由第一节所述 QueryPlanner 完成意图识别、子查询分解与查询改写,产出结构化检索参数(final_k、fusion_strategy 等)并缓存至本轮上下文,供后续阶段复用。该阶段职责单一,只做参数生成,不做"是否需要进一步检索"的判断——这一判断由下一阶段的 Reasoning 承担。意图识别在所有查询上都会执行,区别仅在于下游动作:复杂意图(compare/summary/clause/verification)额外触发一轮预检索,提前获取初始证据并注入生成上下文;简单意图(general)不预检索,但会缓存部分参数——若后续 Reasoning 阶段判断需要检索,工具调用会直接复用这份缓存参数(含查询改写结果),无需重新规划,避免重复调用带来的额外延迟。
Reasoning(推理决策):进入生成阶段后,大模型基于累积上下文自主判断当前证据是否足够回答问题、是否需要调用检索工具、调用时使用何种参数(如更换关键词、调整检索范围)。这一阶段对所有意图都会执行,只是起始上下文不同:复杂意图下模型手中已有 Plan 阶段预取的证据,判断的是"是否需要补充检索";简单意图下模型没有预置证据,判断的是"是否需要从零开始检索"。该阶段是循环中真正的自主决策环节,与 Plan 阶段的确定性参数生成明确分离:Plan 产出参数,Reasoning 决定是否使用、如何使用。
Act(执行):Reasoning 阶段判断需要检索时,触发 rag_retrieve 工具执行实际的检索调用;模型在同一轮对话中也可能调用其他工具(如编辑核心记忆的 core_memory_append)。所有工具调用的结果都会无条件写入对话上下文,供下一轮生成使用;区别在于下一阶段——只有 rag_retrieve 的结果会额外经过 Observe/Reflect 的证据充分性判断,其他工具的结果则直接引导模型基于已有信息作答,不涉及"证据是否充分、要不要重试检索"这类判断。
Observe(观察):rag_retrieve 工具调用返回后,先经过证据验证模块(EvidenceVerifier)的四层校验,再记入本轮观察结果:
若系统加载了 CrossEncoder 重排序模型,验证流程还会引入第二层分层确认:CrossEncoder 分数高于 0.7 时强制判定为有效证据,低于 0.1 时强制判定为无效证据,中间区间保留规则判断结果。该设计使得高置信度与低置信度的结果均可跳过更高成本的判断路径,仅对置信度模糊的中间区间进行精细化处理。
Reflect(反思):反思模块(Reflector)基于当前及历史观察结果,从四个维度评估证据充分性:
判定证据不足时,反思模块会给出针对性建议——检索停滞时建议切换检索策略,覆盖度不足时提示需要补充的具体关键词,并将建议注入下一轮生成上下文,供 Reasoning 阶段决定是否再次调用检索工具、如何调整参数,形成闭环迭代;判定证据充分时,闭环终止,进入最终回答生成。循环设有安全阀(同一轮对话内最多 5 次检索),达到上限后强制基于现有证据生成回答,避免无限迭代。
检索层采用向量检索(Qdrant)、关键词检索(Redis RediSearch 全文索引)与知识图谱检索(Neo4j)三路并行,并使用 RRF(Reciprocal Rank Fusion,倒数排名融合) 算法进行结果融合。融合前各路结果独立执行质量过滤:
命中片段经过两类上下文补全处理:
分块策略本身也非单一算法:混合分块器(HybridChunker)首先通过规则识别代码块、LaTeX 公式、Markdown 表格等不可分割的结构化内容并整体保留,其余文本内容采用嵌入模型的语义分块(按语义边界切分,而非固定字符长度硬切)。针对结构化报告类文档还设有独立的分块处理分支。
最终注入模型上下文的证据按 token 预算截断,并统一编号,供模型在生成回答时进行引用溯源。
该模块的设计参考了 Letta(原 MemGPT)项目的记忆分层思路,本人对其源码进行了深入研究后,将相关设计迁移并适配至本项目:
core_memory_append / core_memory_replace 工具主动编辑,无需检索即可感知,这是其区别于归档记忆的核心特征;压缩策略经过一轮实证迭代:早期版本采用 9 段式摘要结构,但实测发现压缩后长度反而超过原文(压缩比 114.76%),且数字、API 约定、错误日志等硬性细节在概括式摘要中的保真度损失达到 37.5%。据此改进为现行的 5 段式结构(用户需求 / 关键事实,显式保留数字、API 合同、决策结论、错误日志、专有名词 / 涉及文件 / 用户原话,逐条列出不做概括 / 当前状态),并将压缩比强制约束在 60% 以内。
项目需同时兼容本地部署模型(Ollama)、OpenAI 协议兼容接口(DeepSeek / Qwen / MiMo / OpenRouter / Together)、Anthropic Claude 与 Google Gemini,而各家供应商在工具调用协议的 Schema 格式、流式响应中参数分片方式上均存在差异。为此本人设计了一层统一适配(tool_call_adapter),以 OpenAI Chat Completions 格式作为中立的标准格式:
input_schema / tool_use 消息块 / tool_result 消息结构的差异;function_declarations / functionCall / functionResponse 结构的差异。核心组件包括:工具定义的双向转换器(Schema Converter)、流式工具调用的增量聚合器(Stream Aggregator,用于将跨多个流式数据块拆分的工具调用参数重新拼接为完整 JSON,因为各供应商对参数片段的切分粒度均不相同)、响应归一化器,以及供应商能力探测与自动降级机制(Capability Registry)。上层 Agent 逻辑无需感知底层调用的具体供应商。
除单轮 Agentic RAG 外,系统还提供多 Agent 协作的深度研究模式:协调型 Agent(Coordinator Agent)首先分析问题复杂度,从 9 类专家 Agent(文档检索、公式分析、代码分析、概念解释、示例生成、练习设计、实现方案、批判性分析、总结归纳)中选取实际需要的子集,分配具体任务并确定执行依赖关系后并行/串行调度,最终由总结 Agent 汇总结果。默认组织原则围绕"检索 → 论证分析/概念解释 → 批判性校验 → 总结归纳"的主链路展开,避免不必要的 Agent 调用开销。