Files
memory-gateway/README.md

25 KiB
Raw Blame History

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 只使用字母、数字、_-.@,不要使用空格、斜杠或中文。

先创建用户再使用

除健康检查外,业务调用分两步:

  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 不匹配,messagescommitextracttaskssearchprofile 都会返回 401

创建业务 user 时,网关直接为该用户创建一个独立 OpenViking account。默认 account_id<user_id>_accountadmin_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/searchtarget_uri 可选,默认是 viking://user/memories,并默认传 level: 2score_threshold: 0.8

本地三服务安装

Memory Gateway 需要先连上两个后端服务。默认本地端口约定:

服务 默认地址 作用
OpenViking http://127.0.0.1:1933 session、archive、long-term memory、resources、semantic search
EverOS EverCore http://127.0.0.1:1995 profile、episodic memory、recall
Memory Gateway http://127.0.0.1:1934 对业务方暴露统一 /memory-system API

安装 OpenViking

OpenViking 可以直接用 pip install 安装,不需要 clone 源码。Memory Gateway 只依赖它暴露 HTTP API 和一个服务端 root key

python -m venv /Users/tom/.venvs/openviking
source /Users/tom/.venvs/openviking/bin/activate
pip install -U pip
pip install openviking --upgrade --force-reinstall

如果本机已经有 OpenViking 源码或已安装的 openviking-server,可以跳过安装,直接确认 openviking-server --help 可用。

安装 EverOS / EverCore

Memory Gateway 使用 EverOS 里的 EverCore HTTP 服务。按 EverOS / EverCore Quick Start

git clone https://github.com/EverMind-AI/EverOS.git /Users/tom/projects/EverOS
cd /Users/tom/projects/EverOS/methods/EverCore

# 需要 Docker 和 Python 3.12。
cp /Users/tom/projects/memory-gateway/everos.env.example .env
# 编辑 .env至少填入实际可用的 LLM_API_KEY 和 VECTORIZE_API_KEY。

docker compose up -d
uv sync

如果 EverOS 已经在本机存在,只需要进入 methods/EverCore 并补齐 .env

安装 Memory Gateway

cd /Users/tom/projects/memory-gateway
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv sync

如果已经安装过 uv,只需要运行:

uv sync

如果需要在服务器上跑测试或开发检查,再安装开发依赖:

uv sync --extra dev

配置文件

OpenViking 配置

Memory Gateway 仓库内提供 OpenViking 配置模板:

mkdir -p ~/.openviking
cp /Users/tom/projects/memory-gateway/openviking.ov.conf.example ~/.openviking/ov.conf

然后编辑 ~/.openviking/ov.conf,至少替换:

占位符 含义
<OPENVIKING_ROOT_KEY> OpenViking root key必须和 Memory Gateway config.yaml 里的 openviking.api_key 一致
<EMBEDDING_PROVIDER> / <EMBEDDING_API_BASE> / <EMBEDDING_API_KEY> / <EMBEDDING_MODEL> OpenViking embedding provider 配置
<VLM_PROVIDER> / <VLM_API_BASE> / <VLM_API_KEY> / <VLM_MODEL> OpenViking VLM provider 配置
<RERANK_PROVIDER> / <RERANK_API_BASE> / <RERANK_API_KEY> / <RERANK_MODEL> OpenViking rerank provider 配置

配置好后先跑一次:

openviking-server doctor

root_api_key 只放在 OpenViking 和 Memory Gateway 的服务端配置里。业务调用方不要传 root key。

EverOS / EverCore 配置

Memory Gateway 仓库内也提供 EverOS / EverCore 的 .env 模板。复制到 EverCore 目录:

cd /Users/tom/projects/EverOS/methods/EverCore
cp /Users/tom/projects/memory-gateway/everos.env.example .env

然后编辑 .env,至少填入 LLM_API_KEYLLM_MODELVECTORIZE_API_KEYEMBEDDING_MODELRERANK_MODEL。Docker 依赖地址默认使用模板里的本地 compose 端口;如果改过 EverCore 的 compose 配置,需要同步修改 .env

Memory Gateway 配置

复制 Gateway 配置模板:

cd /Users/tom/projects/memory-gateway
cp config.example.yaml config.yaml

然后编辑 config.yaml

server:
  host: "127.0.0.1"
  port: 1934
  api_key: ""

openviking:
  url: "http://127.0.0.1:1933"
  api_key: "<OPENVIKING_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: "./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

启动

按依赖顺序启动三个服务。

终端 1启动 OpenViking。

source /Users/tom/.venvs/openviking/bin/activate
openviking-server --config ~/.openviking/ov.conf

如果使用 Docker 方式运行 OpenViking确认对 Memory Gateway 暴露的是 1933 端口,避免和 Memory Gateway 的 1934 端口冲突。

终端 2启动 EverOS / EverCore。

cd /Users/tom/projects/EverOS/methods/EverCore
docker compose up -d
uv run python src/run.py --port 1995

终端 3启动 Memory Gateway。

cd /Users/tom/projects/memory-gateway
uv run memory-gateway --config config.yaml --host 0.0.0.0 --port 1934

健康检查:

curl -sS http://127.0.0.1:1933/health
curl -sS http://127.0.0.1:1995/health
curl -sS http://127.0.0.1:1934/memory-system/health

如果 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 successpartial_successfailed
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_messageassistant_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_idEverOS 会 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_overviewpre_archive_abstractsmessagesstats
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/userA/memories/preferences/饮食偏好.md
content string 要写入的 Markdown/text 内容
mode create/replace/append 写入模式,默认 create
wait bool 是否等待索引刷新,默认 true

新增 memory 时传 mode: "create"。URI 应直接写到目标分类目录下,不要额外插入 /user 目录;例如偏好类 memory 使用 viking://user/userA/memories/preferences/饮食偏好.md

curl -sS -X POST "$API/memories" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "userA",
    "user_key": "'"$USER_KEY"'",
    "uri": "viking://user/userA/memories/preferences/饮食偏好.md",
    "content": "用户喜欢喝茶",
    "mode": "create",
    "wait": true
  }'

覆盖修改时把 mode 改为 replace

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

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 添加资源。

如果 pathhttp://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 使用 hybridtrue 使用 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/memorieslevel

请求体:

参数 类型 必需 说明
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"
  }'

开发检查

uv run pytest -q
uv run python -m compileall -q memory_system_api plugins eval tests

Hermes Plugin 状态

仓库里仍保留 Hermes memory providerplugins/memory/memory_system

注意Memory System API 已切换为 user_id + user_key 鉴权plugin 代码如需继续使用,需要同步传递这两个字段。