typellm 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {typellm-0.2.3/typellm.egg-info → typellm-0.2.4}/PKG-INFO +1 -1
- {typellm-0.2.3 → typellm-0.2.4}/pyproject.toml +1 -1
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_concurrency.py +32 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/sglang.py +33 -20
- {typellm-0.2.3 → typellm-0.2.4/typellm.egg-info}/PKG-INFO +1 -1
- {typellm-0.2.3 → typellm-0.2.4}/LICENSE +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/README.md +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/setup.cfg +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_batching.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_dependencies.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_grammar_fields.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_images.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_nullable.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_permutations.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_prefill.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_probabilities.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_prompts.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_protocols.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_text.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/tests/test_typellm.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/__init__.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/__main__.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/benchmark.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/cli.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/images.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/protocol.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/runtime.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm/schema.py +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/SOURCES.txt +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/dependency_links.txt +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/entry_points.txt +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/requires.txt +0 -0
- {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/top_level.txt +0 -0
|
@@ -212,6 +212,38 @@ class DeadlineTests(unittest.TestCase):
|
|
|
212
212
|
client._generate({"text": "x"})
|
|
213
213
|
self.assertNotIsInstance(caught.exception, GenerationTimeout)
|
|
214
214
|
|
|
215
|
+
def dropping(self, error, times):
|
|
216
|
+
"""A client whose server drops the first `times` requests with `error`."""
|
|
217
|
+
seen = []
|
|
218
|
+
|
|
219
|
+
def handler(request):
|
|
220
|
+
seen.append(request)
|
|
221
|
+
if len(seen) <= times:
|
|
222
|
+
raise error("connection dropped", request=request)
|
|
223
|
+
return httpx.Response(200, json={"text": "ok"})
|
|
224
|
+
|
|
225
|
+
client = SGLangClient()
|
|
226
|
+
client._http_client = httpx.Client(transport=httpx.MockTransport(handler))
|
|
227
|
+
return client, seen
|
|
228
|
+
|
|
229
|
+
def test_a_dropped_connection_is_retried_once(self):
|
|
230
|
+
for error in (httpx.ReadError, httpx.RemoteProtocolError, httpx.WriteError):
|
|
231
|
+
client, seen = self.dropping(error, 1)
|
|
232
|
+
with self.subTest(error=error.__name__):
|
|
233
|
+
self.assertEqual(client._request("/generate", {"text": "x"}), {"text": "ok"})
|
|
234
|
+
self.assertEqual(len(seen), 2)
|
|
235
|
+
client, seen = self.dropping(httpx.ReadError, 2)
|
|
236
|
+
with self.assertRaisesRegex(SGLangError, "Could not reach"):
|
|
237
|
+
client._request("/generate", {"text": "x"})
|
|
238
|
+
self.assertEqual(len(seen), 2)
|
|
239
|
+
|
|
240
|
+
def test_a_refused_connection_is_not_retried(self):
|
|
241
|
+
for error in (httpx.ConnectError, httpx.ReadTimeout):
|
|
242
|
+
client, seen = self.dropping(error, 1)
|
|
243
|
+
with self.subTest(error=error.__name__), self.assertRaises(SGLangError):
|
|
244
|
+
client._request("/generate", {"text": "x"})
|
|
245
|
+
self.assertEqual(len(seen), 1)
|
|
246
|
+
|
|
215
247
|
def test_invalid_budgets_are_rejected(self):
|
|
216
248
|
class Recording(FakeServer):
|
|
217
249
|
def _request(self, path, payload=None, *, allow_text=False):
|
|
@@ -252,9 +252,11 @@ class SGLangClient:
|
|
|
252
252
|
def _http(self) -> httpx.Client:
|
|
253
253
|
with self._load_lock:
|
|
254
254
|
if self._http_client is None:
|
|
255
|
-
# Keep-alive connections: one call makes many small requests.
|
|
255
|
+
# Keep-alive connections: one call makes many small requests. Idle
|
|
256
|
+
# ones are dropped before SGLang closes them (5 s), so a request
|
|
257
|
+
# rarely goes out on a connection the server has just closed.
|
|
256
258
|
self._http_client = httpx.Client(limits=httpx.Limits(
|
|
257
|
-
max_connections=None, max_keepalive_connections=64,
|
|
259
|
+
max_connections=None, max_keepalive_connections=64, keepalive_expiry=2.0,
|
|
258
260
|
))
|
|
259
261
|
return self._http_client
|
|
260
262
|
|
|
@@ -269,24 +271,15 @@ class SGLangClient:
|
|
|
269
271
|
scope = _call_scope.get()
|
|
270
272
|
timeout = self.timeout if scope is None else scope.socket_timeout(self.timeout)
|
|
271
273
|
try:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
try:
|
|
282
|
-
detail = response.read().decode("utf-8", errors="replace")
|
|
283
|
-
except httpx.HTTPError as read_error:
|
|
284
|
-
detail = f"Could not read error response: {read_error}"
|
|
285
|
-
raise SGLangError(
|
|
286
|
-
f"SGLang {path} returned HTTP {response.status_code}: {detail}",
|
|
287
|
-
status=response.status_code,
|
|
288
|
-
)
|
|
289
|
-
raw = response.read().decode("utf-8")
|
|
274
|
+
try:
|
|
275
|
+
raw = self._send(path, payload is None, body, timeout)
|
|
276
|
+
except (httpx.ReadError, httpx.WriteError, httpx.RemoteProtocolError):
|
|
277
|
+
# The connection dropped before an answer, most often a pooled one
|
|
278
|
+
# the server had just closed. SGLang is fine: try once on a new one.
|
|
279
|
+
if scope is not None:
|
|
280
|
+
scope.check()
|
|
281
|
+
timeout = scope.socket_timeout(self.timeout)
|
|
282
|
+
raw = self._send(path, payload is None, body, timeout)
|
|
290
283
|
except httpx.HTTPError as exc:
|
|
291
284
|
# Connection failures, timeouts, resets and truncated responses.
|
|
292
285
|
raise SGLangError(
|
|
@@ -301,6 +294,26 @@ class SGLangClient:
|
|
|
301
294
|
f"SGLang {path} returned non-JSON data: {raw[:500]}"
|
|
302
295
|
) from exc
|
|
303
296
|
|
|
297
|
+
def _send(self, path: str, get: bool, body: bytes | None, timeout: float) -> str:
|
|
298
|
+
# Streamed so a failed body read still reports the status it follows.
|
|
299
|
+
with self._http().stream(
|
|
300
|
+
"GET" if get else "POST",
|
|
301
|
+
self.base_url + path,
|
|
302
|
+
content=body,
|
|
303
|
+
headers={"Content-Type": "application/json"},
|
|
304
|
+
timeout=timeout,
|
|
305
|
+
) as response:
|
|
306
|
+
if response.status_code >= 400:
|
|
307
|
+
try:
|
|
308
|
+
detail = response.read().decode("utf-8", errors="replace")
|
|
309
|
+
except httpx.HTTPError as read_error:
|
|
310
|
+
detail = f"Could not read error response: {read_error}"
|
|
311
|
+
raise SGLangError(
|
|
312
|
+
f"SGLang {path} returned HTTP {response.status_code}: {detail}",
|
|
313
|
+
status=response.status_code,
|
|
314
|
+
)
|
|
315
|
+
return response.read().decode("utf-8")
|
|
316
|
+
|
|
304
317
|
@contextmanager
|
|
305
318
|
def images(self, images: Sequence[str]) -> Iterator[None]:
|
|
306
319
|
"""Attach encoded images to every /generate request made in this block."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|