CSSwitch 版本
v0.8.1 Linux x64
macOS 版本 + 芯片
Ubuntu 24.04 x86_64 glibc
用的是哪个 provider
自定义 / 其它
复现步骤
现象
第三方模式下 Science 聊天一律失败,与模型无关(deepseek/deepseek-v4-flash-0731、anthropic/claude-sonnet-5、@preset/fable2 均复现)。Science 服务端日志表现为:
[LLM] Streaming sse_server error (attempt N), retrying: Error: {"type":"error","error":{"type":"api_error","message":"upstream SSE protocol error"}}
重试 5+ 次后整帧报错。同一 profile 的非流式请求(validate_connection、activate_profile 的 POST /v1/messages scratch probe、GET /v1/models)全部 outcome=Ok,即认证、目录、非流式链路均正常,只有流式(stream=true)触发。
复现与根因证据
1. 直连上游(绕过 gateway)流式正常。 用 profile 内同一 key 对 https://openrouter.ai/api/v1/messages 发 stream=true 带 tools 请求,HTTP 200 text/event-stream,正文完整。但其帧尾为:
event: message_delta
data: {"type":"message_delta","delta":{...,"stop_reason":"end_turn",...},"usage":{...}}
event: message_stop
data: {"type":"message_stop"}
event: data
data: [DONE]
最后一段 event: data + data: [DONE] 是 OpenAI 风格结束标记,不属于 Anthropic Messages SSE 协议(data 不是合法 JSON 事件体)。
2. 经 gateway 的受控复现。 用与正式 gateway 完全相同的环境变量(CSSWITCH_*,含 anthropic-relay contract、CSSWITCH_RELAY_THINKING=adaptive)在另一端口启动第二个 csswitch-gateway 实例,按 Science 的真实访问方式(ANTHROPIC_BASE_URL=http://127.0.0.1:<port>/<path_secret>)发同样的流式请求。gateway 将上游事件原样转发到 message_delta 为止,随后就地发出:
event: error
data: {"type":"error","error":{"type":"api_error","message":"upstream SSE protocol error"}}
且不转发 message_stop(与 0.8.0 changelog「严格 SSE 生命周期校验:失败或截断的流只发一个终止 error、绝不合成 message_stop」的设计一致)。
期望结果 / 实际结果(含完整报错文字)
gateway 的 Anthropic SSE 校验在收到合法 message_stop 后进入「终止容忍」状态:之后的 data: [DONE](或 event: data 包裹的 [DONE])直接忽略并正常收尾,而不是整流判违规。
日志(可选,强烈建议)
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=false msgs=1 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=false msgs=1 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=false msgs=1 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=@preset/fable2 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=@preset/fable2 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=@preset/fable2 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
POST /v1/messages relay target=deepseek/deepseek-v4-flash-0731 stream=true msgs=2 rules=tool.relay.input-schema-normalize
[oauth] 虚拟登录已就绪(Rust,零 node;action=Created;isolated=true)
隔离运行状态已由 CSSwitch 准备(路径已隐藏)
启动隔离沙箱 Science(虚拟登录)
HOME = [CSSwitch isolated]
data-dir = [CSSwitch isolated Science data]
端口 = 8990 (真实实例 8765 不受影响)
预览端口 = 8991 (显式固定,供本机 Science 预览使用)
二进制 = backend-selected runtime
系统 SSH = 未授权
推理指向 = http://127.0.0.1:18991/****
账号 = virtual@localhost.invalid (本地假账号,不用真实凭证)
外联防卡 = Anthropic HTTPS fast-fail(经 http://127.0.0.1:18991,no_proxy=127.0.0.1,localhost,::1)
已后台启动。验证:
健康: curl -s http://127.0.0.1:8990/health || true
状态: 请使用 CSSwitch 状态灯确认
停止: 请使用 CSSwitch「停止全部」
确认
CSSwitch 版本
v0.8.1 Linux x64
macOS 版本 + 芯片
Ubuntu 24.04 x86_64 glibc
用的是哪个 provider
自定义 / 其它
复现步骤
现象
第三方模式下 Science 聊天一律失败,与模型无关(
deepseek/deepseek-v4-flash-0731、anthropic/claude-sonnet-5、@preset/fable2均复现)。Science 服务端日志表现为:重试 5+ 次后整帧报错。同一 profile 的非流式请求(
validate_connection、activate_profile的POST /v1/messagesscratch probe、GET /v1/models)全部outcome=Ok,即认证、目录、非流式链路均正常,只有流式(stream=true)触发。复现与根因证据
1. 直连上游(绕过 gateway)流式正常。 用 profile 内同一 key 对
https://openrouter.ai/api/v1/messages发stream=true带 tools 请求,HTTP 200text/event-stream,正文完整。但其帧尾为:最后一段
event: data+data: [DONE]是 OpenAI 风格结束标记,不属于 Anthropic Messages SSE 协议(data不是合法 JSON 事件体)。2. 经 gateway 的受控复现。 用与正式 gateway 完全相同的环境变量(
CSSWITCH_*,含anthropic-relaycontract、CSSWITCH_RELAY_THINKING=adaptive)在另一端口启动第二个csswitch-gateway实例,按 Science 的真实访问方式(ANTHROPIC_BASE_URL=http://127.0.0.1:<port>/<path_secret>)发同样的流式请求。gateway 将上游事件原样转发到message_delta为止,随后就地发出:且不转发
message_stop(与 0.8.0 changelog「严格 SSE 生命周期校验:失败或截断的流只发一个终止 error、绝不合成 message_stop」的设计一致)。期望结果 / 实际结果(含完整报错文字)
gateway 的 Anthropic SSE 校验在收到合法
message_stop后进入「终止容忍」状态:之后的data: [DONE](或event: data包裹的[DONE])直接忽略并正常收尾,而不是整流判违规。日志(可选,强烈建议)
确认