news 2026/9/10 9:48:51

OpenViking Tool Stub 设计:从 head+tail 截断到类型化确定性摘要的演进与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenViking Tool Stub 设计:从 head+tail 截断到类型化确定性摘要的演进与实践

OpenViking Tool Stub 设计:从 head+tail 截断到类型化确定性摘要的演进与实践

【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking

导读

本文以 OpenViking 仓库中的 tool-stub-design.md 设计文档为核心骨架,结合 tool_result_synopsis.py、tool_result_store.py、session.py 等源码实现,系统讲解 OpenViking 的tool stub能力:如何识别大体积 tool output、如何将其外置到ToolResultStore、如何用类型化规则摘要替代简单截断生成 preview stub、以及如何通过read/search/list回溯原文。读完本文,你将掌握这一闭环的端到端工作流程、八种摘要类型的识别与处理规则、Stub 文本结构、配置参数及测试边界,能够直接用于排查上下文占用问题或扩展新的摘要类型。


一、概述:什么是 Tool Stub

在 Agent 会话中,工具(tool)的输出往往体积巨大,比如一段 JSON API 响应、一份 CSV 导出、一段日志或一段源码。如果把这些原始输出全部塞进会话上下文,会快速挤占上下文窗口,导致 token 浪费、成本上升、模型注意力分散。

OpenViking 原生已经支持tool result preview:原有链路能在 session 写入阶段把过大的 tool outputexternalize(外置),并在ToolPart中留下一个 preview stub,同时保留 ref 供后续回溯。

本次 Tool Stub 工作的重点不是新建 externalize 机制,而是在现有能力上优化 preview 的生成方式:从偏head + tail的直接截断,升级为按内容类型输出更稳定、更可读的规则化摘要(deterministic synopsis)

保持不变的四个基础能力

  1. 哪些 tool output 需要 externalize,仍由 session 写入阶段决定;
  2. 原始内容仍写入ToolResultStore
  3. ToolPart仍保留 stub 和tool_output_ref
  4. 原文回溯方式仍是read/search/list

本次变化集中在 preview 生成层

  1. 在 session 写入阶段识别哪些 tool output 需要 externalize;
  2. 原始输出写入 session 下的 tool result store;
  3. preview 从简单截断优化为基于内容和 MIME 的 deterministic synopsis
  4. 把原始ToolPart.tool_output替换成 stub 文本,并保留tool_output_ref
  5. 后续通过read/search/list工具按 ref 回溯原文。

需要特别强调:text类型只做规则化摘要,不接 LLM。当前版本不引入 LLM 摘要,只做 deterministic 的规则化摘要,以保证稳定、低成本、可测试。


二、设计目标

  1. 在保留 OV 原生 externalize 和 ref 回溯链路的前提下,优化 preview 的可读性;
  2. 减少大 tool output 对上下文窗口的占用;
  3. 把原有偏head + tail的截断 preview,升级为按类型输出的规则化摘要;
  4. 对常见文本型输出给出稳定、可读的 deterministic synopsis;
  5. 保留原始输出,支持按 ref 精确回溯;
  6. 当前版本不做 LLM 摘要,只做 deterministic 的规则化摘要。

三、端到端流程

1. 选择哪些输出需要 externalize

入口在 session.py 的_externalize_large_tool_output_group()

当前按两类条件触发 externalize:

  1. 单个 tool output 超过threshold_chars
  2. 同一个 assistant turn 中多个 tool output 的总 inline 体积超过assistant_turn_inline_budget_chars(聚合预算)。

命中后会进入 externalization 流程。触发阈值仍沿用 OV 原有配置;规则化摘要本身不改变"什么时候 externalize"

从源码结构看,选择逻辑的细节是:先收集当前消息组中的所有ToolPartp.tool_output非空),计算整个组的原始体积总和group_original_chars;然后遍历找出单个输出超过threshold_chars的 part(reason 记为single_threshold);其余 part 按体积从大到小排序,只要"选中后的预计 inline 体积仍不低于assistant_turn_inline_budget_chars"就继续选中(reason 记为turn_budget),直到聚合体积降到预算以下或没有可选项为止。

2. 外置原始结果并替换 ToolPart

入口在 session.py 的_externalize_tool_part()。这一步会:

  1. 计算原始输出的sha256摘要,把原始tool_output写入ToolResultStore
  2. 调用 preview 生成逻辑产出 stub 文本;
  3. 用 stub 替换ToolPart.tool_output
  4. 把原始结果的 ref 写入ToolPart.tool_output_ref

被 stub 后,消息里保留的是 preview,不再是完整原始 tool result;原始结果仍在外置存储里。ToolPart上还会同步记录一组派生字段:tool_output_truncatedtool_output_original_charstool_output_preview_charstool_output_sha256tool_output_storage_uritool_output_group_idtool_output_externalized_reason等,供上层与调试使用。

需要留意的是,失败处理也有分支:如果外置写入抛异常,会依据配置的failure_mode处理——reject直接抛FailedPreconditionErrorpreview_only则退化为只在本消息内生成 preview(ref 置空);默认preserve_raw则保留原始输出不截断。

3. 生成 synopsis 和 stub

入口在 tool_result_store.py 的make_preview()

  1. 原生 preview/stub 能力保留,但内容生成逻辑从简单截断演进为typed synopsis
  2. generate_tool_result_synopsis()负责类型识别和摘要生成;
  3. render_tool_result_stub()负责把摘要渲染成最终 stub 文本。

核心实现位于 tool_result_synopsis.py 的generate_tool_result_synopsis()与 tool_result_synopsis.py 的render_tool_result_stub()

当前原则:

  1. externalize 触发阈值沿用 OV 原有配置;
  2. 常见类型使用固定规则上限生成 synopsis,不依赖preview_chars控制摘要长度
  3. preview_chars只作为无法规则化时的fallback head/tail 采样预算,并作为兼容字段保留在 stub header / metadata 中。

4. 原文回溯

当前回溯能力由 session 暴露三类工具,对应存储层实现在 tool_result_store.py 起的read()/search()/list()

  1. session.pyread_tool_result():按offset/limit读取原始内容片段;
  2. session.pysearch_tool_result():在原始内容中做关键字搜索,返回带 offset 的 snippet;
  3. session.pylist_tool_results():列出当前 session 已 externalize 的结果,支持按tool_name过滤。

此外,从源码中还可以看到一处防抖设计:_rewrite_source_read_tool_output()会把"通过openviking_tool_result_read读回的 tool output"改写为源引用,而不是当作一个新的 tool result 再次进入 externalize 流程,避免读回大文本时产生二次外置(参见 session.py)。


四、支持的数据类型

当前支持的 synopsis kind 定义在 tool_result_synopsis.py:

json / csv / tsv / yaml / xml / code / text / unknown

类型对照表

类型识别方式处理办法stub 中保留内容
jsonMIME 含json,或内容以{/[开头且能被 JSON decoder 解析解析 top-level shape,提取 keys、array length、标量示例summary + structure + notable_items
csv含逗号,且能按 CSV 读成规则表格统计行列数、列名,保留首条数据样例summary + structure
tsv含制表符,且能按 TSV 读成规则表格统计行列数、列名,保留首条数据样例summary + structure
yaml满足 YAML 启发式并能yaml.safe_load()成 dict/list提取 top-level keys 和 child typesummary + structure
xmlMIME 含xml,或内容以<开头且可解析提取 root tag、属性数、子标签计数summary + structure
code命中代码模式正则提取 imports、symbols、line_countsummary + structure + notable_items
text作为最终 fallback规则化文本摘要,不保留全文 samplesummary
unknown空内容、binary-like 内容,或带明确 MIME 但解析失败的结构化内容无法规则化时使用 fallback head/tail samplesummary + sample

五、类型识别顺序

识别顺序定义在 tool_result_synopsis.py 的generate_tool_result_synopsis()中。当前顺序如下:

  1. 空内容:直接标为unknown
  2. binary-like 内容:出现 NUL 或控制字符比例过高,标为unknown
  3. json
  4. xml
  5. tsv
  6. csv
  7. yaml
  8. code
  9. 最终 fallback 为text

这个顺序的目的是优先识别结构化格式,再识别代码,最后才把剩余内容视作普通文本。日志样式输出不再作为独立类型处理,会走text,与 lossless-claw 的 large-file exploration 行为保持一致。

从源码看,binary-like 的判定标准是:内容含\x00,或前 1000 个字符中非\n\r\t的控制字符占比超过 5%(_looks_binary(),见 tool_result_synopsis.py)。yaml的启发式(_looks_yaml(),见 tool_result_synopsis.py)要求以---开头,或出现key:后紧跟缩进行;若命中代码模式正则则直接排除。


六、各类型处理策略

JSON

实现位于 tool_result_synopsis.py 的_summarize_json()。输出重点:

  1. 顶层类型是 object 还是 array;
  2. top-level keys,最多 10 个;
  3. 子字段是 object / array / scalar;
  4. 最多若干条标量示例(_json_scalar_examples()默认 5 条,单条 80 字符以内);
  5. 若第一个 JSON value 后还有额外字符,会记入trailing_chars_after_first_json_value
  6. 不额外保留原始 JSON sample

JSON shape 的递归深度上限为 2(_JSON_MAX_DEPTH = 2),array 只采样前 3 个元素,object 最多展示 10 个 key。也就是说,一个 10MB 的 JSON 响应最终在上下文里只剩"类型 + 结构 + 少量标量示例",体积可被压缩到极小。

CSV / TSV

实现位于 tool_result_synopsis.py 的_try_table()。输出重点:

  1. 列数与数据行数;
  2. 首行列名(空单元格自动命名为column_N);
  3. 首条数据样例,最多 180 字符(_TABLE_FIRST_ROW_SAMPLE_CHARS = 180)。

只接受"列数基本一致"的表格:要求至少有 2 行且首行至少 2 列,并且前 10 行的列数必须一致,否则判定为不规则分隔文本,不会被误判成表格。TSV 的识别(制表符分隔)优先于 CSV(逗号分隔)。

YAML

实现位于 tool_result_synopsis.py 的_summarize_yaml()(配合_try_yaml(),见 tool_result_synopsis.py)。输出重点:

  1. 顶层是 object 还是 array;
  2. top-level keys,最多 30 个(_YAML_KEY_LIMIT = 30);
  3. 每个 key 对应的 child type(object / array / scalar 等);
  4. 不额外保留 YAML sample。

XML

实现位于 tool_result_synopsis.py 的_summarize_xml()。输出重点:

  1. 根标签名;
  2. 根节点属性数量;
  3. 一级子标签频次(按出现次数排序),最多 30 个(_XML_CHILD_TAG_LIMIT = 30);
  4. 不额外保留 XML sample。

Code

实现位于 tool_result_synopsis.py 的_summarize_code()。输出重点:

  1. 总行数;
  2. import 语句,最多 12 条(_CODE_IMPORT_LIMIT = 12),单条最多 180 字符;
  3. 顶层 symbol,如class Foodef barfn baz,最多 24 条(_CODE_SYMBOL_LIMIT = 24),单条最多 200 字符;
  4. 不额外保留 head/tail sample。

识别代码的正则(_CODE_PATTERNS,见 tool_result_synopsis.py)覆盖 Python(import/from ... import/class/def/async def)、JS/TS(function/export function/const/let/var)、Rust(package/use/pub fn/fn)等常见语言。当前是轻量规则识别,不做 AST 级代码摘要

Text

实现位于 tool_result_synopsis.py 的_summarize_text()text类型明确不接 LLM,只做 deterministic fallback。摘录采用固定上限,不受preview_chars影响。

日志样式输出也归入text。如果需要定位错误/警告行,优先通过 stub 中的 ref 使用openviking_tool_result_search搜索原始 payload,避免仅靠关键字把普通文档误判成日志

输出重点:

  1. Characters
  2. Words
  3. Lines
  4. Detected section headers
  5. Opening excerpt
  6. Closing excerpt

标题提取规则:

  1. Markdown 标题,如# Heading
  2. 全大写风格标题行,如SYSTEM STATUS(匹配^[A-Z0-9][A-Z0-9\s:_-]{6,}$)。

摘录规则:

  1. opening excerpt 固定最多取前500 字符_TEXT_EXCERPT_CHARS = 500);
  2. closing excerpt 固定最多取后500 字符
  3. 先压缩空白(连续空白折叠为单空格),再写入摘要;
  4. 不额外保留sample字段,避免把完整原文重新带回上下文。

section headers 最多提取 18 条(_TEXT_HEADER_LIMIT = 18),单条最长 160 字符,且去重。

Unknown

unknown是当前实现里的保守分类,不是富类型支持。当前会落到unknown的场景包括:

  1. 输出为空(标题为Empty output);
  2. 文本中存在明显二进制控制字符(标题为Binary-like output);
  3. MIME 明确标成 JSON/XML,但内容解析失败(标题为Unparsed JSON-like output/Unparsed XML-like output)。

对这些内容,stub 保留基础说明;非空内容会使用preview_chars生成 head/tail fallback sample(_head_tail_sample(),见 tool_result_synopsis.py,以--- BEGIN SAMPLE HEAD ---/--- BEGIN SAMPLE TAIL ---分段)。原始 payload 仍通过 ref 回溯。


七、Stub 文本结构

渲染逻辑位于 tool_result_synopsis.py 的render_tool_result_stub()。当前 stub 由两部分组成:

Header

首行固定为[OpenViking tool result externalized],随后包含:

  1. tool_name
  2. kind
  3. original_chars
  4. preview_chars
  5. ref
  6. sha256(仅显示前 16 位,用于安全起见不暴露完整哈希)
  7. reason

Body

按 synopsis 内容选择性渲染:

  1. Synopsis(始终存在,逐条-列表)
  2. Structure(有 structure 时渲染)
  3. Notable items(有 notable_items 时渲染)
  4. Sample(有 sample 时渲染)
  5. Explore(有 ref 时渲染)

其中Explore会提示模型使用三类回溯工具:

  1. openviking_tool_result_search(配合ref=定位相关原始片段)
  2. openviking_tool_result_read(配合ref=offset/limit逐段检查原始输出)
  3. openviking_tool_result_list(发现当前 session 中其他已外置的结果)

这样,即使 stub 被替换后,模型依然被明确引导"去哪里找回原文",形成"摘要可读、原文可溯"的闭环。


八、原始内容存储与回溯

实现位于 tool_result_store.py 的ToolResultStore。每个 externalized tool result 会写入两类文件

  1. output.txt:原始输出内容;
  2. metadata.json:元数据和 synopsis。

存储目录为{session_uri}/tool-results/{tool_result_id}/,其中tool_result_idtr_前缀 + 安全化后的tool_id+ sha256 前 16 位构成(build_tool_result_id(),见 tool_result_store.py),保证同一内容的幂等去重(写入时会先按 sha256 检查是否已存在)。

metadata 包括:

  1. tool_result_id
  2. session_id
  3. message_id
  4. tool_id
  5. tool_name
  6. created_at
  7. original_chars
  8. preview_chars
  9. sha256
  10. mime_type
  11. synopsis_kind
  12. synopsis
  13. storage_uri
  14. output_uri
  15. offset_unit=unicode_code_point

其中offset_unit=unicode_code_point明确说明所有 offset 均以 Unicode 码点为单位,而非字节。

读取方式

  1. read():按offset/limit读取原始文本片段(默认limit=20_000limit=-1表示读到底),返回content / offset / limit / total_chars / has_more及可选 metadata,适合长文本逐段展开;
  2. search():在原始文本中查关键词(content.find(query)),返回最多limit(默认 20)条带 offset 的 snippet,每个 snippet 在命中位置前后各保留context_chars(默认 300)字符;
  3. list():按 session 列出已有外置结果(通过 viking_fs 的ls枚举目录并读取各结果 metadata),支持按tool_name过滤,默认limit=50,便于发现 ref。

当前读取模型是**"面向长文本"**的;它适合回看日志、代码、表格文本、普通文本。


九、配置参数(源码确认)

externalize 触发阈值与 preview 预算均由配置模型ToolOutputExternalizationConfig控制,定义在 server/config.py:

参数默认值说明
enabledtrue总开关,关闭后不做 externalize
threshold_chars20000单个 tool output 超过该字符数触发外置
preview_chars2000单个 preview 采样预算;仅作为无法规则化时的 head/tail fallback 预算及兼容字段
assistant_turn_inline_budget_chars100000同一 assistant turn 内多个 tool output 的总 inline 体积预算
assistant_turn_preview_budget_chars10000同一 turn 内所有 preview 的合计预算,用于按数量均摊
min_preview_chars1000preview 预算下限,防止均摊后过小
aggregate_selection_strategylargest_first聚合超限时的选择策略(当前为按体积从大到小优先选中)
failure_modepreserve_raw外置失败时的处理:reject/preserve_raw/preview_only

从 session.py 的_effective_tool_preview_chars()可以看到预算均摊逻辑:当有N个输出被选中外置时,单个 preview 预算取assistant_turn_preview_budget_chars / Npreview_chars的较小值,并受min_preview_chars下限约束。


十、当前边界与取舍

  1. text不接 LLM,原因是我们当前只需要稳定、低成本、可测试的规则化 stub;
  2. 当前回溯接口是read/search/list,更适合大文本原文回看;
  3. offset/limit对"顺序文本展开"很合适,但对图片、二进制、复杂多模态结果并不理想(offset 以 Unicode 码点为单位,无法表达图片/音视频等媒体语义)。

这些取舍决定了当前版本的功能边界:优先解决文本型 tool output 的上下文瘦身问题,媒体解析与 LLM 摘要不在本期范围内。


十一、测试覆盖

当前相关测试包括:

  1. tests/session/test_tool_result_synopsis.py:覆盖类型识别和 synopsis 生成,包括固定 caps、text 的 500/500 deterministic fallback,以及 unknown 的 head/tail fallback(文件内共 13 个测试函数);
  2. tests/session/test_tool_result_externalization.py:覆盖 externalization、stub 替换、阈值边界、aggregate budget、ref 回溯等端到端流程;
  3. tests/server/test_api_sessions.py:覆盖 HTTP API 层的 tool result externalization、stub 文案、read/list/search回溯,以及synopsis_kind/synopsis.kind元数据透出。

当前相关测试共 29 个用例通过,可作为后续继续补齐真实输出回归用例的基础。


十二、结论

OpenViking 当前的tool stub已经具备一版完整闭环:

  1. OV 原生的 externalize、preview stub、ref 回溯链路继续保留;
  2. preview 生成方式已从偏head + tail的截断,升级为按类型的规则化摘要
  3. text类型采用 deterministic 规则摘要,不接 LLM
  4. 能通过read/search/list对外置原文进行回溯。

后续优先级应放在更深的回归测试、更多真实输出样本,以及是否需要继续优化read/search/list的原文回溯体验,而不是先扩展 LLM 摘要或媒体解析。

【免费下载链接】OpenVikingSelf-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.项目地址: https://gitcode.com/GitHub_Trending/op/OpenViking

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 9:45:41

51单片机LCD12864计算器仿真:驱动时序与状态机实战

简介&#xff1a;本资源是一套基于51单片机与LCD12864液晶屏实现的简易计算器Proteus仿真工程&#xff0c;面向嵌入式初学者、单片机课程设计学生及电子类实训人员&#xff0c;旨在帮助理解按键扫描、LCD驱动、算术运算逻辑与软硬件协同仿真实现。压缩包共29个文件&#xff0c;…

作者头像 李华
网站建设 2026/9/10 9:44:31

终极指南:Telegram.Bot.Examples控制台应用详解与实战案例

终极指南&#xff1a;Telegram.Bot.Examples控制台应用详解与实战案例 Telegram.Bot.Examples是基于Telegram.Bot C#库的官方示例项目&#xff0c;提供了从基础到高级的多种Telegram机器人实现方案。本文将聚焦控制台应用场景&#xff0c;通过两个核心示例项目展示如何快速构建…

作者头像 李华