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.
Files changed (33) hide show
  1. {typellm-0.2.3/typellm.egg-info → typellm-0.2.4}/PKG-INFO +1 -1
  2. {typellm-0.2.3 → typellm-0.2.4}/pyproject.toml +1 -1
  3. {typellm-0.2.3 → typellm-0.2.4}/tests/test_concurrency.py +32 -0
  4. {typellm-0.2.3 → typellm-0.2.4}/typellm/sglang.py +33 -20
  5. {typellm-0.2.3 → typellm-0.2.4/typellm.egg-info}/PKG-INFO +1 -1
  6. {typellm-0.2.3 → typellm-0.2.4}/LICENSE +0 -0
  7. {typellm-0.2.3 → typellm-0.2.4}/README.md +0 -0
  8. {typellm-0.2.3 → typellm-0.2.4}/setup.cfg +0 -0
  9. {typellm-0.2.3 → typellm-0.2.4}/tests/test_batching.py +0 -0
  10. {typellm-0.2.3 → typellm-0.2.4}/tests/test_dependencies.py +0 -0
  11. {typellm-0.2.3 → typellm-0.2.4}/tests/test_grammar_fields.py +0 -0
  12. {typellm-0.2.3 → typellm-0.2.4}/tests/test_images.py +0 -0
  13. {typellm-0.2.3 → typellm-0.2.4}/tests/test_nullable.py +0 -0
  14. {typellm-0.2.3 → typellm-0.2.4}/tests/test_permutations.py +0 -0
  15. {typellm-0.2.3 → typellm-0.2.4}/tests/test_prefill.py +0 -0
  16. {typellm-0.2.3 → typellm-0.2.4}/tests/test_probabilities.py +0 -0
  17. {typellm-0.2.3 → typellm-0.2.4}/tests/test_prompts.py +0 -0
  18. {typellm-0.2.3 → typellm-0.2.4}/tests/test_protocols.py +0 -0
  19. {typellm-0.2.3 → typellm-0.2.4}/tests/test_text.py +0 -0
  20. {typellm-0.2.3 → typellm-0.2.4}/tests/test_typellm.py +0 -0
  21. {typellm-0.2.3 → typellm-0.2.4}/typellm/__init__.py +0 -0
  22. {typellm-0.2.3 → typellm-0.2.4}/typellm/__main__.py +0 -0
  23. {typellm-0.2.3 → typellm-0.2.4}/typellm/benchmark.py +0 -0
  24. {typellm-0.2.3 → typellm-0.2.4}/typellm/cli.py +0 -0
  25. {typellm-0.2.3 → typellm-0.2.4}/typellm/images.py +0 -0
  26. {typellm-0.2.3 → typellm-0.2.4}/typellm/protocol.py +0 -0
  27. {typellm-0.2.3 → typellm-0.2.4}/typellm/runtime.py +0 -0
  28. {typellm-0.2.3 → typellm-0.2.4}/typellm/schema.py +0 -0
  29. {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/SOURCES.txt +0 -0
  30. {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/dependency_links.txt +0 -0
  31. {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/entry_points.txt +0 -0
  32. {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/requires.txt +0 -0
  33. {typellm-0.2.3 → typellm-0.2.4}/typellm.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: typellm
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Type-Safe Decoding for Autoregressive LLMs
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://typellm.ai/
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "typellm"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "Type-Safe Decoding for Autoregressive LLMs"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.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
- # Streamed so a failed body read still reports the status it follows.
273
- with self._http().stream(
274
- "GET" if payload is None else "POST",
275
- self.base_url + path,
276
- content=body,
277
- headers={"Content-Type": "application/json"},
278
- timeout=timeout,
279
- ) as response:
280
- if response.status_code >= 400:
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."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: typellm
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Type-Safe Decoding for Autoregressive LLMs
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://typellm.ai/
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