typellm 0.2.6__tar.gz → 0.3.0__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.6 → typellm-0.3.0}/PKG-INFO +1 -1
  2. {typellm-0.2.6 → typellm-0.3.0}/pyproject.toml +1 -1
  3. {typellm-0.2.6 → typellm-0.3.0}/tests/test_batching.py +11 -10
  4. {typellm-0.2.6 → typellm-0.3.0}/tests/test_concurrency.py +9 -9
  5. {typellm-0.2.6 → typellm-0.3.0}/tests/test_grammar_fields.py +6 -6
  6. {typellm-0.2.6 → typellm-0.3.0}/tests/test_images.py +8 -8
  7. {typellm-0.2.6 → typellm-0.3.0}/tests/test_nullable.py +4 -4
  8. {typellm-0.2.6 → typellm-0.3.0}/tests/test_permutations.py +6 -6
  9. {typellm-0.2.6 → typellm-0.3.0}/tests/test_prefill.py +8 -6
  10. {typellm-0.2.6 → typellm-0.3.0}/tests/test_probabilities.py +1 -1
  11. {typellm-0.2.6 → typellm-0.3.0}/tests/test_prompts.py +1 -1
  12. {typellm-0.2.6 → typellm-0.3.0}/tests/test_text.py +3 -3
  13. {typellm-0.2.6 → typellm-0.3.0}/tests/test_typellm.py +256 -20
  14. {typellm-0.2.6 → typellm-0.3.0}/typellm/runtime.py +163 -17
  15. {typellm-0.2.6 → typellm-0.3.0}/typellm/sglang.py +27 -5
  16. {typellm-0.2.6 → typellm-0.3.0}/typellm.egg-info/PKG-INFO +1 -1
  17. {typellm-0.2.6 → typellm-0.3.0}/LICENSE +0 -0
  18. {typellm-0.2.6 → typellm-0.3.0}/README.md +0 -0
  19. {typellm-0.2.6 → typellm-0.3.0}/setup.cfg +0 -0
  20. {typellm-0.2.6 → typellm-0.3.0}/tests/test_dependencies.py +0 -0
  21. {typellm-0.2.6 → typellm-0.3.0}/tests/test_protocols.py +0 -0
  22. {typellm-0.2.6 → typellm-0.3.0}/typellm/__init__.py +0 -0
  23. {typellm-0.2.6 → typellm-0.3.0}/typellm/__main__.py +0 -0
  24. {typellm-0.2.6 → typellm-0.3.0}/typellm/benchmark.py +0 -0
  25. {typellm-0.2.6 → typellm-0.3.0}/typellm/cli.py +0 -0
  26. {typellm-0.2.6 → typellm-0.3.0}/typellm/images.py +0 -0
  27. {typellm-0.2.6 → typellm-0.3.0}/typellm/protocol.py +0 -0
  28. {typellm-0.2.6 → typellm-0.3.0}/typellm/schema.py +0 -0
  29. {typellm-0.2.6 → typellm-0.3.0}/typellm.egg-info/SOURCES.txt +0 -0
  30. {typellm-0.2.6 → typellm-0.3.0}/typellm.egg-info/dependency_links.txt +0 -0
  31. {typellm-0.2.6 → typellm-0.3.0}/typellm.egg-info/entry_points.txt +0 -0
  32. {typellm-0.2.6 → typellm-0.3.0}/typellm.egg-info/requires.txt +0 -0
  33. {typellm-0.2.6 → typellm-0.3.0}/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.6
3
+ Version: 0.3.0
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.6"
7
+ version = "0.3.0"
8
8
  description = "Type-Safe Decoding for Autoregressive LLMs"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -94,7 +94,7 @@ class PrefixWarmupTests(unittest.TestCase):
94
94
  def test_the_context_is_warmed_before_any_batch_that_shares_it(self):
95
95
  for thinking in (False, True):
96
96
  with self.subTest(thinking=thinking):
97
- client = TypeLLMClient(model="fake")
97
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
98
98
  client.sglang = FakeServer()
99
99
  client.generate(context="Receipt", questions=thinks(self.MIXED) if thinking else self.MIXED).result
100
100
  warm = client.sglang.requests("count")
@@ -105,7 +105,7 @@ class PrefixWarmupTests(unittest.TestCase):
105
105
  self.assertNotIn('{"a":', warm[0]["text"])
106
106
 
107
107
  def test_open_fields_alone_are_not_warmed(self):
108
- client = TypeLLMClient(model="fake")
108
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
109
109
  client.sglang = FakeServer()
110
110
  client.generate(context="Receipt", questions={"a": {"type": "integer"}, "n": {"type": "string"}}).result
111
111
  self.assertEqual(client.sglang.requests("count"), [])
@@ -117,7 +117,7 @@ class PerFieldThinkingTests(unittest.TestCase):
117
117
  for t in ([p["text"]] if isinstance(p["text"], str) else p["text"])]
118
118
 
119
119
  def test_only_fields_that_ask_for_it_think(self):
120
- client = TypeLLMClient(model="fake")
120
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
121
121
  client.sglang = FakeServer()
122
122
  result = client.generate(context="Receipt", questions={
123
123
  "hard": {"type": "boolean", "instructions": "Hard?", "thinking": True},
@@ -133,7 +133,7 @@ class PerFieldThinkingTests(unittest.TestCase):
133
133
  self.assertTrue(any("Easy?" in t and "<think>" not in t for t in answers))
134
134
 
135
135
  def test_thinking_false_is_the_same_as_leaving_it_out(self):
136
- client = TypeLLMClient(model="fake")
136
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
137
137
  client.sglang = FakeServer()
138
138
  client.generate(context="Receipt", questions={
139
139
  "a": {"type": "boolean", "instructions": "A?", "thinking": True},
@@ -146,7 +146,7 @@ class PerFieldThinkingTests(unittest.TestCase):
146
146
  self.assertFalse(any("Skip?" in t for t in prompts))
147
147
 
148
148
  def test_each_field_gets_its_own_budget(self):
149
- client = TypeLLMClient(model="fake")
149
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
150
150
  client.sglang = FakeServer()
151
151
  client.sglang.thinking_budget = 500 # the client's budget, for fields without their own
152
152
  client.generate(context="Receipt", questions={
@@ -170,7 +170,7 @@ class PerFieldThinkingTests(unittest.TestCase):
170
170
  item["meta_info"]["completion_tokens"] = 40 if thinking else 1
171
171
  return response
172
172
 
173
- client = TypeLLMClient(model="fake")
173
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
174
174
  client.sglang = Metered()
175
175
  done = client.generate(context="Receipt", questions={
176
176
  "hard": {"type": "boolean", "instructions": "Hard?", "thinking": True},
@@ -186,7 +186,8 @@ class PerFieldThinkingTests(unittest.TestCase):
186
186
  for field in ({"type": "boolean", "thinking": "yes"}, {"type": "boolean", "thinking_budget": 0},
187
187
  {"type": "boolean", "thinking_budget": 2.5}):
188
188
  with self.subTest(field=field), self.assertRaises(SchemaError):
189
- TypeLLMClient(model="fake").compile_schema({"type": "object", "properties": {"x": field}})
189
+ TypeLLMClient("http://127.0.0.1:30000", model="fake").compile_schema(
190
+ {"type": "object", "properties": {"x": field}})
190
191
 
191
192
 
192
193
  class BatchedThinkingTests(unittest.TestCase):
@@ -198,7 +199,7 @@ class BatchedThinkingTests(unittest.TestCase):
198
199
  }
199
200
 
200
201
  def test_one_thinking_request_covers_every_prompt_in_a_layer(self):
201
- client = TypeLLMClient(model="fake")
202
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
202
203
  client.sglang = FakeServer()
203
204
  result = client.generate(context="Receipt", questions=thinks(self.QUESTIONS)).result
204
205
  self.assertEqual(result, {"flag": True, "count": 7, "name": "blue", "pick": "x"})
@@ -211,7 +212,7 @@ class BatchedThinkingTests(unittest.TestCase):
211
212
  self.assertTrue(all("Reasoned." + THINK_STOP in t for t in texts))
212
213
 
213
214
  def test_dag_thinks_once_per_layer(self):
214
- client = TypeLLMClient(model="fake")
215
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
215
216
  client.sglang = FakeServer()
216
217
  client.generate(context="Receipt", questions=thinks({
217
218
  "a": {"type": "boolean"}, "b": {"type": "integer"},
@@ -221,7 +222,7 @@ class BatchedThinkingTests(unittest.TestCase):
221
222
  self.assertEqual([width(p) for p in client.sglang.requests("think")], [2, 2])
222
223
 
223
224
  def test_images_count_prompt_tokens_in_one_batch(self):
224
- client = TypeLLMClient(model="fake")
225
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
225
226
  client.sglang = FakeServer()
226
227
  client.generate(context="Receipt", images=[PNG],
227
228
  questions=thinks({"a": {"type": "boolean"}, "b": {"type": "boolean"}})).result
@@ -35,7 +35,7 @@ class SlowServer(FakeServer):
35
35
 
36
36
  class SharedClientTests(unittest.TestCase):
37
37
  def test_concurrent_calls_keep_their_own_prompts(self):
38
- client = TypeLLMClient(model="fake")
38
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
39
39
  client.sglang = SlowServer()
40
40
  finished = threading.Barrier(8)
41
41
 
@@ -53,7 +53,7 @@ class SharedClientTests(unittest.TestCase):
53
53
  self.assertIn(f"Receipt {n}", prompt)
54
54
 
55
55
  def test_a_seeded_call_is_reproducible_and_leaves_the_shared_stream(self):
56
- client = TypeLLMClient(model="fake", seed=1)
56
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake", seed=1)
57
57
  client.sglang = FakeServer()
58
58
  state = client.rng.getstate()
59
59
  client.generate(context="Roll", questions=PICK, seed=5).result
@@ -65,7 +65,7 @@ class SharedClientTests(unittest.TestCase):
65
65
  self.assertNotEqual(client.rng.getstate(), state)
66
66
 
67
67
  def test_clients_pickle_after_a_call(self):
68
- client = TypeLLMClient(model="fake")
68
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
69
69
  client.sglang = FakeServer()
70
70
  client.generate(context="Receipt", questions={"b": {"type": "boolean"}}).result
71
71
  copy = pickle.loads(pickle.dumps(client))
@@ -100,7 +100,7 @@ class UsageTests(unittest.TestCase):
100
100
  QUESTIONS = {"a": {"type": "integer"}, "b": {"type": "boolean"}, "c": {"type": "string"}}
101
101
 
102
102
  def test_usage_sums_every_generate_request_of_the_call(self):
103
- client = TypeLLMClient(model="fake")
103
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
104
104
  client.sglang = MeteredServer()
105
105
  usage = client.generate(context="Receipt", questions=self.QUESTIONS).usage
106
106
  n = prompts_sent(client.sglang)
@@ -121,7 +121,7 @@ class UsageTests(unittest.TestCase):
121
121
  "Usage(input_tokens=5, thinking_tokens=0, requests=2, prompt_tokens=30)")
122
122
 
123
123
  def test_concurrent_calls_count_only_their_own_requests(self):
124
- client = TypeLLMClient(model="fake")
124
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
125
125
  client.sglang = MeteredServer()
126
126
  finished = threading.Barrier(4)
127
127
 
@@ -139,7 +139,7 @@ class UsageTests(unittest.TestCase):
139
139
  self.assertEqual(sum(counts), len(client.sglang.payloads))
140
140
 
141
141
  def test_a_failed_call_still_reports_the_requests_it_made(self):
142
- client = TypeLLMClient(model="fake")
142
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
143
143
  client.sglang = MeteredServer(fail_after=2)
144
144
  with self.assertRaisesRegex(SGLangError, "went away") as caught:
145
145
  client.generate(context="Receipt", questions=self.QUESTIONS)
@@ -160,7 +160,7 @@ class DeadlineTests(unittest.TestCase):
160
160
  time.sleep(0.03)
161
161
  return super()._request(path, payload, allow_text=allow_text)
162
162
 
163
- client = TypeLLMClient(model="fake")
163
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
164
164
  client.sglang = Slower()
165
165
  client.generate(context="Receipt", questions=self.QUESTIONS).result
166
166
  needed = len(client.sglang.payloads)
@@ -180,7 +180,7 @@ class DeadlineTests(unittest.TestCase):
180
180
  cancel.set()
181
181
  return response
182
182
 
183
- client = TypeLLMClient(model="fake")
183
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
184
184
  client.sglang = Cancelling()
185
185
  with self.assertRaises(GenerationCancelled):
186
186
  client.generate(context="Receipt", questions=self.QUESTIONS, cancel=cancel).result
@@ -254,7 +254,7 @@ class DeadlineTests(unittest.TestCase):
254
254
  self.paths.append(path)
255
255
  return super()._request(path, payload, allow_text=allow_text)
256
256
 
257
- client = TypeLLMClient(model="fake")
257
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
258
258
  client.sglang = Recording()
259
259
  client.sglang.paths = []
260
260
  for kwargs in ({"timeout": 0}, {"timeout": -1}, {"timeout": True}, {"timeout": "5"},
@@ -61,7 +61,7 @@ class GrammarNumberTests(unittest.TestCase):
61
61
  QUESTIONS = {"a": {"type": "integer"}, "b": {"type": "number"}, "c": {"type": "integer"}}
62
62
 
63
63
  def test_every_number_of_a_layer_takes_one_request(self):
64
- client = TypeLLMClient(model="fake")
64
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
65
65
  client.sglang = FakeServer()
66
66
  result = client.generate(context="Receipt", questions=self.QUESTIONS).result
67
67
  self.assertEqual(result, {"a": 7, "b": 7, "c": 7})
@@ -73,7 +73,7 @@ class GrammarNumberTests(unittest.TestCase):
73
73
  self.assertIn('{"a": 7}', client._last_prompts.get()[0])
74
74
 
75
75
  def test_numbers_and_strings_of_a_layer_share_one_request(self):
76
- client = TypeLLMClient(model="fake")
76
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
77
77
  client.sglang = FakeServer()
78
78
  result = client.generate(context="Receipt", questions={
79
79
  "a": {"type": "integer"}, "n": {"type": "string"},
@@ -87,14 +87,14 @@ class GrammarNumberTests(unittest.TestCase):
87
87
  self.assertIn("null", patterns[3])
88
88
 
89
89
  def test_sampling_passes_the_temperature_and_no_truncation(self):
90
- client = TypeLLMClient(model="fake", temperature=0.7, seed=1)
90
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake", temperature=0.7, seed=1)
91
91
  client.sglang = FakeServer()
92
92
  client.generate(context="Receipt", questions={"a": {"type": "integer"}}).result
93
93
  [params] = regex_requests(client.sglang)[0]["sampling_params"]
94
94
  self.assertEqual((params["temperature"], params["top_p"], params["top_k"]), (0.7, 1.0, -1))
95
95
 
96
96
  def test_nullable_numbers_can_be_null_in_the_same_request(self):
97
- client = TypeLLMClient(model="fake")
97
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
98
98
  client.sglang = NullServer()
99
99
  result = client.generate(context="Receipt", questions={"tip": {"type": ["number", "null"]}}).result
100
100
  self.assertEqual(result, {"tip": None})
@@ -104,7 +104,7 @@ class GrammarNumberTests(unittest.TestCase):
104
104
  def test_an_incomplete_or_cut_off_number_is_an_error(self):
105
105
  for reply, finish, error in ((" 3.}", "stop", ValueError), (" 12", "length", SGLangError)):
106
106
  with self.subTest(reply=reply):
107
- client = TypeLLMClient(model="fake")
107
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
108
108
  client.sglang = ReplyServer(reply, finish)
109
109
  with self.assertRaises(error):
110
110
  client.generate(context="Receipt", questions={"a": {"type": "number"}}).result
@@ -112,7 +112,7 @@ class GrammarNumberTests(unittest.TestCase):
112
112
  def test_negative_and_spaceless_numbers_parse(self):
113
113
  for reply, value in ((" -3.5}", -3.5), ("42}", 42.0), (" 0", 0.0)):
114
114
  with self.subTest(reply=reply):
115
- client = TypeLLMClient(model="fake")
115
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
116
116
  client.sglang = ReplyServer(reply)
117
117
  self.assertEqual(client.generate(context="R", questions={"a": {"type": "number"}}).result,
118
118
  {"a": value})
@@ -161,7 +161,7 @@ class ImageEncodingTests(unittest.TestCase):
161
161
 
162
162
  class PicklingTests(unittest.TestCase):
163
163
  def test_clients_pickle_and_copy_without_sharing_images(self):
164
- client = TypeLLMClient(model="m")
164
+ client = TypeLLMClient("http://127.0.0.1:30000", model="m")
165
165
  for copy in (pickle.loads(pickle.dumps(client)), deepcopy(client)):
166
166
  self.assertEqual(copy.sglang.model, "m")
167
167
  token = client.sglang._active_images.set(("a",))
@@ -177,7 +177,7 @@ class PlaceholderTests(unittest.TestCase):
177
177
  self.assertEqual(FakeServerClient().image_placeholder(), VISION)
178
178
 
179
179
  def test_text_only_template_is_rejected_before_any_request(self):
180
- client = TypeLLMClient(model="fake")
180
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
181
181
  client.sglang = FakeServerClient(VisionTokenizer(renders_images=False))
182
182
  with self.assertRaisesRegex(SGLangError, "does not render image content"):
183
183
  client.generate(context="Receipt", images=[PNG],
@@ -185,7 +185,7 @@ class PlaceholderTests(unittest.TestCase):
185
185
  self.assertEqual(client.sglang.generate_payloads, [])
186
186
 
187
187
  def test_context_containing_image_tokens_is_rejected(self):
188
- client = TypeLLMClient(model="fake")
188
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
189
189
  client.sglang = FakeServerClient()
190
190
  with self.assertRaisesRegex(SGLangError, "2 image placeholders for 1 images"):
191
191
  client.generate(context=VISION, images=[PNG],
@@ -199,7 +199,7 @@ class ImageRequestTests(unittest.TestCase):
199
199
  }
200
200
 
201
201
  def run_generate(self, images, **kwargs):
202
- client = TypeLLMClient(model="fake")
202
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
203
203
  client.sglang = FakeServerClient()
204
204
  client.generate(context="Receipt", images=images, **kwargs).result
205
205
  return client.sglang.generate_payloads
@@ -239,14 +239,14 @@ class ImageRequestTests(unittest.TestCase):
239
239
  self.assert_images_attached(self.run_generate(images, questions=dag), images)
240
240
 
241
241
  def test_numeric_decoding_carries_the_images(self):
242
- client = TypeLLMClient(model="fake")
242
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
243
243
  client.sglang = FakeServerClient()
244
244
  client.generate(context="Receipt", images=[PNG],
245
245
  questions={"n": {"type": "integer", "instructions": "Count?"}}).result
246
246
  self.assert_images_attached(client.sglang.generate_payloads, [encode_image(PNG)])
247
247
 
248
248
  def test_images_are_scoped_to_one_call(self):
249
- client = TypeLLMClient(model="fake")
249
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
250
250
  client.sglang = FakeServerClient()
251
251
  client.generate(context="Receipt", images=[PNG], questions=self.QUESTIONS).result
252
252
  client.sglang.generate_payloads.clear()
@@ -273,7 +273,7 @@ class ImageRequestTests(unittest.TestCase):
273
273
 
274
274
  class ImageInputTokenTests(unittest.TestCase):
275
275
  def test_images_count_as_the_server_expands_them(self):
276
- client = TypeLLMClient(model="fake-vl")
276
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake-vl")
277
277
  client.sglang = FakeServerClient()
278
278
  questions = {"a": {"type": "boolean"}}
279
279
  usage = client.generate(context="Receipt", images=[PNG], questions=questions).usage
@@ -302,7 +302,7 @@ class RuntimeContentTests(unittest.TestCase):
302
302
  self.messages.append(messages)
303
303
  return "x"
304
304
 
305
- client = TypeLLMClient(model="fake")
305
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
306
306
  client.sglang = Recording()
307
307
  client.generate(context="Receipt", images=[PNG, PNG],
308
308
  questions={"paid": {"type": "boolean", "instructions": "Paid?"}}).result
@@ -47,7 +47,7 @@ class CompileTests(unittest.TestCase):
47
47
 
48
48
  class RuntimeTests(unittest.TestCase):
49
49
  def test_nullable_number_can_answer_null(self):
50
- client = TypeLLMClient(model="fake")
50
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
51
51
  client.sglang = NullServer()
52
52
  result = client.generate(context="Receipt", questions={
53
53
  "tip": {"type": ["number", "null"]}, "count": {"type": "integer"},
@@ -56,7 +56,7 @@ class RuntimeTests(unittest.TestCase):
56
56
  self.assertIn("Return null only if there is no value.", client._last_prompts.get()[0])
57
57
 
58
58
  def test_nullable_string_writes_null_in_its_text_request(self):
59
- client = TypeLLMClient(model="fake")
59
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
60
60
  client.sglang = NullServer()
61
61
  result = client.generate(context="Receipt", questions={
62
62
  "note": {"type": ["string", "null"]}, "name": {"type": "string"},
@@ -74,7 +74,7 @@ class RuntimeTests(unittest.TestCase):
74
74
  self.assertIn('{"note": null}', client._last_prompts.get()[0])
75
75
 
76
76
  def test_nullable_boolean_scores_null_as_a_choice(self):
77
- client = TypeLLMClient(model="fake")
77
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
78
78
  client.sglang = FakeSGLang(selected_ids=[ord("C")])
79
79
  result = client.generate(context="Receipt", questions={
80
80
  "paid": {"type": ["boolean", "null"], "return_probabilities": True},
@@ -83,7 +83,7 @@ class RuntimeTests(unittest.TestCase):
83
83
  self.assertEqual(set(result["paid"]["probabilities"]), {True, False, None})
84
84
 
85
85
  def test_dependents_see_null(self):
86
- client = TypeLLMClient(model="fake")
86
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
87
87
  client.sglang = NullServer()
88
88
  client.generate(context="Receipt", questions={
89
89
  "tip": {"type": ["number", "null"]},
@@ -12,7 +12,7 @@ from tests.test_dependencies import DependencyFake
12
12
 
13
13
  class PermutationTests(unittest.TestCase):
14
14
  def run_case(self, dependent, values, **options):
15
- client = TypeLLMClient(seed=42, **options)
15
+ client = TypeLLMClient("http://127.0.0.1:30000", seed=42, **options)
16
16
  client.sglang = DependencyFake([65] * 100)
17
17
  fields = {
18
18
  'roll': {'type': 'string' if isinstance(values[0], str) else 'integer',
@@ -61,7 +61,7 @@ class PermutationTests(unittest.TestCase):
61
61
  ] + [{'type': 'string', 'enum': ['a', 'b'], 'permutations': v}
62
62
  for v in (0, -1, True, False, 2.0, None, '8', 'AUTO', {}, [])]
63
63
  invalid.append({'type': 'integer', 'enum': list(range(7)), 'permutations': 'all'})
64
- client = TypeLLMClient()
64
+ client = TypeLLMClient("http://127.0.0.1:30000")
65
65
  with patch.object(client.sglang, 'single_token') as tokens:
66
66
  for field in invalid:
67
67
  with self.subTest(field=field), self.assertRaises(SchemaError):
@@ -69,7 +69,7 @@ class PermutationTests(unittest.TestCase):
69
69
  tokens.assert_not_called()
70
70
 
71
71
  def test_plain_output_boolean_enum_and_clamped_budget(self):
72
- client = TypeLLMClient()
72
+ client = TypeLLMClient("http://127.0.0.1:30000")
73
73
  client.sglang = DependencyFake([65] * 3)
74
74
  result = client.generate(context='x', questions={
75
75
  'x': {'type': 'boolean', 'enum': [False, True], 'permutations': 100},
@@ -80,7 +80,7 @@ class PermutationTests(unittest.TestCase):
80
80
  self.assertEqual(len(client.sglang.batch_prompts[0]), 3)
81
81
 
82
82
  def test_average_probabilities_not_logits(self):
83
- client = TypeLLMClient()
83
+ client = TypeLLMClient("http://127.0.0.1:30000")
84
84
  fake = DependencyFake()
85
85
  client.sglang = fake
86
86
  with patch.object(fake, 'score_candidates_batch', return_value=([
@@ -114,7 +114,7 @@ class PermutationTests(unittest.TestCase):
114
114
  self.assertEqual(len(value_orders(['a', 'b', 'c', 'd', 'e', 'f'])), 6)
115
115
 
116
116
  def test_auto_cancels_a_pure_position_bias(self):
117
- client = TypeLLMClient()
117
+ client = TypeLLMClient("http://127.0.0.1:30000")
118
118
  client.sglang = DependencyFake()
119
119
  biased = {65: math.log(.5), 66: math.log(.2), 67: math.log(.15), 68: math.log(.15)}
120
120
  with patch.object(client.sglang, 'score_candidates_batch',
@@ -129,7 +129,7 @@ class PermutationTests(unittest.TestCase):
129
129
  def test_one_matches_default(self):
130
130
  outputs = []
131
131
  for setting in ({}, {'permutations': 1}):
132
- client = TypeLLMClient()
132
+ client = TypeLLMClient("http://127.0.0.1:30000")
133
133
  client.sglang = DependencyFake([66])
134
134
  outputs.append(client.generate(context='x', questions={'x': {
135
135
  'type': 'string', 'enum': ['a', 'b'], 'return_probabilities': True, **setting}}).result)
@@ -13,7 +13,7 @@ class PrefillTests(unittest.TestCase):
13
13
  }
14
14
 
15
15
  def run_generate(self, questions, **kwargs):
16
- client = TypeLLMClient(model="fake")
16
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
17
17
  client.sglang = FakeServer()
18
18
  result = client.generate(context="Receipt", questions=questions, **kwargs).result
19
19
  return client, result
@@ -74,12 +74,14 @@ class StringConstraintTests(unittest.TestCase):
74
74
  client._request = text_reply(' "say \\"hi\\""}')
75
75
  self.assertEqual(client.generate_texts(['{"a":'], after_key=True), ['say "hi"'])
76
76
 
77
- def test_an_unfinished_string_fails(self):
78
- from typellm import SGLangError
77
+ def test_running_out_of_tokens_cuts_the_string_off(self):
78
+ # A string stops at text_max_tokens: the text written by then is its value.
79
79
  client = FakeServer()
80
- client._request = text_reply("Edamame and", finish="length")
81
- with self.assertRaisesRegex(SGLangError, "did not complete normally"):
82
- client.generate_texts(['{"a":'], after_key=True)
80
+ for text, value in [(' "Edamame and', "Edamame and"),
81
+ (' "tab\\', "tab"), # cut inside an escape
82
+ (' "smile \\ud83d\\ude', "smile ")]: # cut inside a surrogate pair
83
+ client._request = text_reply(text, finish="length")
84
+ self.assertEqual(client.generate_texts(['{"a":'], after_key=True), [value])
83
85
 
84
86
 
85
87
  if __name__ == "__main__":
@@ -36,7 +36,7 @@ class FieldProbabilityTests(unittest.TestCase):
36
36
  {'type': 'boolean', 'return_probabilities': value}
37
37
  for value in (None, 0, 1, 'true')
38
38
  ]
39
- client = TypeLLMClient()
39
+ client = TypeLLMClient("http://127.0.0.1:30000")
40
40
  with patch.object(client.sglang, 'single_token') as tokens:
41
41
  for field in fields:
42
42
  with self.subTest(field=field), self.assertRaises(SchemaError):
@@ -4,7 +4,7 @@ from typellm import TypeLLMClient
4
4
 
5
5
 
6
6
  def prompts(fields):
7
- client = TypeLLMClient(model="fake")
7
+ client = TypeLLMClient("http://127.0.0.1:30000", model="fake")
8
8
  client.sglang.single_token = lambda label: (ord(label), label)
9
9
  return {c.name: c.opening_text() for c in client.compile_schema({"type": "object", "properties": fields})}
10
10
 
@@ -29,7 +29,7 @@ class TextTests(unittest.TestCase):
29
29
  self.assertEqual(client.generate_texts(['p']),[''])
30
30
 
31
31
  def test_mixed_fields_run_together(self):
32
- client=TypeLLMClient()
32
+ client=TypeLLMClient("http://127.0.0.1:30000")
33
33
  fake=FakeSGLang([ord('7'),3,ord('A')])
34
34
  calls=[]
35
35
  def generate(prefixes, **kwargs):
@@ -57,10 +57,10 @@ class TextTests(unittest.TestCase):
57
57
  self.assertEqual(constructor.call_args.kwargs['text_max_tokens'],24)
58
58
  for budget in (0,-1,True):
59
59
  with self.assertRaises(ValueError):
60
- TypeLLMClient(text_max_tokens=budget)
60
+ TypeLLMClient("http://127.0.0.1:30000", text_max_tokens=budget)
61
61
 
62
62
  def test_thinking_then_constrained_text(self):
63
- client=TypeLLMClient()
63
+ client=TypeLLMClient("http://127.0.0.1:30000")
64
64
  client.sglang._context_length_cache=8192
65
65
  client.sglang._chat_tokenizer=FakeChatTokenizer()
66
66
  client.sglang._chat_tokenizer.apply_chat_template=lambda *args,**kw: "assistant\n<think>\n" if kw["enable_thinking"] else "completed"