OpenViking 记忆提取挂了三天,最后发现是模型太聪明
事情要从三天前说起。Hermes Agent 接上 OpenViking 做长期记忆之后,我发现它从来不主动搜自己的知识库。每次对话都像失忆了一样,之前存的东西完全不记得。
一开始以为是配置没配对。毕竟 OpenViking 是字节跳动的开源项目,这种量级的团队不太可能犯低级错误,大概率是我哪里搞错了。版本 v0.3.14,VLM 一开始本地部署的 Ollama,后来切成了 MiniMax 的 API,embedding 是本地的 bge-small-zh。服务健康检查 200 OK,日志没报错,看起来一切正常。
但 viking_search 永远返回空。
第一层:向量库是空的
先确认最基本的——数据到底有没有进去。
curl http://127.0.0.1:1933/api/v1/observer/vikingdb
结果:Vector Count = 0。一整个向量库,空空如也。但 Semantic-Nodes 队列显示 3335 条正在处理和已完成——说明数据管道在跑,只是没入库。
等了几分钟再查,0 变成了 856。原来是异步队列积压,处理完就正常了。search 也通了。
但这只是资源索引(viking_add_resource)的部分。记忆提取(viking_remember 写入的会话记忆)还是另一回事。
第二层:VLM 提取了,但一个记忆都没存下来
viking_remember 调用返回 stored,消息确实进了 session 队列。session commit 也正常触发,task 状态 completed,VLM 甚至确认被调用了:
llm_token_usage: {prompt_tokens: 30893, completion_tokens: 4050, total_tokens: 34943}
三万多 token 的 prompt,四千 token 的回复。VLM 确实在工作。但 memories_extracted 是一个空对象 {}——八个分类全为零。
这就奇怪了。VLM 调了、回了、task 成功了,记忆去哪了?
翻 systemd 日志找到了答案:
Direct model validation failed: 1 validation error for StructuredMemoryOperations
profile.name
Extra inputs are not permitted [type=extra_forbidden]
M2.5 确实提取了内容,质量还不错——它准确地识别出了 OpenViking 的五个工具、版本号、配置信息。但它输出的 JSON 长这样:
{"entities": [...], "tools": [...], "profile": {"name": "default", "content": "..."}, "identity": {...}}
而 OpenViking 的 Pydantic schema 期望的格式是:
{"reasoning": "...", "write_uris": [...], "edit_uris": [...], "delete_uris": [...]}
M2.5 凭自己的理解重新发明了 JSON 结构——它把记忆按类别分成了 entities、tools、profile 这些 key,甚至给 profile 加了一个 schema 里不存在的 name 字段。
Pydantic 的 extra='forbid' 策略直接把所有内容拒之门外。VLM 花了 35000 token 提取出来的记忆,一秒就被丢进了垃圾桶。
这个行为其实不怪 OpenViking。extra='forbid' 是工程上正确的防御——如果随便接受模型自由发挥的字段,脏数据就入库了。GPT 和 Claude 在指令遵循上足够强,不会犯这种错。但 M2.5 的指令遵循没想到这么弱——自己发明字段名、把 string 嵌套成 dict,完全脱离了 prompt 里定义的 schema。
第三层:不是模型越新越好
我开始怀疑是不是 M2.5 特有的问题。于是顺手测了 M2.1 和 M2——同样的默认模板,同样的 session,只换模型。加上之前 M2.7 也测过(但它的 API 限流太猛,语义摘要动不动 429,没法正经评估),实际对比了这三个:
| 模型 | 代数 | 默认模板提取 | 日志 |
|---|---|---|---|
| M2.5 | 较新 | 0 条 | extra_forbidden 爆炸 |
| M2.1 | 中间 | 1 条 | 零报错 |
| M2 | 最老 | 4 条 | 零报错 |
最老的 M2 表现最好,较新的 M2.5 反而挂零。这个结果有点反直觉,但仔细想想也合理——新一代模型往往更"有主见",倾向于用自己的方式组织输出。在创意性任务上这是优势,但在结构化 JSON 这种机械任务上反而是劣势。
类似的事在业界也有记录。有些模型升级后 benchmark 总分涨了,但 format-following 的得分跌了。只是之前看的都是别人踩的坑,这次轮到自己了。
解决
切回 M2,模板不动,一切正常:
VLM: minimax/MiniMax-M2 (litellm)
memories_extracted: 6 write + 1 edit
向量库: 3770 vectors
检索: 零报错,正常返回
中间也试过改模板——给每个 category 的 description 加上显式的 JSON 字段约束。效果立竿见影,M2.5 也从 0 变成了 7 条。但这不是正解——pip install --upgrade 会把模板覆盖回去,而且一旦 PR #1045(memory v2 重构)发版,提取器本身会更宽容,到时候模板补丁反而是多余的。
所以最终的方案就是:VLM 用 M2,其他不动。等官方更新。
几点收获
-
viking_search 空结果,先看向量库是不是 0。 异步队列积压是正常现象,等几分钟就行。
-
OpenViking 的 VLM 提取和检索是两条独立的管道。 提取依赖配置的 VLM 模型(ov.conf 里的
vlm段),检索依赖 embedding 模型(embedding段)。一个挂了不影响另一个。 -
M2.7 的 API 有限流。 MiniMax 最新模型 M2.7 的并发限制比较紧,资源索引的语义摘要生成会打满配额,动不动 429。这也是最终用 M2 而不是 M2.7 的另一个原因——早期模型并发限制更宽松,实际效果反而更好。
-
不要迷信新模型的结构化输出能力。 benchmark 涨了不代表"听话"了。在这个场景里,最老的 M2 反而是最靠谱的。
-
别在模板上打补丁。 能换模型解决的问题,不要改配置。配置是临时的,升级就丢。