Memory Gateway
Memory Gateway 是一个轻量级记忆网关项目,用统一的 HTTP API 把上层应用接到两个记忆后端:
- OpenViking:负责会话归档、长期记忆抽取、按
user_id / session_id隔离的语义检索。 - EverOS:负责用户画像、episodic memory、profile 查询等补充记忆能力。
当前重点模块是 memory_system_api。它不是 OpenViking 的替代品,而是一个更窄的业务网关:上层只面对 user、user key、session 和 message;网关内部为每个业务用户创建独立的 OpenViking admin account。
核心模型
身份字段
| 字段 | 含义 | 是否自定义 | 备注 |
|---|---|---|---|
user_id |
真实终端用户 | 是 | 先通过 /users 创建,后续写入、查询、commit 时使用 |
user_key |
OpenViking 返回的用户 key | 否 | /users 返回并写入 SQLite;业务调用时随请求体或 query 传入 |
session_id |
一次会话 ID | 是 | OpenViking session 容器,同时用于带 session context 的搜索 |
建议 ID 只使用字母、数字、_、-、.、@,不要使用空格、斜杠或中文。
先创建用户再使用
除健康检查外,业务调用分两步:
- 调用
POST /memory-system/users创建 OpenViking user。 - 保存返回的
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 时,网关直接为该用户创建一个独立 OpenViking account。默认 account_id 是 <user_id>_account,admin_user_id 是传入的 user_id:
POST /api/v1/admin/accounts
{"account_id": "<user_id>_account", "admin_user_id": "<user_id>"}
网关不再调用 /api/v1/admin/accounts/admin/users。所有返回的 admin_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 | X-API-Key: <openviking root key> |
| 普通用户 API,例如 session/message/commit/task/search | X-API-Key: <openviking user key> |
Session 与搜索范围
OpenViking session 由请求里的 session_id 创建和提交。/memory-system/search 的 OpenViking 分支固定调用 OpenViking /api/v1/search/search,target_uri 可选,默认是 viking://user/memories,并默认传 level: 2、score_threshold: 0.8。
安装
cd /home/tom/memory-gateway
python -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -e ".[dev]"
配置
复制配置模板:
cp config.example.yaml config.yaml
主要配置:
server:
host: "127.0.0.1"
port: 1934
api_key: ""
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"
环境变量也可以覆盖部分配置:
| 环境变量 | 覆盖字段 |
|---|---|
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 |
启动
先启动 OpenViking 和 EverOS,再启动 Memory System API:
python -m memory_system_api.server --config config.yaml --host 0.0.0.0 --port 1934
如果 server.api_key 非空,所有请求还要加:
-H "X-API-Key: <memory-system-api-key>"
API 总览
Base URL:
http://127.0.0.1:1934/memory-system
下面的示例默认先设置:
API=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/POST |
/sessions/{session_id}/context |
查询 OpenViking 会话上下文,并用同一 query 搜索 EverOS 记忆 | 需要 |
GET/POST |
/openviking/tasks/{task_id} |
查询 OpenViking 后台任务状态 | 需要 |
GET |
/memories |
列出 OpenViking memory URI | 需要 |
GET |
/memories/content |
读取某条 memory 内容 | 需要 |
POST |
/memories |
创建、覆盖或追加写入 memory | 需要 |
DELETE |
/memories |
删除某条 memory URI | 需要 |
POST |
/resources |
上传本地文件或远程 URL 到 OpenViking resources | 需要 |
DELETE |
/resources |
删除 OpenViking resource URI | 需要 |
POST |
/search |
同时搜索 OpenViking 和 EverOS 记忆 | 需要 |
GET/POST |
/users/{user_id}/profile |
查询 EverOS profile,并补充 OpenViking 用户记忆搜索结果 | 需要 |
API 详情
GET /health
检查两个后端是否可访问。
curl -sS "$API/health"
返回字段:
| 字段 | 含义 |
|---|---|
status |
success、partial_success 或 failed |
backends.openviking |
OpenViking 健康检查结果 |
backends.everos |
EverOS 健康检查结果 |
POST /users
创建新的 OpenViking admin account,并把返回的 admin_user_id / user_key 保存到本地 SQLite。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_id |
string | 是 | 要创建的用户 ID |
示例:
curl -sS -X POST "$API/users" \
-H "Content-Type: application/json" \
-d '{"user_id":"userA"}'
返回中的 account.result.user_key 是后续业务调用要传的 user_key:
{
"status": "success",
"account": {
"status": "ok",
"result": {
"account_id": "userA_account",
"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 | 否 | 预留字段 |
示例:
USER_KEY="..."
curl -sS -X POST "$API/messages" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1",
"user_message": "请记住:我喜欢拿铁。",
"assistant_message": "好的,我记住了。"
}'
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 |
示例:
curl -sS -X POST "$API/sessions/sessionA1/commit" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'"
}'
如果返回里有 OpenViking task_id,需要继续查询任务直到完成。
POST /sessions/{session_id}/extract
立即触发 OpenViking extract,只作用于 OpenViking,不触发 EverOS flush。适合调试或明确要求“现在就抽取”的场景。
参数同 commit:
curl -sS -X POST "$API/sessions/sessionA1/extract" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'"
}'
GET/POST /sessions/{session_id}/context
查询一次会话的当前上下文。网关会调用 OpenViking:
GET /api/v1/sessions/{session_id}/context
X-API-Key: <user_key>
同时用同一个 query 调用 EverOS /api/v1/memories/search,返回相关 episodic/profile/raw message 记忆。适合在回答用户问题前,把“当前 session 工作记忆”和“EverOS 相关记忆”一起取回。
路径参数:
| 参数 | 类型 | 说明 |
|---|---|---|
session_id |
string | 要查询的会话 ID |
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_id |
string | 是 | 用户 ID |
user_key |
string | 是 | /users 返回的 user key |
query |
string | 是 | 用于 EverOS search 的查询文本 |
limit |
int | 否 | EverOS 记忆返回条数,默认 10,范围 1 到 100 |
POST 示例:
curl -sS -X POST "$API/sessions/sessionA1/context" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"query": "我喜欢喝什么?",
"limit": 10
}'
返回字段:
| 字段 | 含义 |
|---|---|
context |
OpenViking result,包含 latest_archive_overview、pre_archive_abstracts、messages、stats |
items |
EverOS 搜索命中的精简记忆结果,含 source_backend: "everos" |
backends |
两个后端的精简诊断信息,不重复返回完整 OpenViking context 或 EverOS original_data |
GET/POST /openviking/tasks/{task_id}
查询 OpenViking 后台任务状态,例如 commit 返回的任务。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_id |
string | 是 | 用户 ID |
user_key |
string | 是 | /users 返回的 user key |
session_id |
string/null | 否 | 会话 ID;传入后会按同一 session identity 查询 |
示例:
curl -sS -X POST "$API/openviking/tasks/${TASK_ID}" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1"
}'
GET /memories
列出 OpenViking memory URI。网关会调用 OpenViking:
GET /api/v1/fs/ls?uri=viking://user/memories&recursive=true
X-API-Key: <user_key>
Query 参数:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_id |
string | 是 | 用户 ID |
user_key |
string | 是 | /users 返回的 user key |
uri |
string | 否 | 要列出的 memory 根 URI,默认 viking://user/memories |
recursive |
bool | 否 | 是否递归列出,默认 true |
示例:
curl -sS -G "$API/memories" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories" \
--data-urlencode "recursive=true"
GET /memories/content
读取某条 memory 内容。先用 /memories 或 /search 找到 viking://user/memories/... URI,再读取:
curl -sS -G "$API/memories/content" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories/preferences/python.md"
POST /memories
创建、覆盖或追加写入 memory。网关会调用 OpenViking /api/v1/content/write,写入后由 OpenViking 刷新语义和向量索引。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_id |
string | 是 | 用户 ID |
user_key |
string | 是 | /users 返回的 user key |
uri |
string | 是 | 目标 memory URI,例如 viking://user/memories/profile.md |
content |
string | 是 | 要写入的 Markdown/text 内容 |
mode |
create/replace/append |
否 | 写入模式,默认 create |
wait |
bool | 否 | 是否等待索引刷新,默认 true |
覆盖修改:
curl -sS -X POST "$API/memories" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"uri": "viking://user/memories/preferences/python.md",
"content": "# Python 偏好\n\n用户偏好使用 Python 做数据分析,常用 pandas。",
"mode": "replace",
"wait": true
}'
追加补充时把 mode 改为 append;新增 memory 时可用默认的 create。
DELETE /memories
删除某条 memory。默认非递归删除;如果 OpenViking 提示目标是目录或复合资源,再把 recursive 设为 true。
curl -sS -X DELETE -G "$API/memories" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories/preferences/python.md" \
--data-urlencode "recursive=false"
返回中的 memory 是 OpenViking 对应接口的原始响应。
POST /resources
上传文件资源到 OpenViking。网关只调用 OpenViking,不写 EverOS。
如果 path 是本地路径,文件必须能被 Memory System API 服务进程读取。网关会先调用 OpenViking /api/v1/resources/temp_upload 上传临时文件,取返回的 temp_file_id,再调用 /api/v1/resources 添加资源。
如果 path 是 http:// 或 https:// URL,网关会直接调用 OpenViking /api/v1/resources,并把 URL 作为 path 传给 OpenViking。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_id |
string | 是 | 用户 ID |
user_key |
string | 是 | /users 返回的 user key |
path |
string | 是 | 本地文件路径,或 http:// / https:// URL |
to |
string | 是 | 目标 OpenViking resource URI |
reason |
string/null | 否 | 上传原因,透传给 OpenViking |
wait |
bool | 否 | 是否等待处理完成,默认 true |
directly_upload_media |
bool | 否 | 是否直接上传媒体,默认 true |
本地文件示例:
curl -sS -X POST "$API/resources" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"path": "/home/tom/memory-gateway/tests/大语言模型应用.pdf",
"to": "viking://resources/userA/files/大语言模型应用.pdf",
"reason": "userA 上传的文件",
"wait": true,
"directly_upload_media": true
}'
远程 URL 示例:
curl -sS -X POST "$API/resources" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"path": "https://example.com/images/photo.png",
"to": "viking://resources/userA/images/photo.png",
"reason": "userA 上传的远程图片",
"wait": true,
"directly_upload_media": true
}'
返回中的 resource 是 OpenViking /api/v1/resources 的原始响应,backends.openviking 保留调用状态和错误信息。
DELETE /resources
删除 OpenViking resource URI。网关会调用 OpenViking:
DELETE /api/v1/fs?uri=<OPENVIKING_URI>&recursive=<true|false>
X-API-Key: <user_key>
Query 参数:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_id |
string | 是 | 用户 ID |
user_key |
string | 是 | /users 返回的 user key |
uri |
string | 是 | 要删除的 OpenViking URI |
recursive |
bool | 否 | 是否递归删除,默认 true |
示例:
curl -sS -X DELETE -G "$API/resources" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://resources/userA/files/大语言模型应用.pdf" \
--data-urlencode "recursive=true"
返回中的 resource 是 OpenViking /api/v1/fs 删除接口的原始响应。
POST /search
同时查询 OpenViking 和 EverOS,并合并结果。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_id |
string | 是 | 用户 ID |
user_key |
string | 是 | /users 返回的 user key |
session_id |
string/null | 否 | 会话 ID;用于 EverOS 过滤和鉴权身份 |
query |
string | 是 | 查询文本 |
use_llm |
bool | 否 | 只影响 EverOS 检索方式:false 使用 hybrid,true 使用 agentic |
limit |
int | 否 | 返回条数,默认 10,范围 1 到 100 |
level |
int | 否 | OpenViking search level,默认 2 |
score_threshold |
float | 否 | OpenViking 最低分数阈值,默认 0.8,范围 0 到 1 |
target_uri |
string | 否 | OpenViking 搜索范围,默认 viking://user/memories |
示例:
curl -sS -X POST "$API/search" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1",
"query": "我喜欢喝什么?",
"use_llm": false,
"limit": 10,
"level": 2,
"score_threshold": 0.8,
"target_uri": "viking://user/memories"
}'
返回字段:
| 字段 | 含义 |
|---|---|
status |
总体状态 |
items |
合并后的记忆结果,含 source_backend |
backends |
两个后端的精简诊断信息,例如命中数量、query plan 或查询过滤条件;不再回传完整原始命中和 original_data |
GET/POST /users/{user_id}/profile
读取用户画像。该接口需要 user_key,用于确认调用方属于该 user。网关会读取 EverOS profile,并用同一个 query 调 OpenViking /api/v1/search/search,固定传 target_uri: viking://user/memories 和 level。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
user_key |
string | 是 | /users 返回的 user key |
query |
string | 否 | OpenViking 搜索文本,默认 用户画像 |
limit |
int | 否 | OpenViking 返回条数,默认 10,范围 1 到 100 |
level |
int | 否 | OpenViking search level,默认 2 |
示例:
curl -sS -X POST "$API/users/userA/profile" \
-H "Content-Type: application/json" \
-d '{
"user_key": "'"$USER_KEY"'",
"query": "我想喝东西",
"limit": 10,
"level": 2
}'
返回中的 profile 是 EverOS profile 原始结果,items 是 OpenViking 命中的用户记忆,backends 只保留两个后端的状态和计数诊断,不重复返回完整 EverOS profile 或 OpenViking 命中列表。
完整测试流程
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": "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
}'
OpenViking commit 返回 task_id 后,轮询到 completed 再搜索长期 memory:
curl -sS -X POST "$API/openviking/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"session_id": "sessionA1"
}'
开发检查
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 代码如需继续使用,需要同步传递这两个字段。