语言: English · Tiếng Việt · 中文
业务规则、部署流程、测试矩阵、故障排查手册。这是你在把 Viking rollout 给团队之前要读的东西。
决策:OpenViking 自托管运行,每个团队一个实例(或单人开发者一人一个)。不是 SaaS 模式。
原因:
- OpenViking 的默认端口
127.0.0.1:1933和单服务器架构暗示 "local-first" - OpenSpace 的隐私优先哲学(self-evolution、本地 skill DB)与此契合
- 团队局域网部署很便宜(单个 docker 容器)
- 无数据出境意味着监管合规简单直接
部署形态:
- 单人开发者:在笔记本上
docker run -p 1933:1933 volcengine/openviking:latest - 小团队:团队局域网或内部云上的单个共享 Viking 实例
- 企业:多个 Viking 实例按团队或数据分类划分
决策:
- Agent 记忆(
tools、patterns、skills、cases)→ 团队共享 - 用户记忆(
preferences、profile)→ 按用户隔离
原因:工具知识是集体智能的全部意义 —— 每个用户复制一份 "chromedriver 版本兼容性" 就失去了意义。用户偏好则是个人的:"Alice 偏好柱状图" 绝不能出现在 Bob 的会话中。
强制执行:通过 namespace + user_id config 进行 URI 前缀隔离。Client 的 agent_memory_uri() 返回团队作用域路径;user_memory_uri() 返回用户作用域路径。Viking 的 target_uri 目录过滤器在服务器端强制边界。
威胁模型覆盖:
- 共享 Viking 上的诚实用户:隔离防止意外偏好泄漏
- 处于错误 namespace 的诚实团队成员:namespace 配置错误会落到错误的记忆桶,但不会外泄数据
- 恶意用户:超出范围 —— 需要 Viking 级别的 RBAC(API key 作用域)
决策:
- 原始 session 数据 → 30 天 TTL(在 Viking 侧配置)
- 抽取的记忆 → 永久(在 session 删除后仍然存在)
原因:
- Session 是审计跟踪 —— 大,在抽取后与记忆重复
- 记忆经过去重和整理 —— 是永久存储
- 在 100 用户 × 10 task/天 × 3 进化 skill = 3,000 session/天 ≈ 90k/月 × ~5KB = ~450MB/月原始 session 数据
- 30 天给调试提供足够的取证窗口,又不会让存储失控
实现:Viking 侧配置(sessions.retention_days)。OpenSpace 不管理保留;它只创建 session,让 Viking 自行老化。
决策:团队拥有推送的 skill 资源;作者在元数据中被致谢。
原因:集体智能意味着整个团队从任何成员的进化 skill 中受益。个人所有权与跨 Agent 共享的全部意义相冲突。归因保留 "谁贡献了什么" 的叙事,而不 gate 访问。
实现:push_evolved_skills() 附加元数据:
{
"source": "openspace",
"origin": "derived" | "captured" | "fixed",
"description": "<skill 描述>",
"parent_skill_ids": ["parent1", "parent2"],
"change_summary": "相对 parent 改变了什么",
"generation": <int>
}团队管理员通过 Viking 自身的资源管理进行删除(超出 OpenSpace 范围)。
决策:静态环境变量,重启时轮转。没有热重载。
原因:
- OpenSpace 进程通常是短生命周期的(CLI 单次调用、MCP 服务器)
- 重启轮转简单可靠
- 热重载 secret 引入 race window,运维收益极小
- Viking 支持多个同时有效的 key,可以实现无停机的滚动轮转
运维流程:
- 通过管理 API 向 Viking 添加新 key
- 用新 key 更新团队
.env - 滚动重启 OpenSpace 实例(或就等下一次 CLI 调用)
- 传播后从 Viking 移除旧 key
- OpenViking 服务器运行中,
curl <URL>/health返回 200 -
OPENVIKING_URL设置正确(检查端口、如适用检查 TLS) - 如果 Viking 启用了认证,配置
OPENVIKING_API_KEY -
OPENVIKING_NAMESPACE设置为团队标识符(或有意为单用户留空) -
OPENVIKING_USER_ID策略已决定(自动回退 vs 显式按用户) -
OPENVIKING_PUSH_SKILLS已根据隐私政策评估
# 验证 client 可以到达 Viking
python3 -c "
import asyncio
from openspace.viking import OpenVikingClient
async def check():
c = OpenVikingClient()
print('available:', await c.is_available())
print('namespace:', c.namespace)
print('user_id:', c.user_id)
print('agent tools uri:', c.agent_memory_uri('tools'))
print('user prefs uri:', c.user_memory_uri('preferences'))
await c.close()
asyncio.run(check())
"期望输出:
available: True
namespace: <你的 namespace>
user_id: <你的 user id>
agent tools uri: viking://tenants/<ns>/agent/memories/tools/
user prefs uri: viking://tenants/<ns>/user/<uid>/memories/preferences/
openspace --query "simple task to prime the memory base"检查日志:
tail -n 50 logs/openspace/*.log | grep "Viking"期望看到:
✓ OpenViking integration active [namespace=..., user_id=...]
Viking telemetry: available=True hits=0 enrich_chars=0 feedback=committed pushed=1
在记忆库填充起来之前 hit 会是 0。这对第一个 task 是预期的。
监控这些指标:
- 命中率(
hits > 0):应随记忆库增长呈上升趋势 - 反馈状态:应大多为
committed——failed表示 Viking 问题 - 推送 skill 数:启用推送时应与进化 skill 数匹配
- 增强字符数:典型范围 500–2000;>3000 暗示记忆过于冗长
63 个测试 覆盖每个新代码路径。运行方式:
python3 -m pytest tests/test_viking_client.py -v --asyncio-mode=auto| 领域 | 测试数 |
|---|---|
| Client graceful failure | 6 —— 不可达服务器、find 方法、session 方法、skill push |
| Namespace URI 构建 | 3 —— 有/无 namespace/user_id 组合 |
| 查询组合 | 4 —— 仅 task、历史合并、multimodal 内容、截断 |
| 健康缓存 | 1 —— 成功 vs 失败 TTL 区别 |
| Skill push | 3 —— graceful fail、跳过不完整 record、truthy 响应处理 |
| 增强流水线 | 4 —— 空检索、分析 context、历史感知、丰富反馈 |
| 身份解析 | 5 —— 覆盖、环境变量、OS 用户回退、清理、空状态 |
| Push 豁免环境变量 | 3 —— truthy/falsy/回退到 config |
| VikingExecutionStats | 2 —— 默认形状、变异隔离 |
| Score threshold + resolvers(Round 6) | 7 —— min score env 覆盖、范围限制、回退、scrub pii 开关、find 传播 |
| PII 清理器(Round 6) | 12 —— Anthropic/GitHub/AWS keys、email、basic-auth URL、JWT、私钥块、CC Luhn、非 CC 数字、幂等、None 处理、嵌套 records |
| Negative feedback + anti-patterns(Round 6) | 5 —— graceful fail、antipatterns URI、stale memory report、antipattern key 存在、env fingerprint 形状 |
| MCP 工具(Round 6) | 8 —— unavailable、missing query、no-client status、polarity 校验、forget 需要 URI、category 规范化、register count、tool_memory_status |
| 迭代中 RetrieveMemoryTool(Round 6) | 3 —— unavailable、empty query、tool shape |
每个测试都针对端口 127.0.0.1:19999(故意不可达)。这意味着测试套件:
- 无论 OpenViking 是否运行都能通过
- 执行真实部署最常遇到的失败路径
- 总运行时间不到一秒(无网络等待)
- 永远不会污染真实的 Viking 实例
针对真实 Viking 实例的集成测试不在本套件中 —— 它们应该放在单独的 tests/integration/ 目录中,由 @pytest.mark.viking_live 这样的 marker 守护,仅在 CI 中用专用 Viking 容器运行。
原因:Client 初始化失败。通常是:
httpx未安装(ModuleNotFoundError)OPENVIKING_URL不正确(无法解析)openspace.viking.config中的 import 错误(不太可能,但检查 sanitization 函数)
修复:检查 .log 文件中的完整堆栈。logger.debug 包含异常。
原因:is_available() 把 False 结果缓存了 12 秒。
修复:
- 等 12 秒再重试
- 直接从 OpenSpace 主机
curl <OPENVIKING_URL>/health - 检查 OpenSpace 和 Viking 之间的防火墙 / 网络
- 检查 Viking 在预期端口监听(默认 1933)
如果 curl 有效但 OpenSpace 的 client 无效,检查:
- 代理环境变量(
HTTP_PROXY、HTTPS_PROXY)干扰 - 不同的主机名解析(例如
localhostvs127.0.0.1)
原因:记忆库为空或查询不匹配。
诊断:
# 检查 Viking 是否有任何记忆
curl -X POST http://localhost:1933/api/v1/search/find \
-H "Content-Type: application/json" \
-d '{"query": "anything", "limit": 5}'如果为空,Viking 还没有抽取的记忆。抽取是异步的 —— 等几次 task commit 之后。
如果非空但 OpenSpace 仍然 0 hit:
- 检查 namespace 匹配:OpenSpace 查询 URI vs 记忆存储 URI
- 用明确的
target_uricurl,匹配 OpenSpace 发送的内容 - 记录
resolve_viking_identity()输出以验证使用了哪个 URI 前缀
原因:在反馈 session 创建或 commit 期间出现异常。
诊断:检查 .log 中的 OpenViking feedback skipped: <exception>。常见原因:
- Viking 拒绝 session 格式(当前实现不应出现)
- Viking 慢,某条消息超时(每个请求 5s)
- Viking 返回 4xx/5xx(检查 Viking 自己的日志)
修复:通常是临时的。如果持续:
- 增加
client.py中的_REQUEST_TIMEOUT(不是用户可配置;需要 fork) - 检查 Viking 磁盘 / 队列容量
原因:is_available() 没有被缓存,每个 task 都命中 /health。
诊断:如果 Viking 在 available→unavailable 之间反复切换,缓存会 churn。通常表明 Viking 本身不健康或网络不稳定。
修复:稳定 Viking。缓存 TTL 不能再降低,否则对温缓存命中的回归不可接受。
原因:优先级顺序。OpenSpaceConfig.openviking_auto_push_skills=True + 环境变量未正确解析。
诊断:
from openspace.viking.config import resolve_viking_push_enabled
print(resolve_viking_push_enabled(config_default=True))如果环境变量设置正确应该打印 False。接受的值:0 false no off。
修复:确保环境变量值恰好是识别字符串之一(大小写不敏感)。OPENVIKING_PUSH_SKILLS=no 有效;OPENVIKING_PUSH_SKILLS=NO 有效;OPENVIKING_PUSH_SKILLS=nope 回退到 config 默认。
预期行为。OpenViking 独立于 OpenSpace 的本地 skill DB 存储记忆(从 session 抽取的 abstract)。删除本地 skill 不会删除它的 Viking 记忆。
清理:如需使用 Viking 自己的管理 API 删除记忆,或通过 host agent MCP 工具(Round 6)调用 openviking_forget_memory(uri)。这是故意的 —— 跨 Agent 传播要求记忆在任何单个 Agent 的本地状态之外持久存在。
原因:阈值高于任何记忆的实际分数。Viking 的嵌入相似度通常为好匹配提供 0.3–0.8;设置 OPENVIKING_MIN_SCORE=0.9 会 drop 几乎所有东西。
诊断:
# 暂时禁用阈值并检查原始分数
OPENVIKING_MIN_SCORE=0.0 openspace --query "typical task"
# 检查 result["viking"]["hit_counts"] —— 如果现在 >0,阈值就太高修复:从 OPENVIKING_MIN_SCORE=0.0(默认)开始,观察几天真实工作负载的分数分布,然后提高到只丢弃底部 10–20% 命中的水平。
预期行为。PII 清理器(OPENVIKING_SCRUB_PII=true,默认)在写入 Viking 之前将 secrets、凭证和 PII 替换为 [REDACTED_*] 占位符。这保护生产 secrets 不泄漏到共享记忆中。
仅用于测试:如果你需要原始数据通过 Viking 往返,在你的测试环境中设置 OPENVIKING_SCRUB_PII=false。永远不要在生产环境中禁用。
原因:Host agent 的 MCP 客户端在启动时缓存了工具列表,还没有在 OpenSpace 的 Viking 工具注册后重新获取。
诊断:
- 检查 OpenSpace 日志中的
Registered 5 OpenViking MCP tools for host agent access - 从 host agent 明确列出 MCP 工具
- 验证直接调用
openviking_memory_status()返回{"status": "ok"}
修复:重启 host agent 以便它重新列出 MCP 工具。大多数 host(Claude Code、Codex、OpenClaw)在 MCP 服务器连接时获取列表。
原因:要么 Viking 未配置(_viking_client 为 None),要么 is_available() 返回 False。
诊断:响应字典包含 reason 字段:
result = await openspace.provide_feedback(task_id, "positive")
# {"status": "skipped", "reason": "OpenViking not configured"} 或
# {"status": "skipped", "reason": "OpenViking unavailable"}修复:以上面"为什么 Viking 什么都没做?"相同的方式验证 Viking 配置。
短任务中的预期。工具只在 openviking_mid_iter_tool=True(默认)且 Viking 客户端可用时才注册。LLM 决定何时调用它 —— 它不是每个任务都调用,只在初始 enrichment 不够用时才调用。
诊断:检查日志中的 Added retrieve_memory tool (OpenViking mid-iteration)。如果不存在:
- 验证
config.openviking_mid_iter_tool为True - 验证 Viking 在 OpenSpace 初始化期间可用
- 检查
openspace._grounding_agent._viking_client不为None
如果工具已注册但从未被调用:LLM 判断初始 enrichment 已足够。这是正确的行为。工具作为安全阀存在,用于 iter-1 提示误导或不完整的情况。
如果出了问题,需要立即禁用 Viking:
# 选项 1:最快 —— 环境变量禁用
export OPENVIKING_ENABLED=false
# 重启 OpenSpace 进程
# 选项 2:源头禁用 config
# 在 OpenSpaceConfig 中:openviking_enabled=False
# 选项 3:核选项 —— 卸载 httpx
# Client._get_client() 在 httpx 缺失时返回 None → 完全 graceful failOpenSpace 的行为将和集成之前完全一致。无数据丢失,无需迁移,无 skill DB 影响。
稍后要重新启用,就翻转环境变量。记忆在 Viking 侧持久存在,重新启用后的第一次查询就会找到。
在生产环境宣告 Viking "工作" 之前,测量以下之一:
# 总 task 数
grep "Viking telemetry" logs/*.log | wc -l
# 至少有一个命中的 task
grep "Viking telemetry" logs/*.log | grep -v "hits=0" | wc -l
# 计算命中率
echo "scale=2; $(grep 'Viking telemetry' logs/*.log | grep -v 'hits=0' | wc -l) / $(grep 'Viking telemetry' logs/*.log | wc -l)" | bc目标:使用第一周后命中率 >30%。
用 Viking 启用运行相同 task 10 次,用 OPENVIKING_ENABLED=false 运行 10 次。对比:
- 结果中的平均
execution_time - 平均 iteration 数
- Token 数(从 recording 元数据)
期望:温缓存运行在两个维度都减少 20–40%。
运行 GDPVal 或你内部的 task 套件两次:
- 一次禁用 Viking
- 一次启用 Viking(在 prime 记忆库之后)
OpenSpace 公布数字:46% 更少 token。与 Viking 叠加,期望:比 baseline(非 OpenSpace)Agent 减少 55–65% token。
如果你在团队规模下运行 Viking,考虑对以下情况告警:
| 告警 | 阈值 | 行动 |
|---|---|---|
feedback_status=failed 率 |
1h 内 >5% | 检查 Viking 健康、磁盘、队列 |
available=False 率 |
1h 内 >10% | 检查 Viking 正常运行时间 / 网络 |
hits=0 率 |
1 周后 >70% | 调查记忆抽取流水线 |
pushed_skills 单调下降 |
7 天内下降趋势 | 检查 push_skills 环境变量是否被翻转关闭 |
平均 enrichment_chars |
>5000 | 记忆膨胀 —— 考虑 Viking 侧清理 |
这些从每次执行的 viking stats dict 导出。通过 metrics exporter 管道到你的可观测性栈(Prometheus、Datadog、CloudWatch 等)—— 不内置于 OpenSpace。
第 1 天(引导期):
- 大多数 task 上
available=True→ 集成在工作 - 大多数 task 上
hits=0→ 记忆库为空,这是正确的 - 带 evolution 的 task 上
feedback=committed→ Viking 接收到反馈
第 7 天:
- 一些 task 上
hits>0→ 记忆抽取赶上了 - 重复 task 模式开始出现 iteration 减少的迹象
- 开始看到 selector 选出与无 hints 时不同的 skill
第 30 天:
- 稳定的命中率 30–60%,取决于 task 多样性
- 相对 baseline 可测量的 iteration/token 减少
- 用户偏好开始在无需明确提示的情况下塑造 Agent 行为
- 需要原始 Agent 性能的 benchmark 运行
- 有严格确定性要求的 task
- 隐私敏感的一次性 task,连抽取的 abstract 都不能离开执行
- 记忆填充成本超过节省的单 task 流水线
- 学习状态会混淆信号的调试会话
其他所有情况,保持开启。失败模式是"集成不可见",不是"集成破坏 OpenSpace"。