Interview Prep

OpenViking 上下文数据库复习笔记

火山引擎开源的 AI Agent 上下文数据库:viking:// 文件系统范式、L0/L1/L2 分层加载、目录递归检索、三种上下文类型、会话与记忆提炼,以及 Go 客户端/Agent 的实现八股。

OpenViking 上下文数据库复习笔记

这份笔记围绕 OpenViking(火山引擎开源的 AI Agent 上下文数据库)展开:它是什么、解决什么问题、核心概念怎么串,以及用 Go 接入时的实现细节。内容基于官方文档(docs.openviking.ai,逐页核对)、GitHub 仓库,以及本仓库 viking/client.goviking/extra.gocmd/agent/main.go 的源码,并对照了生产侧 final_sys_agent 里基于官方 Go SDK 的接入实现。

主要模块:

  • 一句话定义、四个痛点、与传统向量库/RAG 的本质区别
  • 核心概念:viking:// URI、L0/L1/L2 分层、目录递归检索、三种上下文类型、会话与记忆提炼
  • 存储与检索架构:AGFS + Vector Index 双存储、find vs search、rerank、事务模型与路径锁
  • 多租户与鉴权:account/user/peer、三种 auth 模式、两类 key
  • Go 集成与源码八股:统一 do 方法、错误处理、超时与优雅退出、分层加载、生产接入模式
  • 经典相关八股:embedding 与 rerank、token 成本优化、记忆系统、MCP
  • 常见坑与速记

INTERACTIVE REVIEW

OpenViking 交互式背诵站

把 viking://、L0/L1/L2、目录递归检索、记忆提炼和 Go 接入八股做成可翻卡片。

打开完整演示
在文中展开演示

1. 一句话定义 + 四个痛点

一句话:OpenViking 是火山引擎开源的 AI Agent 上下文数据库(Context Database)——把记忆(memory)、资源(resource)、技能(skill)统一组织成 viking:// 虚拟文件系统,让 Agent 像操作文件一样管理上下文,而不是对着一个黑盒向量库发查询。

传统 RAG 在 Agent 场景下的四个痛点,以及 OpenViking 的对应:

痛点传统 RAGOpenViking 的应对
上下文碎片化向量切片没结构,召回结果丢了周围上下文文件系统范式:一切上下文都有 viking:// URI,用 ls/tree/find/read 确定性操作
Token 成本失控要么全量塞 prompt,要么手动截断L0/L1/L2 分层加载:写入时自动生成摘要/概览/原文,按需加载
记忆不进化对话结束就结束,偏好与经验不沉淀会话变记忆:commit 后异步提炼用户偏好与 Agent 经验到长期记忆
检索黑盒结果错了不知道哪条路径召回目录递归检索:先锁高分目录再逐层下钻,结果自带上下文,轨迹可回看

本质区别(对照官方 FAQ 对比表):存储模型(扁平向量存储 vs 层级化文件系统 AGFS)、检索方式(单一向量相似度 vs 目录递归检索 + 意图分析 + Rerank)、输出形式(原始分块 vs L0/L1/L2 结构化上下文)、记忆能力(无 vs 内置多种记忆类型自动提取持续迭代)、可观测性(黑箱 vs 检索轨迹可追溯)、上下文类型(仅文档 vs Resource + Memory + Skill)。

1.1 公开 benchmark:LoCoMo10 数据(面经常考数字)

OpenViking 用 ACL 2024 的 LoCoMo10 长期对话记忆基准(Snap Research,1,540 条长对话)做对比,挂在 OpenClaw 上测:

配置任务完成率输入 Token 成本
OpenClaw 原生 memory(基线)35.65%24.6M
OpenClaw + LanceDB(关原生)44.55%51.6M
OpenClaw + OpenViking(关原生)52.08%4.3M
OpenClaw + OpenViking(开原生)51.23%2.1M

结论:完成率相对提升约 4 成(开原生口径约 +43%),输入 token 降约 8–9 成(关原生 −83%、开原生 −91%)。各文章宣传口径略有差异(43% / 49%),以 README 表格为准。

面试必追问「为什么完成率也跟着涨」:答案不在召回率本身,而在 Agent 拿到的是结构化、带目录上下文的检索结果而非孤立碎片,生成质量更高;同时记忆回写让后续任务少做无效检索。注意 LanceDB 那一行——token 反而从 24.6M 涨到 51.6M,说明「换个向量库」解决不了上下文问题,这正是 OpenViking 换抽象的原因。

1.2 工程形态:不是单语言项目 + 一句金句

仓库不是单语言 Python 包:openviking(核心 Python 包)+ crates/ov_cli(Rust CLI)+ AGFS(Go 1.22+ 构建)+ src/native extension(性能热点)+ bot(Agent 接入层)。分工务实:Python 管生态与开发效率,Rust 做 CLI,Go 做系统能力,native 承接性能敏感部分。

CLI 入门四件套:

pip install openviking --upgrade
openviking-server                        # REST + /mcp 同进程启动
ov add-resource <url|path> --wait        # 加资源并等待语义处理完成
ov tree viking://resources/xxx -L 2      # 浏览目录树
ov chat                                  # 内置 VikingBot 直接对话

面试金句:「上下文管理是数据库问题,不是 prompt engineering 问题。」 一句话说清 OpenViking 的定位和它跟 RAG 的关系,比背一堆接口名更有说服力。


2. 核心概念

2.1 viking:// URI:一切上下文都是路径

格式 viking://{scope}/{path}。顶层布局:

viking://
├── resources/                    # 共享知识库:文档、代码、网页(客观知识)
├── user/{user_id}/
│   ├── profile.md                # 用户画像
│   ├── memories/                 # 长期记忆:preferences/entities/events/...
│   ├── resources/                # 用户私有资源
│   ├── skills/                   # 用户技能
│   ├── peers/{peer_id}/          # 关于某交互对象的记忆/资源
│   └── sessions/{session_id}/    # 会话与历史归档
└── agent/                        # account 全局共享:skills/、endpoints/、tools/、payments/

作用域(scope)规则:

作用域放什么生命周期可见性
resources客观知识(文档、代码、规范)长期account 全局
user用户级数据(记忆、私有资源、技能、会话)长期或会话期当前用户
agentAgent 能力与配置(技能、端点、工具、支付)长期account 全局
temp/queue/upload内部临时临时内部,公开 API 不可访问

三条必须记住的规则:

  • viking://user/... 是短路径:服务端按当前请求身份展开为 viking://user/{user_id}/...;旧的 viking://session/{id} 只是当前用户 session 路径的向后兼容别名,不是独立存储根。
  • resources 作用域只放知识类数据;技能/工具/支付配置属于 agent 作用域。
  • POST /resourcestarget 只能在 viking://resources/(0.1.18 起),写用户内容走 Session API 或 user 作用域 URI。

2.2 L0/L1/L2:分层加载

每个目录/文件写入时自动生成,按需加载省 token:

层级特殊文件内容规模用途
L0.abstract.md一句话摘要~100 token向量召回、快速过滤相关性
L1.overview.md结构与要点、导航~1k–2k tokenRerank、内容导航、任务规划
L2原文完整内容无限制确认需要细节时才读

生成机制:资源添加时 Parser 解析 → SemanticQueue 自底向上(叶子 → 父目录 → 根)逐目录生成 L0/L1 → 写入向量索引;session 归档时同样为归档段生成 L0/L1。子目录的 L0 会聚合进父目录的 L1,形成层级导航。目录自身也带 .abstract/.overview,所以在读到任何原文之前就能判断相关性。多模态内容(图片/视频/音频)的 L0/L1 也是文本描述。

一句话记忆:L0 判断要不要,L1 判断读哪个,L2 才花钱读全文。 典型顺序:先 L0 批量粗筛 → 对候选读 L1 看结构 → 真正要引用原文时才 read L2,比一次性塞全文通常省 50%+ token。

2.3 目录递归检索:先锁目录,再逐层下钻

平铺向量检索只返回「语义最像的碎片」,碎片一旦离开所在目录就丢了上下文。目录递归检索分五步:

  1. 意图分析:拆解查询生成多个检索条件(只有 search() 才用 LLM 做这步,生成 0–5 个 TypedQuery;find() 不做,直接向量检索,延迟更低);
  2. 初始定位:全局向量检索定位初始切片所在的高分目录;
  3. 精细探索:在目录内二次检索,更新候选集合;
  4. 递归下探:子目录逐层递归(优先队列驱动),直到 top-k 连续 3 轮不变收敛;
  5. 结果汇总:返回最相关上下文。

分数传播:final_score = α × 向量相似度 + (1 − α) × 父目录分。官方 FAQ 常用 α=0.5 举例(0.5/0.5 加权);实际由 retrieval.score_propagation_alpha 配置,默认 1.0(只取子项自身分数、忽略父目录分)。核心思想:高分目录下的内容天然加权,这正是「结果自带上下文」的来源。每次检索保留目录浏览轨迹,结果不对时可以回看走哪条路径召回的。

2.4 三种上下文类型:Resource / Memory / Skill

类型用途生命周期谁来写
Resource知识与规则(API 文档、代码仓库、论文、FAQ)长期、相对静态用户添加
MemoryAgent 的认知(偏好、实体、事件、经验)长期、动态更新Agent 记录
Skill可调用的能力定义(.abstract.md L0 + SKILL.md L1 + scripts L2)长期、静态用户或系统添加

内置记忆类型(按用途分组):用户/环境理解(profilepreferencesentitiesevents),助手身份/连续性(identitysoul),任务执行/学习(casestrajectoriesexperiencestoolsskills)。应用可扩展或调整记忆类型。一次 find() 同时返回 memories/resources/skills 三类结果。

2.5 Session 与记忆提炼

生命周期 create → add messages → commit

POST /sessions                    创建会话,拿 session_id
POST /sessions/{id}/messages      逐条追加 user/assistant 消息(可带结构化 parts)
POST /sessions/{id}/commit        归档 + 触发异步记忆提炼

        ├─ Phase 1(同步):写 messages.jsonl,清空当前消息,返回 task_id
        └─ Phase 2(异步后台):生成归档段 .abstract/.overview → 提炼长期记忆
                              → 写 memory_diff.json(变更审计)→ 写 .done

记忆提炼内部流:Messages → LLM 抽取候选记忆 → 向量预筛找相似记忆 → LLM 去重决策(skip/create/merge/delete)→ 写入 AGFS → 向量化memory_diff.json 每次 commit 都写,记录 adds/updates/deletes,支持审计与回滚。

结构化消息:messages 支持 parts 数组(text/context 引用/tool 调用轨迹),让记忆提炼能看到 Agent 实际用过的上下文与工具——Go 端封装为 Session.AddPartsMessagePart 结构见 viking/extra.go


3. 存储与检索架构

3.1 AGFS + Vector Index 双存储

VikingFS (URI 抽象:URI 映射、层级访问、关系管理)
   ├── Vector Index(语义检索:URI、向量、元数据,不含文件内容)
   └── AGFS(内容存储:L0/L1/L2 全文、多媒体、关系)

设计收益:职责清晰;索引不含内容省内存;单一数据源(一切内容从 AGFS 读,向量索引只存引用);可独立扩容。核心原则:FS 是 source of truth,VectorDB 是 derived index——索引丢了能重建,源数据丢了不能。所以设计哲学是 「宁漏检,不误检」(Better to miss a search result than return a bad one)。AGFS 已用 Rust 重写为 RAGFS。

特性find()search()
会话上下文不需要需要
意图分析无(直接向量检索)LLM 分析,生成 0–5 个 TypedQuery
延迟
适用简单查询、明确目标复杂任务、多上下文类型

一句话:明确找什么用 find();复杂任务需要拆意图、跨 memory/resource/skill 用 search()

3.3 Rerank

两阶段检索:向量召回(L0)→ Rerank 精排(L1)。rerank 需要配置 rerank 模型(如 doubao-seed-rerank),在全局候选评估与每层子目录评估两处使用;未配置或失败时回退到向量分数。

3.4 事务模型:路径锁与崩溃恢复

写独占通过 路径锁(PathLock)保证:

  • 两种锁:EXACT(单路径:文件写、单文件删除、sidecar 回写)和 TREE(子树:目录删除、资源生命周期、子目录保护);
  • fencing token{handle_id}:{time_ns}:{lock_type} 写入锁文件)防止 TOCTOU 竞态;
  • 过期锁自动清理(默认 lock_expire 1800s / 30min;LockManager 每 60s 扫描);
  • rm 反向删除:先删 VectorDB 索引再删文件(索引消失即不可见,FS 删除失败重试安全);mv 先拷贝再更新索引再删源。

session commit 之所以两阶段,是因为 LLM 提炼延迟 5s–60s+ 不能占着锁:Phase 1 归档(无锁)→ Phase 2 记忆提取走持久化 session_commit 队列(SQLite 持久化,崩溃后重启继续跑,幂等)。


4. 多租户与鉴权

身份边界是 account(外层租户:团队/客户)与 user(账号内用户),peer_id 只是 user 内部的内容作用域(交互对象),不改变身份。

三种角色:ROOT(全局:建/删 account、跨租户、用户管理)、ADMIN(单 account:管用户、重置 user key)、USER(单 account:自己的 user/peer/session 数据 + 共享 resources)。

三种 auth 模式(server.auth_mode):

模式身份来源适用
api_keyroot key / user key标准部署(配了 root_api_key 自动进入)
trusted上游网关注入 X-OpenViking-Account/X-OpenViking-User受信网关之后
dev无鉴权,一律 ROOT,身份 default/default未配 key 时自动进入,只允许回环地址

两类 keyroot_key 只管 Admin API 和管理操作,不能访问租户数据接口(api_key 模式下会 PERMISSION_DENIED);日常数据接口(find/ls/sessions)要用 Admin API 签发的 user_key/health/ready 从不要求鉴权。请求头 X-API-Key: <key>Authorization: Bearer <key>

一个常见误解:root key 不是业务访问 key;没有 root_api_key 不等于单租户生产(那是 dev 模式)。


5. Go 集成与源码八股

5.1 统一 do 方法(极简 stdlib 客户端)

viking/client.go 的核心是一个统一方法,处理鉴权、编码、统一响应、业务错误:

func (c *Client) do(ctx context.Context, method, path string, query url.Values, body any, out any) error

几个值得背的点:

  • 鉴权apiKey != "" 时才带 X-API-Key 头(服务端未配 key 时不带);
  • 请求编码json.Marshal(body)bytes.NewReaderhttp.NewRequestWithContext(context 贯穿,超时/取消可传导);
  • 响应大小上限maxResponseBytes = 32 << 20(32MB),io.LimitReader 读,超限直接报错——防止异常大包耗尽内存;
  • 统一响应解析{"status","result","error","time"}status != "ok" 时转成 *Error{Code, Message}
  • 非 2xx 且非 JSON:返回带状态码的明确错误,而不是糊一个 decode 错。

5.2 业务错误码

type Error struct { Code, Message string }
func (e *Error) Error() string { return fmt.Sprintf("openviking %s: %s", e.Code, e.Message) }

调用方用 errors.As(err, &ve) 取出 *viking.Error,读 ve.Code/ve.Message。常见错误码:UNAUTHENTICATED(401)、INVALID_ARGUMENT/INVALID_URI(400)、NOT_FOUND(404)、ALREADY_EXISTS(409)、SESSION_EXPIRED(410)、DEADLINE_EXCEEDED(504)、UNAVAILABLE(503)。

5.3 超时与优雅退出(cmd/agent/main.go

  • 启动即校验checkLLMEnv 检查 LLM_BASE_URL/LLM_API_KEY/LLM_MODEL,缺失直接 os.Exit(1),不浪费请求;
  • 分层超时:客户端 http.Client{Timeout: 120s}(整体),每轮召回与 LLM 调用各有 context.WithTimeout(..., 60s)
  • 优雅退出signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) 捕获 Ctrl+C,退出循环后走 defer 里的 session.Commit(独立 30s 超时),保证记忆提炼照常触发;
  • 召回降级buildContext 失败不丢这一轮,stderr 警告后降级为无上下文继续对话。

5.4 分层加载的实现

buildContextfind 命中的 abstract 就是 L0 摘要,命中列表本身不额外花 token;命中没摘要就降级读 L1 概览:

for _, h := range hits {
    if h.Abstract != "" { /* 直接用 L0 摘要 */ } else { /* 降级读 Overview(L1) */ }
}

5.5 最小闭环

recall → generate → record → commitFind 召回 → 拼 system prompt 调 LLM → AddMessage 写回会话 + 本地历史 → 退出 defer Commit,服务端异步提炼记忆。

5.6 生产接入模式(官方 Go SDK)

生产侧(final_sys_agent/internal/knowledge/openviking)不自己拼 HTTP,而是包官方 SDK github.com/volcengine/OpenViking/sdk/go,做 provider-neutral 的 adapter。几个值得背的工程判断:

  • find vs search 按需选:无痕检索 RetrieveFind();带 incident session 的追踪检索 RetrieveWithTraceSearch(),保留 query_plan/query_results。检索与追踪只在 session telemetry 上有别,其余契约一致。
  • L2 水合有降级hitsFromResources 优先 Read() 全文,但某个已索引资源 ReadNOT_FOUND 时,退回该命中自带的 overview/abstract,不让一条资源失败拖垮整次查询。
  • peer-scoped 记忆memory.Write 只写到「确切的 peer/kind 子树」下,Searchcontext_type=memory 只查该子树;写入时 encodeMemoryDocument 把 provenance 留在可读的 L2 内容里,再套 OpenViking 要求的元数据信封。
  • archive-only 记忆策略:incident 场景 CommitIncidentarchiveOnlyMemoryPolicy()——只归档不提炼,避免把一次性排障过程污染长期记忆。
  • 异步任务轮询waitTask 用 250ms ticker 轮询 GetTask,直到 completed/failed/未知状态/ctx 取消;add_resource 导入仓库后拿到 root_uri+task_id 就靠它等语义处理完成。
  • 写入后校验Write(wait=true) 后校验返回的 semantic_status/vector_statuscomplete,才认为入库完成。
  • URI scope 钳制scopedURI 用 segment-boundary 检查(viking://resources/redcart-secret 不能匹配 viking://resources/redcart 的裸前缀),把用户输入钳回授权的资源子树,防止工具越权浏览。

6. 经典相关八股

6.1 Embedding:dense / sparse / hybrid

  • dense:稠密向量,语义相似度(余弦),主流 embedding 模型(doubao、openai、jina、voyage 等),维度如 1024;
  • sparse:稀疏向量(词项级),适合精确词匹配,只有 volcengine/vikingdb 实现,无 BM25 回退;
  • hybrid:dense + sparse 混合,需要 storage.vectordb.sparse_weight > 0 才生效。

索引策略默认 flat_hybridDistance: cosineQuant: int8(量化省内存)。

6.2 两阶段检索:召回 + Rerank

向量召回(粗筛,L0)→ Rerank 精排(L1)。rerank 模型对候选重排,比纯向量相似度准;未配置/失败时回退向量分。这是「检索质量」与「成本」的经典平衡。

6.3 Token 成本优化

省 token 不是「因为有摘要」这么简单,而是四个机制叠加(面试答到这里比只答「分层加载」更有深度):

  1. 目录摘要先挡无关区域:很多无关内容在目录层就被刷掉,根本轮不到 L2;
  2. L0/L1 缩小平均读取粒度:不少问题到 L1 就够判断下一步,不需要原文;
  3. 递归下钻逐层缩小候选集合:候选越缩越小,rerank 更容易做准;
  4. 记忆回写减少重复检索:结果沉淀成 user/agent 记忆后,不用每次翻原始资源。

本质是上下文只装任务需要的层级:L0 批量筛 → L1 看结构 → L2 按需读全文,配合 session 归档压缩(近期保留、远期摘要),比「全量塞 prompt」或「手动截断」更省也更准。

6.4 记忆系统与去重

记忆提炼不是「存最新的」就完事,而是一个有状态的写路径:新记忆入库前先向量预筛语义相近的旧记忆,再由 LLM 判定 skip/create/merge/delete——和数据库事务的「读-改-写」一个思路。memory_diff.json 记录每次变更,支持审计与回滚。

6.5 MCP 集成

OpenViking 内置 /mcp 端点(和 REST 同进程同端口),任何 MCP 客户端(Claude Code、Cursor、ChatGPT、Manus 等)直接挂载,模型自主调 find/search/read/ls/grep/recall 等工具,省去手写召回逻辑。也提供各 Agent 运行时插件(Claude Code Memory Plugin、LangChain/LangGraph 集成等)。

6.6 自进化闭环与周边生态

  • 自进化闭环:session 结束 → 抽取长期记忆 → 写 viking://user/memories + viking://agent/memories——用系统自动压缩取代手写 MEMORY.md
  • VikingBotopenviking[bot] 内置的 Agent 框架,开箱即用(openviking-server --with-botov chat);
  • ME.md:人/Agent 可携带的上下文协议(一个文件描述「我是谁、我的栈、我的反模式」),ov add-resource 直接入库;
  • ACP:Agent 上下文交接协议;OpenViking 是 ME.md / ACP 底下的存储层。

这些是 OpenViking 生态里常被问到的名词,知道它们和 OpenViking 的关系即可,不必深挖。


7. 常见坑 Top5

  1. INVALID_ARGUMENT: add_resource only supports resources scopePOST /resourcestarget 只能在 viking://resources/ 下;写用户内容走 Session API 或 user 作用域 URI。
  2. 检索不到刚写入的资源:语义处理是异步的——写入传 "wait": true,或调 POST /api/v1/system/wait 补等(Go 端 vc.Wait(ctx))。
  3. 401 UNAUTHENTICATED:服务端配了 root_api_key 而请求没带 X-API-Key;或者拿 root key 调数据接口(该用 user key)。
  4. dev 模式只监听回环:未配 root_api_key 时只允许 127.0.0.1,局域网访问会失败;要对外暴露就配 server.auth_mode="api_key" + root_api_key
  5. 响应字段与部署版本对不上find 返回的 results[].uri/score/abstract 以官方 Python SDK 行为为准;版本不符用 curl 实测一次响应体再调结构体。

8. 速记版:背这些就够

  1. OpenViking = AI Agent 上下文数据库:记忆/资源/技能统一成 viking:// 文件系统。
  2. 最小闭环:recall(find)→ generate(LLM)→ record(add message)→ commit(提炼记忆)
  3. L0 摘要 ~100 token 判相关性,L1 概览 ~1k–2k token 看结构,L2 原文按需读。
  4. find = 先锁高分目录、再逐层下钻的目录递归检索;final_score = α×向量相似度 + (1−α)×父目录分(默认 α=1.0)。
  5. 三种上下文类型:Resource(客观知识)/ Memory(动态认知)/ Skill(能力定义)。
  6. find vs search:前者纯向量快,后者加意图分析(0–5 查询)+ 会话上下文。
  7. 双存储:AGFS 存内容(truth),Vector Index 存引用(derived)——「宁漏检,不误检」。
  8. commit 两阶段:同步归档 + 异步记忆提炼(LLM 抽取 → 向量预筛 → LLM 去重)。
  9. 多租户:account/user 身份边界 + peer 内容作用域;三种 auth(api_key/trusted/dev)、两类 key(root/user)。
  10. 语义处理异步:写入带 wait=true 或调 system/wait 补等;服务端配了 key 就带 X-API-Key

参考资料

  • volcengine/OpenViking - GitHub
  • 官方文档 docs.openviking.ai(概念:viking-uri / context-types / context-layers / retrieval / session / multi-tenant;API Overview)
  • 本仓库:README.md(9 节教程)、viking/client.goviking/extra.gocmd/agent/main.godocs/openviking-101.md(八股速查)
  • 生产接入:final_sys_agent/internal/knowledge/openviking/(官方 Go SDK adapter)
  • 面经 / 深度分析参考:IceYao《从原理到落地:基于OpenViking构建多模态RAG方案》、mager.co《OpenViking: The Open-Source Context Database Your Agents Have Been Waiting For》、知乎《Openviking详解》、CSDN《字节开源OpenViking全解》、DeepWiki/Zread《LoCoMo Long-Term Memory Benchmark》

说明:文中「~100 token / ~1k–2k token」「0.5 × Embedding + 0.5 × 父目录分」「0.1.18 起」「0–5 个查询」「32MB / 120s / 60s」等数字均来自官方文档或本仓库源码/README 的既有表述;score_propagation_alpha 默认 1.0 以官方检索文档为准。具体版本行为若有出入,以官方文档和你部署版本的实测为准。