Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

NeMo Relay 共享指标功能

对于那些已有 NeMo Relay 官方预编译包发布的平台,Hermes 会将其作为常规运行时依赖项一并包含。共享指标集成已内置于 Hermes 中,因此无需额外的可观测性插件。在其他原生目标平台上,即便未安装 Relay,Hermes 依然可以正常导入。此类目标会使用一种显式的低功能空操作主机:虽然仍可执行 Hermes 相关操作,但 Relay 的作用域、中间件、插件及订阅功能将不可用。hermes-agent[nemo-relay] 这一附加参数仅作为现有安装命令的兼容性别名存在,实际并无功能。

Warning

此更改会移除 Hermes 的 observability/nemo_relay 插件。现有用户必须从 plugins.enabled 中删除该插件(或其旧版别名 nemo_relay),并将导出器配置移至通过 HERMES_NEMO_RELAY_PLUGINS_TOML 参数指定的 Relay 相关 plugins.toml 文件中。旧的 HERMES_NEMO_RELAY_ATOF_* 和 HERMES_NEMO_RELAY_ATIF_* 变量已无法用于激活导出器。在没有这些新变量的情况下,Hermes 将不会执行 Relay 插件检测、配置层叠处理、中间件功能或导出器相关操作。

Hermes 要求使用的 NeMo Relay 版本须为 0.8 系列中的 0.8.3 及更高版本。该版本提供了 Hermes 用于管理型提供程序及工具调用的提供程序编码解码器以及标准化的工具结果契约。

运行时依赖关系与数据边界

Hermes会从限定在>=0.8.3,<0.9版本范围内的依赖库中,安装针对特定平台的nemo-relay原生轮包。该打包后的软件包是基于NVIDIA NeMo Relay仓库构建的。对于不支持的平台,则会使用上文所述的空操作运行时,而不会下载其他版本的实现方案。

由操作员提供的带类型标注的原生插件必须为Relay 0.8版本重新构建。当grpc-v1工作节点使用工具回调、工具执行拦截功能或手动工具端API时,也需对这些工作节点进行重新生成与构建。

在启用Relay托管执行模式时,提供方发送的请求与接收到的响应都会经过Hermes进程中的该原生模块,这样即可让已配置的拦截器对实际调用操作进行处理。这一机制与共享指标数据协议是相互独立的。在共享指标模式下,不会安装任何高级可观测性网络导出工具,其订阅者仅能接收下文所述的带版本标识且经过白名单过滤后的数据格式。附录A中提到的可选包发送功能是唯一的向外传输路径:除非用户同时启用enabled和send选项,否则该功能不会传输任何数据,且传输的是完整的数据包而非实时的时间跨度数据。若要启用单独配置的高级可观测性插件或动态插件,则会形成不同的数据传输路径,这类插件还需经过独立的策略审核。

除非Hermes的策略允许,否则数据收集功能将保持关闭状态。

telemetry:
  shared_metrics:
    enabled: true

该选项的值取自配置文件自身的 config.yaml。由系统管理的配置覆盖层无法代表该配置文件启用或禁用共享指标功能。

Relay 插件的激活由原生运行时控制,且始终需要手动开启。若要启用已配置的中间件、导出器或动态插件,需将 HERMES_NEMO_RELAY_PLUGINS_TOML 设置为指定的 plugins.toml 文件。当该变量未被设置时,Hermes 不会调用 Relay 的插件初始化器,因此 Relay 也不会执行插件配置发现或层叠操作。一旦该变量被设置且指定文件加载成功,Relay 就会识别出系统中受支持的用户级和系统级的 plugins.toml 文件,并将选定的静态配置叠加到这些文件之上。位于仓库内的 .nemo-relay/plugins.toml 文件将被忽略。只有从指定文件中才会加载动态的 [[plugins.dynamic]] 配置项。如果指定的文件无法加载,Hermes 会报告错误,且不会尝试初始化 Relay 或采用环境自动发现机制。

连续会话的会话周期分割

当 Relay 的作用域关闭时,它就会导出一个时间跨度记录。由于连续的网关会话可能会持续开放数天,因此尽管每次对话都会正常导出对应的时间跨度,整个会话的周期仍会保持开启状态。可选的分割功能仅在每次对话结束时会切换会话的作用域:

gateway:
  telemetry:
    session_segments:
      on_compaction: false  # rotate after context compaction
      max_turns: 0          # 0 = unlimited; N = turns per segment
键值默认值行为说明
on_compactionfalse在压缩操作完成后,于下一个轮次边界时进行切换。
max_turns0每完成 N 轮后进行切换;若设置为 0,则取消此限制。

这两种默认设置均能确保整个会话期间保持单一的会话作用域。经过切换的片段会保留相同的 session_id,同时新增 hermes.session.segment 以及 hermes.session.segment_reason(值为 compaction 或 max_turns)。`

全流程插件策略与配置隔离

中继插件配置属于进程级部署选项,而非 Hermes 配置文件中的设置。第一个被加载的配置文件会触发延迟初始化,此后该 Hermes 进程中加载的其余所有配置文件都将共享由此生成的静态中间件、动态插件、订阅器、导出模块以及约束策略。初始化成功后,Hermes 会输出相关日志:

Relay plugins are active process-wide and apply to all profiles hosted by this Hermes process.

在同一个共享策略下,各配置文件的作用域依然能够保持因果隔离性。ATIF会根据事件所属的顶级Agent作用域对它们进行分组,因此同时运行的多个配置文件会生成独立的轨迹,而不会形成混合轨迹。ATOF以及其他全局订阅者则可以监控所有已部署配置文件中的事件。静态和动态中间件也会为来自每个配置文件的管理调用提供服务。

在独立工作进程中运行的工作插件并不会创建针对单个配置文件的安全边界。一次全进程范围的激活操作会将所有已部署配置文件的调用转发至该工作进程,同时保留发起调用的配置文件所对应的Relay作用域栈结构。而原生动态插件则会被加载到Hermes进程中,因此它们共享相同的策略边界。

当不同的配置文件需要不同的信任级别、插件凭证、数据导出目标或约束策略时,应将其运行在独立的Hermes进程之中。这种全进程范围的插件契约不会改变每个配置文件各自的独立共享指标授权设置、本地SQLite状态,或是ATIF的轨迹分组方式。

对于每个Hermes会话,其核心组件会管理一个Relay主机以及一个独立的Relay会话作用域。核心生命周期管理工具会使用agent.relay_runtime来获取共享会话句柄,或在该会话上下文中运行Relay作用域、大语言模型、工具相关的API函数。新的产品标记功能无需进行Hermes插件注册。不过,共享指标相关的标记内容仍必须仅包含经过版本化允许列表审批的字段;这一硬性要求并不会改变数据的收集方式或隐私政策。

当前切片

当前的垂直切片会记录匿名用户画像活动、逻辑模型调用、顶层任务执行情况、工具与审批结果,以及技能的生命周期与复用情况:

Hermes turn, API, tool, and approval hooks
  -> Relay session, task, LLM, tool, and mark lifecycle
  -> Hermes shared-metrics subscriber
  -> SQLite counters
  -> immutable JSON delta package

Hermes会向相关指标系统发送一个空的LLMRequest。这并不涉及上文所描述的通过原生运行时进行的独立管理式执行调用。终端指标事件中会包含Hermes在逻辑调用过程中使用的模型标识符以及提供商路径,例如通过openrouter连接的nvidia/nemotron-3-ultra。这些标识符会被转换为小写形式且具有结构上的限制,但并不会经过经过验证的模型目录进行标准化处理。定价信息与模型系列分类则由指标后端负责管理。提示词、响应内容、接口地址、错误信息、会话ID、任务ID以及请求ID均不会被包含在指标事件或数据包中。新的调用会使用hermes.model_route.count指标;而之前的hermes.model_call.count指标仍可读取,其目的是为了能够在不丢失数据的前提下导出旧版本系统生成的本地计数器值。

当首次启动经过用户同意的会话时,系统会发送一个空的hermes.client.active Relay标记。基于用户配置文件的订阅者会生成一个随机UUID作为安装标识,并通过事务性的比较-设置操作,在任意24小时的时间窗口内最多记录一个客户端活跃计数器。该指标没有维度信息;Hermes版本、操作系统系列、架构以及安装方式则属于受限的包级资源。由于多个Hermes进程会共享SQLite锁机制,因此同时启动的进程不会对同一次安装进行重复计数。后续的会话或任务可以再次尝试发送该标记,但订阅者会在当前24小时时间窗口结束之前抑制这些重复请求。

每次任务执行都会在所属的 Hermes 会话下创建一个名为 hermes.task_run 的 Relay Function 层级结构。启动计数器仅记录有限的执行范围与入口点相关数值,而终端计数器则包含有限的结果值、结束原因、终止状态、持续时间、逻辑模型调用次数、终端工具调用次数以及提供者重试次数等统计项。重试指的是针对同一个 Hermes API 请求 ID 所进行的额外提供者尝试,不会增加逻辑模型调用次数。在获取到终端工具的响应后,工具调用会通过其唯一的 Hermes 工具调用 ID 进行去重处理。无论任务是正常结束、提前终止、因异常中止还是被取消,最外层的 AIAgent 执行边界都会为该任务画上句号。如果在上下文压缩过程中 Hermes 更换了对话会话,任务的归属权仍会跟随任务 ID 保持不变。

每次工具调用都会通过一个名为 hermes.tool_call 的 Relay 工具生命周期来表示。终端计数器仅包含有限类别的统计项,这些类别包括工具类型、执行结果、审批结果、延迟时间以及明确的重试次数。Hermes 会从其运行时注册表中已声明的工具集来确定工具类型;对于自定义或未被识别的工具集,则会被归类为 other,而不会显示具体的工具或插件名称。Hermes 不会通过重复出现的工具名称或连续的调用次数来推断是否需要重试;如果钩子函数未指定明确的重试关系,那么该重试类别将被标记为 unknown。审批结果会以 hermes.tool_approval 的形式被记录下来,同时会注明其归属于某次工具调用,或者被明确标记为 unattributed。工具名称、调用 ID、参数、结果、指令、描述以及错误信息均不会包含在共享指标事件或数据包中。当某个已启动的工具在其任务终止时仍未完成时,它会被视为失败、超时或被取消处理,并仍计入该任务的工具计数类别中。

成功的技能变更会生成带有受限操作信息及来源标识的 hermes.skill.lifecycle 标记;而成功的加载操作则会生成包含受限来源信息、首次使用状态、再次使用状态、补丁后使用状态以及使用次数统计的 hermes.skill.load 标记。Hermes 通过其现有的 skills/.usage.json 状态以事务方式来确定技能的再次使用情况以及补丁生成的连续性;技能名称及具体的使用次数或生成次数绝不会被记录到 Relay 指标事件、SQLite 维度数据或相关包中。在应用新补丁后的使用行为会被计为一次“补丁后再次使用”;之后的使用则仍属于普通再次使用,直到下一个补丁出现为止。至于补丁应用后的任务结果归因,则需待其时间窗口及多技能语义得到明确定义之后才能进行。本地状态存储路径如下:

$HERMES_HOME/telemetry/shared_metrics/metrics.sqlite3
$HERMES_HOME/telemetry/shared_metrics/outbox/*.json

该数据库用于存储事务聚合数据以及打包待发送状态。每个打包文件都是符合固定 JSON 架构的不可变增量文档,通过原子替换方式写入。每个打包文件都会记录 Hermes 版本、操作系统系列、架构以及安装方式等信息,这些均被视为受限的客户端资源。对于无法识别的平台或安装参数,系统会将其标记为“unknown”;而原始的平台字符串、主机名及路径则绝不会被包含在内。已完全打包的聚合数据行以及成功导出的打包行和文件会在本地保留 30 天。那些处于处理中的打包行以及包含未导出增量数据的计数器则永远不会被清除。

对于现有的待发送文件,其对应的打包架构版本仍为 v1。新生成的打包文件则采用 v2 版本,该版本同时兼容已废弃的 model-call 订阅模式与当前的 model-route 订阅模式,从而能够确保在升级过程中安全地处理那些仍处于处理中的计数器。

每个打包文件都包含一个通过随机生成方式得到的 install_id,其形式为 UUID。尽管该字段在架构中有所定义,但其实际作用范围仅限于一个 HERMES_HOME 环境,因此更准确地说它是一种持久的匿名配置标识符。该标识符并非源自硬件信息、账户数据、主机信息、路径或凭证数据,因此在同一配置下的不同打包文件之间能够保持稳定,从而实现这些本地打包文件之间的关联。若删除 $HERMES_HOME/telemetry/shared_metrics 目录,该标识符以及所有的聚合数据和打包文件都将被一并清除。

远程数据传输为可选功能,默认处于关闭状态。若要在远程环境中重复使用该持久化的本地标识符,则需要通过单独的产品决策及隐私政策来明确同意机制、身份范围、重置规则、数据保留期限以及删除方式——相关决策现已作出。

这些决策记录在 附录A中,且用于实现这些功能的导出工具也已发布。仅进行数据收集操作本身不会传输任何信息:只有当telemetry.shared_metrics.send也为真时,发送功能才会被触发。每个被传输的数据包都会原封不动地携带稳定的install_id(产品决策日期:2026年8月27日——详情参见A.2节,其中还包括已被取代的HMAC伪标识设计)。

该安装身份仅适用于一个HERMES_HOME环境。如需重置该身份,需停止Hermes进程并删除$/HERMES_HOME/telemetry/shared_metrics目录。此操作会刻意一并移除旧的身份信息、聚合数据库以及待处理的本地数据包;下次在用户同意的情况下启动应用时,将会生成新的身份。禁用共享指标功能虽可阻止新的数据收集,但不会自动删除之前已收集的本地数据。

烟雾测试

使用真实的Hermes CLI命令针对确定性的本地模型服务器进行测试:

./.venv/bin/python scripts/smoke_nemo_relay_shared_metrics.py

该脚本默认会使用已安装的 nemo-relay 依赖项。仅在测试本地构建的 Relay 绑定时,才需使用 --relay-python ../nemo-relay/python 参数。

在返回最终响应之前,该测试会先让本地模型发起一次真实的 read_file 工具调用,随后通过已安装的 Relay 绑定依次触发创建、加载、重用、修补、编辑、标记为过时、归档、恢复以及安装技能转换等操作。它还会验证 SQLite 中的模型、提供者、任务、工具及技能计数器,根据既定架构检查所有导出的增量包,核实匿名客户端活跃计数器的正确性,并确保这些包中不包含提示信息、响应内容、工具调用 ID、工具结果以及技能名称等相关数据。

附录 A:远程导出功能决策(第二阶段)

状态:已实现。 本附录针对“当前版本”中暂未解决、需通过未来远程导出功能处理的产品与隐私相关问题给出了答案。它记录了各项决策的内容及其依据,从而确保在功能实现后这些决策逻辑依然清晰可查。

默认情况下,数据发送功能处于关闭状态,必须同时启用 telemetry.shared_metrics.enabled 和 telemetry.shared_metrics.send 才能使用。

该导出工具会将已写入 $HERMES_HOME/telemetry/shared_metrics/outbox/ 目录中的包文件发送至 Hermes 遥测数据接收服务。该服务仅会对数据包的封装结构(即 schema_version 和 UUID 格式的 package_id)进行验证,然后将数据包的内容原封不动地存储在 S3 中。

A.1 同意机制

数据传输属于与数据收集分开的独立授权流程,可通过一个新的配置键来控制启用状态:

telemetry:
  shared_metrics:
    enabled: false   # collect locally
    send: false      # NEW: transmit to the Nous telemetry service
  • send 的默认值为 false。仅进行数据收集时绝不会触发传输。
  • send 功能依赖于 enabled 参数的开启。二者并非互为前提:传输标志绝不能在未明确开启收集功能的情况下自动生效。若设置 send: true 但 enabled: false,系统会发出警告且不会执行任何操作。
  • 与 enabled 一样,send 也是由对应配置文件控制的,不会被管理级配置覆盖。

只有当某个数据包的整个有效周期完全处于已记录的同意时间窗口内时,才会被发送。 同意状态以明确的区间形式存储在 shared-metrics SQLite 数据库中的 send_consent_windows 表中:一旦首次检测到 send: true,就会开启一个时间窗口;后续的所有检测结果都会进一步确认该窗口的有效性;而当检测到 send: false 时,该窗口即会关闭——关闭时间以最后一次被确认的时刻为准,而非实际时钟时间。每个进程启动时,都有一个专门的协调器会根据配置生成此表,因此通过向导进行的修改、对 config.yaml 的手动编辑,以及运行过程中的取消操作,都会遵循相同的处理流程,确保没有任何过渡状态会被遗漏。

凡是有效周期早于首个时间窗口、位于窗口之间,或超过最新确认时间的数据包都会被排除——此时审核门将判定为拒绝。因此,一个新的数据包需要在其有效周期结束后最多等待一次进程启动,才有资格被发送。

数据筛选的依据是时间周期,而非软件包的创建时间。一个时间周期会分布在不同日期创建的多个软件包中:当天的第一个软件包会在当天生成,而同一周期的最后一个软件包通常会在次日产生。如果以创建时间为依据进行筛选,就会在丢弃该周期的第一个软件包的同时也丢弃最后一个软件包,从而导致该天的数据被隐性少计。而以时间周期为依据进行筛选,则能确保仅向前传递已获得同意的数据,且每个传输的时间周期都保持完整。

本地历史记录的最长保留期限为30天,这些数据是在“绝不上传任何信息”的承诺下收集的。遵循仅向前传递数据的原则,最多只会导致我们无法发送那些本无权限传输的30天旧数据。

A.2 身份范围——稳定的install_id将原样传输

决策记录。该导出工具的初始设计(以及本附录的1至8版)传输的是经过加密处理的假名,而非原始标识符:即HMAC-SHA256(密钥=本地存储的动态盐值,消息=install_id),其中盐值每30天更换一次。在2026年8月27日,在该功能正式上线之前(当时尚无用户同意使用,也没有任何实际数据传输),产品负责人认为分析工作需要的是一个稳定的跨周期身份标识——用于分析留存曲线和用户的长期使用行为,而这种设计上的动态更换机制会破坏这一需求。因此,相关加密层被彻底移除,而非仅作弱化处理。

目前传输的内容为:

  • 每个软件包都会原封不动地携带 install_id:即上文所述的、具有持久性且基于用户配置文件的随机 UUID。
  • 该值是在本地通过 uuid4 生成的,不包含任何与硬件、账户、用户或机器相关的信息,用于标识一个用户配置文件而非具体个人。
  • 只要用户未删除共享指标目录,该值就会保持稳定;一旦删除,系统将重新生成该值(详见 A.4 节)。

问题会直接明确说明,而不加以掩饰:

  • 同一用户配置文件下的各个软件包之间的关联是永久性的,而非仅限于单个窗口周期。现在,某个安装实例的每日数据序列能够长期保持关联,这已是设计初衷,而非偶然现象。
  • 旧设计中的 A.3 节所述残留问题(即稳定的 resource 元组以及用于连接不同轮换窗口的连续时间段)已不再存在——因为已不存在需要连接的窗口边界。
  • 设置向导中的同意条款已明确说明了这一身份模型;该条款是在删除原有生成逻辑的同时进行更新的,因此在任何已发布的版本中,都未曾依据旧条款收集过用户同意信息。

完全相同的字节数据在重新发送时依然有效。 当软件包首次被准备时,传输的 ID 会被记录在对应行(sent_install_id 列)中,而数据内容始终会基于该记录值进行重建,因此重试时发送的数据仍为完全相同的字节序列。协议规范也要求如此:若重新发送的内容与原有 package_id 不同,则属于未定义行为。(由于 ID 是稳定的,已记录的副本不再具备应对轮换机制的作用——它依然作为审计列存在,同时也能为未来身份语义的任何变化提供低成本保障。)

A.3 旋转机制 — 已移除(决策记录)

盐值旋转功能连同其派生机制一同被删除(产品决策,2026年8月27日)。保留本节内容是为了记录早期设计的具体方案以及为何最终决定移除该功能:

  • 采用旋转机制的目的是限制长期关联性:每个30天周期内仅允许一个身份标识,不同周期间的身份彼此无关。
  • 文档中记载的遗留问题(完整分析见Git历史记录)是:对于少数特殊配置而言,信封结构中稳定且熵值较低的resource元组加上连续的每日数据段仍有可能实现跨周期关联,因此设置此类边界只会增加成本,而非真正起到隔离作用。
  • 导致该功能被移除的产品需求是:保留分析恰恰需要跨周期的连续性。一旦稳定身份标识成为必要条件,那种虽然会妨碍正常分析,却仅对刻意进行关联的操作方增加成本的边界设计,显然已被视为不合理的权衡。

在存储系统中不再存在盐值,流程中也不存在任何旋转调度或派生标识符。

A.4 重置行为

如上文所述,删除$/HERMES_HOME/telemetry/shared_metrics仍会重置本地身份标识、聚合数据及包文件。此时需满足两项前提条件:

  • 重置操作会重新生成 install_id,从而使后续发送的包拥有新的身份标识。本地重置同样能生成新的远程身份标识。
  • 重置功能无法撤销已发送的包。那些已经发送的包会保留在摄取服务中的存储系统中,使用其初始的标识符进行存储。在 v1 协议中并未提供读取或删除这些包的 API。

将 send: false 设置为 true 可立即停止数据发送:系统会在每个包发送前重新读取用户同意状态,因此正在传输中的数据流会在当前这个包发送完成后停止,而不会继续处理整个批次。此操作既不会删除之前已发送的包,也不会停止本地数据的收集。

关闭发送功能同时也会关闭同意窗口——判断标准是实际检测到用户同意的那一刻,而非时钟显示的时间。那些处于两个窗口之间的时间段内的数据包将永远不会被发送,即便之后又重新开启了发送功能也是如此。这一规则适用于任意次数的开启/关闭循环,无论是在没有程序运行的手动编辑模式下,还是时钟出现正向或反向跳变的情况下均适用(新的同意窗口的起始时间不会早于系统中已存储的所有时间戳;每次检测时,同意标记只会按固定步长前进,因此单个异常的提前检测不会将确认时间大幅推后;关闭操作也绝不会在最后一次检测的时间之后才生效)。

与早期仅有一个动态变化的同意日期不同,关闭和重新开启功能不会丢弃之前同意窗口中尚未发送的数据包——这些数据包仍会留在其对应的时间内,且依然符合发送条件。

一个由升级路径带来的必然后果是:在旧版同意机制下(即send_consent_windows功能出现之前)导出的数据包,其生成时间早于首个记录的时间窗口,因此升级后将永远不会被传输。这是一种“故障即停止”的处理方式——若试图重新导入旧的日期戳以释放这些数据,实际上就是在重新启用已被五轮审查证明存在缺陷的逻辑机制;而且这样做最多只会造成未发送数据的积压,而不会丢失已收集的数据。

A.5 数据保留策略

  • 本地存储:保持不变——成功导出的历史数据保留30天,而待处理的增量数据则会一直保存直至被导出。发送状态并不会延长本地数据的保留时间:那些根本无法被发送的数据仍会在30天后被清除。与一个永久无法访问的端点进行无限量的本地数据积累,其造成的后果比丢失已故障一个月的设备所记录的指标更为严重。
  • 远程存储:在正式环境中,原始数据包会无限期地保存在S3中;而在测试环境中,则保留30天。

A.6 数据删除

v1版本协议中并未规定远程删除机制,本附录也不会自行添加此类功能。用户可采取以下操作:

操作效果
send: false停止后续数据包的发送
enabled: false停止数据收集,但现有的本地数据仍会保留
删除.../shared_metrics目录重置本地标识、聚合数据及相关文件;后续发送将使用新的install_id
删除已发送的数据无法自行操作——需由管理员在S3存储桶中执行删除操作
如果决定实施按需删除功能,其查找路径将变得非常直接:用户的 install_id(可从其本地存储中读取)即为存储其数据的关键键。是否构建服务端的删除 API 属于产品层面的决策,而非实现细节。

A.7 outbox 目录的作用

之所以记录此内容,是因为在第二阶段规划期间曾出现一次误读,导致差点删除用户数据。

该目录属于本地历史记录,而非发送队列。package_outbox 是一个 SQLite 表,其 exported_at 列表示“已写入磁盘”,而非“已发送”。这些文件是不可修改的,并且仅根据文件年龄就会被自动清理。

数据导入规范要求发送方应在 202 时刻从自己的 outbox 中删除对应包。但导出模块并不会执行此操作。如果在收到确认信号后才进行删除,就会将用户保留的30天本地历史记录误用作传输队列,从而破坏原本承诺的数据状态。实际上,数据状态是存储在 package_outbox 表中的新列中;这些文件不会因传输操作而受到任何影响。

A.8 范围说明

包体中的 install_id 字段会按照生成器写入时的原样进行传输(该值实际上是从行的固定字段 sent_install_id 重写的,后者记录的也是相同数值)。其他载荷字段均不会发生改变,也不会添加任何新内容,且服务端会将整个包体视为不可见数据。因此,载荷结构的演变依然像以前一样,属于发送方需要处理的问题。