typellm 0.2.0__tar.gz → 0.2.1__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.0/typellm.egg-info → typellm-0.2.1}/PKG-INFO +1 -1
- {typellm-0.2.0 → typellm-0.2.1}/pyproject.toml +1 -1
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_batching.py +24 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_grammar_fields.py +15 -1
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_typellm.py +6 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm/runtime.py +28 -34
- {typellm-0.2.0 → typellm-0.2.1}/typellm/sglang.py +111 -57
- {typellm-0.2.0 → typellm-0.2.1/typellm.egg-info}/PKG-INFO +1 -1
- {typellm-0.2.0 → typellm-0.2.1}/LICENSE +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/README.md +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/setup.cfg +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_concurrency.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_dependencies.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_images.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_nullable.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_permutations.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_prefill.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_probabilities.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_prompts.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_protocols.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/tests/test_text.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm/__init__.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm/__main__.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm/benchmark.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm/cli.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm/images.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm/protocol.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm/schema.py +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm.egg-info/SOURCES.txt +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm.egg-info/dependency_links.txt +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm.egg-info/entry_points.txt +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm.egg-info/requires.txt +0 -0
- {typellm-0.2.0 → typellm-0.2.1}/typellm.egg-info/top_level.txt +0 -0
|
@@ -83,6 +83,30 @@ def width(payload):
|
|
|
83
83
|
return 1 if isinstance(payload["text"], str) else len(payload["text"])
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
class PrefixWarmupTests(unittest.TestCase):
|
|
87
|
+
MIXED = {"a": {"type": "integer"}, "b": {"type": "number"}, "n": {"type": "string"},
|
|
88
|
+
"k": {"type": "string", "enum": ["x", "y"]}}
|
|
89
|
+
|
|
90
|
+
def test_the_context_is_warmed_before_any_batch_that_shares_it(self):
|
|
91
|
+
for thinking in (False, True):
|
|
92
|
+
with self.subTest(thinking=thinking):
|
|
93
|
+
client = TypeLLMClient(model="fake")
|
|
94
|
+
client.sglang = FakeServer(thinking=thinking)
|
|
95
|
+
client.generate(context="Receipt", questions=self.MIXED)
|
|
96
|
+
warm = client.sglang.requests("count")
|
|
97
|
+
self.assertEqual(len(warm), 1)
|
|
98
|
+
# First, before the thinking, number, text and scoring batches.
|
|
99
|
+
self.assertIs(client.sglang.payloads[0], warm[0])
|
|
100
|
+
self.assertIn("Receipt", warm[0]["text"])
|
|
101
|
+
self.assertNotIn('{"a":', warm[0]["text"])
|
|
102
|
+
|
|
103
|
+
def test_open_fields_alone_are_not_warmed(self):
|
|
104
|
+
client = TypeLLMClient(model="fake")
|
|
105
|
+
client.sglang = FakeServer()
|
|
106
|
+
client.generate(context="Receipt", questions={"a": {"type": "integer"}, "n": {"type": "string"}})
|
|
107
|
+
self.assertEqual(client.sglang.requests("count"), [])
|
|
108
|
+
|
|
109
|
+
|
|
86
110
|
class BatchedThinkingTests(unittest.TestCase):
|
|
87
111
|
QUESTIONS = {
|
|
88
112
|
"flag": {"type": "boolean"},
|
|
@@ -5,7 +5,7 @@ import unittest
|
|
|
5
5
|
from typellm import SGLangError, TypeLLMClient
|
|
6
6
|
from typellm.runtime import _numeric_text_is_complete, numeric_pattern
|
|
7
7
|
|
|
8
|
-
from tests.test_batching import FakeServer, width
|
|
8
|
+
from tests.test_batching import FakeServer, is_number_pattern, width
|
|
9
9
|
from tests.test_nullable import NullServer
|
|
10
10
|
|
|
11
11
|
|
|
@@ -72,6 +72,20 @@ class GrammarNumberTests(unittest.TestCase):
|
|
|
72
72
|
self.assertEqual(numbers["sampling_params"][0]["temperature"], 0)
|
|
73
73
|
self.assertIn('{"a": 7}', client.last_prompts[0])
|
|
74
74
|
|
|
75
|
+
def test_numbers_and_strings_of_a_layer_share_one_request(self):
|
|
76
|
+
client = TypeLLMClient(model="fake")
|
|
77
|
+
client.sglang = FakeServer()
|
|
78
|
+
result = client.generate(context="Receipt", questions={
|
|
79
|
+
"a": {"type": "integer"}, "n": {"type": "string"},
|
|
80
|
+
"b": {"type": "number"}, "m": {"type": ["string", "null"]}})
|
|
81
|
+
self.assertEqual(result, {"a": 7, "n": "blue", "b": 7, "m": "blue"})
|
|
82
|
+
[request] = regex_requests(client.sglang)
|
|
83
|
+
self.assertEqual(width(request), 4)
|
|
84
|
+
patterns = [p["regex"] for p in request["sampling_params"]]
|
|
85
|
+
self.assertEqual([is_number_pattern(p) for p in patterns], [True, True, False, False])
|
|
86
|
+
self.assertTrue(all(t.endswith(k) for t, k in zip(request["text"], ('{"a":', '{"b":', '{"n":', '{"m":'))))
|
|
87
|
+
self.assertIn("null", patterns[3])
|
|
88
|
+
|
|
75
89
|
def test_sampling_passes_the_temperature_and_no_truncation(self):
|
|
76
90
|
client = TypeLLMClient(model="fake", mode="sample", temperature=0.7, seed=1)
|
|
77
91
|
client.sglang = FakeServer()
|
|
@@ -60,6 +60,12 @@ class FakeSGLang:
|
|
|
60
60
|
self.number_patterns.extend(patterns)
|
|
61
61
|
return [next(self.numbers, " 7}") for _ in prefixes]
|
|
62
62
|
|
|
63
|
+
def generate_fields(self, number_prefixes, patterns, number_max_tokens, text_prefixes, max_lengths,
|
|
64
|
+
*, temperature=0, number_seed=0, text_seed=0, after_key=False, nullable=None):
|
|
65
|
+
numbers = self.generate_numbers(number_prefixes, patterns, number_max_tokens)
|
|
66
|
+
texts = self.generate_texts(text_prefixes, max_lengths) if text_prefixes else []
|
|
67
|
+
return numbers, texts
|
|
68
|
+
|
|
63
69
|
def score_candidates(self, prefix, candidate_ids):
|
|
64
70
|
self.prompts.append(prefix)
|
|
65
71
|
self.candidate_sets.append(list(candidate_ids))
|
|
@@ -510,23 +510,10 @@ def numeric_pattern(numeric_type: str, max_digits: int, nullable: bool = False)
|
|
|
510
510
|
return " ?" + value + r"\}?"
|
|
511
511
|
|
|
512
512
|
|
|
513
|
-
def
|
|
514
|
-
|
|
515
|
-
items: Sequence[tuple[str, Choice]],
|
|
516
|
-
mode: str,
|
|
517
|
-
temperature: float,
|
|
518
|
-
rng: random.Random,
|
|
519
|
-
max_digits: int,
|
|
513
|
+
def _parse_numbers(
|
|
514
|
+
items: Sequence[tuple[str, Choice]], texts: Sequence[str],
|
|
520
515
|
) -> list[tuple[int | float | None, str, str]]:
|
|
521
|
-
"""
|
|
522
|
-
patterns = [numeric_pattern(d.numeric_type or "", max_digits, d.nullable) for _, d in items]
|
|
523
|
-
texts = client.generate_numbers(
|
|
524
|
-
[prompt for prompt, _ in items], patterns,
|
|
525
|
-
# Digits, sign, point and the closing brace; tokens hold one or more characters.
|
|
526
|
-
max_new_tokens=max_digits + 4,
|
|
527
|
-
temperature=0 if mode == "argmax" else temperature,
|
|
528
|
-
seed=rng.randrange(2**31),
|
|
529
|
-
)
|
|
516
|
+
"""Read the grammar-generated numbers: (value, closed prompt, value text)."""
|
|
530
517
|
outputs = []
|
|
531
518
|
for (prompt, decision), raw in zip(items, texts):
|
|
532
519
|
text = raw.strip()
|
|
@@ -758,6 +745,14 @@ def _execute_batch_decisions(
|
|
|
758
745
|
{label: token_id for label, (token_id, _) in label_tokens.items()},
|
|
759
746
|
)
|
|
760
747
|
|
|
748
|
+
# Warm the exact common prefix once, before any batch that shares it: prompts
|
|
749
|
+
# prefilled in the same batch cannot reuse each other's cache, so thinking,
|
|
750
|
+
# number and text batches would each prefill the context once per prompt.
|
|
751
|
+
# SGLang then forks the cached state; TypeLLM never reads or moves KV tensors.
|
|
752
|
+
if finite_indexes and not incremental:
|
|
753
|
+
cache_meta = client.cache_prefix(shared_prefix)
|
|
754
|
+
LOG.info("batch_shared_prefix_cached_tokens=%s", cache_meta.get("cached_tokens"))
|
|
755
|
+
|
|
761
756
|
ready = client.prepare_answer_prefixes(raw_prompts) if defer else raw_prompts
|
|
762
757
|
prompts = [ready[decision_slots[index]] for index in finite_indexes]
|
|
763
758
|
scoring_prompts = [ready[slot] + prefill for slot, prefill in zip(scoring_slots, scoring_prefills)]
|
|
@@ -771,37 +766,36 @@ def _execute_batch_decisions(
|
|
|
771
766
|
numeric_pending = [item for item in open_pending if item[1].numeric_type is not None]
|
|
772
767
|
text_pending = [item for item in open_pending if item[1].text_type]
|
|
773
768
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
values = client.generate_texts(
|
|
769
|
+
# A layer's numbers and strings decode side by side in one request.
|
|
770
|
+
if numeric_pending or text_pending:
|
|
771
|
+
number_items = [(prompt + decision.answer_prefill, decision)
|
|
772
|
+
for _, decision, _, prompt in numeric_pending]
|
|
773
|
+
number_seed = rng.randrange(2**31) if numeric_pending else 0
|
|
774
|
+
text_seed = rng.randrange(2**31) if text_pending else 0
|
|
775
|
+
raw_numbers, values = client.generate_fields(
|
|
776
|
+
[prompt for prompt, _ in number_items],
|
|
777
|
+
[numeric_pattern(d.numeric_type or "", numeric_max_digits, d.nullable) for _, d in number_items],
|
|
778
|
+
# Digits, sign, point and the closing brace; tokens hold one or more characters.
|
|
779
|
+
numeric_max_digits + 4,
|
|
786
780
|
# From {"name": the model writes the string's first token, quote included.
|
|
787
781
|
[prompt + decision.answer_prefill for _, decision, _, prompt in text_pending],
|
|
788
782
|
[decision.max_length for _, decision, _, _ in text_pending],
|
|
789
783
|
temperature=0 if mode == "argmax" else temperature,
|
|
790
|
-
|
|
784
|
+
number_seed=number_seed,
|
|
785
|
+
text_seed=text_seed,
|
|
791
786
|
after_key=all(decision.answer_prefill for _, decision, _, _ in text_pending),
|
|
792
787
|
# A nullable string writes null or its text in the same request.
|
|
793
788
|
nullable=[decision.nullable for _, decision, _, _ in text_pending],
|
|
794
789
|
)
|
|
790
|
+
for (index, decision, messages, prompt), (value, _completed, generated_text) in zip(
|
|
791
|
+
numeric_pending, _parse_numbers(number_items, raw_numbers)):
|
|
792
|
+
open_results[index] = (open_row(decision, value),
|
|
793
|
+
complete(prompt, messages, _closed_answer(decision, generated_text)))
|
|
795
794
|
for (index, decision, messages, prompt), value in zip(text_pending, values):
|
|
796
795
|
completed = complete(prompt, messages, _closed_answer(decision, json.dumps(value, ensure_ascii=False)))
|
|
797
796
|
open_results[index] = (open_row(decision, value), completed)
|
|
798
797
|
|
|
799
|
-
# Warm the exact common prefix once, then let SGLang fork the cached state
|
|
800
|
-
# across the K batched prompts. TypeLLM never reads or moves KV tensors.
|
|
801
798
|
if prompts:
|
|
802
|
-
if not incremental:
|
|
803
|
-
cache_meta = client.cache_prefix(shared_prefix)
|
|
804
|
-
LOG.info("batch_shared_prefix_cached_tokens=%s", cache_meta.get("cached_tokens"))
|
|
805
799
|
scored, elapsed = client.score_candidates_batch(scoring_prompts, scoring_ids)
|
|
806
800
|
grouped_scores = []
|
|
807
801
|
offset = 0
|
|
@@ -744,35 +744,26 @@ class SGLangClient:
|
|
|
744
744
|
results.append((scores, meta if isinstance(meta, Mapping) else {}))
|
|
745
745
|
return results, elapsed
|
|
746
746
|
|
|
747
|
-
def
|
|
748
|
-
|
|
749
|
-
prefixes:
|
|
750
|
-
patterns: Sequence[str],
|
|
751
|
-
max_new_tokens: int,
|
|
752
|
-
*,
|
|
753
|
-
temperature: float = 0,
|
|
754
|
-
seed: int = 0,
|
|
755
|
-
) -> list[str]:
|
|
756
|
-
"""Generate one regex-constrained value per prompt in a native batch.
|
|
757
|
-
|
|
758
|
-
SGLang masks every step to the pattern, so a number takes one request
|
|
759
|
-
instead of one per token. Returns the raw generated text.
|
|
760
|
-
"""
|
|
761
|
-
if len(prefixes) != len(patterns):
|
|
762
|
-
raise ValueError("prefixes and patterns must have the same length")
|
|
763
|
-
if not prefixes:
|
|
764
|
-
return []
|
|
765
|
-
params = [{"max_new_tokens": max_new_tokens, "temperature": temperature,
|
|
766
|
-
"top_p": 1.0, "top_k": -1, "min_p": 0.0,
|
|
767
|
-
"sampling_seed": seed + i, "regex": pattern}
|
|
768
|
-
for i, pattern in enumerate(patterns)]
|
|
769
|
-
response = self._generate({"text": list(prefixes), "sampling_params": params})
|
|
747
|
+
def _batch(self, prefixes: Sequence[str], params: Sequence[Mapping[str, Any]], what: str) -> list[Any]:
|
|
748
|
+
"""One /generate request with one sampling-params dict per prompt."""
|
|
749
|
+
response = self._generate({"text": list(prefixes), "sampling_params": list(params)})
|
|
770
750
|
if isinstance(response, Mapping) and len(prefixes) == 1:
|
|
771
751
|
response = [response]
|
|
772
752
|
if not isinstance(response, list) or len(response) != len(prefixes):
|
|
773
|
-
raise SGLangError("Unexpected
|
|
753
|
+
raise SGLangError(f"Unexpected {what} batch response shape")
|
|
754
|
+
return response
|
|
755
|
+
|
|
756
|
+
@staticmethod
|
|
757
|
+
def _number_params(patterns: Sequence[str], max_new_tokens: int, temperature: float, seed: int) -> list[dict]:
|
|
758
|
+
return [{"max_new_tokens": max_new_tokens, "temperature": temperature,
|
|
759
|
+
"top_p": 1.0, "top_k": -1, "min_p": 0.0,
|
|
760
|
+
"sampling_seed": seed + i, "regex": pattern}
|
|
761
|
+
for i, pattern in enumerate(patterns)]
|
|
762
|
+
|
|
763
|
+
@staticmethod
|
|
764
|
+
def _read_numbers(items: Sequence[Any]) -> list[str]:
|
|
774
765
|
texts = []
|
|
775
|
-
for item in
|
|
766
|
+
for item in items:
|
|
776
767
|
meta = item.get("meta_info", {}) if isinstance(item, Mapping) else {}
|
|
777
768
|
finish = meta.get("finish_reason", {}) if isinstance(meta, Mapping) else {}
|
|
778
769
|
kind = finish.get("type") if isinstance(finish, Mapping) else finish
|
|
@@ -782,34 +773,10 @@ class SGLangClient:
|
|
|
782
773
|
texts.append(text)
|
|
783
774
|
return texts
|
|
784
775
|
|
|
785
|
-
def
|
|
786
|
-
|
|
787
|
-
prefixes: Sequence[str],
|
|
788
|
-
max_lengths: Sequence[int | None],
|
|
789
|
-
*,
|
|
790
|
-
temperature: float = 0,
|
|
791
|
-
seed: int = 0,
|
|
792
|
-
after_key: bool = False,
|
|
793
|
-
nullable: Sequence[bool] | None = None,
|
|
794
|
-
) -> list[str | None]:
|
|
795
|
-
"""Generate JSON strings in a native batch, then validate every value.
|
|
796
|
-
|
|
797
|
-
With after_key, every prompt ends with '{"name":', and the model writes the
|
|
798
|
-
opening quote, the characters and the closing '"}'. Writing the quote
|
|
799
|
-
itself lets it start with a merged token such as ' "$', which keeps the
|
|
800
|
-
first character. A max length then truncates: generation is capped near
|
|
801
|
-
that many tokens and the string is cut to that many characters, as a
|
|
802
|
-
length-bounded grammar would.
|
|
803
|
-
"""
|
|
804
|
-
if len(prefixes) != len(max_lengths):
|
|
805
|
-
raise ValueError("prefixes and max_lengths must have the same length")
|
|
806
|
-
nullable = [False] * len(prefixes) if nullable is None else list(nullable)
|
|
807
|
-
if len(nullable) != len(prefixes):
|
|
808
|
-
raise ValueError("prefixes and nullable must have the same length")
|
|
776
|
+
def _text_params(self, max_lengths: Sequence[int | None], nullable: Sequence[bool],
|
|
777
|
+
after_key: bool, temperature: float, seed: int) -> list[dict]:
|
|
809
778
|
if any(nullable) and not after_key:
|
|
810
779
|
raise ValueError("nullable text needs the prefilled '{\"name\":' prompt")
|
|
811
|
-
if not prefixes:
|
|
812
|
-
return []
|
|
813
780
|
params = []
|
|
814
781
|
for limit, can_be_null in zip(max_lengths, nullable):
|
|
815
782
|
budget = self.text_max_tokens
|
|
@@ -832,13 +799,13 @@ class SGLangClient:
|
|
|
832
799
|
constraint = {"json_schema": json.dumps(schema)}
|
|
833
800
|
params.append({"max_new_tokens": budget,
|
|
834
801
|
"temperature": temperature, "sampling_seed": seed, **constraint})
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
802
|
+
return params
|
|
803
|
+
|
|
804
|
+
@staticmethod
|
|
805
|
+
def _read_texts(items: Sequence[Any], max_lengths: Sequence[int | None],
|
|
806
|
+
nullable: Sequence[bool], after_key: bool) -> list[str | None]:
|
|
840
807
|
values: list[str | None] = []
|
|
841
|
-
for item, limit, can_be_null in zip(
|
|
808
|
+
for item, limit, can_be_null in zip(items, max_lengths, nullable):
|
|
842
809
|
if not isinstance(item, Mapping):
|
|
843
810
|
raise SGLangError("Invalid text response")
|
|
844
811
|
meta = item.get("meta_info", {})
|
|
@@ -874,6 +841,93 @@ class SGLangClient:
|
|
|
874
841
|
values.append(value)
|
|
875
842
|
return values
|
|
876
843
|
|
|
844
|
+
@staticmethod
|
|
845
|
+
def _check_lengths(nullable, prefixes, max_lengths):
|
|
846
|
+
if len(prefixes) != len(max_lengths):
|
|
847
|
+
raise ValueError("prefixes and max_lengths must have the same length")
|
|
848
|
+
nullable = [False] * len(prefixes) if nullable is None else list(nullable)
|
|
849
|
+
if len(nullable) != len(prefixes):
|
|
850
|
+
raise ValueError("prefixes and nullable must have the same length")
|
|
851
|
+
return nullable
|
|
852
|
+
|
|
853
|
+
def generate_numbers(
|
|
854
|
+
self,
|
|
855
|
+
prefixes: Sequence[str],
|
|
856
|
+
patterns: Sequence[str],
|
|
857
|
+
max_new_tokens: int,
|
|
858
|
+
*,
|
|
859
|
+
temperature: float = 0,
|
|
860
|
+
seed: int = 0,
|
|
861
|
+
) -> list[str]:
|
|
862
|
+
"""Generate one regex-constrained value per prompt in a native batch.
|
|
863
|
+
|
|
864
|
+
SGLang masks every step to the pattern, so a number takes one request
|
|
865
|
+
instead of one per token. Returns the raw generated text.
|
|
866
|
+
"""
|
|
867
|
+
if len(prefixes) != len(patterns):
|
|
868
|
+
raise ValueError("prefixes and patterns must have the same length")
|
|
869
|
+
if not prefixes:
|
|
870
|
+
return []
|
|
871
|
+
params = self._number_params(patterns, max_new_tokens, temperature, seed)
|
|
872
|
+
return self._read_numbers(self._batch(prefixes, params, "number"))
|
|
873
|
+
|
|
874
|
+
def generate_texts(
|
|
875
|
+
self,
|
|
876
|
+
prefixes: Sequence[str],
|
|
877
|
+
max_lengths: Sequence[int | None],
|
|
878
|
+
*,
|
|
879
|
+
temperature: float = 0,
|
|
880
|
+
seed: int = 0,
|
|
881
|
+
after_key: bool = False,
|
|
882
|
+
nullable: Sequence[bool] | None = None,
|
|
883
|
+
) -> list[str | None]:
|
|
884
|
+
"""Generate JSON strings in a native batch, then validate every value.
|
|
885
|
+
|
|
886
|
+
With after_key, every prompt ends with '{"name":', and the model writes the
|
|
887
|
+
opening quote, the characters and the closing '"}'. Writing the quote
|
|
888
|
+
itself lets it start with a merged token such as ' "$', which keeps the
|
|
889
|
+
first character. A max length then truncates: generation is capped near
|
|
890
|
+
that many tokens and the string is cut to that many characters, as a
|
|
891
|
+
length-bounded grammar would.
|
|
892
|
+
"""
|
|
893
|
+
nullable = self._check_lengths(nullable, prefixes, max_lengths)
|
|
894
|
+
if not prefixes:
|
|
895
|
+
return []
|
|
896
|
+
params = self._text_params(max_lengths, nullable, after_key, temperature, seed)
|
|
897
|
+
return self._read_texts(self._batch(prefixes, params, "text"), max_lengths, nullable, after_key)
|
|
898
|
+
|
|
899
|
+
def generate_fields(
|
|
900
|
+
self,
|
|
901
|
+
number_prefixes: Sequence[str],
|
|
902
|
+
patterns: Sequence[str],
|
|
903
|
+
number_max_tokens: int,
|
|
904
|
+
text_prefixes: Sequence[str],
|
|
905
|
+
max_lengths: Sequence[int | None],
|
|
906
|
+
*,
|
|
907
|
+
temperature: float = 0,
|
|
908
|
+
number_seed: int = 0,
|
|
909
|
+
text_seed: int = 0,
|
|
910
|
+
after_key: bool = False,
|
|
911
|
+
nullable: Sequence[bool] | None = None,
|
|
912
|
+
) -> tuple[list[str], list[str | None]]:
|
|
913
|
+
"""Numbers and strings of one layer in a single request, decoded side by side.
|
|
914
|
+
|
|
915
|
+
Returns what generate_numbers and generate_texts would for each part.
|
|
916
|
+
"""
|
|
917
|
+
if len(number_prefixes) != len(patterns):
|
|
918
|
+
raise ValueError("prefixes and patterns must have the same length")
|
|
919
|
+
nullable = self._check_lengths(nullable, text_prefixes, max_lengths)
|
|
920
|
+
params = (self._number_params(patterns, number_max_tokens, temperature, number_seed)
|
|
921
|
+
+ (self._text_params(max_lengths, nullable, after_key, temperature, text_seed)
|
|
922
|
+
if text_prefixes else []))
|
|
923
|
+
prefixes = list(number_prefixes) + list(text_prefixes)
|
|
924
|
+
if not prefixes:
|
|
925
|
+
return [], []
|
|
926
|
+
items = self._batch(prefixes, params, "field")
|
|
927
|
+
split = len(number_prefixes)
|
|
928
|
+
return (self._read_numbers(items[:split]),
|
|
929
|
+
self._read_texts(items[split:], max_lengths, nullable, after_key))
|
|
930
|
+
|
|
877
931
|
def flush_cache(self) -> None:
|
|
878
932
|
reply = self._request("/flush_cache", {}, allow_text=True)
|
|
879
933
|
if isinstance(reply, str) and not reply.startswith("Cache flushed."):
|
|
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
|