MyContext 与私有化部署的上下文持久化方案

文章导读
私有化部署中,MyContext 的持久化问题通常不在模型本身,而在上下文存储层。默认的内存存储只在单进程内有效,进程退出即丢失;若团队要求数据不出内网,就需要把 MyContext 的上下文写入自有数据库或文件系统。可行方向是把 MyContext 对存储的访问收敛到一套小接口上,再根据现有基础设施选择一个后端实现,先跑通“写入—重启—读取”闭环,再考虑扩展。
📋 目录
  1. A 了解 MyContext 持久化接口的抽象边界
  2. B 选择私有化环境中的存储后端
  3. C 实现一个基于文件的持久化适配器
  4. D 配置 MyContext 指向自定义存储路径
  5. E 验证重启后上下文不丢失
A A

私有化部署中,MyContext 的持久化问题通常不在模型本身,而在上下文存储层。默认的内存存储只在单进程内有效,进程退出即丢失;若团队要求数据不出内网,就需要把 MyContext 的上下文写入自有数据库或文件系统。可行方向是把 MyContext 对存储的访问收敛到一套小接口上,再根据现有基础设施选择一个后端实现,先跑通“写入—重启—读取”闭环,再考虑扩展。

判断:MyContext 私有化持久化的核心是替换存储后端,而不是改造模型调用。处理方向:先确认 MyContext 暴露的存储接口边界,按团队已有设施选择文件、关系型数据库或对象存储;建议先用基于文件的 JSON 适配器验证重启不丢失,再换成生产后端。验证方式:写入上下文、杀进程、重启、读取同一上下文并断言内容一致。风险边界:不同版本 MyContext 对存储接口的命名可能不同,需要结合当前版本确认接口签名。

了解 MyContext 持久化接口的抽象边界

MyContext 对外暴露的存储接口通常抽象为四类基本操作:读、写、删、按条件查。实现一个持久化后端不需要关心上下文如何被嵌入到模型提示词中,只需要保证这四类操作在自有存储上语义正确。

  • 写(put/set):入参是上下文 ID(key)和上下文内容(value),可能附带元数据如创建时间、更新时间、TTL。出参通常是写入是否成功,或返回当前版本号。
  • 读(get):入参是上下文 ID,出参是对应的上下文内容;若不存在,应返回空或明确错误,而不是抛出异常。
  • 删(delete):入参是上下文 ID,出参是删除结果;某些实现要求支持按前缀批量删除,例如清理某个会话下的所有子上下文。
  • 按条件查(query/scan):入参是过滤条件,例如用户 ID、会话 ID、创建时间范围;出参是匹配的上下文 ID 列表或内容列表。这个接口在私有化环境中可能被用于后台管理和审计。

MyContext 本身不强制存储必须是数据库或文件,它只依赖这些接口的存在。因此,选型时可以先把“接口实现难度”和“团队维护成本”并列评估,不要因为 MyContext 文档里没写某种存储就认为不支持。

选择私有化环境中的存储后端

选型原则是:优先使用团队已经在运维的组件,避免为单个上下文功能引入新服务。比较常见的三类选择如下。

  • 文件目录:把每个上下文 ID 映射为文件路径,内容写成 JSON。优点是无额外依赖、备份简单、肉眼可读;缺点是并发写入需要自行加锁,查询能力弱,跨多节点共享困难。适合单机部署、上下文量不大、对审计要求高的场景。
  • 关系型数据库:使用 PostgreSQL 或 MySQL 建一张表存上下文。优点是事务、锁、索引都现成,按条件查方便,团队一般已有运维经验;缺点是要维护表结构和迁移,高频读写可能成为瓶颈。适合已有数据库服务、需要多人并发访问或按用户维度做权限控制的场景。
  • 对象存储:把上下文作为对象写入 MinIO、S3 兼容服务等。优点是容量大、按 key 读写、天然支持版本管理;缺点是延迟相对高,不适合频繁小写入,且团队不一定有现成对象存储设施。适合上下文文件较大、以归档为主、读取频率低的场景。

选择依据可以简化为三个问题:是否需要跨节点共享?是否需要按条件查?运维团队能承受哪个组件的维护成本?如果三个答案分别是“不需要、不需要、越少越好”,直接选文件目录;如果答案是“需要、需要、能管数据库”,选关系型数据库。

MyContext 与私有化部署的上下文持久化方案

实现一个基于文件的持久化适配器

以下 Python 骨架把 MyContext 的上下文按 key 写入目录,每个 key 对应一个子目录,内容保存为 JSON 文件。写入时使用临时文件再重命名,避免进程崩溃导致半截文件。

import json
import os
import tempfile

class FileContextStore:
    def __init__(self, base_dir):
        self.base_dir = base_dir
        os.makedirs(base_dir, exist_ok=True)

    def _path_for(self, key):
        # 将 key 做安全处理,避免路径穿越
        safe_key = key.replace("/", "_").replace("..", "_")
        return os.path.join(self.base_dir, safe_key + ".json")

    def put(self, key, value):
        path = self._path_for(key)
        dir_name = os.path.dirname(path)
        os.makedirs(dir_name, exist_ok=True)
        # atomic write:先写临时文件,再重命名
        fd, tmp_path = tempfile.mkstemp(dir=dir_name, suffix=".tmp")
        try:
            with os.fdopen(fd, "w", encoding="utf-8") as f:
                json.dump(value, f, ensure_ascii=False, indent=2)
            os.replace(tmp_path, path)
        except Exception:
            os.unlink(tmp_path)
            raise

    def get(self, key):
        path = self._path_for(key)
        if not os.path.exists(path):
            return None
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)

    def delete(self, key):
        path = self._path_for(key)
        if os.path.exists(path):
            os.remove(path)
            return True
        return False

    def query(self, prefix=None):
        results = []
        for name in os.listdir(self.base_dir):
            if name.endswith(".json"):
                key = name[:-5]
                if prefix is None or key.startswith(prefix):
                    results.append((key, self.get(key)))
        return results

这个骨架只实现了基本操作,没有加锁。单进程下够用;如果 MyContext 自身会多线程调用,建议在 put 方法上加一个线程锁,必要时再考虑进程间锁。文件存储的目录结构可以按业务前缀分目录,但要注意 MyContext 传进来的 key 是否包含路径分隔符,上述代码会把分隔符替换掉,保持扁平结构。

配置 MyContext 指向自定义存储路径

MyContext 默认使用内存存储,切换到文件存储通常有两种方式:环境变量或启动参数。具体名称需要看当前版本的定义,下面给出常见设置样例。

export MYCONTEXT_STORAGE_BACKEND=file
export MYCONTEXT_FILE_STORE_DIR=/var/lib/mycontext/data

# 或者通过启动参数
your_agent_process `--mycontext-backend` file `--mycontext-dir` /var/lib/mycontext/data

启动 MyContext 后,先写入一条测试上下文,然后检查目录结构。预期目录下会出现一个以安全 key 命名的 JSON 文件,例如 key 为 session_001 时,文件路径为 /var/lib/mycontext/data/session_001.json。检查命令:

ls -l /var/lib/mycontext/data/
cat /var/lib/mycontext/data/session_001.json

如果 MyContext 启动时没有加载到对应配置,通常会在日志中提示回退到内存模式。确认方法是启动后查看进程环境变量是否生效,或调用 MyContext 的一个状态接口查看 storage backend 类型。不要只凭配置文件猜测,要实际写入再重启验证。

MyContext 与私有化部署的上下文持久化方案

验证重启后上下文不丢失

验证目的是确认“持久化”不是只写进内存,而是真正落盘且能被重新加载。按以下步骤操作:

  1. 启动 MyContext,写入一条上下文,例如 key 为 restart_test,内容为 {"step": 1}
  2. 记录当前进程 PID,然后直接 kill 进程,模拟非正常退出:kill -9 <pid>
  3. 重新启动 MyContext,使用相同的环境变量或启动参数。
  4. 读取 key 为 restart_test 的上下文,断言内容等于 {"step": 1}

如果 MyContext 提供了命令行读取工具,可以直接用;否则写一段最小脚本调用存储接口:

from mycontext_storage_adaptor import FileContextStore
store = FileContextStore("/var/lib/mycontext/data")
print(store.get("restart_test"))

断言命令参考:

test "$(cat /var/lib/mycontext/data/restart_test.json)" = '{"step": 1}' && echo "persistence ok"

这一步能通过,说明持久化链路是通的。后续再考虑接入关系型数据库或对象存储,本质上只是替换 putget 的实现。若读取不到,优先排查配置是否被覆盖、目录权限是否可写、key 是否被改写,而不是直接怀疑 MyContext 本身不支持自定义存储。