typellm 0.2.2__tar.gz → 0.2.3__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.2/typellm.egg-info → typellm-0.2.3}/PKG-INFO +7 -4
- {typellm-0.2.2 → typellm-0.2.3}/README.md +6 -3
- {typellm-0.2.2 → typellm-0.2.3}/pyproject.toml +1 -1
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_concurrency.py +6 -1
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_images.py +15 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/runtime.py +5 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/sglang.py +29 -1
- {typellm-0.2.2 → typellm-0.2.3/typellm.egg-info}/PKG-INFO +7 -4
- {typellm-0.2.2 → typellm-0.2.3}/LICENSE +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/setup.cfg +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_batching.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_dependencies.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_grammar_fields.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_nullable.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_permutations.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_prefill.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_probabilities.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_prompts.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_protocols.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_text.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/tests/test_typellm.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/__init__.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/__main__.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/benchmark.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/cli.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/images.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/protocol.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm/schema.py +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm.egg-info/SOURCES.txt +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm.egg-info/dependency_links.txt +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm.egg-info/entry_points.txt +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/typellm.egg-info/requires.txt +0 -0
- {typellm-0.2.2 → typellm-0.2.3}/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
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Type-Safe Decoding for Autoregressive LLMs
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://typellm.ai/
|
|
@@ -424,11 +424,14 @@ result = client.generate(
|
|
|
424
424
|
cancel=cancel, # set it from another thread; raises GenerationCancelled
|
|
425
425
|
)
|
|
426
426
|
print(client.last_usage)
|
|
427
|
-
# Usage(requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7, thinking_tokens=0)
|
|
427
|
+
# Usage(requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7, thinking_tokens=0, input_tokens=410)
|
|
428
428
|
```
|
|
429
429
|
|
|
430
|
-
`last_usage` reports the
|
|
431
|
-
|
|
430
|
+
`last_usage` reports the tokens of the last call made in the current thread,
|
|
431
|
+
including a call that failed partway. `input_tokens` is what you sent, each part
|
|
432
|
+
counted once: the context, the questions as JSON, and the images. The other
|
|
433
|
+
counts are SGLang's for the call's requests, where every prompt carries the
|
|
434
|
+
shared context. A timeout or cancel stops
|
|
432
435
|
the call before its next SGLang request.
|
|
433
436
|
|
|
434
437
|
## Cost analysis
|
|
@@ -409,11 +409,14 @@ result = client.generate(
|
|
|
409
409
|
cancel=cancel, # set it from another thread; raises GenerationCancelled
|
|
410
410
|
)
|
|
411
411
|
print(client.last_usage)
|
|
412
|
-
# Usage(requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7, thinking_tokens=0)
|
|
412
|
+
# Usage(requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7, thinking_tokens=0, input_tokens=410)
|
|
413
413
|
```
|
|
414
414
|
|
|
415
|
-
`last_usage` reports the
|
|
416
|
-
|
|
415
|
+
`last_usage` reports the tokens of the last call made in the current thread,
|
|
416
|
+
including a call that failed partway. `input_tokens` is what you sent, each part
|
|
417
|
+
counted once: the context, the questions as JSON, and the images. The other
|
|
418
|
+
counts are SGLang's for the call's requests, where every prompt carries the
|
|
419
|
+
shared context. A timeout or cancel stops
|
|
417
420
|
the call before its next SGLang request.
|
|
418
421
|
|
|
419
422
|
## Cost analysis
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import pickle
|
|
2
3
|
import sys
|
|
3
4
|
import threading
|
|
@@ -104,10 +105,13 @@ class UsageTests(unittest.TestCase):
|
|
|
104
105
|
self.assertIsNone(client.last_usage)
|
|
105
106
|
client.generate(context="Receipt", questions=self.QUESTIONS)
|
|
106
107
|
n = prompts_sent(client.sglang)
|
|
108
|
+
# The input counts once, though every prompt carries it.
|
|
109
|
+
sent = client.sglang.count_tokens("Receipt") + client.sglang.count_tokens(json.dumps(self.QUESTIONS))
|
|
107
110
|
self.assertEqual(client.last_usage, Usage(
|
|
108
111
|
requests=len(client.sglang.payloads),
|
|
109
|
-
prompt_tokens=10 * n, cached_tokens=4 * n, completion_tokens=n,
|
|
112
|
+
prompt_tokens=10 * n, cached_tokens=4 * n, completion_tokens=n, input_tokens=sent,
|
|
110
113
|
))
|
|
114
|
+
self.assertGreater(client.last_usage.requests, 1)
|
|
111
115
|
client.sglang.payloads.clear()
|
|
112
116
|
client.generate(context="Receipt", questions={"b": {"type": "boolean"}})
|
|
113
117
|
self.assertEqual(client.last_usage.requests, len(client.sglang.payloads))
|
|
@@ -136,6 +140,7 @@ class UsageTests(unittest.TestCase):
|
|
|
136
140
|
with self.assertRaisesRegex(SGLangError, "went away"):
|
|
137
141
|
client.generate(context="Receipt", questions=self.QUESTIONS)
|
|
138
142
|
self.assertEqual(client.last_usage.requests, 2)
|
|
143
|
+
self.assertGreater(client.last_usage.input_tokens, 0)
|
|
139
144
|
with self.assertRaises(ValueError):
|
|
140
145
|
client.generate(questions=self.QUESTIONS)
|
|
141
146
|
self.assertIsNone(client.last_usage)
|
|
@@ -271,6 +271,21 @@ class ImageRequestTests(unittest.TestCase):
|
|
|
271
271
|
self.assertEqual(rest, [])
|
|
272
272
|
|
|
273
273
|
|
|
274
|
+
class ImageInputTokenTests(unittest.TestCase):
|
|
275
|
+
def test_images_count_as_the_server_expands_them(self):
|
|
276
|
+
client = TypeLLMClient(model="fake-vl")
|
|
277
|
+
client.sglang = FakeServerClient()
|
|
278
|
+
questions = {"a": {"type": "boolean"}}
|
|
279
|
+
client.generate(context="Receipt", images=[PNG], questions=questions)
|
|
280
|
+
first = client.sglang.generate_payloads[0]["text"]
|
|
281
|
+
first = first if isinstance(first, str) else first[0]
|
|
282
|
+
count = client.sglang.count_tokens
|
|
283
|
+
# The server reported 900 prompt tokens, with the placeholder expanded.
|
|
284
|
+
image = 900 - count(first) + count(VISION)
|
|
285
|
+
self.assertEqual(client.last_usage.input_tokens,
|
|
286
|
+
count("Receipt") + count('{"a": {"type": "boolean"}}') + image)
|
|
287
|
+
|
|
288
|
+
|
|
274
289
|
class RuntimeContentTests(unittest.TestCase):
|
|
275
290
|
def test_fake_backends_receive_image_parts(self):
|
|
276
291
|
class Recording(FakeSGLang):
|
|
@@ -395,6 +395,11 @@ class TypeLLMClient:
|
|
|
395
395
|
with call_scope(timeout, cancel) as scope:
|
|
396
396
|
try:
|
|
397
397
|
decisions = self.compile_schema(schema)
|
|
398
|
+
count = getattr(self.sglang, "count_tokens", None)
|
|
399
|
+
if callable(count):
|
|
400
|
+
scope.usage.input_tokens = sum(map(count, (
|
|
401
|
+
context, json.dumps(questions if questions is not None else schema, ensure_ascii=False))))
|
|
402
|
+
scope.unmeasured_images = len(encoded_images)
|
|
398
403
|
# Independent fields run together; depends_on turns the fields into a
|
|
399
404
|
# graph whose layers run in order.
|
|
400
405
|
run = (_execute_dependency_decisions if any(d.depends_on is not None for d in decisions)
|
|
@@ -37,7 +37,13 @@ class GenerationCancelled(RuntimeError):
|
|
|
37
37
|
|
|
38
38
|
@dataclass
|
|
39
39
|
class Usage:
|
|
40
|
-
"""
|
|
40
|
+
"""The tokens of one generate() call.
|
|
41
|
+
|
|
42
|
+
input_tokens is what the caller sent, each part counted once: the context,
|
|
43
|
+
the questions or schema as JSON, and the images as the server expands them.
|
|
44
|
+
The other counts are what SGLang reported for the call's /generate requests,
|
|
45
|
+
where the shared context is part of every prompt.
|
|
46
|
+
"""
|
|
41
47
|
|
|
42
48
|
requests: int = 0
|
|
43
49
|
prompt_tokens: int = 0
|
|
@@ -45,6 +51,7 @@ class Usage:
|
|
|
45
51
|
completion_tokens: int = 0
|
|
46
52
|
# The part of completion_tokens that is reasoning, not typed answers.
|
|
47
53
|
thinking_tokens: int = 0
|
|
54
|
+
input_tokens: int = 0
|
|
48
55
|
|
|
49
56
|
def _add(self, response: Any) -> None:
|
|
50
57
|
self.requests += 1
|
|
@@ -75,6 +82,8 @@ class _CallScope:
|
|
|
75
82
|
usage: Usage = field(default_factory=Usage)
|
|
76
83
|
deadline: float | None = None # time.monotonic() when the call runs out of time
|
|
77
84
|
cancel: threading.Event | None = None
|
|
85
|
+
# Images whose tokens input_tokens still lacks; the first response measures them.
|
|
86
|
+
unmeasured_images: int = 0
|
|
78
87
|
|
|
79
88
|
def expired(self) -> bool:
|
|
80
89
|
return self.deadline is not None and time.monotonic() >= self.deadline
|
|
@@ -351,8 +360,27 @@ class SGLangClient:
|
|
|
351
360
|
raise GenerationTimeout("generate() ran out of time") from exc
|
|
352
361
|
raise
|
|
353
362
|
scope.usage._add(response)
|
|
363
|
+
if scope.unmeasured_images:
|
|
364
|
+
self._measure_images(scope, payload, response)
|
|
354
365
|
return response
|
|
355
366
|
|
|
367
|
+
def count_tokens(self, text: str) -> int:
|
|
368
|
+
return len(self._get_chat_tokenizer().encode(text, add_special_tokens=False))
|
|
369
|
+
|
|
370
|
+
def _measure_images(self, scope: _CallScope, payload: Mapping[str, Any], response: Any) -> None:
|
|
371
|
+
"""Add the images' tokens to input_tokens: the server's prompt count less the local one."""
|
|
372
|
+
text = payload.get("text")
|
|
373
|
+
prompt = text if isinstance(text, str) else text[0] if isinstance(text, list) and text else None
|
|
374
|
+
item = response[0] if isinstance(response, list) and response else response
|
|
375
|
+
meta = item.get("meta_info") if isinstance(item, Mapping) else None
|
|
376
|
+
served = meta.get("prompt_tokens") if isinstance(meta, Mapping) else None
|
|
377
|
+
if not isinstance(prompt, str) or type(served) is not int:
|
|
378
|
+
return
|
|
379
|
+
# The server replaces each placeholder with the image's tokens.
|
|
380
|
+
placeholders = scope.unmeasured_images * self.count_tokens(self.image_placeholder())
|
|
381
|
+
scope.usage.input_tokens += max(0, served - self.count_tokens(prompt) + placeholders)
|
|
382
|
+
scope.unmeasured_images = 0
|
|
383
|
+
|
|
356
384
|
def _with_images(self, payload: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
357
385
|
images = self._active_images.get()
|
|
358
386
|
if not images:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typellm
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Type-Safe Decoding for Autoregressive LLMs
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://typellm.ai/
|
|
@@ -424,11 +424,14 @@ result = client.generate(
|
|
|
424
424
|
cancel=cancel, # set it from another thread; raises GenerationCancelled
|
|
425
425
|
)
|
|
426
426
|
print(client.last_usage)
|
|
427
|
-
# Usage(requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7, thinking_tokens=0)
|
|
427
|
+
# Usage(requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7, thinking_tokens=0, input_tokens=410)
|
|
428
428
|
```
|
|
429
429
|
|
|
430
|
-
`last_usage` reports the
|
|
431
|
-
|
|
430
|
+
`last_usage` reports the tokens of the last call made in the current thread,
|
|
431
|
+
including a call that failed partway. `input_tokens` is what you sent, each part
|
|
432
|
+
counted once: the context, the questions as JSON, and the images. The other
|
|
433
|
+
counts are SGLang's for the call's requests, where every prompt carries the
|
|
434
|
+
shared context. A timeout or cancel stops
|
|
432
435
|
the call before its next SGLang request.
|
|
433
436
|
|
|
434
437
|
## Cost analysis
|
|
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
|