OpenViking Runtime Query Config 指南:OpenClaw 插件的运行时召回与检索参数动态调优
【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking
OpenViking Runtime Query Config(运行时查询配置)是 OpenClaw 插件(位于 examples/openclaw-plugin)中一项面向运维与调试的实时调参能力:操作员可以在不重启 OpenClaw、不重载插件的前提下,动态调整召回数量、候选规模、得分阈值、目标资源类型与本地排序权重,并即时作用于后续的召回与检索流程。本指南以 openviking-runtime-query-config.md 为骨架,结合仓库内query-config.ts、openviking-query-config-command.ts、auto-recall.ts等源码实现,完整讲解配置分层、作用域、支持字段、斜杠命令、持久化机制与工具交互,帮助读者掌握一套可复制、可验证的线上调参工作流。
为什么需要运行时查询配置
OpenViking 的召回链路由自动召回(auto-recall,随回合上下文注入)与显式工具(memory_recall、ov_search)两部分组成。在过去,这些行为只能通过静态插件配置(如recallLimit、recallScoreThreshold)在启动时固化,线上调试召回不足、误召回、注入超预算等问题都需要修改配置并重启进程,成本高、反馈周期长。
Runtime Query Config 的设计目标正是为了解决这一痛点:将召回相关的关键参数提升为可按 peer / session 维度实时读写的运行时状态,覆盖召回上限(recall limit)、候选数量(candidate count)、得分阈值(score threshold)、目标资源类型(resource types)与排序权重(ranking weights)。它从更广泛的 #2613 工作项移植而来,并在拆分后的 OpenViking 插件 PR 系列中落地为独立能力(见 openviking-runtime-query-config.md 的说明)。
配置分层:每次请求的生效优先级
有效查询配置(Effective Query Config)按每次请求动态解析,优先级从高到低为:
explicit tool request arguments(工具调用的显式参数) > session runtime config(会话级运行时配置) > peer runtime config(peer 级运行时配置) > static plugin config(静态插件配置) > code defaults(代码内置默认值)高层配置逐字段覆盖低层。具体合并规则由 query-config.ts 中的RuntimeQueryConfigStore.getEffective()与applyLayer()实现:
- 对象字段浅合并:如
rankingWeights、categoryWeights、resourceTypeWeights这类嵌套权重对象,高层只覆盖自己声明了的键,未声明的键保留低层值。源码中通过{ ...effective.rankingWeights, ...params.rankingWeights }逐层展开实现; - 数组字段整体替换:如
resourceTypes,高层一旦声明即以新数组整体替换低层数组,不做逐元素合并; - 候选数联动:当某层只设置了
recallLimit而未显式设置candidateLimit时,candidateLimit会随recallLimit × candidateMultiplier重新推导;而如果低层已经显式设置了candidateLimit,高层只改recallLimit不会覆盖它(测试preserves lower-priority explicit candidateLimit...专门验证了这一行为,见 query-config.test.ts)。
作用域:peer 与 session
运行时配置具有两个持久化作用域,对应存储文件(RuntimeFile)中的claws与sessions两个 bucket:
| 作用域 | 含义 |
|---|---|
peer | 作用于当前 OpenClaw peer/assistant 身份,跨会话生效(存储中以 peerId 为键)。 |
session | 仅作用于当前会话身份(存储中优先以ov:${ovSessionId},其次session:${sessionId},最后key:${sessionKey}为键,见resolveSessionQueryConfigKey())。 |
为了命令兼容,--scope claw被接受为peer的别名——源码中getStringFlag(parsed.flags, "scope") === "claw" ? "claw" : "session"的判定使claw与peer在存储层共用同一个 bucket(openviking-query-config-command.ts)。
支持的字段与默认值
以下字段可通过运行时配置进行调优(完整类型定义见 query-config.ts 中的RuntimeQueryParams):
| 字段 | 用途 | 归一化钳制范围 |
|---|---|---|
recallLimit | 最终注入或展示的召回结果数量 | 整数 1–50 |
candidateLimit | 显式memory_recall请求本地排序/过滤前的候选数量 | 整数 1–200 |
candidateMultiplier | 当candidateLimit未显式设置时,用于推导显式memory_recall候选数的乘数 | 整数 1–20(默认 4) |
scoreThreshold | 最低得分阈值;自动召回将其传给上下文搜索,显式memory_recall还会做本地后处理过滤 | 数值 0–1 |
maxInjectedChars | 注入预算;自动召回按每 token 4 字符将其转换为服务端max_tokens | 整数 100–50 000 |
recallPreferAbstract | 是否优先摘要注入:自动召回固定服务端detail=abstract;显式memory_recall在本地优先摘要 | 布尔值 |
resourceTypes | 默认语义召回目标类型:user、agent、resource | 允许值仅这三种,非法值直接报错 |
targetUri | 强制搜索单个viking://目标 URI | 必须以viking://开头 |
ovSearchLimit | ov_search的默认结果数量 | 整数 1–100(默认 10) |
rankingWeights | 显式memory_recall的本地排序权重,如baseScore、lexicalOverlapMax | 每项数值 0–2 |
categoryWeights | 显式memory_recall可选的分类级权重调整 | 每项数值 -1–2 |
resourceTypeWeights | 显式memory_recall可选的资源类型权重调整 | 每项数值 -1–2 |
静态配置层(config.ts)提供的代码默认值与之衔接:DEFAULT_RECALL_LIMIT = 6、DEFAULT_RECALL_SCORE_THRESHOLD = 0.15、DEFAULT_RECALL_MAX_INJECTED_CHARS = 4000、DEFAULT_RECALL_PREFER_ABSTRACT = false、默认目标类型["user", "agent"](资源召回需通过recallResources/recallTargetTypes显式开启)。
数值归一化与钳制(源码细节)
所有运行时数值在写入前都会经过 query-config.ts 的normalizeRuntimeQueryParams()钳制,保证写入值合法:
recallLimit钳制到[1, 50],candidateMultiplier到[1, 20],candidateLimit到[1, 200];scoreThreshold钳制到[0, 1];maxInjectedChars到[100, 50000];ovSearchLimit到[1, 100];- 权重类字段通过
shallowNumberRecord()逐键钳制:rankingWeights每项[0, 2],categoryWeights/resourceTypeWeights每项[-1, 2](允许负权重做降权); - 若
candidateLimit < recallLimit,会自动将candidateLimit提升至recallLimit并产生"candidateLimit was raised to recallLimit"警告; targetUri不以viking://开头会直接抛错,防止误配置外部 URI。
以上行为均有单元测试覆盖,见 query-config.test.ts 中的clamps values and normalizes resourceTypes与rejects non-viking targetUri。
自动召回与显式工具对字段的消费差异
需要特别区分两类消费路径(文档强调的要点,代码中也严格分开):
- 自动召回(auto-recall):使用一次会话感知的上下文搜索(
searchContext),只消费recallLimit、scoreThreshold、maxInjectedChars、recallPreferAbstract、resourceTypes五个字段。在 auto-recall.ts 中可以看到它把maxInjectedChars按LEGACY_CHARS_PER_TOKEN = 4换算为服务端max_tokens:maxTokens = min(32000, max(64, round(maxInjectedChars / 4))),并将recallPreferAbstract映射为detail: "abstract"; - 显式
memory_recall:候选扩展与本地排序权重字段(candidateLimit、candidateMultiplier、rankingWeights、categoryWeights、resourceTypeWeights)仅对其生效。在 openviking-memory-recall-tools.ts 中,candidateLimit作为find()的请求候选数,随后经postProcessMemories()与pickMemoriesForInjection()本地排序、过滤,最终按recallLimit注入。
另外,会话历史(session history)不是语义召回目标;需要做会话档案恢复时,应使用ov_archive_search与ov_archive_expand。
斜杠命令操作
插件注册了ov-query-config命令(定义见 openviking-command-definitions.ts),支持get/set/unset/reset四个动作,底层由 openviking-query-config-command.ts 的createOpenVikingQueryConfigCommandHandler()实现,并内置了完整的引号、转义、--flag value/--flag=value两种赋值形式的命令行解析。
查询(get)
/ov-query-config get --scope session /ov-query-config get --scope peerget返回生效配置(effective config)以及每个字段的来源(request/session/claw/static/default),这对于排查"为什么这个值生效"非常有用——例如某个值明明在 peer 作用域设置过,却发现来源是session,说明存在更高层的覆盖。
设置(set)
/ov-query-config set --scope peer \ --recallLimit 6 \ --candidateLimit 40 \ --scoreThreshold 0.15 \ --resourceTypes user,agent,resource/ov-query-config set --scope session \ --ovSearchLimit 8 \ --recallPreferAbstract true- 数值参数直接传数字;布尔参数支持
true/false/1/0/yes/no/on/off; --resourceTypes使用逗号分隔,仅允许user、agent、resource,传入其他值会报错;- 同一作用域内多次
set是增量合并的:先set recallLimit=2再set scoreThreshold=0.4,两者同时生效(测试merges incremental set calls within the same runtime scope验证了这一行为); - 空 patch(未提供任何有效参数)会被拒绝,提示
No query config parameters provided for /ov-query-config set。
权重设置(weights)
/ov-query-config set --scope peer \ --weight baseScore=1,leaf=0.12,lexicalOverlapMax=0.2 \ --categoryWeight preference=0.2,event=0.1 \ --resourceTypeWeight user=0.15,resource=0.1权重类参数使用key=value逗号分隔的映射语法,命令解析同时接受单数/复数别名:--weight或--rankingWeights、--categoryWeight或--categoryWeights、--resourceTypeWeight或--resourceTypeWeights(见parseQueryConfigPatch())。rankingWeights的代码默认值(query-config.ts 的DEFAULT_RANKING_WEIGHTS)为:
| 权重键 | 默认值 | 含义 |
|---|---|---|
baseScore | 1 | 基础得分 |
leaf | 0.12 | 叶子节点偏好 |
event | 0.1 | 事件类目权重 |
preference | 0.08 | 偏好类目权重 |
lexicalOverlapMax | 0.2 | 词法重叠的最大加成 |
取消(unset)与重置(reset)
/ov-query-config unset recallLimit scoreThreshold rankingWeights --scope sessionunset逐字段删除,未声明字段回落到低层配置(测试unsets one field and resets scoped runtime config验证:unsetrecallLimit后该值回落到静态配置的 6,而未 unset 的scoreThreshold仍保留 0.4)。
/ov-query-config reset --scope session /ov-query-config reset --scope peerreset删除整个作用域下当前 key 的全部运行时记录,完全回落到静态配置与默认值。
持久化与热加载
运行时查询配置默认只保存在内存中(RuntimeQueryConfigStore.createInMemory())。需要持久化时,在 OpenClaw 插件配置中指定runtimeQueryConfigPath:
{ "plugins": { "entries": { "openviking": { "config": { "runtimeQueryConfigPath": "/path/to/runtime-query-config.json" } } } } }持久化文件遵循schemaVersion: "1.0"结构(query-config.ts 的RuntimeFile类型),包含claws与sessions两个记录桶,每条记录带updatedAt、updatedBy与peerId元数据。结合存储实现,有三点值得注意:
- 容错:文件缺失时存储以空记录启动;文件格式损坏(JSON 解析失败或 schemaVersion 不匹配)时,插件保留上一次已知良好的内存配置并告警,不会破坏召回链路——
loadFromDisk()与reloadIfChanged()的 catch 分支都遵循"保留最后已知良好配置"策略; - 热重载:每次
getEffective()前会通过stat()比对文件 mtime,检测到外部修改(例如运维手动编辑 JSON 文件)即重新加载,无需重启; - 原子写入:
persist()采用"临时文件 +rename"的原子替换方式,并通过内部写队列(writeQueue)串行化并发写操作;测试keeps the persistence queue usable after a transient write failure还验证了写入失败后队列仍可继续使用,避免一次磁盘故障卡死后续所有配置写入。
runtimeQueryConfigPath在静态配置解析时支持~主目录展开(见 config.ts 中的expandHomeDir)。
与工具的交互规则
memory_recall与ov_search把运行时配置作为默认值使用,而当前工具调用上的显式参数始终优先:
- 若 peer 作用域设置了
ovSearchLimit=8,则ov_search默认返回 8 条结果; - 若某次
ov_search调用显式传入limit=3,该请求只返回 3 条结果,且不会改写运行时配置(ov_search的参数解析见 openviking-query-tools.ts,其limit仅透传给本次请求); - 若 session 作用域设置了
scoreThreshold,则仅该会话内覆盖 peer/静态阈值,其他会话不受影响; memory_recall的limit、scoreThreshold、targetUri、resourceTypes显式参数通过getEffective(ctx, requestOverrides)作为最高优先级request层参与合并(openviking-memory-recall-tools.ts)。
测试 query-config.test.ts 的merges default, static, claw, session, and request layers with field sources完整演示了这一优先级:静态recallLimit=6→ peer 设recallLimit=8→ session 设recallLimit=3→ 请求显式scoreThreshold=0.05,最终recallLimit=3(来源session)、scoreThreshold=0.05(来源request)、resourceTypes保持 peer 层的["resource"](来源claw)。
验证与测试覆盖
拆分的实现由 query-config.test.ts 等单元测试覆盖,对应文档"Verification"一节的各项能力:
- 归一化与持久化:
clamps values and normalizes resourceTypes、persists runtime config and reloads modified files; - 层优先级:
merges default, static, claw, session, and request layers with field sources,以及"低层显式 candidateLimit 不被高层 recallLimit 覆盖"的专项用例; - 空 patch 拒绝:
set无有效参数时报错(命令层逻辑,见 openviking-query-config-command.ts); peer与claw作用域兼容:命令层将--scope claw映射为clawbucket,与 peer 共用存储;memory_recall/ov_search运行时默认值:工具通过queryConfigStore.getEffective()读取运行时默认;- 显式请求参数覆盖运行时默认:
getEffective()的requestOverrides作为最高层合并。
典型调参场景小结
- 线上召回过多/过少:用
--scope session --recallLimit N临时调整单会话注入量,验证效果后再决定是否提升到 peer 或固化到静态配置; - 误召回噪声大:调高
scoreThreshold,或通过--resourceTypeWeight降低某类资源权重、--categoryWeight削弱低价值类目; - 注入超预算:收紧
maxInjectedChars,自动召回会按 4 字符/token 换算服务端 token 预算; - 定向排查:
--scope peer设置targetUri强制收敛搜索范围,配合get返回的 per-field sources 快速定位覆盖来源。
需要再次强调的是,本能力仅针对语义召回链路(auto-recall /memory_recall/ov_search);会话历史恢复请使用ov_archive_search与ov_archive_expand,两者不在运行时查询配置的管辖范围内。
【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考