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.
Files changed (33) hide show
  1. {typellm-0.2.3/typellm.egg-info → typellm-0.3.0.dev1}/PKG-INFO +31 -1
  2. {typellm-0.2.3 → typellm-0.3.0.dev1}/README.md +30 -0
  3. {typellm-0.2.3 → typellm-0.3.0.dev1}/pyproject.toml +1 -1
  4. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_typellm.py +116 -0
  5. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/runtime.py +101 -17
  6. {typellm-0.2.3 → typellm-0.3.0.dev1/typellm.egg-info}/PKG-INFO +31 -1
  7. {typellm-0.2.3 → typellm-0.3.0.dev1}/LICENSE +0 -0
  8. {typellm-0.2.3 → typellm-0.3.0.dev1}/setup.cfg +0 -0
  9. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_batching.py +0 -0
  10. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_concurrency.py +0 -0
  11. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_dependencies.py +0 -0
  12. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_grammar_fields.py +0 -0
  13. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_images.py +0 -0
  14. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_nullable.py +0 -0
  15. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_permutations.py +0 -0
  16. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_prefill.py +0 -0
  17. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_probabilities.py +0 -0
  18. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_prompts.py +0 -0
  19. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_protocols.py +0 -0
  20. {typellm-0.2.3 → typellm-0.3.0.dev1}/tests/test_text.py +0 -0
  21. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/__init__.py +0 -0
  22. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/__main__.py +0 -0
  23. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/benchmark.py +0 -0
  24. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/cli.py +0 -0
  25. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/images.py +0 -0
  26. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/protocol.py +0 -0
  27. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/schema.py +0 -0
  28. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm/sglang.py +0 -0
  29. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm.egg-info/SOURCES.txt +0 -0
  30. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm.egg-info/dependency_links.txt +0 -0
  31. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm.egg-info/entry_points.txt +0 -0
  32. {typellm-0.2.3 → typellm-0.3.0.dev1}/typellm.egg-info/requires.txt +0 -0
  33. {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.2.3
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
@@ -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.3.0.dev1"
8
8
  description = "Type-Safe Decoding for Autoregressive LLMs"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -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
- """Compile ordered schemas into constrained single-token decisions."""
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 = "http://127.0.0.1:30000",
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 = 120.0,
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.sglang = SGLangClient(
166
- base_url,
167
- model,
168
- timeout,
169
- tokenizer=tokenizer,
170
- thinking_budget=thinking_budget,
171
- text_max_tokens=text_max_tokens,
172
- answer_reserve_tokens=numeric_max_digits + 3,
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
- Set even when the call raised, so partial work is still counted.
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 whole call in seconds and
373
- raises GenerationTimeout; setting cancel raises GenerationCancelled.
374
- Both stop the call before its next request to SGLang.
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.2.3
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