typellm 0.2.3__tar.gz → 0.3.0.dev1__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.3.0.dev1}/PKG-INFO +31 -1
- {typellm-0.2.3 → typellm-0.3.0.dev1}/README.md +30 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/pyproject.toml +1 -1
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_typellm.py +116 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/runtime.py +101 -17
- {typellm-0.2.3 → typellm-0.3.0.dev1/typellm.egg-info}/PKG-INFO +31 -1
- {typellm-0.2.3 → typellm-0.3.0.dev1}/LICENSE +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/setup.cfg +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_batching.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_concurrency.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_dependencies.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_grammar_fields.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_images.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_nullable.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_permutations.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_prefill.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_probabilities.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_prompts.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_protocols.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_text.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/__init__.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/__main__.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/benchmark.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/cli.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/images.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/protocol.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/schema.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/sglang.py +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm.egg-info/SOURCES.txt +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm.egg-info/dependency_links.txt +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm.egg-info/entry_points.txt +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm.egg-info/requires.txt +0 -0
- {typellm-0.2.3 → typellm-0.3.0.dev1}/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.dev1
|
|
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`, `mode`, `temperature`
|
|
83
|
+
(for sampling), `seed` 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`, `mode`, `temperature`
|
|
68
|
+
(for sampling), `seed` 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,5 +910,118 @@ 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, mode="sample", 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()
|
|
@@ -16,6 +16,8 @@ from itertools import permutations as all_permutations
|
|
|
16
16
|
from string import ascii_uppercase, digits
|
|
17
17
|
from typing import Any, Mapping, Sequence
|
|
18
18
|
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
19
21
|
from .schema import (
|
|
20
22
|
MAX_ENUM_CHOICES,
|
|
21
23
|
SchemaError,
|
|
@@ -23,10 +25,12 @@ from .schema import (
|
|
|
23
25
|
dependency_layers,
|
|
24
26
|
)
|
|
25
27
|
from .images import encode_images
|
|
26
|
-
from .sglang import SGLangClient, Usage, call_scope
|
|
28
|
+
from .sglang import GenerationTimeout, SGLangClient, SGLangError, Usage, call_scope
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
LOG = logging.getLogger("typellm")
|
|
32
|
+
HOSTED_URL = "https://api.typellm.ai"
|
|
33
|
+
DEFAULT_SOCKET_TIMEOUT = 120.0
|
|
30
34
|
|
|
31
35
|
|
|
32
36
|
def _closed_answer(decision: "Choice", value_json: str) -> str:
|
|
@@ -140,37 +144,54 @@ class Choice:
|
|
|
140
144
|
|
|
141
145
|
|
|
142
146
|
class TypeLLMClient:
|
|
143
|
-
"""
|
|
147
|
+
"""Generate typed answers with local SGLang or the hosted API."""
|
|
144
148
|
|
|
145
149
|
DEFAULT_LABEL_POOL = tuple(ascii_uppercase + digits)
|
|
146
150
|
|
|
147
151
|
def __init__(
|
|
148
152
|
self,
|
|
149
|
-
base_url: str =
|
|
153
|
+
base_url: str | None = None,
|
|
150
154
|
model: str | None = None,
|
|
151
155
|
*,
|
|
156
|
+
api_key: str | None = None,
|
|
152
157
|
mode: str = "argmax",
|
|
153
158
|
temperature: float = 1.0,
|
|
154
159
|
seed: int | None = None,
|
|
155
|
-
timeout: float =
|
|
160
|
+
timeout: float | None = None,
|
|
156
161
|
label_pool: Sequence[str] | None = None,
|
|
157
162
|
numeric_max_digits: int = 32,
|
|
158
163
|
tokenizer: str | None = None,
|
|
159
164
|
thinking_budget: int | None = None,
|
|
160
165
|
text_max_tokens: int = 128,
|
|
161
166
|
) -> None:
|
|
167
|
+
"""Run on your own SGLang server, by default http://127.0.0.1:30000.
|
|
168
|
+
|
|
169
|
+
With api_key, calls go to the hosted API instead, by default
|
|
170
|
+
https://api.typellm.ai. It compiles and runs the schema itself. Model
|
|
171
|
+
chooses one of its models; mode, temperature, seed and timeout apply.
|
|
172
|
+
A hosted timeout set here is the default for generate() calls; without
|
|
173
|
+
one, the service uses its own default.
|
|
174
|
+
"""
|
|
162
175
|
_validate_decoding(mode, temperature)
|
|
163
176
|
if type(numeric_max_digits) is not int or numeric_max_digits <= 0:
|
|
164
177
|
raise ValueError("numeric_max_digits must be a positive integer")
|
|
165
|
-
self.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
178
|
+
self.api_key = api_key
|
|
179
|
+
if api_key is None:
|
|
180
|
+
self.sglang = SGLangClient(
|
|
181
|
+
base_url or "http://127.0.0.1:30000",
|
|
182
|
+
model,
|
|
183
|
+
DEFAULT_SOCKET_TIMEOUT if timeout is None else timeout,
|
|
184
|
+
tokenizer=tokenizer,
|
|
185
|
+
thinking_budget=thinking_budget,
|
|
186
|
+
text_max_tokens=text_max_tokens,
|
|
187
|
+
answer_reserve_tokens=numeric_max_digits + 3,
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
self.sglang = None
|
|
191
|
+
self.base_url = (base_url or HOSTED_URL).rstrip("/")
|
|
192
|
+
self.model = model
|
|
193
|
+
self.timeout = timeout
|
|
194
|
+
self._transport: httpx.BaseTransport | None = None # tests put a MockTransport here
|
|
174
195
|
self.mode = mode
|
|
175
196
|
self.temperature = temperature
|
|
176
197
|
self.rng = random.Random(seed)
|
|
@@ -215,7 +236,8 @@ class TypeLLMClient:
|
|
|
215
236
|
def last_usage(self) -> Usage | None:
|
|
216
237
|
"""Requests and tokens of the last generate() call made in this thread or task.
|
|
217
238
|
|
|
218
|
-
|
|
239
|
+
Local calls set this even when they raise, so partial work is counted.
|
|
240
|
+
Hosted success responses set input_tokens and thinking_tokens.
|
|
219
241
|
"""
|
|
220
242
|
return self._last_usage.get()
|
|
221
243
|
|
|
@@ -242,6 +264,8 @@ class TypeLLMClient:
|
|
|
242
264
|
|
|
243
265
|
def compile_schema(self, schema: Mapping[str, Any]) -> list[Choice]:
|
|
244
266
|
"""Compile standard JSON Schema or the original ordered-list format."""
|
|
267
|
+
if self.api_key is not None:
|
|
268
|
+
raise ValueError("compile_schema needs your own server when using api_key")
|
|
245
269
|
if not isinstance(schema, Mapping):
|
|
246
270
|
raise SchemaError("schema must be a mapping")
|
|
247
271
|
if schema.get("type", "object") != "object":
|
|
@@ -369,9 +393,10 @@ class TypeLLMClient:
|
|
|
369
393
|
A seed fixes this call's own random choices and leaves the client's shared
|
|
370
394
|
stream alone; without one, calls share that stream. The server's numerics
|
|
371
395
|
can still vary with its cache and batching, so a seed does not guarantee
|
|
372
|
-
identical results. timeout caps the
|
|
373
|
-
|
|
374
|
-
|
|
396
|
+
identical results. timeout caps the call and raises GenerationTimeout.
|
|
397
|
+
In local mode, setting cancel raises GenerationCancelled; timeout and
|
|
398
|
+
cancel stop the call before its next request to SGLang. Hosted mode
|
|
399
|
+
sends timeout to the service and does not support cancel.
|
|
375
400
|
"""
|
|
376
401
|
self._last_usage.set(None)
|
|
377
402
|
self._last_thinking.set({})
|
|
@@ -390,6 +415,12 @@ class TypeLLMClient:
|
|
|
390
415
|
active_mode = self.mode if mode is None else mode
|
|
391
416
|
active_temperature = self.temperature if temperature is None else temperature
|
|
392
417
|
_validate_decoding(active_mode, active_temperature)
|
|
418
|
+
if self.api_key is not None:
|
|
419
|
+
if questions is None or cancel is not None or print_final_prompt:
|
|
420
|
+
raise ValueError("with api_key, generate() takes questions; schema, cancel and "
|
|
421
|
+
"print_final_prompt need your own server")
|
|
422
|
+
return self._generate_hosted(context, questions, encoded_images, active_mode,
|
|
423
|
+
active_temperature, seed, timeout)
|
|
393
424
|
rng = self.rng if seed is None else random.Random(seed)
|
|
394
425
|
# The time budget covers compiling too: labels are tokenized by SGLang.
|
|
395
426
|
with call_scope(timeout, cancel) as scope:
|
|
@@ -437,6 +468,59 @@ class TypeLLMClient:
|
|
|
437
468
|
_print_final_prompts(prompts)
|
|
438
469
|
return output
|
|
439
470
|
|
|
471
|
+
def _generate_hosted(self, context: str, questions: Mapping[str, Any], images: Sequence[str],
|
|
472
|
+
mode: str, temperature: float, seed: int | None,
|
|
473
|
+
timeout: float | None) -> dict[str, Any]:
|
|
474
|
+
"""One POST /v1/generate. HTTP errors carry their status in SGLangError."""
|
|
475
|
+
active_timeout = self.timeout if timeout is None else timeout
|
|
476
|
+
if active_timeout is not None and (type(active_timeout) not in (int, float) or
|
|
477
|
+
not active_timeout > 0):
|
|
478
|
+
raise ValueError("timeout must be a positive number of seconds or None")
|
|
479
|
+
options: dict[str, Any] = {
|
|
480
|
+
"mode": mode,
|
|
481
|
+
# Without a seed, calls draw theirs from the client's stream, as locally.
|
|
482
|
+
"seed": self.rng.randrange(2**32) if seed is None else seed,
|
|
483
|
+
}
|
|
484
|
+
if mode == "sample":
|
|
485
|
+
options["temperature"] = temperature
|
|
486
|
+
body: dict[str, Any] = {
|
|
487
|
+
"context": context,
|
|
488
|
+
"questions": questions,
|
|
489
|
+
"images": list(images),
|
|
490
|
+
"options": options,
|
|
491
|
+
}
|
|
492
|
+
if self.model is not None:
|
|
493
|
+
body["model"] = self.model
|
|
494
|
+
if active_timeout is not None:
|
|
495
|
+
body["timeout"] = active_timeout # the service's own default (60 s) otherwise
|
|
496
|
+
try:
|
|
497
|
+
with httpx.Client(transport=self._transport) as http:
|
|
498
|
+
# The service times the call itself. The margin covers its queue, image
|
|
499
|
+
# scaling and the grace it gives its own workers past the timeout.
|
|
500
|
+
response = http.post(self.base_url + "/v1/generate", json=body,
|
|
501
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
502
|
+
timeout=(active_timeout if active_timeout is not None
|
|
503
|
+
else DEFAULT_SOCKET_TIMEOUT) + 60)
|
|
504
|
+
except httpx.HTTPError as exc:
|
|
505
|
+
raise SGLangError(f"Could not reach the TypeLLM API at {self.base_url}: {exc!r}") from exc
|
|
506
|
+
if response.status_code != 200:
|
|
507
|
+
error = GenerationTimeout if response.status_code == 504 else SGLangError
|
|
508
|
+
raise error(f"TypeLLM API returned HTTP {response.status_code}: {response.text}",
|
|
509
|
+
status=response.status_code)
|
|
510
|
+
try:
|
|
511
|
+
data = response.json()
|
|
512
|
+
usage = data["usage"]
|
|
513
|
+
result = data["result"]
|
|
514
|
+
thinking = data.get("thinking") or {}
|
|
515
|
+
input_tokens = usage["input_tokens"]
|
|
516
|
+
thinking_tokens = usage["thinking_tokens"]
|
|
517
|
+
except (ValueError, TypeError, KeyError) as exc:
|
|
518
|
+
raise SGLangError("TypeLLM API returned an invalid response",
|
|
519
|
+
status=response.status_code) from exc
|
|
520
|
+
self._last_usage.set(Usage(input_tokens=input_tokens, thinking_tokens=thinking_tokens))
|
|
521
|
+
self._last_thinking.set(thinking)
|
|
522
|
+
return result
|
|
523
|
+
|
|
440
524
|
|
|
441
525
|
def candidate_softmax(
|
|
442
526
|
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.dev1
|
|
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`, `mode`, `temperature`
|
|
83
|
+
(for sampling), `seed` 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
|