Files
Hy-Memory/docs/usage.md
T
26c33ee40f refactor(sdk): internalize legacy data migration (#146)
* refactor(sdk): keep legacy migration out of client APIs

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(migration): stage SDK data before cutover

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: alvinfei <alvinfei@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 20:49:23 +08:00

362 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hy Memory 使用指南
本文只描述当前 `HyMemoryClient` 已公开的方法和返回结构。
## 安装与快速开始
```bash
pip install hy-memory==2.0.4
# 可选后端
pip install 'hy-memory[qdrant]==2.0.4'
pip install 'hy-memory[faiss]==2.0.4'
pip install 'hy-memory[graph]==2.0.4' # Neo4j;Kuzu 已在核心依赖中
pip install 'hy-memory[mysql]==2.0.4'
pip install 'hy-memory[rerank]==2.0.4' # 本地多语言 cross-encoder rerank
```
默认 LLM 和 Embedding 均使用 OpenAI-compatible 接口。使用 OpenAI 时可只设置:
```bash
export OPENAI_API_KEY="sk-your-key"
```
```python
from hy_memory import HyMemoryClient
with HyMemoryClient(mode="pro") as client:
client.add(
"用户喜欢科幻电影,特别是《星际穿越》",
user_id="user_001",
)
result = client.search(
"用户喜欢什么?",
user_ids=["user_001"],
)
for channel, memories in result["memories"].items():
for memory in memories:
print(channel, memory["score"], memory["content"])
```
## 配置
SDK 会加载项目目录中的 `.env`。完整配置见 [env_reference.md](env_reference.md)。
```bash
MEMORY_MODE=pro
MEMORY_LLM_MODEL=deepseek-chat
MEMORY_LLM_API_KEY=sk-your-key
MEMORY_LLM_BASE_URL=https://api.deepseek.com
MEMORY_EMBEDDER_MODEL=text-embedding-v4
MEMORY_EMBEDDER_API_KEY=sk-your-key
MEMORY_EMBEDDER_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
MEMORY_EMBEDDING_DIMS=1024
```
也可以使用当前公开的 `from_config()`:
```python
client = HyMemoryClient.from_config({
"mode": "pro",
"llm": {
"model": "deepseek-chat",
"api_key": "sk-xxx",
"base_url": "https://api.deepseek.com",
},
"embedder": {
"model": "text-embedding-v4",
"api_key": "sk-xxx",
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"embedding_dims": 1024,
},
"vector_store": {
"provider": "chroma",
"embedding_dims": 1024,
},
})
```
通过 `from_config()` 配置时,Embedder 与 Vector Store 的维度是两个独立字段,必须保持
一致;环境变量方式则统一使用 `MEMORY_EMBEDDING_DIMS`。
支持的向量后端为 `chroma`、`qdrant`、`faiss`。Graph 后端通常使用
`kuzu` 或 `neo4j`。
## Client API
同步和异步方法使用相同参数。异步方法以 `async_` 为前缀。
### `add()` / `async_add()`
```python
result = client.add(
[
{"role": "user", "content": "推荐一部电影"},
{"role": "assistant", "content": "推荐《星际穿越》"},
],
user_id="user_001",
agent_id="default_agent",
session_id="default_session",
source="openclaw",
scene=None,
memory_at=None,
metadata=None,
meta=None,
embed_override=None,
)
```
`metadata` 与 `meta` 不是同一字段:
- `metadata` 是兼容旧调用方保留的 SDK 处理选项,会进入内部
`WriteRequest.extra`,不保证持久化或随查询结果返回。
- `meta` 是调用方的业务上下文,会持久化并在 `search()`、`get()` 等结果中返回。
同一记忆被多次合并时,各次 `meta` 会按写入顺序保存在列表中。
需要以后读回的数据必须放在 `meta`,不要放在 `metadata`。
`scene` 的公开取值:
- `None`:普通文本直接走 chat;messages 含 tool 时执行快速场景判断。
- `chat`:强制走普通记忆链路。
- `working`:强制走生产力记忆链路。
- `coding`:强制走软件工程变体,并维护版本演进链。
强制 `working` / `coding` 时输入必须含 tool 消息,否则回退 chat。普通 chat 写入返回
`success`、`scene`、`memory_id`、`request_id`、`elapsed_ms` 等字段;生产力链路返回其
双层 reconcile 操作、`experience_ids`、`fact_ids` 等字段,完整结构见
[scene_coding_usage.md](scene_coding_usage.md#4-返回结构)。
`mode="lite"` 不执行 LLM 抽取,写入内容直接以 `L1_RAW` 形式保存。lite 的 Search 是
严格的 raw-only 向量检索:无论调用方传入什么 `layers`、`scene` 或 `search_mode`,都只
召回 `L1_RAW`,不会进入 working/coding、Schema 或 Intention 链路,也不会调用场景判断
LLM。pro / ultra 仍遵循各自的层级与场景参数,默认不公开原始对话。
`source` 是调用方应用来源,例如 `openclaw`、`cursor`、`codex`。它与 `agent_id`
正交:前者说明“从哪个产品/入口写入”,后者仍是记忆隔离中的 agent 身份。source 会
执行 `strip().lower()` 后存储,并随 chat、working、coding 记忆一起返回。
### `search()` / `async_search()`
普通 chat 检索:
```python
result = client.search(
query="用户喜欢什么?",
user_ids=["user_001"],
agent_ids=None,
session_ids=None,
include_sources=["openclaw", "codex"],
exclude_sources=["cursor"],
limit=10,
min_score=0.4,
schema_limit=10,
schema_min_score=0.4,
intention_limit=0,
reader="", # HY_MEMORY_READER;默认 legacy
layers=None,
created_after=None,
search_mode="default",
)
```
source 过滤语义为:
```text
source IN include_sources AND source NOT IN exclude_sources
```
任一列表为空时省略对应条件;两者冲突时黑名单优先。Chroma/Qdrant/Graph 会在数据库
候选截断前执行过滤,FAISS 使用 ID selector 在索引搜索前限定候选,不采用多路召回或
扩大 top-k 后过滤。多来源证据派生出的聚合记忆标记为 `mixed`;开启任意 source 过滤
时默认排除 `mixed`,只有显式将 `mixed` 放入白名单才返回。
chat 成功路径返回:
```python
{
"request_id": "...",
"memories": {
"normal": [], # L2/L3 普通记忆;L4/L5 当前未启用
"schema": [], # L6 Schema
"proactive": [], # L7 Intention;默认关闭
},
"rewrite": {
"used": False,
"original_query": "用户喜欢什么?",
"effective_query": "用户喜欢什么?",
"created_after": None,
"scene": "normal",
},
"elapsed_ms": 12.3,
}
```
Reader 执行失败时返回空的 `memories` 三桶和 `request_id` / `elapsed_ms`;当前失败兜底不
包含 `rewrite`。
L0 用户画像不在 Search 中。`schema_limit` 只控制 L6,不会被 L0 占位。
Search 也可以路由到独立生产力记忆库:
```python
result = client.search(
"这个异常最后是怎么修复的?",
user_ids=["user_001"],
scene="coding",
workspace_id="repo-id",
branch="main",
)
```
`working` / `coding` 路径返回 `scene="productivity"`、`facts`、`experiences`,不使用
chat 的三桶结构;`coding` 还会返回 `chain_block` 和 `chain_used` 字段(默认空;
需 `MEMORY_CODING_CHAIN_BLOCK_ENABLED=true` 才注入 diff 块)。
#### `search_mode`
| `search_mode` | `scene` | 行为 |
|---|---|---|
| `default` | `None` + 单个 `query` | 固定 chat/normal,零 LLM |
| `default` | `None` + `queries=[...]` | 行为由 Coding 与 Query Rewrite 开关共同决定,见下表 |
| `agentic` | `None` + 单/多 query | 一次 LLM 完成场景判断、改写和时间解析 |
| 任意 | 显式 `chat/working/coding` | 直接路由,不调用 judge/rewrite LLM |
`query` 与 `queries` 二选一;`queries` 最后一项是目标 query,前面的项用于上下文消解。
LLM 或解析失败时回退到 chat/normal 和原 query。
default 多-query 在总字符数不超过 `MEMORY_QUERY_REWRITE_MAX_CHARS` 时:
| `MEMORY_CODING_ENABLED` | `MEMORY_ENABLE_QUERY_REWRITE` | 行为 |
|---|---|---|
| `true` | 任意 | 一次 LLM 完成 `chat / working / coding` 分类和 query rewrite |
| `false` | `true` | 一次 LLM 完成 rewrite 与时间解析,不做场景分类 |
| `false` | `false` | 零 LLM,固定 chat/normal,使用最后一条 query |
超过长度保护时不做 rewrite;Coding 开启时只执行有界场景分类,关闭时零 LLM。
### `get_user_profile()` / `async_get_user_profile()`
用户画像只由 `user_id` 唯一确定,不接收 `agent_id` 或 `session_id`:
```python
result = client.get_user_profile(user_id="user_001")
profile = result["profile"]
```
```python
{
"request_id": "...",
"profile": {
"memory_id": "...",
"user_id": "user_001",
"content": "...",
"layer": "l0_basic_info",
"basic_info": {"name": "Alice"},
"catalog": {"attached": False},
"supplement": {"attached": False},
"tags": [],
"memory_at": 1234567890,
"gmt_created": 1234567890,
},
"elapsed_ms": 1.2,
}
```
不存在 L0 且没有可附加的 balanced catalog 时,`profile` 为 `None`。该接口不调用
Embedding 或 LLM。启用对应开关后,active catalog 和 supplement 会附加到同一画像;
catalog 也可以在没有基础 L0 时形成只读的合成画像。
### CRUD、列表与克隆
以下均为当前公开方法:
```python
memory = client.get("memory_id")
# raw ID 返回 L1_RAW,fact ID 返回对应事实层。
raw_memory = client.get("raw_id")
result = client.update("memory_id", "更新后的内容")
result = client.delete("memory_id")
result = client.delete_all(user_id="user_001")
page = client.list_memories(
user_id="user_001",
limit=100,
offset=0,
order="desc",
layers=["l1_raw"], # 显式列出 raw;不传仍沿用默认语义层列表
with_evolution=False,
)
result = client.clone_user("user_001", "user_002", agent_id=None)
```
对应异步方法为 `async_get()`、`async_update()`、`async_delete()`、
`async_delete_all()`、`async_list_memories()`、`async_clone_user()`。
`get()` / `async_get()` 是统一的 ID 精确读取:返回结果的 `layer`、`kind` 和
`variant` 表明实际类型。授权由调用 SDK 前的产品边界完成,不由 SDK 根据 layer
猜测。`list_memories()` 默认仍不包含 `L1_RAW`;显式传
`layers=["l1_raw"]` 时按同一列表合同返回 raw。
普通 `list_memories()` 返回 `vdb` 桶,并在 Graph 有结果时附带 `graph`;传入
`workspace_id` 时返回生产力记忆的 `scene="productivity"` 和 `experiences`。
### `evolve()` / `async_evolve()`(Ultra)
System 2 不在 `add()` 中自动触发,当前也没有 `get_write_status()` 接口。调用方完成一批
写入后显式执行:
```python
result = client.evolve(
user_id="user_001",
agent_id="default_agent",
)
```
`evolve()` 仅允许 `mode="ultra"`;同步方法阻塞到本次认知加工完成。它读取当前 VDB
全量,生成或更新 L6 Schema,并执行配置允许的 System 2 处理。
### 其他当前公开方法
- `finalize_session()` / `async_finalize_session()`:在功能开关开启时归档指定 session。
- `get_user_basic_info()` / `async_get_user_basic_info()`:读取 L0 basic info 记录。
- `sweep()` / `async_sweep()`:Ultra 下显式执行 cross-domain sweeper。
- `get_metrics()` / `async_get_metrics()`:读取 SDK 指标。
- `build_memory_graph()` / `async_build_memory_graph()`:回填普通记忆关系图。
- `build_entity_store()` / `async_build_entity_store()`:回填独立 Entity Store。
- `history()` / `async_history()`、`get_recent_history()` / `async_get_recent_history()`。
## Embedding 端点覆盖
`add()` / `async_add()` / `search()` / `async_search()` 支持 per-call
`embed_override`:
```python
override = {
"base_url": "http://embedding-host:8000/v1",
"api_key": "sk-xxx",
"extra_headers": {"X-Route": "direct"},
"timeout": 15,
}
client.add("用户喜欢篮球", user_id="u1", embed_override=override)
client.search("兴趣爱好", user_ids=["u1"], embed_override=override)
```
它只覆盖接入端点,不覆盖 model、维度或 `extra_body`。新端点必须运行与已入库数据兼容的
Embedding 模型。
## 数据隔离与处理模式
普通记忆使用 `user_id → agent_id → session_id` 三层作用域。Search 必须传非空
`user_ids`;不传 `agent_ids` / `session_ids` 时可跨对应维度检索。
用户画像是例外:L0、balanced catalog 和 specific supplement 从生成到读取都只由
`user_id` 唯一确定。
- `lite`:原文向量化,不调用提取 LLM。
- `pro`:Embedding + LLM 提取 + reconcile;Graph 可通过 `MEMORY_ENABLE_GRAPH` 开启。
- `ultra`:具备 pro 能力并强制启用 Graph;System 2 由调用方显式调用 `evolve()`。
Reader、Rerank 和 Search 参数详见 [hy_memory_search_skill.md](hy_memory_search_skill.md)。