Files
Artemis-Eapil/docs/llm-stream-diagnostics.md

13 KiB
Raw Permalink Blame History

模型流式参数诊断

小型内置动作的参数流仍受 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 重复解析,异常参数仍由最终校验拒绝。离线通过不代表原业务任务的稳定性已验收。