"""Answer-label schemes: exact lists, the <=26 guarantee, and end-to-end mapping.""" import hashlib import json import re import string from pathlib import Path import httpx import pytest from jev_adapter.labels import ( DEFAULT_LABEL_SCHEME, MAX_LABELS, follows_scheme_order, label_candidates, resolve_label_scheme, select_labels, ) from jev_adapter.server import create_app from jev_adapter.sglang import SGLangBackend GOLDEN_PATH = Path(__file__).parent / "data" / "upper-labels-ministral3.json" GOLDEN = json.loads(GOLDEN_PATH.read_text()) UPPER = GOLDEN["labels"] COUNTS = (2, 26, 27, 77, 151, 255) class VocabularyTokenizer: """Every character is one token; listed multi-letter strings are one token. With the golden vocabulary this has exactly the pinned Ministral-3 single-token pattern for the upper labels (e.g. AJ and AQ are split). """ all_special_ids = (0,) def __init__(self, words): self.word_ids = {word: 100_000 + index for index, word in enumerate(words)} self.words = {token: word for word, token in self.word_ids.items()} def encode(self, text, add_special_tokens=False): ids, position = [], 0 while position < len(text): for length in (3, 2): piece = text[position : position + length] if len(piece) == length and piece in self.word_ids: ids.append(self.word_ids[piece]) position += length break else: ids.append(ord(text[position]) + 1000) position += 1 return ids def decode(self, ids, skip_special_tokens=True): return "".join( self.words[token] if token in self.words else chr(token - 1000) for token in ids ) def ministral_like(): return VocabularyTokenizer([label for label in UPPER if len(label) > 1]) def test_default_and_resolution(): assert DEFAULT_LABEL_SCHEME == "upper" assert resolve_label_scheme(environ={}) == "upper" assert resolve_label_scheme(environ={"JEV_LABEL_SCHEME": "served"}) == "served" assert resolve_label_scheme("upper", {"JEV_LABEL_SCHEME": "served"}) == "upper" assert resolve_label_scheme("", {"JEV_LABEL_SCHEME": ""}) == "upper" for bad in ("lower", "UPPER", 3): with pytest.raises(ValueError, match="label scheme"): resolve_label_scheme(bad, {}) with pytest.raises(ValueError, match="label scheme"): resolve_label_scheme(None, {"JEV_LABEL_SCHEME": "lower"}) def test_candidate_orders(): upper = label_candidates("upper") assert upper[:28] == [*string.ascii_uppercase, "AA", "AB"] assert upper[26 + 675] == "ZZ" and upper[26 + 676] == "AAA" assert upper[-1] == "ZZZ" and len(upper) == 26 + 26**2 + 26**3 assert all(re.fullmatch(r"[A-Z]{1,3}", label) for label in upper) served = label_candidates("served") # The served order is unchanged from before the scheme option existed. assert served[:62] == list( string.ascii_uppercase + string.ascii_lowercase + string.digits ) assert served[62:64] == ["000", "001"] and served[1062:1064] == ["AA", "AB"] assert served[-1] == "zz" and len(served) == 62 + 1000 + 2 * 26**2 @pytest.mark.parametrize("count", COUNTS) def test_upper_lists_for_each_option_count(count): labels, token_ids = select_labels(ministral_like(), "upper", limit=count) assert labels == UPPER[:count] assert len(set(labels)) == len(set(token_ids)) == count assert all(re.fullmatch(r"[A-Z]{1,2}", label) for label in labels) assert labels[: min(count, 26)] == list(string.ascii_uppercase[:count]) if count > 26: assert labels[26:29] == ["AA", "AB", "AC"][: count - 26] # AJ, AQ and AW are not single Ministral-3 tokens, so they are skipped. assert not {"AJ", "AQ", "AW"} & set(labels) def test_exact_boundary_labels(): labels, _ = select_labels(ministral_like(), "upper") assert len(labels) == MAX_LABELS assert labels[26] == "AA" assert labels[76] == "CG" # the 77th label: the last of BANKING77's options assert labels[150] == "FT" # the 151st: the last of CLINC150 with oos assert labels[254] == "MH" @pytest.mark.parametrize("count", range(2, 27)) def test_at_most_26_options_keep_a_to_z_in_both_schemes(count): tokenizer = ministral_like() upper = select_labels(tokenizer, "upper", limit=count) served = select_labels(tokenizer, "served", limit=count) assert upper == served assert upper[0] == list(string.ascii_uppercase[:count]) def test_served_scheme_still_uses_lowercase_after_z(): labels, _ = select_labels(ministral_like(), "served") assert labels[26:30] == ["a", "b", "c", "d"] assert labels[52:62] == list(string.digits) assert labels[62] == "AA" @pytest.mark.parametrize("count", COUNTS) def test_label_index_round_trip(count): labels, token_ids = select_labels(ministral_like(), "upper", limit=count) index = {label: position for position, label in enumerate(labels)} token_index = {token: position for position, token in enumerate(token_ids)} assert len(index) == len(token_index) == count for position, (label, token) in enumerate(zip(labels, token_ids)): assert index[label] == token_index[token] == position assert labels[index[label]] == label # Every label is also a prefix-free answer at the list level: a longer label # is one token, never the token of a shorter label followed by another. tokenizer = ministral_like() assert all( tokenizer.encode(label) == [token] for label, token in zip(labels, token_ids) ) def test_three_letter_continuation_when_two_letters_run_out(): # Only AA..AJ are two-letter tokens; the list continues with AAA, AAB, ... two = [f"A{letter}" for letter in "ABCDEFGHIJ"] three = ["AAA", "AAB", "ABA", "ZZZ"] tokenizer = VocabularyTokenizer(two + three) labels, _ = select_labels(tokenizer, "upper") assert labels == list(string.ascii_uppercase) + two + three def test_special_duplicate_and_lossy_tokens_are_skipped(): class Tricky(VocabularyTokenizer): all_special_ids = (ord("B") + 1000,) def decode(self, ids, skip_special_tokens=True): text = super().decode(ids, skip_special_tokens) return "ab" if text == "AB" else text def encode(self, text, add_special_tokens=False): return [ord("C") + 1000] if text == "AC" else super().encode(text) labels, _ = select_labels(Tricky(["AA", "AB", "AC", "AD"]), "upper") assert labels[:3] == ["A", "C", "D"] and "B" not in labels assert labels[25:] == ["AA", "AD"] def test_scheme_order_check(): assert follows_scheme_order("upper", ["A", "B", "AA", "AB", "MH", "AAA"]) assert not follows_scheme_order("upper", ["A", "a"]) assert not follows_scheme_order("upper", ["B", "A"]) assert not follows_scheme_order("upper", ["AA", "Z"]) assert follows_scheme_order("served", ["A", "Z", "a", "0", "AA", "aa"]) assert not follows_scheme_order("served", ["a", "A"]) assert not follows_scheme_order("served", ["A", "AAA"]) def test_pinned_ministral3_tokenizer_matches_the_golden_list(): transformers = pytest.importorskip("transformers") tokenizer_info = GOLDEN["tokenizer"] try: tokenizer = transformers.AutoTokenizer.from_pretrained( tokenizer_info["id"], revision=tokenizer_info["revision"], local_files_only=True, ) except Exception: # noqa: BLE001 - absent from this machine's HF cache pytest.skip("pinned Ministral-3 tokenizer is not cached locally") labels, token_ids = select_labels(tokenizer, "upper") assert labels == GOLDEN["labels"] assert token_ids == GOLDEN["token_ids"] # The recorded probe below is what this tokenizer really does. for candidate, ids in PROBE["encode"].items(): assert tokenizer.encode(candidate, add_special_tokens=False) == ids for token, text in PROBE["decode"].items(): assert tokenizer.decode([int(token)], skip_special_tokens=True) == text assert set(tokenizer.all_special_ids) <= ProbeTokenizer().all_special_ids from jev_adapter.native_tokenizer import NativeTokenizer # Every label continues the native chat prompt by exactly its token. NativeTokenizer(tokenizer).prepare("Choose.", tuple(labels), tuple(token_ids), None) # Raw encode/decode results of the pinned Ministral-3 tokenizer for every one- # and two-letter uppercase candidate, recorded on the GPU host with # AutoTokenizer and cross-checked against tekken.json (mistral_common) and the # release tokenizer.json. Unlike ministral_like(), which is built from the # golden list, this table is tokenizer data only, so the tests below check the # selection rule against the real tokenizer on every CI run. PROBE_PATH = Path(__file__).parent / "data" / "ministral3-label-probe.json" PROBE = json.loads(PROBE_PATH.read_text()) class ProbeTokenizer: """Replays the recorded tokenizer; a string outside the table is an error.""" def __init__(self): self.all_special_ids = { token for first, last in PROBE["special_id_ranges"] for token in range(first, last + 1) } def encode(self, text, add_special_tokens=False): assert add_special_tokens is False return list(PROBE["encode"][text]) def decode(self, ids, skip_special_tokens=True): assert skip_special_tokens is True return "".join(PROBE["decode"][str(token)] for token in ids) def test_probe_records_the_golden_lists_tokenizer(): assert PROBE["schema"] == "jev-tokenizer-probe-v1" assert PROBE["tokenizer"] == GOLDEN["tokenizer"] expected = PROBE["expected_upper"] assert expected["labels"] == GOLDEN["labels"] assert expected["token_ids"] == GOLDEN["token_ids"] assert expected["source_sha256"] == hashlib.sha256( GOLDEN_PATH.read_bytes() ).hexdigest() candidates = [*string.ascii_uppercase] + [ first + second for first in string.ascii_uppercase for second in string.ascii_uppercase ] assert list(PROBE["encode"]) == candidates singles = [label for label, ids in PROBE["encode"].items() if len(ids) == 1] counts = GOLDEN["single_token_counts"] assert sum(len(label) == 1 for label in singles) == counts["one_letter"] == 26 assert sum(len(label) == 2 for label in singles) == counts["two_letter"] == 439 def test_rule_on_the_recorded_ministral3_tokenizer_gives_the_golden_list(): # Three-letter candidates are absent from the table, so consulting one # would raise: 255 labels must come from one and two letters. labels, token_ids = select_labels(ProbeTokenizer(), "upper") assert labels == GOLDEN["labels"] assert token_ids == GOLDEN["token_ids"] for count in COUNTS: assert select_labels(ProbeTokenizer(), "upper", limit=count) == ( GOLDEN["labels"][:count], GOLDEN["token_ids"][:count], ) @pytest.mark.asyncio async def test_http_backend_picks_the_golden_list_from_the_recorded_tokenizer(): engine = LabelEngine(ProbeTokenizer(), "A") async with httpx.AsyncClient(transport=httpx.MockTransport(engine)) as upstream: backend = SGLangBackend( "http://engine", "standard-one", client=upstream, label_scheme="upper" ) await backend.start() try: for count in COUNTS: assert backend.labels(count) == ( tuple(GOLDEN["labels"][:count]), tuple(GOLDEN["token_ids"][:count]), ) finally: await backend.close() class LabelEngine: """A remote engine whose tokenizer has two-letter tokens and prefers one label.""" def __init__(self, tokenizer, preferred): self.tokenizer = tokenizer self.preferred = preferred self.prompts = [] def __call__(self, request): body = json.loads(request.content) if request.content else None path = request.url.path if path == "/model_info": return httpx.Response( 200, json={ "served_model_name": "standard-one", "model_path": "org/standard-one", "is_generation": True, }, ) if path == "/server_info": return httpx.Response(200, json={"speculative_algorithm": None}) encode = self.tokenizer.encode if path == "/v1/tokenize": if "messages" in body: content = body["messages"][-1]["content"] self.prompts.append(content) return httpx.Response( 200, json={"tokens": encode(f"{content}")} ) return httpx.Response( 200, json={"tokens": [encode(text) for text in body["prompt"]]} ) if path == "/v1/detokenize": tokens = body["tokens"] decode = self.tokenizer.decode text = ( [decode(ids) for ids in tokens] if isinstance(tokens[0], list) else decode(tokens) ) return httpx.Response(200, json={"text": text}) if path == "/generate": preferred = encode(self.preferred)[0] entries = [ [-0.1 if token == preferred else -9.0, token, None] for token in body["token_ids_logprob"] ] return httpx.Response( 200, json={ "meta_info": { "completion_tokens": 0, "prompt_tokens": len(body["input_ids"]), "cached_tokens": 0, "output_token_ids_logprobs": [entries], } }, ) raise AssertionError(path) @pytest.mark.asyncio @pytest.mark.parametrize( "scheme,count,preferred,expected_option", [ ("upper", 77, "CG", 76), ("upper", 77, "AA", 26), ("upper", 151, "FT", 150), ("upper", 26, "Z", 25), ("served", 77, "a", 26), ("served", 26, "Z", 25), ], ) async def test_label_maps_to_its_option_through_the_http_stack( scheme, count, preferred, expected_option ): engine = LabelEngine(ministral_like(), preferred) async with httpx.AsyncClient(transport=httpx.MockTransport(engine)) as upstream: backend = SGLangBackend( "http://engine", "standard-one", client=upstream, label_scheme=scheme ) app = create_app(backend) async with ( app.router.lifespan_context(app), httpx.AsyncClient( transport=httpx.ASGITransport(app), base_url="http://adapter" ) as client, ): models = (await client.get("/v1/models")).json() assert models["data"][0]["label_scheme"] == scheme options = {f"intent-{index}": None for index in range(count)} response = await client.post( "/v1/systemone", json={ "model": "standard-one", "state": "I lost my card.", "questions": { "intent": { "type": "choice", "instructions": "Which intent?", "criteria": options, } }, }, ) assert response.status_code == 200, response.text result = response.json() assert result["metadata"]["label_scheme"] == scheme assert result["answers"]["intent"]["choice"] == f"intent-{expected_option}" lines = engine.prompts[-1].split("Options:\n")[1].splitlines() labels = [line.split(":", 1)[0] for line in lines] expected, _ = select_labels(ministral_like(), scheme, limit=count) assert labels == expected assert lines[expected_option] == f"{preferred}: intent-{expected_option}"