这个坑值得记一笔。自建网关转发 OpenAI 的 SSE,前端偶尔出现一个方块字符,Python 侧用 httpx 逐 chunk decode,日志里能看到 b'\xe4\xb8' 这种半截字节,只在中文回答时出现。

刚开始盯着「网关侧做了字符串截断,把字节流当文本切」,折腾了半个多小时,排掉了。

真正麻烦的地方在于,几个原因的现象几乎一样。按 chunk 直接 decode,多字节汉字被切断;用了 errors=ignore,丢字节后不报错只缺字;前端按 \n\n 分包但没处理跨包的 UTF-8。

上来先改成增量解码器,保留不完整字节

python -c "import codecs;d=codecs.getincrementaldecoder('utf-8')();print(d.decode(b'\xe4\xb8',final=False));print(d.decode(b'\xad\xe6\x96\x87',final=True))"

随后用 curl 抓一段原始字节确认边界

curl -N -s https://api.openai.com/v1/chat/completions -H "Authorization: Bearer $OPENAI_API_KEY" -d '{"model":"gpt-4o-mini","stream":true,"messages":[{"role":"user","content":"写一句中文"}]}' -o raw.bin

然后检查字节流里是否有被截断的序列

python -c "b=open('raw.bin','rb').read();print(len(b), b.count(b'\xe4'))"

末了在服务端改为按字节透传不做 decode

python -c "print('response.content 直接 write,不做 str 转换')"

别只看报错消失,验证要多做一步。中文回答完整无方块,日志里不再出现孤立的高位字节,长回答与短回答表现一致。

流式传输的边界按字节走,任何一层提前 decode 都可能切开汉字,增量解码器是标配。这类问题最好一次改到位,反复小改反而容易把原因搅混。

作者 9823189