OpenViking 隐私配置(Privacy Configs)深度解析:敏感字段版本化管理与 Skill 密钥占位/恢复机制
【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking
隐私配置(Privacy Configs)是 OpenViking 提供的按category + target_key管理敏感字段版本的能力:每次写入都会生成不可变的版本快照,支持查询历史、回滚并切换生效版本。本文基于仓库中的 API 实现、存储模型与技能处理链路,完整讲解隐私配置的接口用法、底层存储结构、并发控制机制,以及它如何与 Skill 的占位符自动脱敏/读取时恢复机制协同工作,读完后可直接用于密钥轮换、版本回滚和敏感配置的安全管理实战。
一、隐私配置解决什么问题
在 Agent 场景中,Skill(技能)文件往往内嵌api_key、base_url等敏感配置。如果把这些值直接写死在SKILL.md内容里,密钥一旦泄露就需要重新分发整个技能文件,且无法做轮换与审计。OpenViking 的隐私配置模块把敏感值从技能内容中抽离出来,单独按目标管理:
- 为某个 skill 保存密钥等敏感配置
- 轮换密钥(写入新版本)
- 回滚到历史版本
- 在读取 skill 内容时按占位符自动恢复配置值
从源码结构看,隐私配置按用户空间(user space)隔离存储:服务入口 UserPrivacyConfigService 通过canonical_user_root(ctx)定位当前请求用户的根目录,因此不同用户(请求头X-OpenViking-User)之间的隐私配置互不可见。
二、存储结构与数据模型
隐私配置并非存在专门的数据库中,而是落在 OpenViking 的 VikingFS 命名空间下。路径构造逻辑集中在 helpers.py:
viking://user/{user_space}/privacy/{category}/{target_key}/ ├── current.json # 当前生效版本快照 ├── .meta.json # 元信息(active_version / latest_version / labels 等) └── history/ ├── version_001.json ├── version_002.json └── version_003.json # 文件名正则:^version_(\d+)\.json$对应两个核心数据模型(models.py):
current(当前生效版本,UserPrivacyConfigVersion)
{ "version": 3, "category": "skill", "target_key": "byted-viking-search-knowledgebase", "values": { "api_key": "***", "base_url": "https://example.com" }, "created_at": "2026-04-27T10:00:00+08:00", "created_by": "alice", "change_reason": "rotate key" }meta(元信息,UserPrivacyConfigMeta)
{ "category": "skill", "target_key": "byted-viking-search-knowledgebase", "active_version": 3, "latest_version": 5, "created_at": "2026-04-21T10:00:00+08:00", "updated_at": "2026-04-27T10:00:00+08:00", "updated_by": "alice", "last_accessed_at": "2026-04-27T10:00:00+08:00", "labels": { "env": "prod" } }两个字段值得注意:
active_version与latest_version分离:写入永远追加新版本(latest_version单调递增),而“生效版本”由current.json指向,可以停留在任意历史版本上,这就是回滚能力的来源;labels是自由键值标签,用于给配置打上env: prod之类的自定义标记,仅随写入请求更新,不参与版本快照。
三、API 总览
HTTP 路由注册在 privacy_configs.py,前缀为/api/v1/privacy-configs。所有接口都需要请求头X-API-Key、X-OpenViking-Account、X-OpenViking-User(见 鉴权与请求上下文 相关实现)。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/privacy-configs | 列出隐私配置分类 |
| GET | /api/v1/privacy-configs/{category} | 列出分类下目标 |
| GET | /api/v1/privacy-configs/{category}/{target_key} | 获取当前生效配置(meta + current) |
| POST | /api/v1/privacy-configs/{category}/{target_key} | 写入新版本并激活 |
| GET | /api/v1/privacy-configs/{category}/{target_key}/versions | 列出版本号 |
| GET | /api/v1/privacy-configs/{category}/{target_key}/versions/{version} | 获取指定版本详情 |
| POST | /api/v1/privacy-configs/{category}/{target_key}/activate | 激活指定版本 |
下面按接口逐一展开。
四、接口详解与调用示例
以下示例默认服务监听在localhost:1933。
4.1 列出分类:list_privacy_categories
curl -X GET http://localhost:1933/api/v1/privacy-configs \ -H "X-API-Key: your-key" \ -H "X-OpenViking-Account: default" \ -H "X-OpenViking-User: alice"响应:
{ "status": "ok", "result": ["skill"], "time": 0.01 }实现上就是ls用户空间下的privacy/目录并排序返回条目名(service.py 的 list_categories),目录不存在时返回空列表而不是报错。
4.2 列出分类下的目标:list_privacy_targets
curl -X GET http://localhost:1933/api/v1/privacy-configs/skill \ -H "X-API-Key: your-key" \ -H "X-OpenViking-Account: default" \ -H "X-OpenViking-User: alice"{ "status": "ok", "result": ["byted-viking-search-knowledgebase"], "time": 0.01 }同理,这是对privacy/skill/目录的列举(list_targets)。
4.3 获取当前生效配置:get_privacy_current
curl -X GET "http://localhost:1933/api/v1/privacy-configs/skill/byted-viking-search-knowledgebase" \ -H "X-API-Key: your-key" \ -H "X-OpenViking-Account: default" \ -H "X-OpenViking-User: alice"{ "status": "ok", "result": { "meta": { "category": "skill", "target_key": "byted-viking-search-knowledgebase", "active_version": 3, "latest_version": 5 }, "current": { "version": 3, "category": "skill", "target_key": "byted-viking-search-knowledgebase", "values": { "api_key": "***", "base_url": "https://example.com" } } }, "time": 0.01 }若 target 不存在,返回
NOT_FOUND。
路由层通过 _require_privacy_target 统一做存在性检查,再同时读取meta与current返回,便于调用方一次性拿到版本状态和实际值。
4.4 写入新版本:upsert_privacy_config
写入新版本并将其设为当前生效版本。
行为说明(与 service.py 的 upsert 实现 一致):
values按整包快照写入(本次传入内容成为新版本的values),因此更新前若只想改一个 key,客户端需要先读取 current 再合并回传;- 传入新 key 会直接写入(允许新增),旧 key 未传则视为被移除(整包替换语义);
- 若与当前版本完全一致(按
json.dumps(sort_keys=True)规范化后比较,见 canonicalize_values),则复用当前版本号,不新建版本,只刷新 meta 的last_accessed_at等信息; - 首次写入版本号从 1 开始,之后取
latest_version + 1。
HTTP API
POST /api/v1/privacy-configs/{category}/{target_key}请求体(请求模型定义见 UpsertPrivacyConfigRequest)
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| values | object | 是 | - | 隐私配置键值 |
| change_reason | string | 否 | "" | 变更原因 |
| labels | object | 否 | null | 元信息标签 |
curl -X POST "http://localhost:1933/api/v1/privacy-configs/skill/byted-viking-search-knowledgebase" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-key" \ -H "X-OpenViking-Account: default" \ -H "X-OpenViking-User: alice" \ -d '{ "values": { "api_key": "secret-2", "base_url": "https://example.com", "region": "cn" }, "change_reason": "rotate key", "labels": { "env": "prod" } }'响应
{ "status": "ok", "result": { "version": 4, "category": "skill", "target_key": "byted-viking-search-knowledgebase", "values": { "api_key": "secret-2", "base_url": "https://example.com", "region": "cn" }, "change_reason": "rotate key" }, "time": 0.02 }从写入顺序看,upsert会依次写history/version_NNN.json、current.json,最后落盘meta.json(service.py),整个读改写过程被分布式路径锁保护(下一节)。
4.5 列出版本:list_privacy_versions
curl -X GET "http://localhost:1933/api/v1/privacy-configs/skill/byted-viking-search-knowledgebase/versions" \ -H "X-API-Key: your-key" \ -H "X-OpenViking-Account: default" \ -H "X-OpenViking-User: alice"{ "status": "ok", "result": [1, 2, 3, 4], "time": 0.01 }若 target 不存在,返回
NOT_FOUND。
实现上是ls历史目录后用正则^version_(\d+)\.json$解析文件名并排序(list_versions),非版本文件会被忽略。
4.6 获取历史版本:get_privacy_version
curl -X GET "http://localhost:1933/api/v1/privacy-configs/skill/byted-viking-search-knowledgebase/versions/2" \ -H "X-API-Key: your-key" \ -H "X-OpenViking-Account: default" \ -H "X-OpenViking-User: alice"{ "status": "ok", "result": { "version": 2, "category": "skill", "target_key": "byted-viking-search-knowledgebase", "values": { "api_key": "secret-1", "base_url": "https://example.com" } }, "time": 0.01 }若 target/version 不存在,返回
NOT_FOUND。
历史快照是只读的:读取不会改变active_version,也不会刷新 meta 中的版本指针。
4.7 激活历史版本:activate_privacy_version
切换当前生效版本,即“回滚”操作。
HTTP API
POST /api/v1/privacy-configs/{category}/{target_key}/activate请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| version | int | 是 | 要激活的版本号 |
curl -X POST "http://localhost:1933/api/v1/privacy-configs/skill/byted-viking-search-knowledgebase/activate" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-key" \ -H "X-OpenViking-Account: default" \ -H "X-OpenViking-User: alice" \ -d '{"version": 2}'响应
{ "status": "ok", "result": { "version": 2, "category": "skill", "target_key": "byted-viking-search-knowledgebase", "values": { "api_key": "secret-1", "base_url": "https://example.com" } }, "time": 0.01 }若 target/version 不存在,返回
NOT_FOUND。
从实现看(activate_version),激活只是把指定历史快照的内容重新写入current.json并更新meta.active_version,latest_version不受影响——后续继续 upsert 时版本号仍从最新历史版本递增,不会与已存在快照冲突。
五、并发安全:路径锁保护读改写
密钥类配置常被多个客户端并发更新,upsert/activate/delete都运行在 _config_lock 上下文管理器中:它通过底层 AGFS 的pathlock_acquire_tree在配置根目录上获取路径树锁(超时 30 秒),确保“读 meta/current → 生成新版本 → 写 history/current/meta”的整个临界区串行化。可以推断该锁机制同样覆盖多实例部署下的跨进程场景,因为锁建立在存储层而非进程内。
测试方面,tests/server/test_api_privacy_configs.py覆盖了该接口的端到端行为,可作为接口回归依据。
六、与 Skill 的联动:占位脱敏与读取时恢复
隐私配置在category="skill"时与技能处理链路深度集成,构成“写入时脱敏、读取时还原”的闭环。
6.1 占位符格式
敏感值在技能内容中统一被替换为形如 build_placeholder 生成的占位符:
{{ov_privacy:skill:{skill_name}:{field_name}}}例如byted-viking-search-knowledgebase技能的api_key会写成{{ov_privacy:skill:byted-viking-search-knowledgebase:api_key}}。
6.2 写入侧:LLM 提取 + 替换
技能入库前,skill_processor.py 的 prepare_skill_privacy 调用extract_skill_privacy_values(skill_extractor.py),通过提示词模板skill.privacy_extraction让 VLM/LLM 从技能内容中识别出敏感字段,返回{"values": {...}};随后 placeholderize_skill_content_with_blocks 把原文中的敏感值替换为占位符。替换策略分两层:
- 结构化替换:带引号的形式(
"值"/'值')整体替换; - 行尾裸值替换:匹配
key: 值/key=值这类行尾模式(_replace_structured_value)。
且字段按值长度降序处理,避免短值误替换长值的子串。提取出的值经 apply_skill_privacy 调用privacy_configs.upsert(category="skill", target_key=技能名, ...)落盘为隐私配置;若本次提取为空且允许删除,则会调用delete清理旧配置(delete 实现 同样走路径锁递归删除配置目录)。
6.3 读取侧:自动还原
读取SKILL.md时,文件系统服务会在返回内容前自动还原占位符:fs_service.py 的 read 方法 先解析 URI,用 get_skill_name_from_uri 识别出这是某个技能的SKILL.md(要求路径形如.../skills/{name}/SKILL.md),若命中则读取该技能的category="skill"当前生效版本,交给 restore_skill_content 完成替换:
- 内容中的每个占位符按
field_name从current.values取值回填; - 配置存在但内容未引用的 key,会在文末追加
Configured but not referenced in content: ...提示; - 内容引用了但配置缺失的字段,占位符保留并追加
Missing config: field=<missing>提示,方便定位配置缺口。
由于还原永远取current.json指向的生效版本,前文的“写入新版本 / 激活历史版本”两个 API 就直接决定了 Agent 读取技能时拿到的密钥是哪一版——这就是密钥轮换与回滚对上层完全透明的原理。
七、CLI 快速操作
Rust 实现的 CLI 在 main.rs 的 PrivacyCommands 中定义了对应子命令:
# 分类/目标 openviking privacy categories openviking privacy list skill # 当前生效配置(支持快捷形式) openviking privacy get skill byted-viking-search-knowledgebase openviking privacy skill byted-viking-search-knowledgebase # 更新(整包 JSON) openviking privacy upsert skill byted-viking-search-knowledgebase \ --values-json '{"api_key":"secret-2","base_url":"https://example.com"}' # 仅更新部分 key(先读取 current 再合并) openviking privacy upsert skill byted-viking-search-knowledgebase \ --key-api_key secret-3 # 版本查询与切换 openviking privacy versions skill byted-viking-search-knowledgebase openviking privacy version skill byted-viking-search-knowledgebase 2 openviking privacy activate skill byted-viking-search-knowledgebase 2结合 CLI 参数定义可以补充几点实操细节:
upsert除--values-json外还支持--values-file <path>,二者互斥(conflicts_with),适合 values 较大时避免命令行转义问题;--key key=value可重复传入(Vec<String>),对应“先读 current 再合并”的部分更新语义;- 还支持
--change-reason(默认空串)与--labels-json,与 HTTP 请求体字段一一对应。
八、小结与延伸阅读
隐私配置模块用“目录 + JSON 快照”的轻量结构实现了完整的版本化敏感配置管理:current.json决定生效版本、history/version_NNN.json保留不可变历史、.meta.json维护版本指针与标签,配合路径锁保证多端并发安全;再叠加 Skill 的占位符提取与读取时自动还原,使得密钥轮换、回滚、审计都可以只通过category/target_key维度的 API 或 CLI 完成,无需改动技能内容本身。
相关文档:
- 技能 - 技能写入与读取
- 文件系统 -
read/write/ls等 - 系统 - 服务状态与可观测性
核心源码入口:路由层、存储服务、数据模型、路径与版本辅助、技能脱敏提取、占位符替换、读取时还原、接口测试。
【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考