> For the complete documentation index, see [llms.txt](https://tinyhumans.gitbook.io/openhuman/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tinyhumans.gitbook.io/openhuman/zh/gong-neng/obsidian-wiki/retrieval.md).

# 检索与回忆

这个 [记忆树](/openhuman/zh/gong-neng/obsidian-wiki/memory-tree.md) 是写入路径：它将你一天中的流式内容折叠为磁盘上的分块、得分和分层摘要树。 **检索** 是读取路径——代理如何找到正确的节点、加载正确的原始分块，并在回答你之前将“Alice”解析为稳定的 id。

这里有意地 **没有分类器、门控或组合器** 在检索层中。原语是确定性的且具备作用域特定性；决定 *调用哪个* 原语以及 *如何* 如何组合结果留给调用方代理决定（或者，对于确定性的 `遍历`，则交给纯路由算法）。源码： `src/openhuman/memory/tree/retrieval/mod.rs`.

***

## 这个 `memory_tree` 工具：单一模式分发器

面向代理的接口是一个名为 `memory_tree` (`src/openhuman/memory/query/mod.rs`）。其 `mode` 字段会路由到一个底层实现。每种检索模式都会返回相同的 `RetrievalHit` 结构，因此无论运行了哪种模式，模型看到的都是统一的 schema。

| 模式                | 用途                                                               | 典型用法                                             |
| ----------------- | ---------------------------------------------------------------- | ------------------------------------------------ |
| `search_entities` | 模糊 `LIKE` 在规范实体索引上进行查找；将表面名称解析为规范 id。                            | 调用 **优先** 当用户按名字提到某人时（“Alice 说了什么？”）。            |
| `query_source`    | 按来源的摘要检索，依据来源类型 + 时间窗口过滤，并可选进行语义重排序。                             | “总结我上周的 Slack #eng。”                             |
| `下钻`              | 对摘要节点的 `child_ids`进行 BFS 遍历，可下探一层或多层，并可选重排序。                     | 将粗粒度摘要展开为更细粒度的子节点。                               |
| `cover_window`    | 覆盖一个 `[since_ms, until_ms]` 时间窗口的最小节点集合。                         | “过去 24 小时”回顾以及其他有时间边界的补进。                        |
| `fetch_leaves`    | 按 id 批量加载原始叶子分块（上限 20）。                                          | 在命中摘要后拉取精确的源文本用于引用。                              |
| `ingest_document` | 将文档写入树中以供未来检索（这是 **写入** 模式）。                                     | 持久化抓取的网页 / GitHub 文件；重新摄入相同的 `source_id` 会替换旧分块。 |
| `遍历` / `智能遍历`     | 确定性的 E2GraphRAG 检索——提取查询实体，在实体图与稠密摘要之间路由，且 **不使用 LLM**，返回排序后的证据。 | 一次性回答自然语言问题，无需代理循环。                              |

历史上的 `query_global` 以及 `query_topic` 模式是 **移除**：源树保存全部内容，而遍历源层级加上实体索引可以重建时间和主题投影（`mod.rs` 分发器测试断言它们不存在）。

***

## 这个 `RetrievalHit` 结构

每个原语都会输出 `RetrievalHit` (`src/openhuman/memory/tree/retrieval/types.rs`）。重要字段包括：

* `node_id`, `node_kind` - `叶子` （一个原始的 `mem_tree_chunks` 行）或 `摘要` （一个封存的 `mem_tree_summaries` 行）。消费者会据此分支（例如“只 `下钻` 在摘要上”）。
* `tree_id` / `tree_kind` / `tree_scope` / `层级` ——来源，因此 UI 可以显示“来自 Slack #eng”。
* `内容` ——片段（摘要文本或原始分块正文）。
* `实体` / `主题` ——节点上携带的规范 id 和标签。
* `time_range_start` / `time_range_end` ——RFC3339，因此来自不同工具的命中会沿共同轴排序。
* `得分` ——相关性。
* `child_ids` ——下一层（叶子节点为空）；用于 `下钻`.
* `source_ref` ——指向原始来源的回指针（在叶子节点上填充）。

查询式模式会将命中包装在 `QueryResponse { hits, total, truncated }` 其中 `总数` 是截断前的匹配数量，因此代理可以判断更高限额的后续查询是否会显露更多结果。

***

## 实体解析与规范 id

名字很混乱；id 不会。在回答关于某人的问题之前，代理会将表面形式解析为一个 **规范 id** 例如 `person:alice` 或 `email:alice@example.com`.

* `search_entities` 对树摘要器维护的实体索引进行模糊查找。
* 规范注册表位于 `src/openhuman/memory_entities/` ——每个实体对应一个 Markdown 文件，位于 `<content_root>/entities/<kind>/<canonical_id>.md`，带有 YAML frontmatter（`id`, `类型`, `显示名称`, `别名`, `邮箱`, `账号名`）再加上用户可在 Obsidian 中编辑的自由格式备注正文。 `查找别名` 按别名 / 邮箱 / 账号名 / 显示名称匹配，不区分大小写。
* `类型` 匹配 `memory_tree::score::extract::EntityKind`，因此评分器发出的 id 会通过注册表双向往返且保持不变。Vault 是唯一真相来源——Obsidian、grep 和向量搜索看到的都是同一份数据，不需要单独的存储。

***

## 实体图（只读、派生）

`src/openhuman/memory_graph/` 公开实体关系 **在没有** 一个并行的三元组存储表。其前提是： *这张图就是被映射出来的树*。两个在同一个树节点上共同出现的实体构成一条边；权重是共享的不同节点数。

* `co_occurring_entities(config, subject, limit)` - `GraphEdge { subject, object, weight }` 按权重排序。
* `neighbors(config, subject, limit)` ——仅邻居 id。

它是对 `mem_tree_entity_index` ——没有新表，没有新 schema。这个图正是支撑下面确定性 `遍历` 路由的核心。

***

## 确定性遍历（`遍历` / `智能遍历`，不使用 LLM）

`遍历` 以及 `智能遍历` 两者都通过 `fast_retrieve` (`src/openhuman/memory/tree/retrieval/fast.rs`），这是一个 **E2GraphRAG 风格的** 算法，取代了旧的逐轮代理循环。它从不调用 LLM。路由完全由查询实体和共现图的跳数距离决定：

1. 提取查询实体 `等于` （spaCy NLP，正则回退）。
2. `等于` 为空 -> **全局**：对摘要树进行稠密重排序。
3. 否则计算在 `h` 跳内的相关实体对：
   * 无相关 -> **按出现次数排名的全局检索**：稠密 top-2k，再按每个摘要提及了多少 `等于` 个实体进行重排序。
   * 找到相关对 -> **局部**：求每一对实体索引节点集合的交集，在 `h` 候选项超过 `k`时持续收紧，然后按实体覆盖率和新近性对剩余项排序。

可调参数（`FastRetrieveOptions`): `限制` (`k`，默认 10，上限 100）， `max_hops` (`h`，默认 2，上限 4），以及一个可选的 `time_window_days` 用于稠密分支的回溯窗口。输出是结构化的 `QueryResponse` 命中结果——不生成综合性散文——供更高层的上下文代理消费。

***

## 按时间窗口回忆

对于“过去 24 小时发生了什么”这类问题， `cover_window` 会计算 **最小节点集合** 来覆盖 `[since_ms, until_ms]` （epoch 毫秒）。由于摘要节点携带 `time_range_start` / `time_range_end`，一个高层摘要就可以覆盖整个窗口，而无需扩散到每个叶子节点——只有在需要细节或引用时，代理才会下钻或获取叶子。

***

## `memory_recall` ——旧版键值搜索

不同于树， `memory_recall` (`src/openhuman/memory/tools/recall.rs`）会搜索旧版带命名空间的键值内存： `memory_recall { namespace, query, limit }` 在如下命名空间上进行搜索： `全局`, `background`, `autocomplete`，或 `skill-{id}`。它返回带分数的结果，最适合用于精确的偏好 / 事实查询（“用户是否偏好深色模式？”），这些内容早于树结构。

***

## 记忆代理（专门子代理）

`src/openhuman/memory/agent/` 拥有一个专门的检索子代理，通过 `call_memory_agent` 工具调用。它通过组合原语所暴露的策略来导航记忆树并回答问题：向量搜索、对原始文件的关键词搜索、实体搜索与关系追踪、分层树浏览、直接内容读取，以及源列表。

其工具允许列表（`src/openhuman/memory/agent/agent/agent.toml`）就是完整的检索面： `memory_tree` （包含上面所有模式，包括确定性的 `遍历` / `智能遍历`), `memory_recall`，以及 `query_memory`。提示词和迭代上限与之共同位于 `agent/prompt.md` + `agent/prompt.rs`；性能由基准测试框架在 `ops.rs` (`scripts/bench-memory-walk.sh`).

***

## 另请参阅

* [memory-tree.md](/openhuman/zh/gong-neng/obsidian-wiki/memory-tree.md) ——构建供检索读取的树的写入路径。
* [memory-diff.md](/openhuman/zh/gong-neng/obsidian-wiki/memory-diff.md) ——记忆变化如何随时间被跟踪。
* [README.md](/openhuman/zh/gong-neng/obsidian-wiki.md) ——Obsidian 支持的 wiki 的功能索引。
* [../subconscious.md](broken://pages/8f1b13c03e1006c2498f2a57983552beefceb5ac) ——消费回忆上下文的后台循环。
