typellm 0.2.5__tar.gz → 0.3.0.dev2__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.5/typellm.egg-info → typellm-0.3.0.dev2}/PKG-INFO +31 -1
- {typellm-0.2.5 → typellm-0.3.0.dev2}/README.md +30 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/pyproject.toml +1 -1
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_typellm.py +116 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/runtime.py +103 -19
- {typellm-0.2.5 → typellm-0.3.0.dev2/typellm.egg-info}/PKG-INFO +31 -1
- {typellm-0.2.5 → typellm-0.3.0.dev2}/LICENSE +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/setup.cfg +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_batching.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_concurrency.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_dependencies.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_grammar_fields.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_images.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_nullable.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_permutations.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_prefill.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_probabilities.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_prompts.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_protocols.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/tests/test_text.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/__init__.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/__main__.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/benchmark.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/cli.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/images.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/protocol.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/schema.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm/sglang.py +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm.egg-info/SOURCES.txt +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm.egg-info/dependency_links.txt +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm.egg-info/entry_points.txt +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/typellm.egg-info/requires.txt +0 -0
- {typellm-0.2.5 → typellm-0.3.0.dev2}/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.
|
|
3
|
+
Version: 0.3.0.dev2
|
|
4
4
|
Summary: Type-Safe Decoding for Autoregressive LLMs
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://typellm.ai/
|
|
@@ -66,6 +66,36 @@ Evaluated on 231 public [JevBench](https://github.com/fstandhartinger/jevbench)
|
|
|
66
66
|
|
|
67
67
|
## Quick start
|
|
68
68
|
|
|
69
|
+
### Hosted API (early access)
|
|
70
|
+
|
|
71
|
+
No GPU? [Early-access](https://typellm.ai/early-access) users get an API key
|
|
72
|
+
by email, and the hosted API runs the model:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from typellm import TypeLLMClient
|
|
76
|
+
|
|
77
|
+
client = TypeLLMClient(api_key="YOUR_API_KEY") # calls https://api.typellm.ai
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`generate()` then works as in the example below, with these differences:
|
|
81
|
+
|
|
82
|
+
- The service compiles and runs the schema. Use `model`, `temperature`, `seed`
|
|
83
|
+
and `timeout`; local compilation settings do not apply.
|
|
84
|
+
A call runs for at most 300 seconds, 60 by default. Set `timeout` on the
|
|
85
|
+
client for a new default, or on `generate()` to override it for one call.
|
|
86
|
+
- Pass `questions`; `schema=`, `cancel` and `print_final_prompt` need your
|
|
87
|
+
own server. Images can be files, bytes, PIL images or data: URIs, not URLs.
|
|
88
|
+
- Each key runs a limited number of calls at once, told with the key. Calls
|
|
89
|
+
beyond it fail right away with HTTP 429.
|
|
90
|
+
- Errors raise `SGLangError` with the HTTP status in `.status`, and a call
|
|
91
|
+
that runs out of time raises `GenerationTimeout`. The service caps the
|
|
92
|
+
context size and the images, answering 400 past them, and stops string
|
|
93
|
+
answers at 128 tokens (`maxLength` is refused).
|
|
94
|
+
- Successful calls set `last_usage` from the service's input and thinking token
|
|
95
|
+
counts. Failed hosted calls may not return usage.
|
|
96
|
+
|
|
97
|
+
To run on your own GPU instead, start here:
|
|
98
|
+
|
|
69
99
|
### 1. Serve a model with SGLang
|
|
70
100
|
|
|
71
101
|
Use [SGLang](https://github.com/sgl-project/sglang) to configure and serve a
|
|
@@ -51,6 +51,36 @@ Evaluated on 231 public [JevBench](https://github.com/fstandhartinger/jevbench)
|
|
|
51
51
|
|
|
52
52
|
## Quick start
|
|
53
53
|
|
|
54
|
+
### Hosted API (early access)
|
|
55
|
+
|
|
56
|
+
No GPU? [Early-access](https://typellm.ai/early-access) users get an API key
|
|
57
|
+
by email, and the hosted API runs the model:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from typellm import TypeLLMClient
|
|
61
|
+
|
|
62
|
+
client = TypeLLMClient(api_key="YOUR_API_KEY") # calls https://api.typellm.ai
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`generate()` then works as in the example below, with these differences:
|
|
66
|
+
|
|
67
|
+
- The service compiles and runs the schema. Use `model`, `temperature`, `seed`
|
|
68
|
+
and `timeout`; local compilation settings do not apply.
|
|
69
|
+
A call runs for at most 300 seconds, 60 by default. Set `timeout` on the
|
|
70
|
+
client for a new default, or on `generate()` to override it for one call.
|
|
71
|
+
- Pass `questions`; `schema=`, `cancel` and `print_final_prompt` need your
|
|
72
|
+
own server. Images can be files, bytes, PIL images or data: URIs, not URLs.
|
|
73
|
+
- Each key runs a limited number of calls at once, told with the key. Calls
|
|
74
|
+
beyond it fail right away with HTTP 429.
|
|
75
|
+
- Errors raise `SGLangError` with the HTTP status in `.status`, and a call
|
|
76
|
+
that runs out of time raises `GenerationTimeout`. The service caps the
|
|
77
|
+
context size and the images, answering 400 past them, and stops string
|
|
78
|
+
answers at 128 tokens (`maxLength` is refused).
|
|
79
|
+
- Successful calls set `last_usage` from the service's input and thinking token
|
|
80
|
+
counts. Failed hosted calls may not return usage.
|
|
81
|
+
|
|
82
|
+
To run on your own GPU instead, start here:
|
|
83
|
+
|
|
54
84
|
### 1. Serve a model with SGLang
|
|
55
85
|
|
|
56
86
|
Use [SGLang](https://github.com/sgl-project/sglang) to configure and serve a
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import random
|
|
1
3
|
import unittest
|
|
2
4
|
|
|
3
5
|
import httpx
|
|
4
6
|
|
|
5
7
|
from typellm.runtime import numeric_pattern
|
|
6
8
|
from typellm import (
|
|
9
|
+
GenerationTimeout,
|
|
7
10
|
SGLangClient,
|
|
8
11
|
SGLangError,
|
|
9
12
|
SchemaError,
|
|
@@ -907,6 +910,119 @@ class JsonSchemaExecutionTests(unittest.TestCase):
|
|
|
907
910
|
self.assertEqual(scored[0][0], {65: -0.1, 66: -2.0})
|
|
908
911
|
self.assertEqual(scored[1][0], {65: -3.0, 66: -0.2})
|
|
909
912
|
|
|
913
|
+
|
|
914
|
+
class HostedApiTests(unittest.TestCase):
|
|
915
|
+
def client(self, handler, **options):
|
|
916
|
+
client = TypeLLMClient(api_key="k", **options)
|
|
917
|
+
client._transport = httpx.MockTransport(handler)
|
|
918
|
+
return client
|
|
919
|
+
|
|
920
|
+
def test_a_call_is_one_request_to_the_hosted_api(self):
|
|
921
|
+
seen = []
|
|
922
|
+
|
|
923
|
+
def handler(request):
|
|
924
|
+
seen.append(request)
|
|
925
|
+
return httpx.Response(200, json={
|
|
926
|
+
"id": "gen_1", "model": "typellm-latest", "result": {"total": 12.5},
|
|
927
|
+
"thinking": {"total": "The receipt says 12.50."},
|
|
928
|
+
"usage": {"input_tokens": 40, "thinking_tokens": 9}, "elapsed": 0.5,
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
client = self.client(handler, seed=7)
|
|
932
|
+
questions = {"total": {"type": "number", "thinking": True}}
|
|
933
|
+
image = "data:image/png;base64,iVBORw0KGgo="
|
|
934
|
+
result = client.generate(context="Total: 12.50", questions=questions, images=[image], timeout=90)
|
|
935
|
+
|
|
936
|
+
self.assertEqual(result, {"total": 12.5})
|
|
937
|
+
[request] = seen
|
|
938
|
+
self.assertEqual(str(request.url), "https://api.typellm.ai/v1/generate")
|
|
939
|
+
self.assertEqual(request.headers["authorization"], "Bearer k")
|
|
940
|
+
self.assertEqual(json.loads(request.content), {
|
|
941
|
+
"context": "Total: 12.50", "questions": questions, "images": [image], "timeout": 90,
|
|
942
|
+
# Each call draws its seed from the client's seeded stream.
|
|
943
|
+
"options": {"mode": "argmax", "seed": random.Random(7).randrange(2**32)},
|
|
944
|
+
})
|
|
945
|
+
self.assertEqual((client.last_usage.input_tokens, client.last_usage.thinking_tokens), (40, 9))
|
|
946
|
+
self.assertEqual(client.last_thinking, {"total": "The receipt says 12.50."})
|
|
947
|
+
|
|
948
|
+
def test_hosted_temperature_only_applies_to_sampling(self):
|
|
949
|
+
bodies = []
|
|
950
|
+
|
|
951
|
+
def handler(request):
|
|
952
|
+
bodies.append(json.loads(request.content))
|
|
953
|
+
return httpx.Response(200, json={
|
|
954
|
+
"result": {"a": True}, "usage": {"input_tokens": 1, "thinking_tokens": 0},
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
self.client(handler, temperature=0).generate(
|
|
958
|
+
context="x", questions={"a": {"type": "boolean"}})
|
|
959
|
+
self.client(handler, temperature=0.7).generate(
|
|
960
|
+
context="x", questions={"a": {"type": "boolean"}})
|
|
961
|
+
|
|
962
|
+
self.assertNotIn("temperature", bodies[0]["options"])
|
|
963
|
+
self.assertEqual(bodies[1]["options"]["temperature"], 0.7)
|
|
964
|
+
|
|
965
|
+
def test_constructor_timeout_sets_hosted_limit_with_per_call_override(self):
|
|
966
|
+
seen = []
|
|
967
|
+
|
|
968
|
+
def handler(request):
|
|
969
|
+
seen.append(request)
|
|
970
|
+
return httpx.Response(200, json={
|
|
971
|
+
"result": {"a": True}, "usage": {"input_tokens": 1, "thinking_tokens": 0},
|
|
972
|
+
})
|
|
973
|
+
|
|
974
|
+
questions = {"a": {"type": "boolean"}}
|
|
975
|
+
client = self.client(handler, timeout=240)
|
|
976
|
+
client.generate(context="x", questions=questions)
|
|
977
|
+
client.generate(context="x", questions=questions, timeout=30)
|
|
978
|
+
self.client(handler).generate(context="x", questions=questions)
|
|
979
|
+
|
|
980
|
+
self.assertEqual([json.loads(request.content).get("timeout") for request in seen],
|
|
981
|
+
[240, 30, None])
|
|
982
|
+
self.assertEqual([request.extensions["timeout"]["read"] for request in seen],
|
|
983
|
+
[300, 90, 180])
|
|
984
|
+
self.assertEqual(TypeLLMClient().sglang.timeout, 120)
|
|
985
|
+
|
|
986
|
+
def test_hosted_rejects_invalid_timeouts_before_sending(self):
|
|
987
|
+
sent = []
|
|
988
|
+
client = self.client(lambda request: sent.append(request))
|
|
989
|
+
questions = {"a": {"type": "boolean"}}
|
|
990
|
+
|
|
991
|
+
for timeout in (0, -1, True, "5", float("nan")):
|
|
992
|
+
with self.subTest(timeout=timeout), self.assertRaises(ValueError):
|
|
993
|
+
client.generate(context="x", questions=questions, timeout=timeout)
|
|
994
|
+
with self.assertRaises(ValueError):
|
|
995
|
+
self.client(lambda request: sent.append(request), timeout=0).generate(
|
|
996
|
+
context="x", questions=questions)
|
|
997
|
+
self.assertEqual(sent, [])
|
|
998
|
+
|
|
999
|
+
def test_api_errors_keep_their_status(self):
|
|
1000
|
+
for status, error in ((401, SGLangError), (429, SGLangError), (504, GenerationTimeout)):
|
|
1001
|
+
client = self.client(lambda request: httpx.Response(status, json={"error": {"message": "no"}}))
|
|
1002
|
+
with self.assertRaises(error) as caught:
|
|
1003
|
+
client.generate(context="x", questions={"a": {"type": "boolean"}})
|
|
1004
|
+
self.assertEqual(caught.exception.status, status)
|
|
1005
|
+
with self.assertRaises(ValueError): # only local compilation takes a raw schema
|
|
1006
|
+
client.generate(context="x", schema={"type": "object", "properties": {"a": {"type": "boolean"}}})
|
|
1007
|
+
|
|
1008
|
+
def test_hosted_compile_schema_requires_own_server(self):
|
|
1009
|
+
client = TypeLLMClient(api_key="k")
|
|
1010
|
+
with self.assertRaisesRegex(ValueError, "compile_schema needs your own server"):
|
|
1011
|
+
client.compile_schema({"type": "object", "properties": {"a": {"type": "boolean"}}})
|
|
1012
|
+
|
|
1013
|
+
def test_hosted_invalid_responses_fail_clearly(self):
|
|
1014
|
+
questions = {"a": {"type": "boolean"}}
|
|
1015
|
+
client = TypeLLMClient(api_key="k")
|
|
1016
|
+
for response in (httpx.Response(200, text="not JSON"),
|
|
1017
|
+
httpx.Response(200, json={"result": {"a": True}}),
|
|
1018
|
+
httpx.Response(302, headers={"Location": "/elsewhere"})):
|
|
1019
|
+
with self.subTest(status=response.status_code):
|
|
1020
|
+
client._transport = httpx.MockTransport(lambda request: response)
|
|
1021
|
+
with self.assertRaises(SGLangError) as caught:
|
|
1022
|
+
client.generate(context="x", questions=questions)
|
|
1023
|
+
self.assertEqual(caught.exception.status, response.status_code)
|
|
1024
|
+
|
|
1025
|
+
|
|
910
1026
|
if __name__ == "__main__":
|
|
911
1027
|
unittest.main()
|
|
912
1028
|
|
|
@@ -17,6 +17,8 @@ from itertools import permutations as all_permutations
|
|
|
17
17
|
from string import ascii_uppercase, digits
|
|
18
18
|
from typing import Any, Mapping, Sequence
|
|
19
19
|
|
|
20
|
+
import httpx
|
|
21
|
+
|
|
20
22
|
from .schema import (
|
|
21
23
|
MAX_ENUM_CHOICES,
|
|
22
24
|
SchemaError,
|
|
@@ -24,10 +26,12 @@ from .schema import (
|
|
|
24
26
|
dependency_layers,
|
|
25
27
|
)
|
|
26
28
|
from .images import encode_images
|
|
27
|
-
from .sglang import SGLangClient, Usage, call_scope
|
|
29
|
+
from .sglang import GenerationTimeout, SGLangClient, SGLangError, Usage, call_scope
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
LOG = logging.getLogger("typellm")
|
|
33
|
+
HOSTED_URL = "https://api.typellm.ai"
|
|
34
|
+
DEFAULT_SOCKET_TIMEOUT = 120.0
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
def _closed_answer(decision: "Choice", value_json: str) -> str:
|
|
@@ -141,40 +145,57 @@ class Choice:
|
|
|
141
145
|
|
|
142
146
|
|
|
143
147
|
class TypeLLMClient:
|
|
144
|
-
"""
|
|
148
|
+
"""Generate typed answers with local SGLang or the hosted API."""
|
|
145
149
|
|
|
146
150
|
DEFAULT_LABEL_POOL = tuple(ascii_uppercase + digits)
|
|
147
151
|
|
|
148
152
|
def __init__(
|
|
149
153
|
self,
|
|
150
|
-
base_url: str =
|
|
154
|
+
base_url: str | None = None,
|
|
151
155
|
model: str | None = None,
|
|
152
156
|
*,
|
|
157
|
+
api_key: str | None = None,
|
|
153
158
|
mode: str | None = None,
|
|
154
159
|
temperature: float | None = None,
|
|
155
160
|
seed: int | None = None,
|
|
156
|
-
timeout: float =
|
|
161
|
+
timeout: float | None = None,
|
|
157
162
|
label_pool: Sequence[str] | None = None,
|
|
158
163
|
numeric_max_digits: int = 32,
|
|
159
164
|
tokenizer: str | None = None,
|
|
160
165
|
thinking_budget: int | None = None,
|
|
161
166
|
text_max_tokens: int = 128,
|
|
162
167
|
) -> None:
|
|
163
|
-
"""
|
|
164
|
-
|
|
168
|
+
"""Run on your own SGLang server, by default http://127.0.0.1:30000.
|
|
169
|
+
|
|
170
|
+
With api_key, calls go to the hosted API instead, by default
|
|
171
|
+
https://api.typellm.ai. It compiles and runs the schema itself. Model
|
|
172
|
+
chooses one of its models; temperature, seed and timeout apply.
|
|
173
|
+
A hosted timeout set here is the default for generate() calls; without
|
|
174
|
+
one, the service uses its own default.
|
|
175
|
+
|
|
176
|
+
temperature 0 (the default) picks the most likely answer; above 0 samples
|
|
177
|
+
at that temperature. mode is deprecated: temperature alone decides.
|
|
165
178
|
"""
|
|
166
179
|
mode, temperature = _resolve_decoding(mode, temperature)
|
|
167
180
|
if type(numeric_max_digits) is not int or numeric_max_digits <= 0:
|
|
168
181
|
raise ValueError("numeric_max_digits must be a positive integer")
|
|
169
|
-
self.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
self.api_key = api_key
|
|
183
|
+
if api_key is None:
|
|
184
|
+
self.sglang = SGLangClient(
|
|
185
|
+
base_url or "http://127.0.0.1:30000",
|
|
186
|
+
model,
|
|
187
|
+
DEFAULT_SOCKET_TIMEOUT if timeout is None else timeout,
|
|
188
|
+
tokenizer=tokenizer,
|
|
189
|
+
thinking_budget=thinking_budget,
|
|
190
|
+
text_max_tokens=text_max_tokens,
|
|
191
|
+
answer_reserve_tokens=numeric_max_digits + 3,
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
self.sglang = None
|
|
195
|
+
self.base_url = (base_url or HOSTED_URL).rstrip("/")
|
|
196
|
+
self.model = model
|
|
197
|
+
self.timeout = timeout
|
|
198
|
+
self._transport: httpx.BaseTransport | None = None # tests put a MockTransport here
|
|
178
199
|
self.mode = mode
|
|
179
200
|
self.temperature = temperature
|
|
180
201
|
self.rng = random.Random(seed)
|
|
@@ -219,7 +240,8 @@ class TypeLLMClient:
|
|
|
219
240
|
def last_usage(self) -> Usage | None:
|
|
220
241
|
"""Requests and tokens of the last generate() call made in this thread or task.
|
|
221
242
|
|
|
222
|
-
|
|
243
|
+
Local calls set this even when they raise, so partial work is counted.
|
|
244
|
+
Hosted success responses set input_tokens and thinking_tokens.
|
|
223
245
|
"""
|
|
224
246
|
return self._last_usage.get()
|
|
225
247
|
|
|
@@ -246,6 +268,8 @@ class TypeLLMClient:
|
|
|
246
268
|
|
|
247
269
|
def compile_schema(self, schema: Mapping[str, Any]) -> list[Choice]:
|
|
248
270
|
"""Compile standard JSON Schema or the original ordered-list format."""
|
|
271
|
+
if self.api_key is not None:
|
|
272
|
+
raise ValueError("compile_schema needs your own server when using api_key")
|
|
249
273
|
if not isinstance(schema, Mapping):
|
|
250
274
|
raise SchemaError("schema must be a mapping")
|
|
251
275
|
if schema.get("type", "object") != "object":
|
|
@@ -373,9 +397,10 @@ class TypeLLMClient:
|
|
|
373
397
|
A seed fixes this call's own random choices and leaves the client's shared
|
|
374
398
|
stream alone; without one, calls share that stream. The server's numerics
|
|
375
399
|
can still vary with its cache and batching, so a seed does not guarantee
|
|
376
|
-
identical results. timeout caps the
|
|
377
|
-
|
|
378
|
-
|
|
400
|
+
identical results. timeout caps the call and raises GenerationTimeout.
|
|
401
|
+
In local mode, setting cancel raises GenerationCancelled; timeout and
|
|
402
|
+
cancel stop the call before its next request to SGLang. Hosted mode
|
|
403
|
+
sends timeout to the service and does not support cancel.
|
|
379
404
|
"""
|
|
380
405
|
self._last_usage.set(None)
|
|
381
406
|
self._last_thinking.set({})
|
|
@@ -398,6 +423,12 @@ class TypeLLMClient:
|
|
|
398
423
|
if temperature is None:
|
|
399
424
|
temperature = self.temperature or None
|
|
400
425
|
active_mode, active_temperature = _resolve_decoding(mode, temperature)
|
|
426
|
+
if self.api_key is not None:
|
|
427
|
+
if questions is None or cancel is not None or print_final_prompt:
|
|
428
|
+
raise ValueError("with api_key, generate() takes questions; schema, cancel and "
|
|
429
|
+
"print_final_prompt need your own server")
|
|
430
|
+
return self._generate_hosted(context, questions, encoded_images, active_mode,
|
|
431
|
+
active_temperature, seed, timeout)
|
|
401
432
|
rng = self.rng if seed is None else random.Random(seed)
|
|
402
433
|
# The time budget covers compiling too: labels are tokenized by SGLang.
|
|
403
434
|
with call_scope(timeout, cancel) as scope:
|
|
@@ -445,6 +476,59 @@ class TypeLLMClient:
|
|
|
445
476
|
_print_final_prompts(prompts)
|
|
446
477
|
return output
|
|
447
478
|
|
|
479
|
+
def _generate_hosted(self, context: str, questions: Mapping[str, Any], images: Sequence[str],
|
|
480
|
+
mode: str, temperature: float, seed: int | None,
|
|
481
|
+
timeout: float | None) -> dict[str, Any]:
|
|
482
|
+
"""One POST /v1/generate. HTTP errors carry their status in SGLangError."""
|
|
483
|
+
active_timeout = self.timeout if timeout is None else timeout
|
|
484
|
+
if active_timeout is not None and (type(active_timeout) not in (int, float) or
|
|
485
|
+
not active_timeout > 0):
|
|
486
|
+
raise ValueError("timeout must be a positive number of seconds or None")
|
|
487
|
+
options: dict[str, Any] = {
|
|
488
|
+
"mode": mode,
|
|
489
|
+
# Without a seed, calls draw theirs from the client's stream, as locally.
|
|
490
|
+
"seed": self.rng.randrange(2**32) if seed is None else seed,
|
|
491
|
+
}
|
|
492
|
+
if mode == "sample":
|
|
493
|
+
options["temperature"] = temperature
|
|
494
|
+
body: dict[str, Any] = {
|
|
495
|
+
"context": context,
|
|
496
|
+
"questions": questions,
|
|
497
|
+
"images": list(images),
|
|
498
|
+
"options": options,
|
|
499
|
+
}
|
|
500
|
+
if self.model is not None:
|
|
501
|
+
body["model"] = self.model
|
|
502
|
+
if active_timeout is not None:
|
|
503
|
+
body["timeout"] = active_timeout # the service's own default (60 s) otherwise
|
|
504
|
+
try:
|
|
505
|
+
with httpx.Client(transport=self._transport) as http:
|
|
506
|
+
# The service times the call itself. The margin covers its queue, image
|
|
507
|
+
# scaling and the grace it gives its own workers past the timeout.
|
|
508
|
+
response = http.post(self.base_url + "/v1/generate", json=body,
|
|
509
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
510
|
+
timeout=(active_timeout if active_timeout is not None
|
|
511
|
+
else DEFAULT_SOCKET_TIMEOUT) + 60)
|
|
512
|
+
except httpx.HTTPError as exc:
|
|
513
|
+
raise SGLangError(f"Could not reach the TypeLLM API at {self.base_url}: {exc!r}") from exc
|
|
514
|
+
if response.status_code != 200:
|
|
515
|
+
error = GenerationTimeout if response.status_code == 504 else SGLangError
|
|
516
|
+
raise error(f"TypeLLM API returned HTTP {response.status_code}: {response.text}",
|
|
517
|
+
status=response.status_code)
|
|
518
|
+
try:
|
|
519
|
+
data = response.json()
|
|
520
|
+
usage = data["usage"]
|
|
521
|
+
result = data["result"]
|
|
522
|
+
thinking = data.get("thinking") or {}
|
|
523
|
+
input_tokens = usage["input_tokens"]
|
|
524
|
+
thinking_tokens = usage["thinking_tokens"]
|
|
525
|
+
except (ValueError, TypeError, KeyError) as exc:
|
|
526
|
+
raise SGLangError("TypeLLM API returned an invalid response",
|
|
527
|
+
status=response.status_code) from exc
|
|
528
|
+
self._last_usage.set(Usage(input_tokens=input_tokens, thinking_tokens=thinking_tokens))
|
|
529
|
+
self._last_thinking.set(thinking)
|
|
530
|
+
return result
|
|
531
|
+
|
|
448
532
|
|
|
449
533
|
def candidate_softmax(
|
|
450
534
|
logprobs: Mapping[str, float], temperature: float = 1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typellm
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0.dev2
|
|
4
4
|
Summary: Type-Safe Decoding for Autoregressive LLMs
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://typellm.ai/
|
|
@@ -66,6 +66,36 @@ Evaluated on 231 public [JevBench](https://github.com/fstandhartinger/jevbench)
|
|
|
66
66
|
|
|
67
67
|
## Quick start
|
|
68
68
|
|
|
69
|
+
### Hosted API (early access)
|
|
70
|
+
|
|
71
|
+
No GPU? [Early-access](https://typellm.ai/early-access) users get an API key
|
|
72
|
+
by email, and the hosted API runs the model:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from typellm import TypeLLMClient
|
|
76
|
+
|
|
77
|
+
client = TypeLLMClient(api_key="YOUR_API_KEY") # calls https://api.typellm.ai
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`generate()` then works as in the example below, with these differences:
|
|
81
|
+
|
|
82
|
+
- The service compiles and runs the schema. Use `model`, `temperature`, `seed`
|
|
83
|
+
and `timeout`; local compilation settings do not apply.
|
|
84
|
+
A call runs for at most 300 seconds, 60 by default. Set `timeout` on the
|
|
85
|
+
client for a new default, or on `generate()` to override it for one call.
|
|
86
|
+
- Pass `questions`; `schema=`, `cancel` and `print_final_prompt` need your
|
|
87
|
+
own server. Images can be files, bytes, PIL images or data: URIs, not URLs.
|
|
88
|
+
- Each key runs a limited number of calls at once, told with the key. Calls
|
|
89
|
+
beyond it fail right away with HTTP 429.
|
|
90
|
+
- Errors raise `SGLangError` with the HTTP status in `.status`, and a call
|
|
91
|
+
that runs out of time raises `GenerationTimeout`. The service caps the
|
|
92
|
+
context size and the images, answering 400 past them, and stops string
|
|
93
|
+
answers at 128 tokens (`maxLength` is refused).
|
|
94
|
+
- Successful calls set `last_usage` from the service's input and thinking token
|
|
95
|
+
counts. Failed hosted calls may not return usage.
|
|
96
|
+
|
|
97
|
+
To run on your own GPU instead, start here:
|
|
98
|
+
|
|
69
99
|
### 1. Serve a model with SGLang
|
|
70
100
|
|
|
71
101
|
Use [SGLang](https://github.com/sgl-project/sglang) to configure and serve a
|
|
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
|