news 2026/9/10 1:02:05

OpenHuman 记忆树检索:memory_tree 多模式原语、确定性 walk 路由与记忆子智能体

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenHuman 记忆树检索:memory_tree 多模式原语、确定性 walk 路由与记忆子智能体

OpenHuman 记忆树检索:memory_tree 多模式原语、确定性 walk 路由与记忆子智能体

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

OpenHuman 的记忆系统分为“写路径”与“读路径”:Memory Tree 把一天的信息流折叠成磁盘上的分块、评分与层级摘要树,而Retrieval(检索)则负责从树中读出正确答案——找到合适的节点、水合出原始 chunk、并把 “Alice” 这样的表面名字解析成稳定 id。本文基于 gitbooks/features/obsidian-wiki/retrieval.md 展开,结合当前仓库源码,讲清memory_tree工具的 8 种 mode、统一的RetrievalHit返回结构、实体规范化与 co-occurrence 图、无 LLM 的确定性walk算法,以及专职记忆子智能体的配置与性能基准方式。

设计哲学:检索层没有分类器、闸门和编排器

retrieval.md 明确了一点:检索层刻意不设置 classifier、gate 或 composer。各个检索原语是确定性的、作用域单一的;至于“该调哪个原语”“结果如何组合”,完全交给调用方的 agent 决策(对于确定性的walk,则由一个纯路由算法决策)。这意味着模型面对的是一个“工具箱”而非黑盒:每个原语行为可预测,便于测试和组合。

memory_tree工具:单一入口、多模式分发

agent 面向的入口是一个名为memory_tree的多模式工具,定义在 src/openhuman/memory/query/mod.rs。其mode字段路由到底层实现,所有 mode 返回同一种RetrievalHit结构,因此模型看到的 schema 与具体 mode 无关。

源码中MemoryTreeToolparameters_schema给出了全部合法 mode 与参数(mod.rs 的parameters_schema方法):

Mode用途典型场景
search_entities对规范化实体索引做模糊LIKE查询,把表面名字解析为 canonical id用户提到人名时先调用它(“Alice 说过什么?”)
query_source按 source 类型 + 时间窗过滤的 per-source 摘要检索,可选语义重排“总结一下我上周 Slack #eng 的内容”
drill_down对摘要节点的child_ids做 BFS 下钻,一层或多层,可选重排把粗粒度摘要展开成更细的子节点
cover_window求覆盖[since_ms, until_ms]时间窗的最小节点集合“过去 24 小时”类时间限定回顾
fetch_leaves按 id 批量水合原始叶子 chunk(上限 20 个)摘要命中后拉取原文用于引用
ingest_document把文档写入树以备日后检索(唯一的模式)持久化抓取的网页/GitHub 文件;重复source_id会替换旧 chunk
walk/smart_walk确定性 E2GraphRAG 检索——抽取查询实体、在实体图与稠密摘要间路由,全程无 LLM,返回排序后的证据一条自然语言问题一次问完,无需 agent 循环

各 mode 的具体参数(来自源码 schema)包括:

  • querysearch_entities的匹配子串;query_source的可选语义重排查询;walk的自然语言问题;
  • kindssearch_entities的实体类型过滤(emailurlhandleperson等);
  • source_kindquery_source的 source 类型过滤(chatemaildocument等);
  • time_window_daysquery_source/walk/smart_walk的回看窗口(天数),作用于 walk 的稠密分支;
  • max_hopswalk/smart_walk的实体图关联跳数阈值(默认 2,上限 4);
  • node_id/max_depthdrill_down要展开的摘要节点及其深度(默认 1,最大 3);
  • chunk_idsfetch_leaves要拉取的 chunk id 列表;
  • title/body/source_id/provider/source_refingest_document的写入参数,provider缺省为agent,重复source_id的摄取会替换旧 chunk;
  • limit:结果数上限(默认值随 mode 变化),仅mode为必填字段。

一个值得注意的历史演进:早期的query_globalquery_topic两个 mode已被移除——source 树已经承载了全部内容,走 source 层级加实体索引即可重建时间与主题两个投影(retrieval.md 指出 dispatcher 测试断言了它们不存在;src/openhuman/memory/query/mod.rs 中的match分支也确认当前合法 mode 只有上述 8 种,未知 mode 会返回明确的错误提示)。

RetrievalHit:所有原语的统一返回结构

每个原语都输出RetrievalHit,其 JSON schema 在 src/openhuman/memory/tree/retrieval/schemas.rs 中声明(多处TypeSchema::Ref("RetrievalHit")数组即各查询的命中列表)。关键字段:

  • node_id/node_kind——leaf(一条原始mem_tree_chunks行)或summary(一条已封存的mem_tree_summaries行)。消费方据此分支,例如“只对 summary 做drill_down”;
  • tree_id/tree_kind/tree_scope/level—— 溯源信息,UI 可以显示“来自 Slack #eng”;
  • content—— 片段(摘要文本或原始 chunk 正文);
  • entities/topics—— 节点携带的 canonical id 与标签;
  • time_range_start/time_range_end—— RFC3339 格式,让不同工具返回的命中可以按同一时间轴排序;
  • score—— 相关性分数;
  • child_ids—— 下一层的 id(叶子为空),是drill_down的游标;
  • source_ref—— 回指原始 source 的指针(叶子上填充)。

查询类 mode 还会把命中包进QueryResponse { hits, total, truncated },其中total是截断前的匹配总数——agent 由此判断“加大 limit 再查一轮是否会多出结果”,避免误判为“没有更多数据”。

实体解析与 canonical id

名字是脏的,id 不是。回答“关于某人的问题”之前,agent 需要把表面形式解析为 canonical id,例如person:aliceemail:alice@example.com

  • search_entities在树 summariser 维护的实体索引上做模糊查询,完成解析;
  • 规范化注册表位于 Obsidian vault 中:每个实体一个 Markdown 文件,路径为<content_root>/entities/<kind>/<canonical_id>.md,带 YAML frontmatter(idkinddisplay_namealiasesemailshandles),外加用户可在 Obsidian 里直接编辑的自由 notes 正文;lookup_alias按 alias / email / handle / display name 做不区分大小写的匹配;
  • kindmemory_tree::score::extract::EntityKind对齐,保证评分器产出的 id 能原样往返于注册表;
  • vault 是唯一事实来源——Obsidian、grep 与向量搜索看到的是同一份数据,不需要额外数据库。

实体图:只读、派生、纯 SELF-JOIN

OpenHuman 通过实体图暴露实体间关系,但没有平行的三元组表。其前提是:图就是树映射出来的——两个实体共同出现在同一个树节点上就构成一条边,权重为共同节点的个数。

  • co_occurring_entities(config, subject, limit)—— 返回按权重排序的GraphEdge { subject, object, weight }
  • neighbors(config, subject, limit)—— 仅返回邻居 id。

从源码结构看,这是一次对mem_tree_entity_index的只读 SELF-JOIN:不建新表、不改 schema。这个图正是下文确定性walk路由的直接依赖。

确定性walk/smart_walk:无 LLM 的 E2GraphRAG

walksmart_walk都经由fast_retrieve实现(dispatcher 中二者都路由到 src/openhuman/memory/query/fast_walk.rs 的run_fast_walk),这是一个E2GraphRAG 风格算法,用于替代旧的逐轮 agentic 循环,从不调用 LLM。路由完全由查询实体与共现图的跳数距离决定:

  1. 抽取查询实体Eq(spaCy NLP,regex 兜底);
  2. Eq为空 →global模式:在摘要树上做稠密重排;
  3. 否则计算h跳内的相关实体对:
    • 无相关对 →带出现度排序的 global:稠密 top-2k,再按每个摘要提及了多少Eq实体重排;
    • 找到相关对 →local模式:对各实体对的实体索引节点集求交,当候选超过k时收紧h,最后按实体覆盖率与新鲜度排序幸存者。

可调参数(FastRetrieveOptions,字段在 src/openhuman/memory/query/backend.rs 中定义为limitmax_hopstime_window_days等):limitk,默认 10、上限 100)、max_hopsh,默认 2、上限 4)、可选的time_window_days稠密分支回看窗口。输出是结构化的QueryResponse命中列表——不生成任何合成叙述文本——留给上层 context agent 消化。

时间窗检索:cover_window

对“过去 24 小时发生了什么”这类问题,cover_window计算覆盖[since_ms, until_ms](epoch 毫秒)的最小节点集。由于摘要节点自带time_range_start/time_range_end,一个高层摘要节点就可能覆盖整个时间窗,而不必扇出到每个叶子——agent 只有在需要细节或引用时才进一步drill_downfetch_leaves。实现位于 src/openhuman/memory/query/cover_window.rs。

memory_recall:旧版命名空间键值检索

与树并列、独立存在的是memory_recall(src/openhuman/memory/tools/recall.rs),它检索更早的命名空间键值记忆:memory_recall { namespace, query, limit },命名空间如globalbackgroundautocompleteskill-{id}。源码中resolve_namespace在未指定时回落到默认 scope(global),并防止模型丢失本已想好的命名空间。它返回按分排序的结果,最适合树出现之前的精确偏好/事实查询(“用户喜欢深色模式吗?”)。

记忆子智能体:专职检索的 specialist

src/openhuman/memory/agent/ 下是一个专职检索子智能体,通过call_memory_agent工具被调用。它组合各原语暴露的策略来回答关于记忆树的问题:向量搜索、原始文件关键词搜索、实体搜索与关系追踪、层级树浏览、直接读内容、source 列表。

其工具白名单与行为参数定义在 src/openhuman/memory/agent/agent/agent.toml 中,从源码可以看到几个关键配置:

  • named工具列表:memory_recallmemory_tree(含全部 mode,含确定性walk/smart_walk)、query_memorymemory_doctormemory_flavour(只读的人格/风格剖面)、ask_user_clarification
  • max_iterations = 6—— 注释解释了动机:合法答案只需要几步(walk→ 可选drill_down/fetch_leaves→ 作答),过大的预算反而会让子智能体空转约 80 秒后以失败结束,小预算确保在记忆树退化/为空时快速失败;
  • sandbox_mode = "read_only"temperature = 0.2agent_tier = "worker",以及omit_identity = trueomit_safety_preamble = true等上下文裁剪,让子智能体保持轻量;
  • prompt 与迭代上限同目录维护(agent/prompt.md+agent/prompt.rs),性能由基准脚本 scripts/bench-memory-walk.sh 追踪。

该脚本调用 core CLI 对一组测试查询做记忆树遍历基准,支持--query--content-root--max-turns--model--verbose等参数,输出每查询延迟与汇总统计,可用于回归对比检索改动前后的表现。

小结与延伸阅读

OpenHuman 的检索路径可以概括为:统一 schema 的多模式原语(memory_tree)+ 实体规范化(Obsidian vault 为唯一事实来源)+ 派生共现图 + 无 LLM 的确定性路由(walk)+ 专职受限子智能体。每一层都可单独测试(如 src/openhuman/memory/query/ 下每个 mode 都有独立的*_tests.rs),也可通过bench-memory-walk.sh做端到端基准。

相关文档(均以仓库根目录为起点):

  • gitbooks/features/obsidian-wiki/memory-tree.md —— 构建出检索所读之树的写路径;
  • gitbooks/features/obsidian-wiki/memory-diff.md —— 记忆变更如何被追踪;
  • gitbooks/features/obsidian-wiki/README.md —— Obsidian 支持 wiki 的功能索引;
  • gitbooks/features/obsidian-wiki/scoring.md —— 树评分(含实体抽取)的细节;
  • src/openhuman/memory/agent/README.md —— 记忆子智能体模块说明。

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 1:02:02

DBSCAN三维聚类实战:从正态分布造数据到参数调优与可视化

1. 从"三个簇"说起&#xff1a;为什么要用三维正态分布造数据我拿到这个需求的第一反应是&#xff1a;这个场景选得挺巧妙的。DBSCAN这类密度聚类算法&#xff0c;最容易被误解成"又一个K-Means的变体"&#xff0c;而用三维正态分布随机数生成三个簇&#…

作者头像 李华
网站建设 2026/9/10 0:59:32

STM32双串口DMA空闲中断实现全双工透传方案详解

简介&#xff1a;基于STM32CubeMX与HAL库实现的双串口DMA互透传完整工程&#xff0c;面向需要高效串口数据转发的嵌入式开发者。通过UART1与UART2的DMA收发配合&#xff0c;解决传统中断或轮询方式在连续不定长数据下CPU负担重、吞吐率低的问题&#xff0c;适用于设备间双向中继…

作者头像 李华
网站建设 2026/9/10 0:59:29

STM32无源蜂鸣器播放音乐:从驱动电路到PWM定时器配置全解析

简介&#xff1a;面向STM32F103入门学习者&#xff0c;这份资源演示如何用无源蜂鸣器演奏音乐&#xff0c;内置《红海情歌》与《生日快乐》两首曲目&#xff0c;通过修改音调与时间参数即可换成任意旋律&#xff0c;适合用作单片机定时器/PWM输出或音频驱动的练手项目。压缩包共…

作者头像 李华
网站建设 2026/9/10 0:58:02

电赛E题运动目标追踪系统:OpenMV+STM32云台视觉伺服全程实录

简介&#xff1a;面向2023年全国大学生电子设计竞赛E题备赛者&#xff0c;这份压缩包围绕“基于STM32F1的自动追光云台”提供了完整工程与源码参考。包内包含STM32F10x系列外设驱动&#xff08;如ADC、I2C、USART、定时器&#xff09;的C语言源文件及头文件&#xff0c;附带Kei…

作者头像 李华