火山方舟等上游流式返回 data:{...}(data: 后无空格),原解析只认
'data: '(带空格),导致 content 文本与 usage 全部漏解析:
- sseContentText 抽成 sseDataPayload,统一处理 data: 前缀
(支持带/不带空格 + event:+data: 多行块)
- scanUsage 复用 sseDataPayload
- convert/stream.go parseLine 也支持 data:{ 无空格
- 新增回归测试覆盖无空格/带空格/多行块三种形态
49 lines
2.0 KiB
Go
49 lines
2.0 KiB
Go
package proxy
|
||
|
||
import (
|
||
"encoding/json"
|
||
"testing"
|
||
)
|
||
|
||
// 复现线上火山方舟 qwen 流:data:{...} 无空格(省略 data: 后的空格)。
|
||
func TestSSEDataPayloadNoSpace(t *testing.T) {
|
||
lines := []string{
|
||
`data:{"message":{"model":"qwen3.8-flash","id":"msg_1","role":"assistant","type":"message","content":[],"usage":{"input_tokens":31626,"output_tokens":0}},"type":"message_start"}`,
|
||
`data:{"delta":{"type":"text_delta","text":"你好"},"type":"content_block_delta","index":0}`,
|
||
`data:{"delta":{"type":"text_delta","text":"!"},"type":"content_block_delta","index":0}`,
|
||
`data:{"delta":{"stop_reason":"end_turn"},"type":"message_delta","usage":{"cache_creation":{"ephemeral_5m_input_tokens":33065},"output_tokens":8,"cache_creation_input_tokens":33065,"input_tokens":8,"cache_read_input_tokens":0}}`,
|
||
}
|
||
var out string
|
||
for _, l := range lines {
|
||
out += sseContentText([]byte(l))
|
||
}
|
||
if out != "你好!" {
|
||
t.Fatalf("outputText=%q, want %q", out, "你好!")
|
||
}
|
||
// message_delta 的 delta.usage 应能提取(output_tokens=8)
|
||
u := scanUsage([]byte(lines[3]))
|
||
if u == nil {
|
||
t.Fatal("scanUsage returned nil for message_delta with usage")
|
||
}
|
||
var sh usageShape
|
||
if err := json.Unmarshal(u, &sh); err != nil {
|
||
t.Fatalf("unmarshal usage: %v", err)
|
||
}
|
||
if sh.OutputTokens != 8 {
|
||
t.Fatalf("output_tokens=%d, want 8", sh.OutputTokens)
|
||
}
|
||
}
|
||
|
||
// 兼容带空格的单 data: 行(标准 SSE)与 event:+data: 多行块。
|
||
func TestSSEDataPayloadSpacedAndMultiLine(t *testing.T) {
|
||
// 标准:data: {...}
|
||
if got := sseContentText([]byte(`data: {"delta":{"type":"text_delta","text":"hi"},"type":"content_block_delta","index":0}`)); got != "hi" {
|
||
t.Fatalf("spaced single line: got %q, want hi", got)
|
||
}
|
||
// 多行块:event: message_delta\ndata: {...}
|
||
block := []byte("event: message_delta\ndata: {\"delta\":{\"type\":\"text_delta\",\"text\":\"yo\"},\"type\":\"content_block_delta\",\"index\":0}\n")
|
||
if got := sseContentText(block); got != "yo" {
|
||
t.Fatalf("multiline block: got %q, want yo", got)
|
||
}
|
||
}
|