* 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>
362 lines
12 KiB
Markdown
362 lines
12 KiB
Markdown
# 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)。
|