# 模型流式参数诊断 小型内置动作的参数流仍受 16384 字符保护。超限时关闭输出流、丢弃不完整参数,不执行该次动作;一次新响应重试和可用备用端点耗尽后,只结束当前任务,不进入共享的模型暂停。批次仍遵循“失败即停止”、人工暂停和取消设置。网络故障、认证错误和最终验收保留各自的恢复规则。 长度保护不是生成进度判断:小于上限但一直没有闭合或终止的参数,仍可能持续到单次请求或外层任务超时。超时、取消和完整性校验共同约束这条路径,未完成的参数不能作为动作执行。 ## 请求关联 每个模型请求尝试沿用现有 `llm_diagnostic` 记录,不另建日志数据库。 | 字段 / 请求头 | 含义 | | --- | --- | | `request_id` / `X-Client-Request-Id` | 同一次恢复循环内保持稳定的逻辑请求编号;切换备用调用或恢复暂停后可能建立新的逻辑请求 | | `attempt_id` / `X-Client-Attempt-Id` | 本次客户端尝试编号;重试时变化,`previous_attempt_id` 关联前次尝试 | | `client_*_id_matches` | 实际发送的请求头是否与本地编号一致;调用方已指定的头保留原值 | | `server_request_id` | 首个有效的 `X-Oneapi-Request-Id`、`X-Request-Id` 或 `Request-Id`,按此前后顺序选择 | | `response_x_request_id` | 响应中的原始 `X-Request-Id`,不推定它来自模型上游 | | `error_request_id` | SDK 异常带回的编号,不覆盖已经从响应头读取的编号 | | `response_id` | SDK 实际返回的 Responses 响应编号 | 编号只接收有长度限制的可打印白名单字符。请求头不控制会话亲和性,不替换 `thread-id` / `session-id`。网关自身的重试应使用独立编号或序号,不能假装是一次新的客户端尝试。 ## 参数指纹 v1 `tool_observation_layer=langchain_tool_call_chunks` 明确表示客户端观测的是 SDK 转换后的参数片段,不是原始 SSE 帧。每工具记录实际可得的 `call_id`、`chunk_index`、工具名,以及: - `argument_chars`:参数片段的 Unicode 字符数之和。 - `argument_bytes` / `argument_sha256`:按原顺序连接片段后,其 UTF-8 字节长度和 SHA256;不解析、重排参数 JSON,也不规范化空白或 Unicode。 - `argument_deltas`:非空参数片段数,不能直接与网关原始事件数比较。 - `argument_prefixes`:每 4096 字节的累计 SHA256,最多保留前 16 个。可以在分片大小不同、上游预读或中途取消时比较相同字节位置。 - `argument_encoding_valid`:遇到不能编码为 UTF-8 的片段时为 false,当前总字节数和总哈希留空,避免伪造可比结果;诊断本身不改变执行流程。 - `argument_json_whitespace_chars` / `argument_json_whitespace_run` / `argument_json_whitespace_run_max`:实际空格、制表、回车和换行字符的总数、当前尾部连续数和最长连续数。统计的是这四种 JSON 空白码点,也包含字符串内部的实际字符;转义文本 `\\n`、不换行空格不计入。不据此判断 JSON 是否有效。 - `argument_delta_chars_min` / `argument_delta_chars_max`:非空参数增量的最小、最大字符数;尚无非空增量时最小值为空。 - `argument_same_delta_run` / `argument_same_delta_run_max`:连续相同非空增量的当前、最长出现次数,首个增量算一次,空增量不打断。只保留上一增量的 SHA256,不保留正文。相同增量内容不等于重复的协议事件,例如连续空格也可能来自不同的合法事件。 最多记录 64 个工具;超过时设置 `tool_stats_truncated=true`,总参数字符计数仍继续。诊断不保留参数正文。哈希仅证明已收到的字节前缀,不能证明输出完整或动作成功;完整性仍由响应终止状态和解析校验决定。 跨三端优先使用 `response_id + call_id` 关联。LangChain 的 `chunk_index` 可能因文本或推理子块不同于原始 `output_index`,缺失编号时不能按数组位置猜测。正常的 `arguments.done`、`output_item.done`、`response.completed` 快照不重复计入参数;自定义工具转换、非空初始参数或协议异常需要先核对 SDK 映射,不能仅凭跨层哈希不同认定某一端重复发送。 历史日志没有保存的原始帧无法由新增诊断恢复。HTTP 200 只说明响应头已到达;客户端保护后出现的 499 或 Broken pipe 也不能证明最初原因。网关写入本机下游 writer 成功不等于客户端已消费或执行。 ## 客户端原始 Responses 观测 `wire_responses` 被动观察 HTTP 客户端实际读取的 SSE 字节,在 SDK 转换之前统计。同一网络读取中可能包含多个事件,因此原始统计可以领先 SDK;SDK 回调阻塞也会扩大两者差距。这一边界不是服务器写出时刻,也不是工具执行时刻。诊断不增加预读任务、队列或重传,不过滤、改写交给 SDK 的字节。 每个响应记录 `response_id`、事件数及 `sequence_number` 的首末值。`sequence_forward_gaps` / `sequence_forward_missing_total` 统计相邻观测间的向前跳变次数及跳过的序号数;乱序事件可能随后补上,不能直接称为永久丢包。`sequence_non_increasing` 统计非递增,`sequence_equal_previous` 统计与前一个序号相等。`sequence_missing_events` 表示事件本身没有有效序号,不表示传输丢失。没有响应编号时不猜测,记录 `unknown_response_events`;标准流中没有编号的后续事件沿用当前响应编号。 每个函数调用记录与 SDK 相同的参数指纹和形态统计,另记原始 `delta_events`、首末参数到达时间,以及两类 done 事件的次数。只累计非空初始 arguments 和 arguments.delta,不重复累计 done 的完整快照。关联优先使用响应编号与实际 `call_id`,`item_id` / `output_index` 用于同一原始流内关联;`identity_unknown`、`identity_conflicts` 表明身份不足或冲突,此时不能据此归责。 每次尝试最多观察 4 条 HTTP 流;每条流最多 16 个响应、64 个工具、128 个索引别名,每工具最多 16 个固定前缀。单个待解析 SSE 帧最多 256KiB,超出后仅跳过该帧的诊断,继续原样交给 SDK。`oversized_frames`、`parse_errors`、`statistics_truncated`、`wire_streams_truncated` 说明覆盖缺口;`incomplete_sse_frame` 与 `body_eof_observed` 区分未闭合帧和正常字节流结束。待解析字节仅临时保留,关闭时释放,日志不保存帧或参数正文。 原始分帧支持 LF / CRLF。HTTP 体声明 gzip 等压缩编码时,标记 `sse_observation=skipped_content_encoding`,只统计读取字节与时间,不把压缩字节误当成 SSE;SDK 仍按原路径解压。这种记录缺少原始事件统计,不能把零计数当成没有收到参数。 ## 实际工具约束与耗时 `wire_request_constraints` 检查 HTTP 请求已经序列化的正文,不记录提示、图片或 Schema 正文。逐工具记录 `strict` 为 `omitted`、`false`、`true`,异常值为 `invalid`;不能把省略当成 false,更不能把 Pydantic 本地严格校验当成上游约束。Schema 只保存 SHA256、对象数、非 required 的属性数及未关闭对象数。这是语法统计,不展开引用,也不是上游 Schema 兼容性验证。 当前 `click`、`action_sequence`、`drag` 的真实 SDK 离线请求均省略 strict,保留可选字段。不能只把 strict 开关改为 true:应先按目标接口支持的 Schema 子集处理对象和 required,保留可选值的 nullable 语义,再单独验证兼容性。 请求正文超过 4MiB 时跳过检查并记录 `body_too_large`;流式上传正文不可直接读取时记录 `streaming_body_unavailable`,不会为了诊断消费上传流。最多检查 64 个工具或命名空间节点,每个 Schema 最多统计 1024 个节点,达到上限标记截断。响应事件如果回传 tools,则保存同样的 `returned_tool_constraints`;回显不证明服务端实际执行了约束。 `max_output_tokens` 与 `returned_max_output_tokens` 只表示本层实际观察到的字段。本次核对的 CodexManager 通道会过滤输出 token 上限;短合成探针成功也不能证明上限被执行。不能把设置 max_tokens / max_output_tokens 当成这条链路可靠的止损办法,客户端长度、超时和取消保护仍然必要。 `stream_work` 记录每阶段的调用次数、总耗时和最长耗时:`wire_observe` 为原始帧诊断,`sdk_observe` 为 SDK 片段诊断,`merge` 为聚合,`emit` 为界面回调,`await_sdk_chunk` 为等待 SDK 交出下一个片段。最后一项包含网络读取、原始观测、SDK 转换和 SDK 回调等工作,与其他阶段可能重叠,不能相加或直接当成网络延迟。 聚合每 128 个片段合并一次,降低 SDK 反复解析半截 JSON 的开销;长度保护、诊断和界面输出仍逐片段执行,异步压缩继续使用原异步调用路径。分批合并不修复参数 JSON,最终仍须经过完整性和严格解析校验。 ## 取消与关闭 `cancel_observed_at` / `cancel_observed_phase` 记录本次尝试首次在客户端观测到取消的时间与阶段,不等于用户点击停止的时间。`cancel_phase` / `cancel_reason` 沿用恢复层最终记录的取消阶段和来源,可能反映后续重试等待中的停止。首末 SDK 参数时间为 `first_tool_arguments_at` / `last_tool_arguments_at`。原始流内的读取、参数和关闭时间采用 Unix 秒。 SDK 读取中、回调中或两次 yield 之间触发长度保护后,尝试结束路径都会主动关闭已观察的 HTTP 流,不等待垃圾回收。`close_started_at` 记录关闭开始,只有实际成功才写 `closed_at`;失败只记录异常类型,关闭等待最多 1 秒。关闭超时不能覆盖用户停止而变成可重试的请求超时。未能关闭时保留错误证据,不声称连接已成功释放。 ## 已核对的上游异常样本(2026-11-02) 一次真实 `action_sequence` 调用在任务超时前持续接收参数,没有完成该工具调用。该样本发生在 `action_sequence` 纳入参数保护之前。按同一 `response_id + call_id` 核对生产诊断,得到以下字符数;请求编号和设备信息不在文档中保留: | 观测边界 | 参数字符数 | | --- | ---: | | CodexManager 收到的官方上游 WebSocket | 40907 | | CodexManager 本地下游 writer | 40906 | | NewAPI 收到的上游 SSE | 40904 | | NewAPI 本地下游 writer | 40904 | | ARTEMIS 的 SDK 参数片段 | 40619 | 五个边界在 4096 到 36864 字节的九个累计 SHA256 上一致。NewAPI 使用透传模式,收到和写出的完整参数哈希也一致。上游没有终止事件,也没有完成参数快照。该样本证明参数在官方上游返回时就已持续增长,不能归因于 NewAPI 转换、下游重复累计完成快照或 ARTEMIS 最终消息拼接。取消、预读和缓冲会使各层停止计数的位置不同;尾部总长度不同本身不能证明重复发送或字符丢失。 诊断没有参数正文,不能进一步断言是模型生成循环、空白循环、重复描述或官方服务内部的重复事件,也不能排除请求结构触发异常。这个定位只排除已核对样本的下游放大作为最初来源,不能追溯证明没有原始诊断的历史事故。另一次 `click` 异常同样在 NewAPI 接收时已超过四万字符,说明这种现象并非新动作序列独有。 CodexManager 另行修正了普通函数放入 functions 命名空间的兼容性问题。其短上下文、低思考强度合成测试通过,仅能作为兼容性对照,尚不能代替原事故的长上下文、多图、高思考强度复现,也不能称为参数膨胀的根因修复。 本地保护覆盖 `action_sequence`、`drag` 及其他小型内置动作:超限后关闭流、丢弃参数,并按既有策略有限重试;它限制异常对当前任务的影响,不修复上游生成行为,也不保证业务任务一定成功。缩放面板自动隐藏导致的定位失败由动作序列的 `ensure_visible` 准备步骤处理,与参数流异常分别诊断。 ## 离线验证 `tests/unit/test_llm_stream_guard.py` 验证保护、有限重试、相同/不同备用端点,以及不会创建或清除其他请求的全局暂停。`tests/unit/test_request_diagnostics.py` 验证请求编号、超时重试、响应头及单次参数计数。`tests/unit/test_argument_fingerprint.py` 验证不同分片、中文和 emoji、固定字节前缀、有界统计与异常编码。这些测试不调用真实模型或设备。 `tests/unit/test_responses_diagnostics.py` 验证原始分帧、序号、done 去重、有界统计和 strict 三态。`tests/unit/test_responses_wire_lifecycle.py` 使用已安装的真实 OpenAI/LangChain SDK 与内存 HTTP 传输,验证小于长度阈值的未闭合流、SDK 回调背压、请求/任务超时、用户停止、超限后主动关闭和关闭失败,以及实际工具 Schema;不发网络请求。`tests/unit/test_chunk_accumulator.py` 对比逐片段聚合结果,验证交错工具、元数据和使用量保持一致,减少半截 JSON 重复解析,异常参数仍由最终校验拒绝。离线通过不代表原业务任务的稳定性已验收。