公司动态
Mneme:一个会记住你的开源个人学习助手
Mneme一个会记住你的开源个人学习助手项目logo多数知识库问答工具完成的是一次检索上传文件、输入问题、返回答案。但真实学习是连续过程。用户会在不同会话中暴露自己的表达偏好、反复卡住的知识点和当前学习进度。Mneme 希望把资料、对话与长期学习状态连接起来让 Agent 不只“找到资料”还能够“记住你、理解你、持续帮助你”。Mneme 是一个面向个人和小规模团队自托管的开源 Beta 项目。它由 React 学习工作台、Spring Boot 业务网关和 FastAPI AI Agent 三部分组成覆盖账户、资料库、结构化文档解析、RAG、流式对话、三级记忆、学习画像和 Docker 一键启动。本文会从产品目标、架构边界、关键实现、真实 E2E、踩坑过程和后续路线完整说明这个项目。一、项目解决什么问题Mneme 的目标不是再做一个通用聊天框而是围绕“个人资料驱动的持续学习”建立闭环用户上传课件、笔记、论文、报告或简历后系统基于资料回答而不是脱离文档泛泛而谈。回答携带文档名、页码、章节和原文片段用户可以回到证据核验。系统从多轮交流中提取学习偏好、薄弱点和进度并允许用户确认或拒绝不确定记忆。会话、资料和记忆按用户隔离浏览器不能通过伪造user_id读取他人的数据。所有服务可以在本地 Docker 中运行不要求购买域名或部署公网。典型使用流程是注册账号创建资料库上传文档等待解析完成选择资料库提问然后在引用抽屉中核验答案依据。随着对话积累用户可以在记忆页检查系统形成的学习画像。二、为什么拆成 React、Java 和 PythonMneme 没有把所有逻辑堆进一个服务。三端分别承担稳定的职责边界REST / SSE账户、会话、资料元数据缓存、限流可信内部请求浏览器React 学习工作台Spring Boot GatewayMySQLRedisFastAPI AgentLangGraph 决策链结构化 RAG三级记忆ChromaDeepSeek / QwenReact用户工作台前端使用 React 19、Vite、React Router 和 React Markdown。页面包括注册登录、对话、资料库、记忆管理、学习画像等。对话通过fetch ReadableStream消费 POST SSE能够展示生成状态、逐字回答、引用、候选记忆并支持主动停止生成。Spring Boot可信业务边界Java 是浏览器唯一访问的业务入口负责 JWT、安全 Cookie、用户隔离、会话消息、文件保存、资料元数据、任务状态、限流、审计和 Python 流式响应转发。MySQL 结构由 Flyway 管理避免依赖手工初始化。这里有一个关键安全点客户端即使提交自己的user_idJava 也会用 JWT 对应的身份覆盖它。PostMapping(value/stream,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicResponseEntityStreamingResponseBodychatStream(RequestAttribute(userId)LonguserId,ValidRequestBodyChatRequestrequest){request.setUserId(userId.toString());ensureRequestId(request);returnResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).header(X-Accel-Buffering,no).body(chatService.stream(userId,request));}Python 只信任携带内部服务令牌的 Java 请求。这个边界比让浏览器直接访问 Python 更容易统一认证、限流和审计。FastAPIAI 能力层Python 集中实现 LangGraph 编排、文档解析、切片、Embedding、混合检索、Prompt 构建、模型调用、记忆蒸馏和反思。Chroma 保存知识片段和长期语义记忆Redis 保存可恢复的短期会话状态。三、一条问题如何得到答案LLMChromaPython AgentJava GatewayReact用户LLMChromaPython AgentJava GatewayReact用户选择资料库并提问POST /api/v1/chat/stream注入可信 user_id 与 request_id意图识别与记忆检索向量 词法混合检索Top-K 片段及元数据资料、记忆与规则组成 PromptToken 流SSE meta/token/memory/done转发并持久化消息实时回答、引用和记忆确认LangGraph 将请求路由到qa、review、suggest或general。资料问答进入知识检索回顾问题读取历史记忆学习建议结合薄弱点和进度普通交流使用近期上下文。路由不是为了堆节点而是让每种任务取得恰当的上下文避免把全部资料和全部记忆都塞进一次 Prompt。Prompt 的核心约束是资料存在时优先依据资料依据不足时明确说明使用编号引用记忆只能影响表达方式和学习建议不能覆盖资料事实。这样可以减少“为了照顾用户偏好而改变事实”的风险。四、文档解析不是简单的 PDF 转文本Mneme 支持 PDF、DOCX、PPTX、XLSX/XLSM、CSV、Markdown、TXT 和 HTML。不同格式采用不同结构恢复策略格式解析策略PDF页面文本块排序、表格抽取、重复页眉页脚过滤、低文本页 OCRDOCX标题层级、正文段落、表格及内嵌图片PPTX幻灯片页码、标题、文本框、表格及内嵌图片XLSX/XLSM工作表与行列转为结构化文本CSV表头与数据行保留列关系MD/TXT/HTML保留自然段、标题和章节边界PDF 的四步处理识别页面类型原生文本页直接抽取文本不足的扫描页进入 Tesseract OCR图文混排页可按配置进入视觉模型。还原结构使用 PyMuPDF 读取文本块并按坐标排序保留页码使用 pdfplumber 单独提取表格统计跨页重复文本并过滤页眉页脚。语义切片标题与正文保持邻接表格作为独立结构块每个 chunk 携带文档 ID、页码、章节和类型。保留溯源Embedding 写入 Chroma 时元数据一并保存检索结果可以直接映射到前端引用。解析入口会拒绝“成功但没有任何可检索文本”的假成功documentsload_document(file_path)documents[docfordocindocumentsifdoc.page_content.strip()]ifnotdocuments:raiseValueError(文档中没有可检索的文本扫描件请启用 OCR 后重试)chunkssplit_documents(documents)vector_store.add_documents(chunks,idschunk_ids)Docker 镜像安装了tesseract-ocr-chi-sim和英文语言包默认OCR_LANGUAGESchi_simeng。PDF_MIN_TEXT_CHARS控制何时启用 OCR避免文本型 PDF 被重复识别。多模态解析及成本控制图表、公式、流程图和页面图片不能只靠 OCR。设置MULTIMODAL_ENABLEDtrue后系统会把有限数量的页面或 Office 内嵌图片交给 Qwen-VL 描述并把描述作为可检索结构块。MULTIMODAL_MAX_IMAGES限制单文档调用数量防止复杂课件无上限消耗额度。这项能力仍有边界视觉模型输出需要通过页码和原图人工核验复杂公式转写和密集财报表格尚不能宣称百分之百准确。五、RAG向量召回与词法召回融合只用向量检索时型号、编号、姓名和代码这类精确 token 可能召回不稳定。Mneme 同时进行语义检索和轻量词法检索再按文档 ID 合并分数semanticcollection.query(query_embeddings[embeddings.embed_query(query)],n_resultsmax(top_k*2,top_k),wherewhere,)lexical_lexical_candidates(query,where)foriteminlexical:ifitem[id]inmerged:merged[item[id]][score]min(1.0,merged[item[id]][score]*0.75item[score]*0.25)这不是最终形态的搜索引擎但比纯向量检索更适合个人文档中的专有名词。下一步可引入 BM25 索引、查询改写和 Cross Encoder 重排并针对合同、论文、简历等文档类型建立独立评测集。进入 Prompt 的片段按[1]、[2]编号引用元数据通过 SSE 一起送到前端。下图来自真实 Docker 全链路测试虚构资料中的唯一编号QZ-7294被检索并在回答中正确引用而不是由 mock API 生成。六、三级记忆如何协作层级生命周期内容存储工作记忆当前请求窗口最近若干条消息和本次检索上下文Python 内存短期记忆会话级完整历史、增量摘要、冷却时间Redis可降级到内存长期记忆跨会话偏好、薄弱点、学习进度Chroma长期记忆不能把模型的每次猜测直接写入。Mneme 先蒸馏候选记忆再按置信度处理高置信度自动写入中等置信度在前端显示确认卡片低置信度丢弃。用户可以查看、确认和删除记忆避免错误画像持续污染后续回答。 0.80.6 - 0.8 0.6确认对话历史LLM 蒸馏置信度自动写入等待用户确认丢弃长期记忆Redis 不可用时短期记忆会记录警告并退回进程内存核心对话仍可继续代价是服务重启后该会话上下文丢失。这是明确的可用性降级不是静默假装已经持久化。七、模型主备与熔断模型客户端延迟初始化因此没有 API Key 时服务仍能启动并在 readiness 中报告不可用。主模型默认 DeepSeek备用模型可配置为 Qwen。连续失败达到阈值后熔断主模型在恢复窗口结束后重新探测。流式请求有一个容易忽略的约束只有在尚未向用户发送任何 token 时才能切换备用模型。如果主模型已经输出半句话再从备用模型重头生成会拼出语义冲突的答案因此代码明确阻止这种切换asyncdefastream(self,messages,**kwargs):emittedFalsetry:asyncforchunkinmodel.astream(messages,**kwargs):emittedTrueyieldchunkexceptExceptionaserror:ifusing_fallbackoremitted:raiseself._record_failure(error)asyncforchunkinfallback.astream(messages,**kwargs):yieldchunk当前实现兼容invoke、ainvoke和astream并通过 Prometheus 指标记录主备调用结果。它解决的是供应商临时失败不保证在两个供应商都不可用时继续生成答案。八、SSE 与反向代理踩坑复盘真实 E2E 第一次运行时注册请求经http://localhost:3000/api/...返回 405。Java 和 Python 都正常问题出在 CaddySPA 的try_files先把/api/*重写成/index.htmlPOST 最终落到静态文件处理器。修复方式是用互斥handle明确 API、WebSocket 和 SPA 的优先边界handle /api/* { reverse_proxy java-gateway:8080 } handle /ws/* { reverse_proxy java-gateway:8080 } handle { root * /srv/frontend try_files {path} /index.html file_server }这个问题说明“容器健康”和“页面能打开”都不等于业务全链路可用。只有从浏览器入口执行注册、上传、解析、检索、模型生成和引用展示才能发现代理顺序这类跨服务错误。九、认证、邮件与本地自托管忘记密码依赖 SMTP。为了让本地使用者无需先购买邮箱服务Compose 默认包含 MailpitJava 将重置邮件投递到本地 SMTP用户在http://localhost:8025查看邮件。它只用于本地测试不会把邮件发往公网收件箱。本地启动需要Docker Desktop 或 Docker Engine Compose PluginDeepSeek API Key用于主要对话模型DashScope API Key用于 Embedding、备用模型和可选视觉理解至少约 8 GB 可用内存首次构建还需要下载镜像和依赖复制配置模板gitclone https://github.com/CoderDongHuang/Mneme.gitcdMnemecp.env.example .envWindows PowerShell 使用Copy-Item.env.example.env至少填写以下配置DEEPSEEK_API_KEY从 DeepSeek 开放平台获取 DASHSCOPE_API_KEY从阿里云百炼获取 MYSQL_ROOT_PASSWORD本地数据库强密码 SPRING_DATASOURCE_PASSWORD与上面一致 JWT_SECRET至少32字节随机字符串 INTERNAL_SERVICE_TOKEN另一个至少32字节随机字符串两个模型密钥的获取流程和每个变量的具体位置见 本地自托管指南。.env已被 Git 忽略不应提交到仓库。启动完整栈dockercompose-fdocker-compose.yml-fdocker-compose.selfhost.yml up-d--build打开Mnemehttp://localhost:3000Mailpithttp://localhost:8025Java 健康检查http://localhost:8080/api/v1/healthPython 文档仅本机调试http://localhost:8000/docs持久化数据位于仓库的data/停止容器不会删除数据。不要执行docker compose down -v除非明确希望删除数据库卷。项目定位是本地和私有环境自托管不要求域名、HTTPS 证书或公网服务器。十、如何取得 API 配置DeepSeek登录 DeepSeek 开放平台并进入 API Keys。创建密钥并充值可用额度。将密钥写入根目录.env的DEEPSEEK_API_KEY。默认模型为deepseek-chat可通过DEEPSEEK_MODEL修改。阿里云百炼 DashScope登录阿里云百炼控制台并开通模型服务。创建 DashScope API Key。确认账号有text-embedding-v3的调用权限。将密钥写入.env的DASHSCOPE_API_KEY。若启用图片理解还需确认配置的MULTIMODAL_MODEL可用并设置MULTIMODAL_ENABLEDtrue。本地邮件默认无需申请 SMTP。docker-compose.selfhost.yml使用 Mailpit 接收重置邮件。正式连接个人 SMTP 时再覆盖SPRING_MAIL_HOST、端口、用户名、密码和 TLS 配置。十一、测试体系与真实结果Mneme 将测试分为四层层级工具主要覆盖Python 单元/接口Pytest意图、记忆、文档解析、API、检索Java 单元/集成Maven、JUnit、Testcontainers服务、控制器、数据库边界前端单元/E2EVitest、PlaywrightAPI 客户端、页面流程和响应式布局RAG 评测自定义评测脚本HitK、MRR、引用元数据完整率本次发布前实测结果Ruff通过Python67 tests passedJava4 tests passed1 个需要 Docker 的 Testcontainers 用例按环境跳过前端单元测试2 tests passedMock Playwright6 tests passed离线 RAGHit5 1.0、MRR 1.0、引用元数据完整率 1.0真实 Docker E2E1 passed约 25.7 秒真实 E2E 使用虚构资料实际经过注册、创建资料库、文件上传、DashScope Embedding、解析状态轮询、Chroma 检索、DeepSeek 流式回答和引用按钮展示。它不会默认在 GitHub Actions 中执行因为真实模型调用会消耗额度本地显式运行cdfrontendnpmrun test:e2e:real离线基线数据集规模仍小1.0 只表示当前固定样例全部命中不能解释为任意真实文档都达到完美检索。项目文档保留这一区别避免用漂亮数字替代真实质量判断。十二、上一次 CI 失败为什么发生GitHub Actions 运行31106946732对应codex/production-hardening分支。该次运行中 ESLint 和 Vitest 均通过失败步骤是npm run audit间接依赖brace-expansion命中高危公告GHSA-rgw5-rvv9-x895audit-ci按策略返回退出码 1。本次将brace-expansion更新到5.0.9并将postcss更新到8.5.23。React Router 对应公告仍按仓库已有安全策略显式 allowlist这不是“没有漏洞”而是维护者接受当前本地自托管场景的已知风险后续升级路由栈时应删除例外。依赖审计必须结合调用路径和升级影响不应简单把全部公告永久加入忽略列表。十三、当前边界与不足Mneme 当前应标记为Beta 开源项目不能宣传为完整生产级平台OCR 已随 Docker 提供中英文环境但手写体、低清扫描件和复杂印章仍可能识别错误。多模态解析已可选接入 Qwen-VL但复杂公式、密集表格和流程图仍需人工核验。混合检索当前是向量加轻量词法融合尚未使用独立 BM25 和专业重排模型。RAG 指标和真实 E2E 已建立但数据集规模有限需要更多文档类型和反例。Chroma、本地文件目录与单机 MySQL 面向个人和小规模使用不针对公网多实例扩缩容。Mailpit 解决本地密码重置验证若希望真正向外部邮箱发信仍需自行配置 SMTP。备用模型和熔断提高可用性但两个供应商同时失败时无法生成答案。文档删除、备份恢复、模型升级和跨版本迁移还需要更完整的破坏性 E2E。十四、后续路线引入 Docling 或同类版面模型提升复杂 PDF 的表格、公式和阅读顺序恢复。建立正式 BM25 索引、查询改写、Cross Encoder 重排和按文档类型路由。扩充 RAG 评测集加入事实正确性、拒答、引用一致性、OCR 漏字和表格错位指标。增加模型与参数的可视化配置降低自托管使用门槛。完成资料导入导出、删除一致性、备份恢复和版本升级的端到端验证。扩展学习计划、自动测验、错题复习和间隔重复并让记忆更新保持可解释和可撤销。十五、技术栈速览层次技术前端React 19、Vite、React Router、React Markdown、Lucide、Vitest、PlaywrightJava 网关Java 17、Spring Boot 3.2、MyBatis-Plus、Flyway、JWT、SSE、WebSocketPython AgentPython 3.11、FastAPI、LangGraph、LangChain、Pydantic、APScheduler文档处理PyMuPDF、pdfplumber、Tesseract OCR、python-docx、python-pptx、openpyxlAI 与检索DeepSeek、Qwen、DashScope Embedding、Chroma数据与运行MySQL 8、Redis 7、Docker Compose、Caddy、Mailpit工程质量GitHub Actions、Ruff、Pytest、Maven Test、ESLint、Vitest、Playwright、audit-ciMneme 的价值不在于组件数量而在于把账户边界、资料证据、对话状态和长期学习画像串成一条可核验、可控制、可继续演进的链路。它还不是一个完成所有生产能力的平台但已经具备本地自托管所需的核心闭环也明确记录了当前能力与尚未解决的问题。项目地址https://github.com/CoderDongHuang/Mneme