当前位置: 首页 > news >正文

Portkey-AI gateway 的一次“假压缩头”翻车的完整排障记:由 httpx 解压异常引发的根因分析

笔者最近在本地搭建了Portkey AI Gateway(模型路由网关),然后按照文档中的方式进行测试。

在这里插入图片描述
结果发现,网关能够接收到请求,但是Python测试的程序却运行报错。

在这里插入图片描述
Python代码报错信息如下:

Traceback (most recent call last):File "F:\pytorch310\lib\site-packages\httpx\_decoders.py", line 97, in decodereturn self.decompressor.decompress(data)
zlib.error: Error -3 while decompressing data: incorrect header checkThe above exception was the direct cause of the following exception:Traceback (most recent call last):File "F:\pytorch310\lib\site-packages\portkey_ai\_vendor\openai\_base_client.py", line 989, in requestresponse = self._client.send(File "F:\pytorch310\lib\site-packages\httpx\_client.py", line 928, in sendraise excFile "F:\pytorch310\lib\site-packages\httpx\_client.py", line 922, in sendresponse.read()File "F:\pytorch310\lib\site-packages\httpx\_models.py", line 881, in readself._content = b"".join(self.iter_bytes())File "F:\pytorch310\lib\site-packages\httpx\_models.py", line 898, in iter_bytesdecoded = decoder.decode(raw_bytes)File "F:\pytorch310\lib\site-packages\httpx\_decoders.py", line 99, in decoderaise DecodingError(str(exc)) from exc
httpx.DecodingError: Error -3 while decompressing data: incorrect header checkThe above exception was the direct cause of the following exception:Traceback (most recent call last):File "E:\PycharmProjects\PortkeyAIProject\test.py", line 10, in <module>response = client.chat.completions.create(File "F:\pytorch310\lib\site-packages\portkey_ai\api_resources\apis\chat_complete.py", line 183, in createreturn self.normal_create(File "F:\pytorch310\lib\site-packages\portkey_ai\api_resources\apis\chat_complete.py", line 126, in normal_createresponse = self.openai_client.with_raw_response.chat.completions.create(File "F:\pytorch310\lib\site-packages\portkey_ai\_vendor\openai\_legacy_response.py", line 364, in wrappedreturn cast(LegacyAPIResponse[R], func(*args, **kwargs))File "F:\pytorch310\lib\site-packages\portkey_ai\_vendor\openai\_utils\_utils.py", line 287, in wrapperreturn func(*args, **kwargs)File "F:\pytorch310\lib\site-packages\portkey_ai\_vendor\openai\resources\chat\completions\completions.py", line 925, in createreturn self._post(File "F:\pytorch310\lib\site-packages\portkey_ai\_vendor\openai\_base_client.py", line 1259, in postreturn cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))File "F:\pytorch310\lib\site-packages\portkey_ai\_vendor\openai\_base_client.py", line 1021, in requestraise APIConnectionError(request=request) from err
openai.APIConnectionError: Connection error.

然后笔者定位到报错位置,发现是gzip解码时出现了问题。

在这里插入图片描述

症状是 incorrect header check,最终定位到网关在响应里带了 Content-Encoding: gzip 头,但实体其实是明文 JSON(即“假压缩头”)。任何会自动解压的客户端(httpx/Portkey SDK 等)都会在读取阶段失败。

要想在根源上修复这个问题,需要在网关/反代上保证“头与实体一致”——不压就删 Content-Encoding;压了就只压一层并设置正确的头。可是这个网关是直接运行起来的开源项目,所以笔者只好想了一个临时的办法来解决这个问题:客户端用 httpx.stream(...).iter_raw() 读原始字节并手动解析。

复现与第一轮尝试

1) 最初用 Portkey SDK 直连 provider

from portkey_ai import Portkey
client = Portkey(provider="dashscope", Authorization="Bearer <key>")
client.chat.completions.create(model="qwen-plus", messages=[...])

现象httpx.DecodingError
直觉:解压阶段挂了 → 看压缩相关头部。

2) 改走本地网关 base_url

Portkey(base_url="http://localhost:8787/v1")

这时 Portkey 的 provider= 参数不会自动变成网关能识别的路由头,于是网关返回:

400 {'status': 'failure', 'message': 'Either x-portkey-config or x-portkey-provider header is required'}

结论:走自托管网关必须用请求头指明路由(x-portkey-provider)。

我们补上头之后,仍旧遇到 DecodingError。说明不仅是路由问题,压缩/头部也有坑


关键破局:跳过自动解压,直看“线上的原始字节”

为排除客户端自动行为干扰,直接用 httpx.stream(...).iter_raw()

import httpx, jsonGATEWAY = "http://localhost:8787/v1"
payload = {"model": "qwen-plus", "messages": [{"role": "user", "content": "Hello"}]}
headers = {"x-portkey-provider": "dashscope","Authorization": "Bearer <your_secret_key>","Accept-Encoding": "identity","Content-Type": "application/json",
}with httpx.stream("POST", f"{GATEWAY}/chat/completions", json=payload, headers=headers, timeout=30) as r:print("status =", r.status_code)print("Content-Encoding =", r.headers.get("content-encoding"))raw = b"".join(r.iter_raw())  # ★ 不做解压,拿“线上的原始字节”print("raw len =", len(raw))print("raw head =", raw[:80])# 如果服务端其实是明文 JSON(却错误地带了 Content-Encoding)try:print("as text ->", raw[:200].decode("utf-8"))except Exception:  # 如果真的是 gzip,可自行解压看import gzip, iotry:txt = gzip.GzipFile(fileobj=io.BytesIO(raw)).read().decode("utf-8")print("as gzip json ->", txt[:200])except Exception as e:print("manual decode failed:", e)

实际输出(核心证据)

status = 200
Content-Encoding = gzip
raw len = 448
raw head = b'{"choices":[{"message":{"role":"assistant","content":"Hi there! \xd9\xa9(\xe2\x97\x95\xe2\x80\xbf\xe2\x97\x95\xef\xbd\xa1)'
as text -> {"choices":[{"message":{"role":"assistant","content":"Hi there! ٩(◕‿◕。)۶ How can I assist you today?"},"finish_reason":"stop","index":0,"logprobs":null}],"object":"chat.completion","usage":{

结论:服务端响应头声称 gzip,但实体实际上是明文 JSON
这就是“假压缩头”。httpx/SDK 看到 Content-Encoding: gzip 会尝试解压,结果自然报 incorrect header check


为何会出现“假压缩头”?

常见几种错误链路:

  1. 上游原本 gzip,但中间层解压了实体,却忘了删 Content-Encoding
  2. 上游明文,但中间层“不小心”加了 Content-Encoding: gzip
  3. 双重压缩:上游已 gzip,网关又对同一实体再压一层(头与实体层级不一致)。

这类错误必须在服务端/网关侧修。只靠客户端改代码是治标不治本。

Checklist:以后再遇到类似“解压报错”,按这个查

  • 对齐两个关键头Accept-Encoding(请求) vs Content-Encoding(响应)
  • 抓原始字节iter_raw() → 看是否“明文 + gzip 头”
  • 断开自动解压的假象:客户端临时改为手动解析
  • 修服务器:不压就删头;压就只压一层、头与体一致
  • Portkey 自托管:走 base_url 必带 x-portkey-* 路由头;鉴权不要留空

到这里,问题的根因就找到了,并且我们也提出了临时的解决方案。下一步就是深入源码来看看这到底是怎么个事儿!

读者朋友们感兴趣的话,也可以阅读下这个项目的源码(https://github.com/Portkey-AI/gateway),看看它到底是怎么实现的hh~。

http://www.lryc.cn/news/623981.html

相关文章:

  • duiLib 解决点击标题栏中按钮无响应问题
  • C# 反射和特性(自定义特性)
  • 健身房预约系统SSM+Mybatis实现(三、校验 +页面完善+头像上传)
  • RISC-V汇编新手入门
  • 【LeetCode】单链表经典算法:移除元素,反转链表,约瑟夫环问题,找中间节点,分割链表
  • 开发指南132-DOM的宽度、高度属性
  • HTTP0.9/1.0/1.1/2.0
  • SWE-bench:真实世界软件工程任务的“试金石”
  • 人工智能入门②:AI基础知识(下)
  • C++入门自学Day11-- String, Vector, List 复习
  • 如何利用gemini-cli快速了解一个项目以及学习新的组件?
  • 数据结构03(Java)--(递归行为和递归行为时间复杂度估算,master公式)
  • 人脸AI半球梯控/门禁读头的功能参数与技术实现方案
  • MySQL的事务基础概念:
  • 力扣刷题904——水果成篮
  • 黑马商城day08-Elasticsearch作业(个人记录、仅供参考、详细图解)
  • MLArena:一款不错的AutoML工具介绍
  • 【Linux】IO多路复用
  • SpringCloud 07 微服务网关
  • linux-高级IO(上)
  • 【撸靶笔记】第五关:GET - Double Injection - Single Quotes - String
  • Linux目录介绍
  • 002.Redis 配置及数据类型
  • 第三十八天(Node.JS)
  • AUTOSAR ARXML介绍
  • gin结合minio来做文件存储
  • Oracle Undo Tablespace 使用率暴涨案例分析
  • UE5多人MOBA+GAS 49、创建大厅
  • java设计模式之迪米特法则使用场景分析
  • ​​Vue 3 开发速成手册