OpenViking OpenClaw 插件 Recall Trace 召回可观测实践:从配置、工具到 Gateway API 的完整排障指南
【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking
本文面向插件使用者、集成方与排障同学,完整讲解 OpenViking OpenClaw 插件中召回可观测能力 Recall Trace 的启用配置、数据模型、Agent 工具、Slash 命令、Gateway HTTP API 与典型排障路径。读完你将掌握:如何打开 trace 记录、如何用一条命令定位“为什么没有召回”“为什么结果不符合预期”,以及如何理解内存环形缓存与 JSONL 持久化两条查询链路。
1. Recall Trace 是什么
Recall Trace 是 OpenViking 插件的召回可观测能力:启用后,插件会把每一次自动召回(auto recall)、显式记忆召回(memory recall)、资源搜索(ov_search)、归档搜索(ov_archive_search)记录成结构化 trace,让“这轮到底查了什么、查到了什么、为什么没查出来”不再是黑盒。
核心实现位于 recall-trace.ts,插件注册阶段通过 openviking-runtime-state.ts 创建RecallTraceRecorder,其围绕四个问题展开:
- 本轮到底搜索了哪些范围:
resource、user、agent? - 每个范围请求的目标 URI、limit、阈值和耗时是多少?
- 候选结果有哪些?最终哪些被注入 prompt 或展示给用户?
- 为什么没有召回:没有 session 上下文、低于分数阈值、预算不足,还是搜索失败?
此外,启用持久化后,即便 Gateway 重启,也能从 JSONL 文件查到近期 trace。
2. 启用方式与配置项
2.1 最小启用配置
关键点:必须显式设置
traceRecall: true。只配置recallResources或recallTargetTypes只会改变召回范围,不会启用 trace 记录。
{ "plugins": { "entries": { "openviking": { "config": { "traceRecall": true } } } } }traceRecall在配置解析中只有等于布尔值true才会启用(见 config.ts 的cfg.traceRecall === true);插件注册阶段也只有启用后才创建RecallTraceRecorder(见 openviking-runtime-state.ts)。若未启用,查询接口返回空结果并带traceRecall is disabledwarning(见 openviking-recall-trace-runtime.ts)。
2.2 推荐排障配置
{ "plugins": { "entries": { "openviking": { "config": { "traceRecall": true, "traceRecallPersist": true, "traceRecallDir": "~/.openclaw/openviking/recall-traces", "traceRecallRetentionDays": 14, "traceRecallMaxEntries": 1000, "traceRecallMaxResultsPerSearch": 20, "traceRecallPreviewChars": 240, "traceRecallQueryMaxChars": 4000, "traceRecallQueryMaxDays": 14, "recallTargetTypes": ["user", "agent", "resource"] } } } } }2.3 Trace 配置项一览
下表所有默认值均来自 config.ts 的常量定义,解析逻辑在 config.ts:
| 配置项 | 类型 | 默认值 | 取值/限制 | 说明 |
|---|---|---|---|---|
traceRecall | boolean | false | 必须为true才启用 | 总开关;关闭时不记录 trace,查询接口返回空并带traceRecall is disabledwarning |
traceRecallPersist | boolean | false | true/false | 是否写入本地 JSONL;关闭时只保留内存环形缓存 |
traceRecallDir | string | ~/.openclaw/openviking/recall-traces | 支持~展开 | JSONL 文件目录;按 UTC 日期写入YYYY-MM-DD.jsonl(expandHomeDir处理,见 recall-trace.ts) |
traceRecallRetentionDays | number | 14 | 1到3650 | 写入新 trace 时清理超过保留期的 JSONL 文件 |
traceRecallLoadRecentDays | number | 2 | 0到3650 | 配置已解析保留,当前查询路径主要通过内存 + 持久化 fallback 获取数据 |
traceRecallMaxEntries | number | 1000 | 1到1000000 | 内存 ring buffer 最大条数,超出后淘汰最旧记录 |
traceRecallMaxResultsPerSearch | number | 20 | 1到1000 | 每次子搜索最多保存多少候选结果摘要 |
traceRecallPreviewChars | number | 240 | 20到10000 | 候选摘要、选中摘要的预览字符数 |
traceRecallQueryMaxChars | number | 4000 | 200到200000 | trace 中保存的 trigger query 最大长度,超出会截断并设置queryTruncated |
traceRecallQueryMaxDays | number | 14 | 1到3650 | 查询持久化 trace 且未传since/until时最多扫描最近多少天 |
traceRecallIncludeContentByDefault | boolean | false | true/false | 查询 trace 时是否默认读取 selected URI 的内容预览;也可通过查询参数includeContent单次开启 |
traceRecallIncludeRawUserPreview | boolean | false | true/false | 是否允许把原始用户输入预览持久化到 JSONL;默认会脱敏删除 |
其中traceRecallDir在写入时会自动mkdir -p创建目录(见 recall-trace.ts);traceRecallQueryMaxChars的截断由boundTraceQuery实现,并同步设置queryTruncated标记(见 openviking-runtime-utils.ts)。
2.4 召回范围配置与 Trace 的关系
Trace 会记录实际召回范围,但召回范围本身由recallTargetTypes/recallResources决定:
| 配置 | 默认/行为 | 说明 |
|---|---|---|
recallTargetTypes | 默认['user', 'agent'] | 允许值:resource、user、agent;空值回退默认集合(常量见 config.ts) |
recallResources | 默认false | 兼容旧配置;仅在未显式配置recallTargetTypes时,把resource追加到默认召回集合 |
目标类型会被解析为 context type 搜索计划;自动召回把该计划合并进一次服务端 context search:
| resourceType | context type | 说明 |
|---|---|---|
resource | resource | 资源库。 |
user | memory | 当前用户长期记忆。 |
agent | memory | 当前 actor 的长期记忆;与user合并为一个 memory context type,由 actor routing 限定范围。 |
3. Trace 记录来源
四种来源覆盖插件内全部召回路径:
| source | operationType | 触发方式 | selected 语义 | 关键实现 |
|---|---|---|---|---|
auto_recall | semantic_find(兼容值) | Context Engine 在回复前发起服务端 context search | 服务端组装并注入<relevant-memories>的记忆或资源,injected: true | auto-recall.ts 的buildAutoRecallContext() |
memory_recall | semantic_find | Agent 调用memory_recall工具 | 工具返回给模型的记忆,通常injected: true且displayed: true | openviking-memory-recall-tools.ts |
ov_search | semantic_find | Agent 调用ov_search工具或用户执行/ov-search | 搜索结果列表中展示的资源/技能/记忆,displayed: true | trace 记录在 openviking-query-runtime.ts 的searchOpenViking流程中 |
ov_archive_search | archive_grep | Agent 调用ov_archive_search工具 | 展示的归档匹配行,包含line,displayed: true | openviking-archive-tools.ts |
从源码实现看,自动召回的 trace 记录遵循“诊断尽力而为”原则:注释明确说明“Trace persistence is diagnostic best-effort; never put JSONL flush latency on the auto-recall critical path”(见 auto-recall.ts),即记录 trace 不会阻塞回复主流程。
4. Trace 数据结构
4.1RecallTraceEntry
RecallTraceEntry的完整类型定义在 recall-trace.ts。
| 字段 | 类型 | 说明 |
|---|---|---|
schemaVersion | '1.0' | Trace schema 版本。 |
traceId | string | Trace 唯一 ID,通常形如<source>-<timestamp>-<random>。 |
ts | number | Unix timestamp,毫秒。 |
sessionId | string? | OpenClaw session ID。 |
sessionKey | string? | OpenClaw session key。 |
ovSessionId | string? | 映射后的 OpenViking session ID。 |
agentId | string? | 实际发送到 OpenViking 的 agent ID。 |
source | enum | auto_recall、memory_recall、ov_search、ov_archive_search。 |
operationType | enum | semantic_find或archive_grep。 |
resourceTypes | array | 本次 trace 覆盖的召回类型:resource、user、agent。 |
trigger.query | string | 触发搜索的查询文本,受traceRecallQueryMaxChars限制。 |
trigger.derivedKeywords | string[]? | 派生关键词;归档搜索通常保存原 query。 |
trigger.rawUserTextPreview | string? | 原始用户输入预览;默认不持久化。 |
trigger.queryTruncated | boolean? | query是否因过长被截断。 |
searches | array | 本次 trace 中每个逻辑 context type 的搜索明细。 |
selected | array | 最终被注入或展示的结果。 |
stats | object | 候选数、选中数、注入数、估算 token。 |
4.2searches[]
字段定义见 recall-trace.ts。
| 字段 | 类型 | 说明 |
|---|---|---|
resourceType | resource|user|agent|archive | 当前子搜索类型。 |
targetUriInput | string? | 输入或计划中的目标 URI。 |
targetUriResolved | string? | 解析后的目标 URI。 |
limit | number | 请求 limit。自动召回直接使用recallLimit;显式memory_recall可先扩大候选数。 |
scoreThreshold | number? | 分数阈值。自动召回由服务端应用;显式memory_recall仍可在本地后处理。 |
durationMs | number | 子搜索耗时,毫秒。 |
total | number | OpenViking 返回或插件统计的候选总数。 |
results | array | 候选结果摘要,最多traceRecallMaxResultsPerSearch条。 |
archiveId | string? | 归档搜索指定 archive 时存在。 |
caseInsensitive | boolean? | 归档 grep 是否大小写不敏感。 |
error | string? | 子搜索失败或跳过原因。 |
4.3results[]
字段定义见 recall-trace.ts。
| 字段 | 类型 | 说明 |
|---|---|---|
uri | string | 候选 URI。 |
resourceType | string? | 候选类型。归档匹配为archive。 |
category | string? | OpenViking 返回的分类。 |
score | number? | 相似度分数。 |
level | number? | OpenViking memory 层级;插件优先选 leaf memory。 |
abstractPreview | string? | 摘要预览。 |
resultType | enum | memory、resource、skill、archive_match。 |
4.4selected[]
字段定义见 recall-trace.ts。
| 字段 | 类型 | 说明 |
|---|---|---|
uri | string | 选中结果 URI。 |
resourceType | string? | 选中结果类型。 |
category | string? | 分类。 |
score | number? | 分数。 |
line | number? | 归档匹配所在行号。 |
abstractPreview | string? | 选中结果摘要预览。 |
contentPreview | string? | 仅当查询时开启includeContent,并成功读取 URI 内容后出现。 |
readError | string? | 开启includeContent但读取内容失败时出现。 |
injected | boolean? | 是否注入模型上下文。 |
displayed | boolean? | 是否展示给用户或工具调用结果。 |
skippedReason | enum? | 预留跳过原因:score_threshold、dedupe、non_leaf、budget、not_top_k、search_error。 |
4.5 返回示例
{ "schemaVersion": "1.0", "traceId": "ov_search-1780329600000-a1b2c3d4", "ts": 1780329600000, "sessionId": "test-session", "sessionKey": "agent:main:example", "ovSessionId": "8d6e...", "agentId": "main", "source": "ov_search", "operationType": "semantic_find", "resourceTypes": ["resource"], "trigger": { "query": "OpenViking trace API" }, "searches": [ { "resourceType": "resource", "targetUriInput": "viking://resources", "targetUriResolved": "viking://resources", "limit": 20, "scoreThreshold": 0, "durationMs": 35, "total": 1, "results": [ { "uri": "viking://resources/project/spec.md", "resourceType": "resource", "score": 0.88, "abstractPreview": "Recall trace design spec", "resultType": "resource" } ] } ], "selected": [ { "uri": "viking://resources/project/spec.md", "resourceType": "resource", "score": 0.88, "abstractPreview": "Recall trace design spec", "displayed": true } ], "stats": { "candidateCount": 1, "selectedCount": 1, "injectedCount": 0 } }5. Agent 工具:ov_recall_trace
5.1 用途
ov_recall_trace用于在 Agent 内部查询已记录的 trace。它不会重新调用 OpenViking 搜索接口,只查询插件记录;仅当传入includeContent: true或配置了traceRecallIncludeContentByDefault: true时,才会额外调用 OpenVikingread给 selected 结果补充内容预览。工具注册见 openviking-recall-trace-tools.ts。
5.2 参数
参数类型定义见 openviking-recall-trace-tools.ts:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
turn | 'latest'|'all' | 'latest' | latest只返回过滤后最新 1 条;all返回最多limit条。 |
traceId | string | 无 | 精确查询某条 trace。 |
sessionId | string | 当前 session | 按 OpenClaw session ID 过滤;未传时默认当前工具上下文 session。 |
sessionKey | string | 无 | 按 OpenClaw session key 过滤。 |
ovSessionId | string | 当前 session 映射值 | 按 OpenViking session ID 过滤。 |
source | string | 无 | auto_recall、memory_recall、ov_search、ov_archive_search。 |
resourceTypes | string[] 或逗号分隔 string | 无 | 按 trace 的resourceTypes过滤;允许resource、user、agent。 |
since | number | 无 | 毫秒时间戳下界,包含。 |
until | number | 无 | 毫秒时间戳上界,包含。 |
includeContent | boolean | false | 是否读取 selected URI 的内容预览;可能带来额外读请求。 |
limit | number | 20 | 最大返回条数;仅turn: 'all'时返回多条。 |
当前接口不支持自由文本模糊查询 trace trigger。需要按
source、sessionId、ovSessionId、resourceTypes、traceId或时间范围过滤。
5.3 调用示例
查询当前 session 最新一条 trace:
{ "turn": "latest" }查询当前 session 内最近 10 条ov_searchtrace:
{ "turn": "all", "source": "ov_search", "limit": 10 }查询某条 trace 并补充 selected 内容预览:
{ "traceId": "ov_search-1780329600000-a1b2c3d4", "includeContent": true }按时间范围和召回类型查询:
{ "turn": "all", "resourceTypes": ["user"], "since": 1780320000000, "until": 1780406399999, "limit": 50 }5.4 返回值
工具返回 OpenClaw ToolResult:
{ "content": [ { "type": "text", "text": "## Trace 1: ov_search\ntraceId: ...\nquery: ..." } ], "details": { "action": "queried", "count": 1, "lookupLayer": "memory", "warnings": [], "entries": [] } }| 字段 | 说明 |
|---|---|
content[0].text | 人类可读摘要,由formatRecallTraceText生成(见 openviking-recall-trace-runtime.ts,展示traceId、query、resourceTypes、stats 及 selected URI 与分数百分比)。 |
details.count | 本次返回条数。 |
details.lookupLayer | memory表示来自内存环形缓存;persistent表示内存未命中后从 JSONL 文件 fallback 查询。 |
details.warnings | 读取 JSONL 或 selected 内容失败等 warning。 |
details.entries | 完整结构化 trace 数组。 |
6. Slash 命令:/ov-recall-trace
6.1 用途
用户可以在 OpenClaw 会话中直接执行/ov-recall-trace查询 trace。命令注册见 openviking-command-definitions.ts。
6.2 参数
Slash 命令使用--kebab-case参数,解析逻辑parseRecallTraceCommandArgs见 openviking-command-definitions.ts:
| 参数 | 对应工具参数 | 示例 |
|---|---|---|
--turn | turn | --turn all |
--trace-id | traceId | --trace-id ov_search-1780329600000-a1b2c3d4 |
--session-id | sessionId | --session-id test-session |
--session-key | sessionKey | --session-key agent:main:xxx |
--ov-session-id | ovSessionId | --ov-session-id 8d6e... |
--source | source | --source auto_recall |
--resource-types | resourceTypes | --resource-types user,agent |
--since | since | --since 1780320000000 |
--until | until | --until 1780406399999 |
--include-content | includeContent | --include-content |
--limit | limit | --limit 20 |
6.3 示例
/ov-recall-trace --turn all --source auto_recall --limit 5/ov-recall-trace --trace-id ov_search-1780329600000-a1b2c3d4 --include-content/ov-recall-trace --turn all --resource-types user,agent --since 1780320000000 --until 17804063999996.4 返回值
Slash 命令返回:
{ "text": "## Trace 1: auto_recall\ntraceId: ...", "details": { "count": 1, "lookupLayer": "memory", "warnings": [], "entries": [] } }返回结构与ov_recall_trace的details基本一致;text是人类可读摘要,details.entries是机器可读数据。
7. Gateway HTTP API
插件 service 启动时会尝试注册 Recall Trace Gateway 路由(见 openviking-services.ts)。如果当前 Gateway 不支持 route adapter,日志会提示使用ov_recall_trace工具或/ov-recall-trace命令替代。路由定义集中在 recall-trace-routes.ts,除了本文讲解的列表与单条查询外,还提供/api/openviking/recall-traces/latest-ov-search-list与/api/openviking/uri-detail两个辅助端点。
7.1GET /api/openviking/recall-traces
用途
查询多条 trace。路由 handler 见 openviking-recall-trace-runtime.ts。
Query 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
turn | latest|all | latest | 是否只返回最新一条。 |
traceId | string | 无 | 精确过滤 trace ID。 |
sessionId | string | 无 | OpenClaw session ID。 |
sessionKey | string | 无 | OpenClaw session key。 |
ovSessionId | string | 无 | OpenViking session ID。 |
source | string | 无 | auto_recall、memory_recall、ov_search、ov_archive_search。 |
resourceTypes | string | 无 | 逗号或换行分隔,如user,agent。 |
since | number | 无 | 毫秒时间戳下界。 |
until | number | 无 | 毫秒时间戳上界。 |
includeContent | boolean/string | 配置默认值 | 支持1、true、yes。 |
limit | number | 20 | 最大返回条数。 |
请求示例
curl 'http://127.0.0.1:<gateway-port>/api/openviking/recall-traces?turn=all&source=ov_search&limit=10'curl 'http://127.0.0.1:<gateway-port>/api/openviking/recall-traces?turn=all&resourceTypes=user,agent&since=1780320000000&until=1780406399999'返回值
Handler 返回:
{ "status": 200, "body": { "ok": true, "entries": [], "lookupLayer": "memory", "warnings": [] } }根据 Gateway 适配层,客户端通常会看到body中的 JSON:
{ "ok": true, "entries": [], "lookupLayer": "memory", "warnings": [] }7.2GET /api/openviking/recall-traces/:traceId
用途
按traceId查询单条 trace。实现上该路由把 path 参数转换为列表接口的traceIdquery 后复用同一 handler(见 recall-trace-routes.ts)。
Path 参数
| 参数 | 类型 | 说明 |
|---|---|---|
traceId | string | 需要查询的 trace ID。 |
Query 参数
除traceId外,支持与列表接口相同的 query 参数,例如includeContent=true。
请求示例
curl 'http://127.0.0.1:<gateway-port>/api/openviking/recall-traces/ov_search-1780329600000-a1b2c3d4?includeContent=true'返回值
{ "ok": true, "entries": [ { "traceId": "ov_search-1780329600000-a1b2c3d4", "source": "ov_search" } ], "lookupLayer": "memory", "warnings": [] }8. 查询与存储行为
8.1 内存 Ring Buffer
RecallTraceMemoryStore保存最近 N 条 trace,N 由traceRecallMaxEntries控制。从源码看(recall-trace.ts):
- 超出容量时删除最旧记录(
entries.shift())。 - 查询时先按
traceId、source、sessionId、sessionKey、ovSessionId、since/until、resourceTypes过滤,再按ts降序排序。 turn: 'latest'返回过滤结果中最新一条;turn: 'all'返回最多limit条。
8.2 JSONL 持久化
启用traceRecallPersist: true后,每条 trace 会追加到traceRecallDir/YYYY-MM-DD.jsonl(RecallTraceJsonlStore.append见 recall-trace.ts):
- 文件名使用 trace 的 UTC 日期(
jsonlFileNameForTimestamp)。 - 默认不会持久化
trigger.rawUserTextPreview,除非设置traceRecallIncludeRawUserPreview: true(见 recall-trace.ts 的entryForPersistence)。 - 写入新 trace 时会同步清理超过
traceRecallRetentionDays的旧 JSONL 文件(pruneExpiredFiles)。 - 查询时如果内存命中,直接返回内存结果;只有内存未命中且存在持久化 store,才 fallback 扫描 JSONL(
queryWithFallback见 recall-trace.ts)。 - JSONL 中的损坏行会被跳过,并返回 warning(见 recall-trace.ts);每条记录写入前还会经过
isRecallTraceEntry结构校验。
8.3includeContent行为
默认 trace 只保存摘要预览,不读取完整内容。查询时开启includeContent后,插件会对每个selected[].uri调用 OpenViking read,并把结果压缩到selected[].contentPreview(见 openviking-recall-trace-runtime.ts);若读取失败,会在该 item 上写入readError并在warnings中补充原因。
建议只在定位具体 trace 时使用includeContent,避免一次查询大量 trace 触发额外读请求。
9. 常见使用场景
9.1 解释为什么自动召回没有注入记忆
- 打开 trace:
traceRecall: true。 - 复现一轮会话。
- 查询最新自动召回:
/ov-recall-trace --source auto_recall重点查看:
searches[].error是否有搜索失败。searches[].total是否为 0。stats.candidateCount、stats.selectedCount、stats.injectedCount是否逐步变少。trigger.queryTruncated是否为 true。
9.2 查看显式memory_recall查了哪些空间
/ov-recall-trace --turn all --source memory_recall --limit 5重点查看resourceTypes和searches[].targetUriResolved,确认是否默认查了viking://user/memories与agent recall target,或是否按请求resourceTypes改变范围。
9.3 排查/ov-search或ov_search为什么结果不符合预期
/ov-recall-trace --turn all --source ov_search --include-content --limit 3重点查看:
trigger.query是否与预期一致。searches[].targetUriInput是否是正确资源目录。results[]候选是否包含预期文档但未进入selected[]。selected[].contentPreview是否能读到真实内容。
9.4 排查归档搜索没有命中
/ov-recall-trace --turn all --source ov_archive_search --limit 5重点查看:
operationType是否为archive_grep。searches[].targetUriResolved是否指向正确 session archive。searches[].caseInsensitive是否为 true。stats.candidateCount与selected[].line。
10. 错误与排障
| 现象 | 可能原因 | 排查/解决 |
|---|---|---|
查询为空且 warning 包含traceRecall is disabled | 未配置traceRecall: true | 显式启用traceRecall,重启 Gateway 后复现。 |
配了recallTargetTypes但没有 trace | 召回范围配置不等于 trace 开关 | 同时设置traceRecall: true。 |
| Gateway 路由不可用 | 当前 Gateway 未提供registerRouteadapter | 使用 Agent 工具ov_recall_trace或 Slash 命令/ov-recall-trace。 |
| 重启后查不到历史 trace | 未开启traceRecallPersist,或超过traceRecallQueryMaxDays查询窗口 | 开启持久化,必要时传since/until或调大traceRecallQueryMaxDays。 |
includeContent后有readError | selected URI 已不可读、权限不足或 OpenViking read 失败 | 查看warnings与selected[].readError,再用ov_read验证 URI。 |
| JSONL 查询有 corrupted warning | 持久化文件存在损坏行 | 插件会跳过损坏行返回有效记录;可检查对应YYYY-MM-DD.jsonl。 |
11. 测试覆盖与二次开发注意点
相关单元测试集中在:
- tests/ut/recall-trace.test.ts:覆盖召回类型归一化、搜索计划、内存 ring buffer、JSONL 持久化、隐私控制(如“不持久化原始用户预览”“JSONL 追加失败时内存仍可查询”“先查内存再 fallback 持久化”等场景,测试见
RecallTraceMemoryStore/RecallTraceJsonlStore/RecallTraceRecorder三个 describe 块)。 - tests/ut/tools.test.ts:
ov_recall_trace工具、Slash 命令、Gateway 路由、includeContent、显式召回 trace、查询不重新触发搜索。
建议修改 trace 行为后至少运行:
npm run typecheck npm test -- tests/ut/recall-trace.test.ts tests/ut/tools.test.ts12. 快速参考
开启 trace
{ "traceRecall": true, "traceRecallPersist": true }查最新 trace
/ov-recall-trace查最近 10 条自动召回
/ov-recall-trace --turn all --source auto_recall --limit 10查指定 trace 详情
/ov-recall-trace --trace-id <traceId> --include-contentHTTP 查询
curl 'http://127.0.0.1:<gateway-port>/api/openviking/recall-traces?turn=all&source=memory_recall&limit=10'HTTP 查询单条
curl 'http://127.0.0.1:<gateway-port>/api/openviking/recall-traces/<traceId>?includeContent=true'【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考