Refine memory system user-key flow and search output

This commit is contained in:
2026-05-22 16:30:42 +08:00
parent 92632553ab
commit d73f59f38d
16 changed files with 1888 additions and 255 deletions

389
README.md
View File

@ -1,32 +1,72 @@
# Memory System API
# Memory Gateway
Memory System API is a lightweight HTTP facade over two memory backends:
Memory Gateway 是一个轻量级记忆网关项目,用统一的 HTTP API 把上层应用接到两个记忆后端:
- OpenViking stores session conversation memory.
- EverOS stores user profile and episodic memory.
- OpenViking:负责会话归档、长期记忆抽取、按 `user_id / session_id` 隔离的语义检索。
- EverOS负责用户画像、episodic memory、profile 查询等补充记忆能力。
The caller only sends `user_id`, `session_id`, and optional `user_message` / `assistant_message`.
The API creates or reuses the OpenViking user key, writes messages to both backends, and exposes simple endpoints for commit, immediate extraction, search, and profile reads.
当前重点模块是 `memory_system_api`。它不是 OpenViking 的替代品,而是一个更窄的业务网关:上层只面对 user、user key、session 和 message网关内部固定使用 OpenViking `admin` 工作区。
## Endpoints
## 核心模型
Base URL:
### 身份字段
```text
http://127.0.0.1:1934
| 字段 | 含义 | 是否自定义 | 备注 |
|---|---|---:|---|
| `user_id` | 真实终端用户 | 是 | 先通过 `/users` 创建后续写入、查询、commit 时使用 |
| `user_key` | OpenViking 返回的用户 key | 否 | `/users` 返回并写入 SQLite业务调用时随请求体或 query 传入 |
| `session_id` | 一次会话 ID | 是 | OpenViking session 容器,同时用于带 session context 的搜索 |
建议 ID 只使用字母、数字、`_``-``.``@`,不要使用空格、斜杠或中文。
### 先创建用户再使用
除健康检查外,业务调用分两步:
1. 调用 `POST /memory-system/users` 创建 OpenViking user。
2. 保存返回的 `user_key`后续写入消息、commit、extract、task、search、profile 都传 `user_id + user_key`,不需要传 `account_id`
如果没有先创建 user或者 `user_key` 与数据库里的 `user_id` 不匹配,`messages``commit``extract``tasks``search``profile` 都会返回 `401`
第一次创建业务 user 时,如果 SQLite 里还没有 OpenViking `admin` 工作区记录,网关会先调用 OpenViking
```http
POST /api/v1/admin/accounts
{"account_id": "admin", "admin_user_id": "admin"}
```
Routes:
随后创建具体用户:
- `GET /memory-system/health`
- `POST /memory-system/messages`
- `POST /memory-system/sessions/{session_id}/commit`
- `POST /memory-system/sessions/{session_id}/extract`
- `GET /memory-system/openviking/tasks/{task_id}?user_id=...`
- `POST /memory-system/search`
- `GET /memory-system/users/{user_id}/profile`
```http
POST /api/v1/admin/accounts/admin/users
{"user_id": "<user_id>", "role": "user"}
```
## Install
后续创建第二、第三个用户时,网关直接调用 `/api/v1/admin/accounts/admin/users`。所有返回的 `user_id / user_key`、创建过的 `session_id`、commit 返回的 `task_id / archive_uri` 都写入 SQLite不放在进程内缓存里。
### 鉴权
Memory Gateway 有两类 key
| Key | 请求头 | 用途 | 是否必需 |
|---|---|---|---|
| Memory System API key | `X-API-Key` | 保护 Memory Gateway 服务本身 | 仅当 `server.api_key` 配置非空时需要 |
| OpenViking user key | 请求体/query 的 `user_key` | 校验具体用户并调用 OpenViking 普通用户 API | 除 `/health``/users` 外都需要 |
OpenViking 的 root key 配在服务端 `config.yaml` 中,由网关内部使用。调用方不需要也不应该在业务请求中传 OpenViking root key。
OpenViking 内部调用遵循:
| OpenViking 场景 | 鉴权 |
|---|---|
| Admin API例如创建 account/user | `X-API-Key: <openviking root key>` |
| 普通用户 API例如 session/message/commit/task/search | `Authorization: Bearer <openviking user key>` |
### Session 与搜索范围
OpenViking session 由请求里的 `session_id` 创建和提交。普通向量搜索使用显式用户 memory 路径 `viking://user/<user_id>/memories/``use_llm=true` 的智能搜索会同时传 `session_id``target_uri: viking://user/<user_id>/<session_id>`,用于结合当前 session context。
## 安装
```bash
cd /home/tom/memory-gateway
@ -36,15 +76,15 @@ pip install -U pip
pip install -e ".[dev]"
```
## Configure
## 配置
Copy the example config and edit backend URLs or keys as needed:
复制配置模板:
```bash
cp config.example.yaml config.yaml
```
Important fields:
主要配置:
```yaml
server:
@ -55,87 +95,318 @@ server:
openviking:
url: "http://127.0.0.1:1933"
api_key: "your-secret-root-key"
timeout: 30
verify_ssl: true
everos:
url: "http://127.0.0.1:1995"
api_key: ""
timeout: 180
verify_ssl: true
health_path: "/health"
storage:
sqlite_path: "/home/tom/memory-gateway/memory_system_api.sqlite3"
```
If `server.api_key` is set, clients must send `X-API-Key`.
环境变量也可以覆盖部分配置:
## Start
| 环境变量 | 覆盖字段 |
|---|---|
| `MEMORY_SYSTEM_SERVER_API_KEY` | `server.api_key` |
| `MEMORY_SYSTEM_SERVER_HOST` | `server.host` |
| `MEMORY_SYSTEM_SERVER_PORT` | `server.port` |
| `OPENVIKING_URL` / `OPENVIKING_BASE_URL` | `openviking.url` |
| `OPENVIKING_API_KEY` | `openviking.api_key` |
| `EVEROS_URL` / `EVEROS_BASE_URL` | `everos.url` |
| `EVEROS_API_KEY` | `everos.api_key` |
| `MEMORY_SYSTEM_STORAGE_SQLITE_PATH` | `storage.sqlite_path` |
Start OpenViking and EverOS first, then run:
## 启动
先启动 OpenViking 和 EverOS再启动 Memory System API
```bash
python -m memory_system_api.server --config config.yaml --host 127.0.0.1 --port 1934
```
## Real Test Flow
Health:
如果 `server.api_key` 非空,所有请求还要加:
```bash
curl -s http://127.0.0.1:1934/memory-system/health
-H "X-API-Key: <memory-system-api-key>"
```
Write user and assistant messages:
## API 总览
Base URL
```text
http://127.0.0.1:1934/memory-system
```
| 方法 | 路径 | 作用 | User key |
|---|---|---|---|
| `GET` | `/health` | 检查 OpenViking 和 EverOS 健康状态 | 不需要 |
| `POST` | `/users` | 在固定 `admin` 工作区创建 OpenViking user并保存 user key | 不需要 |
| `POST` | `/messages` | 写入一轮或半轮会话消息 | 需要 |
| `POST` | `/sessions/{session_id}/commit` | 提交会话,触发 OpenViking commit 和 EverOS flush | 需要 |
| `POST` | `/sessions/{session_id}/extract` | 立即触发 OpenViking extract | 需要 |
| `GET` | `/openviking/tasks/{task_id}` | 查询 OpenViking 后台任务状态 | 需要 |
| `POST` | `/search` | 同时搜索 OpenViking 和 EverOS 记忆 | 需要 |
| `GET` | `/users/{user_id}/profile` | 查询 EverOS profile | 需要 |
## API 详情
### `GET /health`
检查两个后端是否可访问。
```bash
curl -s -X POST http://127.0.0.1:1934/memory-system/messages \
curl -sS http://127.0.0.1:1934/memory-system/health
```
返回字段:
| 字段 | 含义 |
|---|---|
| `status` | `success``partial_success``failed` |
| `backends.openviking` | OpenViking 健康检查结果 |
| `backends.everos` | EverOS 健康检查结果 |
### `POST /users`
创建新的 OpenViking user并把返回的 `user_id / user_key` 保存到本地 SQLite。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `user_id` | string | 是 | 要创建的用户 ID |
示例:
```bash
curl -sS -X POST http://127.0.0.1:1934/memory-system/users \
-H "Content-Type: application/json" \
-d '{"user_id": "userA"}'
```
返回中的 `account.result.user_key` 是后续业务调用要传的 `user_key`
```json
{
"status": "success",
"account": {
"status": "ok",
"result": {
"account_id": "admin",
"user_id": "userA",
"user_key": "..."
}
}
}
```
### `POST /messages`
写入用户消息和/或助手消息。至少要传 `user_message``assistant_message` 其中一个。网关会先确保 OpenViking user/session 存在,再把消息写入 OpenViking session同时写入 EverOS。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `user_id` | string | 是 | 已创建的用户 ID |
| `user_key` | string | 是 | `/users` 返回的 user key |
| `session_id` | string | 是 | 会话 ID |
| `user_message` | string/null | 否 | 用户消息 |
| `assistant_message` | string/null | 否 | 助手消息 |
| `timestamp` | int/null | 否 | 预留字段 |
| `metadata` | object | 否 | 预留字段 |
示例:
```bash
USER_KEY="..."
curl -sS -X POST http://127.0.0.1:1934/memory-system/messages \
-H "Content-Type: application/json" \
-d '{
"user_id": "real_user_001",
"session_id": "real_sess_001",
"user_message": "我喜欢喝拿铁,不喜欢美式。",
"assistant_message": "好的,我会记住你的咖啡偏好。"
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1",
"user_message": "请记住:我喜欢拿铁。",
"assistant_message": "好的,我记住了。"
}'
```
Commit OpenViking and flush EverOS:
### `POST /sessions/{session_id}/commit`
提交会话。OpenViking 会归档 session 并异步抽取长期记忆,通常会返回 `task_id`EverOS 会 flush 当前 session。
路径参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| `session_id` | string | 要提交的会话 ID |
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `user_id` | string | 是 | 用户 ID |
| `user_key` | string | 是 | `/users` 返回的 user key |
示例:
```bash
curl -s -X POST http://127.0.0.1:1934/memory-system/sessions/real_sess_001/commit \
-H "Content-Type: application/json" \
-d '{"user_id": "real_user_001"}'
```
Search without LLM planning:
```bash
curl -s -X POST http://127.0.0.1:1934/memory-system/search \
curl -sS -X POST http://127.0.0.1:1934/memory-system/sessions/sessionA1/commit \
-H "Content-Type: application/json" \
-d '{
"user_id": "real_user_001",
"session_id": "real_sess_001",
"query": "我喜欢喝什么咖啡?",
"user_id": "userA",
"user_key": "'"$USER_KEY"'"
}'
```
如果返回里有 OpenViking `task_id`,需要继续查询任务直到完成。
### `POST /sessions/{session_id}/extract`
立即触发 OpenViking extract只作用于 OpenViking不触发 EverOS flush。适合调试或明确要求“现在就抽取”的场景。
参数同 commit
```bash
curl -sS -X POST http://127.0.0.1:1934/memory-system/sessions/sessionA1/extract \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'"
}'
```
### `GET /openviking/tasks/{task_id}`
查询 OpenViking 后台任务状态,例如 commit 返回的任务。
Query 参数:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `user_id` | string | 是 | 用户 ID |
| `user_key` | string | 是 | `/users` 返回的 user key |
| `session_id` | string/null | 否 | 会话 ID传入后会按同一 session identity 查询 |
示例:
```bash
curl -sS -X POST "http://127.0.0.1:1934/memory-system/openviking/tasks/${TASK_ID}" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1"
}'
```
### `POST /search`
同时查询 OpenViking 和 EverOS并合并结果。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `user_id` | string | 是 | 用户 ID |
| `user_key` | string | 是 | `/users` 返回的 user key |
| `session_id` | string/null | 否 | 会话 ID`use_llm=true` 时会作为 OpenViking search 的 session context |
| `query` | string | 是 | 查询文本 |
| `use_llm` | bool | 否 | `false` 使用 OpenViking find + EverOS hybrid`true` 使用 OpenViking search + EverOS agentic |
| `limit` | int | 否 | 返回条数,默认 10范围 1 到 100 |
示例:
```bash
curl -sS -X POST http://127.0.0.1:1934/memory-system/search \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1",
"query": "我喜欢喝什么?",
"use_llm": false,
"limit": 10
}'
```
Search with LLM planning:
返回字段:
| 字段 | 含义 |
|---|---|
| `status` | 总体状态 |
| `items` | 合并后的记忆结果,含 `source_backend` |
| `backends` | 两个后端的精简诊断信息例如命中数量、query plan 或查询过滤条件;不再回传完整原始命中和 `original_data` |
### `GET /users/{user_id}/profile`
读取 EverOS profile。该接口需要 `user_key`,用于确认调用方属于该 user。
示例:
```bash
curl -s -X POST http://127.0.0.1:1934/memory-system/search \
curl -sS "http://127.0.0.1:1934/memory-system/users/userA/profile?user_key=$USER_KEY"
```
## 完整测试流程
```bash
API=http://127.0.0.1:1934/memory-system
curl -sS -X POST "$API/users" \
-H "Content-Type: application/json" \
-d '{"user_id":"userA"}'
USER_KEY="把上一步返回的 account.result.user_key 填到这里"
curl -sS -X POST "$API/messages" \
-H "Content-Type: application/json" \
-d '{
"user_id": "real_user_001",
"session_id": "real_sess_001",
"query": "我的偏好是什么?",
"use_llm": true,
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1",
"user_message": "请记住:我在 sessionA1 喜欢拿铁,项目代号 alpha-session。",
"assistant_message": "好的。"
}'
curl -sS -X POST "$API/sessions/sessionA1/commit" \
-H "Content-Type: application/json" \
-d '{"user_id":"userA","user_key":"'"$USER_KEY"'"}'
curl -sS -X POST "$API/search" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1",
"query": "我喜欢喝什么?",
"limit": 10
}'
```
Read EverOS profile:
OpenViking commit 返回 `task_id` 后,轮询到 `completed` 再搜索长期 memory
```bash
curl -s http://127.0.0.1:1934/memory-system/users/real_user_001/profile
curl -sS "$API/openviking/tasks/<task_id>?user_id=userA&user_key=$USER_KEY&session_id=sessionA1"
```
## Development Checks
## 开发检查
```bash
python -m pytest -q
python -m compileall -q memory_system_api tests
PYTHONPATH=/home/tom/memory-gateway pytest -q
python -m compileall -q memory_system_api plugins eval tests
```
## Hermes Plugin 状态
仓库里仍保留 Hermes memory provider`plugins/memory/memory_system`
注意Memory System API 已切换为 `user_id + user_key` 鉴权plugin 代码如需继续使用,需要同步传递这两个字段。