fix(llm): disable brotli for catalog fetch to prevent native crash (#1099, #1101)

models.dev is served by Cloudflare with brotli compression when the
client advertises it, and brotli is a required dependency here, so
aiohttp always negotiates br. A corrupted br stream can crash the
native decoder with a Windows access violation (a Python-level
exception handler cannot catch it), or produce garbage bytes.

Send an explicit "Accept-Encoding: gzip, deflate" header on the model
catalog and Ollama model-list requests so the server never returns
brotli. zlib handles corrupt gzip data by raising ContentEncodingError
(an aiohttp.ClientError subclass), which the existing handlers already
catch and degrade to a warning with an empty-catalog fallback.
This commit is contained in:
Will Miao
2026-09-07 09:54:28 +08:00
parent 08023f0cd9
commit 5ae4aef30e
2 changed files with 39 additions and 3 deletions
+29 -1
View File
@@ -322,8 +322,12 @@ class MockGetSession:
def __init__(self, response):
self._response = response
self.last_url = None
self.last_headers = None
def get(self, url):
def get(self, url, headers=None):
self.last_url = url
self.last_headers = headers
return self._response
async def __aenter__(self):
@@ -373,3 +377,27 @@ class TestModelCatalog:
models = await fetch_ollama_models("http://localhost:11434/v1")
assert models == []
@pytest.mark.asyncio
async def test_catalog_request_disables_brotli_encoding(self):
"""The catalog request must not advertise br — a corrupt brotli stream
can crash the native decoder (Windows access violation, issue #1099)."""
response = MockResponse(200, json_data={})
session = MockGetSession(response)
with mock.patch("aiohttp.ClientSession", return_value=session):
await llm_module._load_model_catalog()
assert session.last_headers == {"Accept-Encoding": "gzip, deflate"}
@pytest.mark.asyncio
async def test_ollama_request_disables_brotli_encoding(self):
"""The Ollama models request must not advertise br either."""
response = MockResponse(200, json_data={"data": [{"id": "llama3"}]})
session = MockGetSession(response)
with mock.patch("aiohttp.ClientSession", return_value=session):
models = await fetch_ollama_models("http://localhost:11434/v1")
assert models == ["llama3"]
assert session.last_headers == {"Accept-Encoding": "gzip, deflate"}