Memory Gateway 2

Memory Gateway 2 是一个轻量级 FastAPI 服务,用于在 EverOS 现有 /api/v1/memory/add/api/v1/memory/flush/api/v1/memory/search 能力之上构建用户资源记忆层。

它只维护 Gateway 自己的 SQLite 元数据表、软删除记录和手动覆盖记录, 不会直接修改 EverOS 的 Markdown、SQLite 或 LanceDB 内部文件。

功能范围

  • 上传用户资源文件、图片、音频、PDF、HTML、普通文档、纯文本。
  • 保存资源元数据到 SQLite。
  • 为每个资源生成独立 EverOS session_id
  • 调用 EverOS addflush 完成资源记忆摄入。
  • 提供资源列表、详情、软删除。
  • 编排记忆搜索,支持当前聊天、资源记忆、全部用户记忆。
  • 支持记忆 tombstone 软删除。
  • 支持记忆手动 override。
  • 搜索结果返回前自动过滤 tombstone 并应用 override。

目录结构

/home/tom/memory-gateway2
├── core/                  # Gateway 核心代码
│   ├── api.py             # FastAPI 路由
│   ├── config.py          # 环境变量配置
│   ├── db.py              # SQLite schema 初始化
│   ├── everos_client.py   # EverOS HTTP client
│   ├── repository.py      # SQLite 读写
│   └── service.py         # 业务编排
├── main.py                # Python 启动入口
├── tests/                 # 测试
├── .env.example           # 环境变量示例
└── pyproject.toml

环境配置

复制示例配置:

cd /home/tom/memory-gateway2
cp .env.example .env

配置项说明:

变量 默认值 说明
EVEROS_BASE_URL http://127.0.0.1:8000 EverOS API 服务地址
MEMORY_GATEWAY_DB_PATH ./data/memory_gateway.sqlite3 Gateway 自己的 SQLite 数据库路径
MEMORY_GATEWAY_STORAGE_DIR ./data/storage 用户上传原始文件保存路径
MEMORY_GATEWAY_RESOURCE_SEARCH_BATCH_SIZE 50 resources scope 搜索时每批 session_id 数量
MEMORY_GATEWAY_HOST 127.0.0.1 Gateway API 监听地址
MEMORY_GATEWAY_PORT 8010 Gateway API 监听端口
MEMORY_GATEWAY_RELOAD false 是否启用 uvicorn reload开发时可设为 true

注意:MEMORY_GATEWAY_DB_PATHMEMORY_GATEWAY_STORAGE_DIR 是 Gateway 自己的存储位置,不要配置成 EverOS 的内部存储目录。

安装依赖

cd /home/tom/memory-gateway2
uv pip install -e .

启动 API

使用 Python 启动:

cd /home/tom/memory-gateway2
python main.py

默认监听:

http://127.0.0.1:8010

session_id 规范

Gateway 遵循以下 session_id 规范:

场景 格式
普通聊天 chat:{conversation_id}
用户上传资源 resource:{user_id}:{resource_id}
用户手动修正 memory_edit:{user_id}

当前实现中,资源上传会自动生成:

resource:{user_id}:{resource_id}

数据库表

启动时会自动创建以下 SQLite 表:

  • user_resources:资源元数据、内部 URI、状态、软删除时间。
  • users:用户 ID 和 Gateway 生成的 user_key
  • memory_tombstones:用户删除的 memory id 或 session_id。
  • memory_overrides:用户手动修正后的 memory 文本。

API 使用说明

POST /users 外,所有业务 API 都需要携带 user_iduser_key。认证失败返回 401

1. 创建用户

POST /users
Content-Type: application/json

请求参数:

参数 类型 必填 说明
user_id string 用户 ID

请求示例:

curl -X POST http://127.0.0.1:8010/users \
  -H 'Content-Type: application/json' \
  -d '{"user_id":"u_123"}'

响应示例:

{
  "user_id": "u_123",
  "user_key": "uk_xxx",
  "created_at": "2026-06-10T10:00:00+00:00"
}

user_key 需要由调用方保存,后续上传、查询、搜索、修改和删除都要传入。如果同一个 user_id 已存在,接口会返回已有 user_key

2. 上传资源

POST /resources
Content-Type: multipart/form-data

表单参数:

参数 类型 必填 默认值 说明
user_id string 用户 ID
user_key string 用户 key
app_id string default EverOS app scope
project_id string default EverOS project scope
file file 上传资源文件
title string null 资源标题
description string null 资源描述

处理流程:

  1. 保存原始文件到 MEMORY_GATEWAY_STORAGE_DIR
  2. 计算 sha256size_bytes
  3. 生成 resource_id
  4. 生成 session_id = resource:{user_id}:{resource_id}
  5. 写入 user_resources,状态为 ingesting
  6. 根据 MIME 类型映射 EverOS content type。
  7. 调用 EverOS /api/v1/memory/add
  8. 调用 EverOS /api/v1/memory/flush
  9. 成功后状态改为 extracted,失败后状态改为 failed

content type 映射:

文件类型 EverOS content type
image/* image
audio/* audio
PDF pdf
HTML html
纯文本、Markdown、CSV、日志 text
其他文档 doc

请求示例:

curl -X POST http://127.0.0.1:8010/resources \
  -F user_id=u_123 \
  -F user_key=uk_xxx \
  -F app_id=default \
  -F project_id=default \
  -F title="合同文件" \
  -F description="2026 年付款合同" \
  -F file=@./contract.pdf

响应示例:

{
  "resource_id": "r_xxx",
  "session_id": "resource:u_123:r_xxx",
  "uri": "resource://u_123/r_xxx",
  "status": "extracted"
}

对外返回的 uri 永远是 resource://{user_id}/{resource_id},不会泄露内部 file:// 路径。

3. 查询资源列表

GET /resources?user_id={user_id}&user_key={user_key}

参数:

参数 类型 必填 说明
user_id string 用户 ID
user_key string 用户 key

只返回当前用户 deleted_at IS NULL 的资源。不同用户的资源彼此隔离。

请求示例:

curl "http://127.0.0.1:8010/resources?user_id=u_123&user_key=uk_xxx"

响应示例:

{
  "resources": [
    {
      "resource_id": "r_xxx",
      "user_id": "u_123",
      "filename": "contract.pdf",
      "content_type": "pdf",
      "mime_type": "application/pdf",
      "uri": "resource://u_123/r_xxx",
      "session_id": "resource:u_123:r_xxx",
      "status": "extracted",
      "title": "合同文件",
      "description": "2026 年付款合同",
      "created_at": "2026-06-10T10:00:00+00:00",
      "updated_at": "2026-06-10T10:00:05+00:00"
    }
  ]
}

如果用户没有上传过资源,返回:

{
  "resources": []
}

4. 查询资源详情

GET /resources/{resource_id}?user_id={user_id}&user_key={user_key}

路径参数:

参数 类型 必填 说明
resource_id string 资源 ID

Query 参数:

参数 类型 必填 说明
user_id string 用户 ID
user_key string 用户 key

请求示例:

curl "http://127.0.0.1:8010/resources/r_xxx?user_id=u_123&user_key=uk_xxx"

响应示例:

{
  "resources": [
    {
      "resource_id": "r_xxx",
      "user_id": "u_123",
      "filename": "contract.pdf",
      "content_type": "pdf",
      "mime_type": "application/pdf",
      "uri": "resource://u_123/r_xxx",
      "session_id": "resource:u_123:r_xxx",
      "status": "extracted",
      "title": "合同文件",
      "description": "2026 年付款合同",
      "created_at": "2026-06-10T10:00:00+00:00",
      "updated_at": "2026-06-10T10:00:05+00:00"
    }
  ]
}

如果当前用户没有上传过 resources、资源不存在、或资源属于其他用户返回

{
  "resources": []
}

这种设计避免通过资源 ID 探测其他用户的数据。uri 同样只返回公开 resource://{user_id}/{resource_id},不会泄露内部 URI。

5. 删除资源

DELETE /resources/{resource_id}?user_id={user_id}&user_key={user_key}

第一版只做软删除:

  • 设置 deleted_at = now()
  • 设置 status = deleted
  • 后续 resources scope 搜索会排除该资源的 session_id
  • 不物理删除 EverOS 内部记忆或索引。

请求示例:

curl -X DELETE "http://127.0.0.1:8010/resources/r_xxx?user_id=u_123&user_key=uk_xxx"

响应示例:

{
  "resource_id": "r_xxx",
  "session_id": "resource:u_123:r_xxx",
  "uri": "resource://u_123/r_xxx",
  "status": "deleted"
}

6. 搜索记忆

POST /memories/search
Content-Type: application/json

请求参数:

参数 类型 必填 默认值 说明
user_id string 用户 ID
user_key string 用户 key
query string 搜索问题
conversation_id string null scope 包含 current_chat 时使用
scope string[] ["current_chat", "resources"] 搜索范围
top_k integer 8 每次 EverOS 搜索返回数量,范围 1..100
app_id string default EverOS app scope
project_id string default EverOS project scope

scope 支持:

scope 说明
current_chat 只搜索 chat:{conversation_id}
resources 搜索当前用户未删除且已提取完成的上传资源
all_user_memory 搜索用户全部记忆,不加 session_id 过滤

请求示例:

curl -X POST http://127.0.0.1:8010/memories/search \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": "u_123",
    "user_key": "uk_xxx",
    "conversation_id": "c_456",
    "query": "合同里的付款条款是什么?",
    "scope": ["current_chat", "resources"],
    "top_k": 8,
    "app_id": "default",
    "project_id": "default"
  }'

搜索编排逻辑:

  1. current_chat:调用 EverOS search过滤 filters.session_id = chat:{conversation_id}
  2. resources:先查当前用户的 user_resources,只取 status = extracted 且未删除资源;再按批次调用 EverOS search过滤这些资源的 session_id
  3. all_user_memory:调用 EverOS search不加 session_id 过滤。
  4. 合并结果。
  5. 过滤 memory_tombstones 命中的 memory_idsession_id
  6. 应用 active memory_overrides,把 text 替换为 override_text

响应示例:

{
  "results": [
    {
      "id": "mem_abc",
      "session_id": "resource:u_123:r_xxx",
      "text": "付款期限为收到发票后 30 天内。",
      "score": 0.82,
      "source_scope": "resources",
      "resource_id": "r_xxx",
      "resource_uri": "resource://u_123/r_xxx",
      "raw": {
        "id": "mem_abc",
        "session_id": "resource:u_123:r_xxx",
        "episode": "原始 EverOS 返回内容"
      }
    }
  ]
}

7. 修改记忆

PATCH /memories/{memory_id}
Content-Type: application/json

请求参数:

参数 类型 必填 说明
user_id string 用户 ID
user_key string 用户 key
session_id string memory 所属 session
override_text string 修正后的记忆文本

该接口只写入或更新 memory_overrides,不会修改 EverOS 原始文件。后续搜索结果命中该 memory_id 时,返回的 text 会替换为 override_text,但保留原始 memory id。

请求示例:

curl -X PATCH http://127.0.0.1:8010/memories/mem_abc \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": "u_123",
    "user_key": "uk_xxx",
    "session_id": "resource:u_123:r_xxx",
    "override_text": "修正后的记忆内容"
  }'

响应示例:

{
  "memory_id": "mem_abc",
  "override_id": "o_xxx",
  "status": "active"
}

8. 删除记忆

DELETE /memories/{memory_id}
Content-Type: application/json

请求参数:

参数 类型 必填 说明
user_id string 用户 ID
user_key string 用户 key
session_id string memory 所属 session
reason string 删除原因

该接口只写入 memory_tombstones,不会修改 EverOS 原始文件。后续搜索结果如果命中 tombstone 的 memory_idsession_id,会被过滤。

请求示例:

curl -X DELETE http://127.0.0.1:8010/memories/mem_abc \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": "u_123",
    "user_key": "uk_xxx",
    "session_id": "resource:u_123:r_xxx",
    "reason": "用户手动删除"
  }'

响应示例:

{
  "memory_id": "mem_abc",
  "tombstone_id": "t_xxx",
  "status": "deleted"
}

EverOS client 封装

Gateway 内部通过 core/everos_client.py 调用 EverOS

  • add_memory(payload) -> POST /api/v1/memory/add
  • flush_memory(session_id, app_id, project_id) -> POST /api/v1/memory/flush
  • search_memory(payload) -> POST /api/v1/memory/search

运行测试

cd /home/tom/memory-gateway2
python -B -m pytest -q -p no:cacheprovider
Description
No description provided
Readme 4 MiB
Languages
Python 100%