MyeongHoJeong commited on
Commit
2b31eeb
·
verified ·
1 Parent(s): 36d7999

Update weights

Browse files
MERGE_REPORT.json CHANGED
@@ -1,31 +1,31 @@
1
  {
2
  "snapshot": "mistralai/Ministral-3-8B-Instruct-2512-BF16@f6fae9795746f63c9be8344932f01275f3c63734",
3
- "adapter": "StandardOne-8B-LoRA v2 (training run r5-8b)",
4
- "adapter_sha256": "53e41238cd55567cfbc75efdf61d354da39771573f56f74bb1440c9ffdd1bd4a",
5
  "changed_params": 238,
6
  "unchanged_params": 293,
7
  "changed_outside_lm_projections": [],
8
- "max_abs_delta": 0.00201416015625,
9
  "changed_sample": [
10
  [
11
  "model.language_model.layers.0.self_attn.q_proj.weight",
12
- 0.00146484375
13
  ],
14
  [
15
  "model.language_model.layers.0.self_attn.k_proj.weight",
16
- 0.0009765625
17
  ],
18
  [
19
  "model.language_model.layers.0.self_attn.v_proj.weight",
20
- 0.0011348724365234375
21
  ],
22
  [
23
  "model.language_model.layers.0.self_attn.o_proj.weight",
24
- 0.001430511474609375
25
  ],
26
  [
27
  "model.language_model.layers.0.mlp.gate_proj.weight",
28
- 0.00112152099609375
29
  ],
30
  [
31
  "model.language_model.layers.0.mlp.up_proj.weight",
@@ -33,7 +33,7 @@
33
  ],
34
  [
35
  "model.language_model.layers.0.mlp.down_proj.weight",
36
- 0.00058746337890625
37
  ],
38
  [
39
  "model.language_model.layers.1.self_attn.q_proj.weight",
@@ -45,10 +45,10 @@
45
  "chat_template.jinja": "74eeb55fd3341286ec3fd44e902b7120721acc81cd394e96b431f85e93a1ea56",
46
  "config.json": "111827aa7ae6d936f48afb2f0776f51b00add45c857c708af2b893499f76d882",
47
  "generation_config.json": "e0923390059f84a9180b00e5501778acc45ea9856cd7f2fd68208b360927c677",
48
- "model-00001-of-00004.safetensors": "5b29a2e3e86366c0b2e21c5038a16fa1f2bed8a2ca69e5e775608126570c89d4",
49
- "model-00002-of-00004.safetensors": "65c9000a4aafa0cb0978540cfdbb6017e39f14a208a8d0523d375656f5cf0343",
50
- "model-00003-of-00004.safetensors": "fe537b22d9b005873c25b0f8ca0548e24f6d3a4fb611db11becc6ba86c849a03",
51
- "model-00004-of-00004.safetensors": "6c3f84685c69360d991bf02b81ac670fcb36cc1b9af2dd51e2137e0f3d36a24a",
52
  "model.safetensors.index.json": "71e022361d37d84c2b6224acbddca2c74bedcc7addc523cfc6a57420d4b22ceb",
53
  "params.json": "81b8377f36c5b3d60900b333214d85160a0500b84576e969092c6fd214f69538",
54
  "processor_config.json": "ece2373c2ae391bce18785a1810543bc6173a1f28b6767cffab2c35dbea5f002",
@@ -56,6 +56,5 @@
56
  "tekken.json": "600bb27946565481ecf51ba8aee252e49b9a68507866080ac9c30185bb312843",
57
  "tokenizer.json": "d5f6046775b112f0e2d456ee9dba450684ab964fe5c4e231599bdc6773028135",
58
  "tokenizer_config.json": "f59f7294e4f26383d0ea93840fe21cf197784be0842a8301a0343e8c34ed0d6d"
59
- },
60
- "seconds": 65.5
61
- }
 
1
  {
2
  "snapshot": "mistralai/Ministral-3-8B-Instruct-2512-BF16@f6fae9795746f63c9be8344932f01275f3c63734",
3
+ "adapter": "StandardOne-8B-LoRA v2.2",
4
+ "adapter_sha256": "123ddd039f4053e82e8ca18d7c247691dc49cbcabb6e1b7d98077bb7c5c7446e",
5
  "changed_params": 238,
6
  "unchanged_params": 293,
7
  "changed_outside_lm_projections": [],
8
+ "max_abs_delta": 0.002109527587890625,
9
  "changed_sample": [
10
  [
11
  "model.language_model.layers.0.self_attn.q_proj.weight",
12
+ 0.00152587890625
13
  ],
14
  [
15
  "model.language_model.layers.0.self_attn.k_proj.weight",
16
+ 0.00091552734375
17
  ],
18
  [
19
  "model.language_model.layers.0.self_attn.v_proj.weight",
20
+ 0.0012216567993164062
21
  ],
22
  [
23
  "model.language_model.layers.0.self_attn.o_proj.weight",
24
+ 0.001377105712890625
25
  ],
26
  [
27
  "model.language_model.layers.0.mlp.gate_proj.weight",
28
+ 0.00113677978515625
29
  ],
30
  [
31
  "model.language_model.layers.0.mlp.up_proj.weight",
 
33
  ],
34
  [
35
  "model.language_model.layers.0.mlp.down_proj.weight",
36
+ 0.0006103515625
37
  ],
38
  [
39
  "model.language_model.layers.1.self_attn.q_proj.weight",
 
45
  "chat_template.jinja": "74eeb55fd3341286ec3fd44e902b7120721acc81cd394e96b431f85e93a1ea56",
46
  "config.json": "111827aa7ae6d936f48afb2f0776f51b00add45c857c708af2b893499f76d882",
47
  "generation_config.json": "e0923390059f84a9180b00e5501778acc45ea9856cd7f2fd68208b360927c677",
48
+ "model-00001-of-00004.safetensors": "cac498368970b8ffb9785583c954648a40472b3cfa061ad0924af7babdee3f53",
49
+ "model-00002-of-00004.safetensors": "9a9fbec75dc4013463a57ad1cf61c2ed6fb558db9d379fb671dba0e448326be0",
50
+ "model-00003-of-00004.safetensors": "2f89f2111ee337b3b8b1278d0ec32bce3c20bfe5f2bab8caa6dd3e01a3c76ad5",
51
+ "model-00004-of-00004.safetensors": "125adfa505e3f9c1b33767601060c773a421876c3630fb8172dcdc886adf137f",
52
  "model.safetensors.index.json": "71e022361d37d84c2b6224acbddca2c74bedcc7addc523cfc6a57420d4b22ceb",
53
  "params.json": "81b8377f36c5b3d60900b333214d85160a0500b84576e969092c6fd214f69538",
54
  "processor_config.json": "ece2373c2ae391bce18785a1810543bc6173a1f28b6767cffab2c35dbea5f002",
 
56
  "tekken.json": "600bb27946565481ecf51ba8aee252e49b9a68507866080ac9c30185bb312843",
57
  "tokenizer.json": "d5f6046775b112f0e2d456ee9dba450684ab964fe5c4e231599bdc6773028135",
58
  "tokenizer_config.json": "f59f7294e4f26383d0ea93840fe21cf197784be0842a8301a0343e8c34ed0d6d"
59
+ }
60
+ }
 
SHA256SUMS CHANGED
@@ -1,8 +1,8 @@
1
  1495e1e757ef4d0925a2350563cf5754bb23c51701a8ec4fb3c5cdcbedae6747 LICENSE
2
- 0fae9e8380c795cd6d6fa9f1733c408df7b27bf009d947ebb15adc96ad40dfab MERGE_REPORT.json
3
  20486cf6b2e42440b0aea913ea475c818664f34e76e4501651d6cb21987a508b NOTICE
4
- 0ffad36ca79c67234dd1bfbb9e24d1d236beaf8d2297c3c2052b9a1d4df30f20 QUICKSTART.md
5
- e96d53b993eb557a54492a0234907503692a05884027a2106d44c398a218ebb7 README.md
6
  66e0fda8c8ab269ccd010dca6196931d475a22c3f2eebe5b6b3a2ec33a354d0e SYSTEM_PROMPT.txt
7
  74eeb55fd3341286ec3fd44e902b7120721acc81cd394e96b431f85e93a1ea56 chat_template.jinja
8
  111827aa7ae6d936f48afb2f0776f51b00add45c857c708af2b893499f76d882 config.json
@@ -17,7 +17,7 @@ dd888be78c0e32894cf04a8e3fbf8757e7218873668fa4443ba47c7c1c48b6dc docs/assets/04
17
  22396bd8908b697d912bd1ec54a510eff2b2b1884cd00b5e1bedd81db450d2fa docs/assets/05-gain-over-base.svg
18
  c43fb809f0b6bff34530fe2970fbd59817ae56e93813da7f45af32a8b9413987 docs/assets/06-vs-jev.png
19
  23fe4cfc32b756068dd3967339bae947391a18f1c4c0de5bad5c0dceeaff3463 docs/assets/06-vs-jev.svg
20
- df15177d9f8022598ac7af696600afcee14dd531875c519c2e2db74f0fc2498c docs/public-classification-suites.md
21
  7866009cccf468a71c56163d0ec27eee5185e5d8dc4c1a495f91445229eb4c95 evidence/calibration-leaderboard-ablate-nosys.json
22
  e3b7eb0fff1504035ab7ddbe01af976db48236872e6b77fc7bcdc766a60fd000 evidence/calibration-leaderboard-merged-native.json
23
  a717a16ab338fb5a3aefe0822a5421732cb2f68d965691fdb8138ded0b4d70b3 evidence/calibration-per-type-served.json
@@ -27,17 +27,17 @@ a33036e10498a440481258cbbc8611b531498d6239115a9278c3e6cafe10f6ad evidence/serve
27
  d47f43be5bd7767b80ed27a96a9a85ece1495b37fb9365e575b1df6b32fbc509 evidence/served-nosys-latency-idle-gpu.json
28
  262f5c7f85fcc5b8a9ccfaa7485d8b9f11a70f3d5a2ff60868ff9ccd1e887b43 evidence/served-nosys-original-report.json
29
  e0923390059f84a9180b00e5501778acc45ea9856cd7f2fd68208b360927c677 generation_config.json
30
- 5b29a2e3e86366c0b2e21c5038a16fa1f2bed8a2ca69e5e775608126570c89d4 model-00001-of-00004.safetensors
31
- 65c9000a4aafa0cb0978540cfdbb6017e39f14a208a8d0523d375656f5cf0343 model-00002-of-00004.safetensors
32
- fe537b22d9b005873c25b0f8ca0548e24f6d3a4fb611db11becc6ba86c849a03 model-00003-of-00004.safetensors
33
- 6c3f84685c69360d991bf02b81ac670fcb36cc1b9af2dd51e2137e0f3d36a24a model-00004-of-00004.safetensors
34
  71e022361d37d84c2b6224acbddca2c74bedcc7addc523cfc6a57420d4b22ceb model.safetensors.index.json
35
  81b8377f36c5b3d60900b333214d85160a0500b84576e969092c6fd214f69538 params.json
36
  ece2373c2ae391bce18785a1810543bc6173a1f28b6767cffab2c35dbea5f002 processor_config.json
37
- 12068f5b55e15718a228e32c5a29d482749216a557f415faa2345c858ad79094 release-manifest.json
38
  1495e1e757ef4d0925a2350563cf5754bb23c51701a8ec4fb3c5cdcbedae6747 server/LICENSE
39
  0f1f023b916d38d7c12af3a71e09f5e82b819a7e156e4992118bb160ee63ede1 server/NOTICE
40
- e58911cf75448ce8951377d46eeb531158c46db0586faedd22cccdfd69193013 server/README.md
41
  88e4dea8cc92f467a03bdd45a5fb968e402517116b09e24edcea85bb15f121e5 server/benchmarks/DECISION_BENCHMARK_SELECTION.md
42
  7fea6b9dc44e34ce448f1d80a9f2d273b76ab9b0ac1859c032fa4faa4f458501 server/benchmarks/METRIC_VALIDATION.md
43
  bb88f12ef294680e389147d781bf1f8680ea0a5ca9ed554730d8ea29f1c25eb9 server/benchmarks/PUBLIC_DATASETS.md
@@ -59,7 +59,7 @@ fee797d1a2ff128a5d61dd436e8252d63bc726b8ce4ba72641a1ffd9196135a6 server/benchma
59
  2544280dd3e669985bf57dfc61c2212bcf16d27230a79cea76f9f4e822476429 server/examples/request.json
60
  7a14fe0f5bf73aca7a4930423c310c0a3b1f9b546d07abc8d3fd5d9f38c9a5d7 server/examples/smoke.py
61
  e589f268969c87f82ddd2c81c0432c6335c08a9f9f2537ce68ddd5708f1e6f9e server/jev_adapter/__init__.py
62
- bf3d7a38ac294b23bf0d1a6b32a66fffb5054affc7d8958a821d9aea4f3ef807 server/jev_adapter/__main__.py
63
  e2f84bb7867934533f8f2a87034cad76af695ca1bbdbf096c545fe60b790c6fa server/jev_adapter/backend.py
64
  2cd67e71c1a601fc9333c266d909197031b7c0dc746b9a2d5475a5fb0c085e95 server/jev_adapter/benchmarks/__init__.py
65
  44104bc2d6431c05345cd697dd2c377cc6ba9fd57f2177df7b548490cf5f53fc server/jev_adapter/benchmarks/compare.py
@@ -68,21 +68,26 @@ a66e6ec289b4c32f79ac990261650870be6b923e47753b9c89ff9e58fe2f982f server/jev_ada
68
  1b78c52f723cb3a3e614d1be4ca531e7595e0a6690e704d6f1bb3045dd948bf0 server/jev_adapter/benchmarks/metrics.py
69
  9c51ce87269c587f1353b9dd32d886f9f0a51613a996721a7bb81695af445c02 server/jev_adapter/benchmarks/prepare.py
70
  0fef99cd01164c5548de530c9ab93255e2a95d17757c12e8e3d23016afd1a03c server/jev_adapter/benchmarks/run.py
 
71
  86e113a3ee9d15d9be93ba5e964d5f5d51171619e7856cc7dde812c6c90f4a37 server/jev_adapter/native_tokenizer.py
72
- ad7dee34b9c15e83ce66b3b8b07266e0b8ce81da7c8ba692a8293dbf078d0e44 server/jev_adapter/protocol.py
73
- bf9c1588e8069ef2c341151c24f08b29f41d9bc57cb16f1866467467b50d8edc server/jev_adapter/server.py
74
- 332068ebc21f19b6eee6c6d8fd24e6821a4fbd5def6251f5a94beae613419a0a server/jev_adapter/service.py
75
- 2e45894cfb8fa204c7278ade4a123cda356de894ddf6fbb60b66e93dfea576c6 server/jev_adapter/sglang.py
76
  159eb400efb830c762a36f81e51ba79888570284ffa70326301adfeee50b5bd3 server/pyproject.toml
 
 
 
77
  f5bf6e9b916ff0d24a2551b4f9f24ed353e7343f5be343b141d38c4c04bd1101 server/tests/test_benchmark_data.py
78
  c197a90d57c7516eb615f7f0626f4c3e95b2fd0773d74e8f32601251804fca66 server/tests/test_benchmark_matrix.py
79
  56a48c36d322d7131978809397da79d0f9abbb6801ab6176d27ed2f9dfb26063 server/tests/test_benchmark_run.py
80
  c492b41c07050d9b76c6c9d7d21bbfcdac6ec137346cf995df3f624cb1e345c8 server/tests/test_disconnect_cleanup.py
81
  83769ec3f35f5034787ffa8160a5ea7c9c7e23200b303d8b908500d71e3fe13f server/tests/test_http_integration.py
82
  746cc73422976ec22b460a74a7c70d937c0ba4fd9fb7151fa6745641dbca5616 server/tests/test_jevbench.py
83
- ade6b76bacf23ae244d9790312f297a36557d6cd8075b81ce36e928359b742da server/tests/test_main.py
 
84
  83084f098020374a4abfa3d4402e74e357d45170ca840843e58dba13162e9986 server/tests/test_native_tokenizer.py
85
- 0114da8966dafa951308ed58563053918b2b2f7b3393dd483c7183e3374b444d server/tests/test_protocol.py
86
  617f08c82448932cf13320166c4df66a1a85b5056a354aa8e9f748ce9b6f57d3 server/tests/test_service.py
87
  340e91fb9a6c870990187386da8d165fafcbcb5f08559056fd9da4e4e78b681f server/tests/test_sglang.py
88
  0a5c981e8c5c6f8886ee007a6d4543a0be6b221cb9ca32a8709384a4c6fc8cbb special_tokens_map.json
 
1
  1495e1e757ef4d0925a2350563cf5754bb23c51701a8ec4fb3c5cdcbedae6747 LICENSE
2
+ 78430ac850a7b8182093fadb6fafa36d70ad79f548b5369d261ef81034343b24 MERGE_REPORT.json
3
  20486cf6b2e42440b0aea913ea475c818664f34e76e4501651d6cb21987a508b NOTICE
4
+ 7fdc581f9a4e8bb935e3c883435b456d283aea2e439f4560103e0923c39080be QUICKSTART.md
5
+ 33f44c5a637384c0153be71319eafeee6013241d801efa06b41183bdc4e30faa README.md
6
  66e0fda8c8ab269ccd010dca6196931d475a22c3f2eebe5b6b3a2ec33a354d0e SYSTEM_PROMPT.txt
7
  74eeb55fd3341286ec3fd44e902b7120721acc81cd394e96b431f85e93a1ea56 chat_template.jinja
8
  111827aa7ae6d936f48afb2f0776f51b00add45c857c708af2b893499f76d882 config.json
 
17
  22396bd8908b697d912bd1ec54a510eff2b2b1884cd00b5e1bedd81db450d2fa docs/assets/05-gain-over-base.svg
18
  c43fb809f0b6bff34530fe2970fbd59817ae56e93813da7f45af32a8b9413987 docs/assets/06-vs-jev.png
19
  23fe4cfc32b756068dd3967339bae947391a18f1c4c0de5bad5c0dceeaff3463 docs/assets/06-vs-jev.svg
20
+ 58d920ce53f77d10b78f857e67bd5fecec86405a23a5643101e3b0d2d367f4db docs/public-classification-suites.md
21
  7866009cccf468a71c56163d0ec27eee5185e5d8dc4c1a495f91445229eb4c95 evidence/calibration-leaderboard-ablate-nosys.json
22
  e3b7eb0fff1504035ab7ddbe01af976db48236872e6b77fc7bcdc766a60fd000 evidence/calibration-leaderboard-merged-native.json
23
  a717a16ab338fb5a3aefe0822a5421732cb2f68d965691fdb8138ded0b4d70b3 evidence/calibration-per-type-served.json
 
27
  d47f43be5bd7767b80ed27a96a9a85ece1495b37fb9365e575b1df6b32fbc509 evidence/served-nosys-latency-idle-gpu.json
28
  262f5c7f85fcc5b8a9ccfaa7485d8b9f11a70f3d5a2ff60868ff9ccd1e887b43 evidence/served-nosys-original-report.json
29
  e0923390059f84a9180b00e5501778acc45ea9856cd7f2fd68208b360927c677 generation_config.json
30
+ cac498368970b8ffb9785583c954648a40472b3cfa061ad0924af7babdee3f53 model-00001-of-00004.safetensors
31
+ 9a9fbec75dc4013463a57ad1cf61c2ed6fb558db9d379fb671dba0e448326be0 model-00002-of-00004.safetensors
32
+ 2f89f2111ee337b3b8b1278d0ec32bce3c20bfe5f2bab8caa6dd3e01a3c76ad5 model-00003-of-00004.safetensors
33
+ 125adfa505e3f9c1b33767601060c773a421876c3630fb8172dcdc886adf137f model-00004-of-00004.safetensors
34
  71e022361d37d84c2b6224acbddca2c74bedcc7addc523cfc6a57420d4b22ceb model.safetensors.index.json
35
  81b8377f36c5b3d60900b333214d85160a0500b84576e969092c6fd214f69538 params.json
36
  ece2373c2ae391bce18785a1810543bc6173a1f28b6767cffab2c35dbea5f002 processor_config.json
37
+ 6741d6b57b543a839be57e56b6189562bd098050a0eca7bf53b8016500e4324c release-manifest.json
38
  1495e1e757ef4d0925a2350563cf5754bb23c51701a8ec4fb3c5cdcbedae6747 server/LICENSE
39
  0f1f023b916d38d7c12af3a71e09f5e82b819a7e156e4992118bb160ee63ede1 server/NOTICE
40
+ 6385bb6dba77ed768d7214a8f17a311db266ea99bdd34e1fab2f0735e4a41e0f server/README.md
41
  88e4dea8cc92f467a03bdd45a5fb968e402517116b09e24edcea85bb15f121e5 server/benchmarks/DECISION_BENCHMARK_SELECTION.md
42
  7fea6b9dc44e34ce448f1d80a9f2d273b76ab9b0ac1859c032fa4faa4f458501 server/benchmarks/METRIC_VALIDATION.md
43
  bb88f12ef294680e389147d781bf1f8680ea0a5ca9ed554730d8ea29f1c25eb9 server/benchmarks/PUBLIC_DATASETS.md
 
59
  2544280dd3e669985bf57dfc61c2212bcf16d27230a79cea76f9f4e822476429 server/examples/request.json
60
  7a14fe0f5bf73aca7a4930423c310c0a3b1f9b546d07abc8d3fd5d9f38c9a5d7 server/examples/smoke.py
61
  e589f268969c87f82ddd2c81c0432c6335c08a9f9f2537ce68ddd5708f1e6f9e server/jev_adapter/__init__.py
62
+ caacf020f66bbfbf1e09ac303a7e9adbe493ad4154a1339c203c8ff87fb3392a server/jev_adapter/__main__.py
63
  e2f84bb7867934533f8f2a87034cad76af695ca1bbdbf096c545fe60b790c6fa server/jev_adapter/backend.py
64
  2cd67e71c1a601fc9333c266d909197031b7c0dc746b9a2d5475a5fb0c085e95 server/jev_adapter/benchmarks/__init__.py
65
  44104bc2d6431c05345cd697dd2c377cc6ba9fd57f2177df7b548490cf5f53fc server/jev_adapter/benchmarks/compare.py
 
68
  1b78c52f723cb3a3e614d1be4ca531e7595e0a6690e704d6f1bb3045dd948bf0 server/jev_adapter/benchmarks/metrics.py
69
  9c51ce87269c587f1353b9dd32d886f9f0a51613a996721a7bb81695af445c02 server/jev_adapter/benchmarks/prepare.py
70
  0fef99cd01164c5548de530c9ab93255e2a95d17757c12e8e3d23016afd1a03c server/jev_adapter/benchmarks/run.py
71
+ 9a3d1c89d7451a52383709487acb883f683dbdcb2ca73b5a9b39b9b75407a09e server/jev_adapter/labels.py
72
  86e113a3ee9d15d9be93ba5e964d5f5d51171619e7856cc7dde812c6c90f4a37 server/jev_adapter/native_tokenizer.py
73
+ 0acba8046427725f4275cd971cfc29f48c04c64e73f96115b83ce8ac1bcbe8b7 server/jev_adapter/protocol.py
74
+ 4375a9eafb6d252a76b2b57dd7cecfd600baae04a9ab4dd0abbd6c86a3778be9 server/jev_adapter/server.py
75
+ 24667f1c1d6967f5a1c90d6e9bf13922bdfe1ce1a3aea8709c18a1837d2fe09c server/jev_adapter/service.py
76
+ 18b3191db6a3e43a2ad01243c709bab2c8db771b1363638de64ab4871b0f1d7b server/jev_adapter/sglang.py
77
  159eb400efb830c762a36f81e51ba79888570284ffa70326301adfeee50b5bd3 server/pyproject.toml
78
+ d4d01e7e2c0d49ccce56516db8aaef0eb32d001b42e8f394e7e355e90047db71 server/tests/conftest.py
79
+ 63046320ac89d71770afccf8f3ad861c795151ede968354f34f59aabff9ec5c3 server/tests/data/ministral3-label-probe.json
80
+ 27119a52ee9a0417809c3a50b5b07e77aa723d4a63266d1065ba8ca82589d512 server/tests/data/upper-labels-ministral3.json
81
  f5bf6e9b916ff0d24a2551b4f9f24ed353e7343f5be343b141d38c4c04bd1101 server/tests/test_benchmark_data.py
82
  c197a90d57c7516eb615f7f0626f4c3e95b2fd0773d74e8f32601251804fca66 server/tests/test_benchmark_matrix.py
83
  56a48c36d322d7131978809397da79d0f9abbb6801ab6176d27ed2f9dfb26063 server/tests/test_benchmark_run.py
84
  c492b41c07050d9b76c6c9d7d21bbfcdac6ec137346cf995df3f624cb1e345c8 server/tests/test_disconnect_cleanup.py
85
  83769ec3f35f5034787ffa8160a5ea7c9c7e23200b303d8b908500d71e3fe13f server/tests/test_http_integration.py
86
  746cc73422976ec22b460a74a7c70d937c0ba4fd9fb7151fa6745641dbca5616 server/tests/test_jevbench.py
87
+ de052504cc7d82c964ef7cf4d6d2fafabf3ec61e4c4b359b9baadeea6b27c7f2 server/tests/test_labels.py
88
+ af1ebfb5c57e2de76b8f90bdb6eec89abaa29ae3a1873595d4de287788c991af server/tests/test_main.py
89
  83084f098020374a4abfa3d4402e74e357d45170ca840843e58dba13162e9986 server/tests/test_native_tokenizer.py
90
+ b296814c9f0837d7c2625e1867b3197021f11c0b1c7ab5b74aa9c4b298b82396 server/tests/test_protocol.py
91
  617f08c82448932cf13320166c4df66a1a85b5056a354aa8e9f748ce9b6f57d3 server/tests/test_service.py
92
  340e91fb9a6c870990187386da8d165fafcbcb5f08559056fd9da4e4e78b681f server/tests/test_sglang.py
93
  0a5c981e8c5c6f8886ee007a6d4543a0be6b221cb9ca32a8709384a4c6fc8cbb special_tokens_map.json
model-00001-of-00004.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:4ef4abdb14796255a76f30897dfd4e98d6d601cc7c48a3c38d2d4bcd955bdb2f
3
  size 4999724576
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cac498368970b8ffb9785583c954648a40472b3cfa061ad0924af7babdee3f53
3
  size 4999724576
model-00002-of-00004.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:042b42a0abae0f07400c6ad515eff96f28399490695996d88eb6ee29ab96b6f0
3
  size 4999820896
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9a9fbec75dc4013463a57ad1cf61c2ed6fb558db9d379fb671dba0e448326be0
3
  size 4999820896
model-00003-of-00004.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:81f47613209ff4149d80961cf63472d58f05c1c5f98ba491464fb9d90970b34f
3
  size 4915917688
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2f89f2111ee337b3b8b1278d0ec32bce3c20bfe5f2bab8caa6dd3e01a3c76ad5
3
  size 4915917688
model-00004-of-00004.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9fa222d724cd363d44f37fffc7f7947eb964dff01ae760623370a65b4d67314b
3
  size 2920659992
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:125adfa505e3f9c1b33767601060c773a421876c3630fb8172dcdc886adf137f
3
  size 2920659992
release-manifest.json CHANGED
@@ -1,40 +1,38 @@
1
  {
2
  "release": "StandardOne-8B",
3
- "version": "v2",
4
  "base_model": {
5
  "repo": "mistralai/Ministral-3-8B-Instruct-2512-BF16",
6
  "revision": "f6fae9795746f63c9be8344932f01275f3c63734"
7
  },
8
  "adapter": {
9
- "source_run": "r5-8b",
10
- "adapter_model_sha256": "53e41238cd55567cfbc75efdf61d354da39771573f56f74bb1440c9ffdd1bd4a"
11
  },
12
  "merge_report_summary": {
13
  "changed_params": 238,
14
  "unchanged_params": 293,
15
  "changed_outside_lm_projections": [],
16
- "max_abs_delta": 0.00201416015625
17
  },
18
  "serving": {
19
- "wording": "native",
20
  "system_prompt": null,
 
21
  "served_model_name": "standard-one-8b",
22
  "engine": "SGLang 0.5.20",
23
- "temperature_by_type": {
24
- "choice": 0.85,
25
- "noul": 0.85,
26
- "score": 0.7
27
- },
28
- "default_temperature": 0.85,
29
- "selection": "wording and temperatures chosen on non-JevBench data (evidence/prompt-wording-choice.json, evidence/calibration-per-type-served.json)",
30
  "alternative_wording": {
31
- "wording": "served",
32
- "default_temperature": 0.8,
 
33
  "temperature_by_type": {
34
- "choice": 0.8,
35
- "noul": 0.9,
36
- "score": 0.95
37
- }
 
38
  }
39
  }
40
  }
 
1
  {
2
  "release": "StandardOne-8B",
3
+ "version": "v2.2",
4
  "base_model": {
5
  "repo": "mistralai/Ministral-3-8B-Instruct-2512-BF16",
6
  "revision": "f6fae9795746f63c9be8344932f01275f3c63734"
7
  },
8
  "adapter": {
9
+ "adapter_model_sha256": "123ddd039f4053e82e8ca18d7c247691dc49cbcabb6e1b7d98077bb7c5c7446e"
 
10
  },
11
  "merge_report_summary": {
12
  "changed_params": 238,
13
  "unchanged_params": 293,
14
  "changed_outside_lm_projections": [],
15
+ "max_abs_delta": 0.002109527587890625
16
  },
17
  "serving": {
18
+ "wording": "served",
19
  "system_prompt": null,
20
+ "label_scheme": "upper",
21
  "served_model_name": "standard-one-8b",
22
  "engine": "SGLang 0.5.20",
23
+ "context_length": 32768,
24
+ "default_temperature": 1.65,
25
+ "selection": "the configuration used for the v2.2 measurements; no temperature was refit for v2.2",
 
 
 
 
26
  "alternative_wording": {
27
+ "wording": "native",
28
+ "max_options": 26,
29
+ "default_temperature": 0.85,
30
  "temperature_by_type": {
31
+ "choice": 0.85,
32
+ "noul": 0.85,
33
+ "score": 0.7
34
+ },
35
+ "note": "temperatures fitted on v2"
36
  }
37
  }
38
  }
server/README.md CHANGED
@@ -11,7 +11,7 @@ pip install -e './server[native-tokenizer]'
11
  CUDA_VISIBLE_DEVICES=0 SGLANG_VLM_CACHE_SIZE_MB=0 python -m sglang.launch_server \
12
  --model-path . --served-model-name standard-one-8b \
13
  --host 127.0.0.1 --port 30000 --tp-size 1 --model-impl sglang --dtype bfloat16 \
14
- --context-length 8192 --max-running-requests 32 --mem-fraction-static 0.8 \
15
  --chunked-prefill-size -1 --disable-radix-cache --mm-preprocess-cache-size-mb 0 \
16
  --model-config-parser hf --load-format safetensors
17
 
@@ -20,13 +20,13 @@ jev-adapter --engine-url http://127.0.0.1:30000 --model standard-one-8b --alias
20
  --host 0.0.0.0 --port 30120 --max-concurrency 1 \
21
  --tokenizer-model mistralai/Ministral-3-8B-Instruct-2512-BF16 \
22
  --tokenizer-revision f6fae9795746f63c9be8344932f01275f3c63734 \
23
- --prompt-wording native --native-system-prompt none --default-temperature 0.85 --temperature-by-type choice=0.85,noul=0.85,score=0.70
24
  ```
25
 
26
- The adapter supports two prompt wordings; each has its own fitted temperatures (details on the model card):
27
 
28
- - `native` (recommended default): `--prompt-wording native --native-system-prompt none --default-temperature 0.85 --temperature-by-type choice=0.85,noul=0.85,score=0.70`
29
- - `served`: `--prompt-wording served --default-temperature 0.80 --temperature-by-type choice=0.80,noul=0.90,score=0.95`
30
 
31
  `--temperature-by-type` sets one default temperature per answer type (`choice`, `noul`, `score`); a request's own
32
  `options.temperature` still wins. Let only this adapter talk to the engine port. The recommended values and the full
@@ -69,6 +69,8 @@ python3 -m venv .venv
69
 
70
  `--default-temperature T` (환경 변수 `JEV_DEFAULT_TEMPERATURE`)는 요청이 `options.temperature`를 생략했을 때만 적용되는 서버 기본 온도입니다. 요청이 온도를 명시하면 요청 값이 우선하고, `options.temperature_scaling: false`이면 기본값과 무관하게 스케일링하지 않습니다. 지정하지 않으면 기존과 같이 1.0입니다. 실제 적용된 온도는 응답의 `metadata.temperature`로 확인합니다.
71
 
 
 
72
  `SGLANG_API_KEY`는 엔진에 보낼 키, `JEV_API_KEY`는 클라이언트가 어댑터에 보낼 키입니다. 설정했다면 각각 `Authorization: Bearer ...` 인증을 사용합니다. 키를 설정하지 않은 어댑터는 인증 없이 동작합니다.
73
 
74
  시작할 때 엔진 정보를 확인하고, 엔진 토크나이저로 라벨을 검증합니다. 엔진을 먼저 실행해야 합니다. 모델/토크나이저를 교체하면 어댑터도 재시작합니다.
 
11
  CUDA_VISIBLE_DEVICES=0 SGLANG_VLM_CACHE_SIZE_MB=0 python -m sglang.launch_server \
12
  --model-path . --served-model-name standard-one-8b \
13
  --host 127.0.0.1 --port 30000 --tp-size 1 --model-impl sglang --dtype bfloat16 \
14
+ --context-length 32768 --max-running-requests 32 --mem-fraction-static 0.8 \
15
  --chunked-prefill-size -1 --disable-radix-cache --mm-preprocess-cache-size-mb 0 \
16
  --model-config-parser hf --load-format safetensors
17
 
 
20
  --host 0.0.0.0 --port 30120 --max-concurrency 1 \
21
  --tokenizer-model mistralai/Ministral-3-8B-Instruct-2512-BF16 \
22
  --tokenizer-revision f6fae9795746f63c9be8344932f01275f3c63734 \
23
+ --prompt-wording served --label-scheme upper --default-temperature 1.65
24
  ```
25
 
26
+ The adapter supports two prompt wordings (details on the model card):
27
 
28
+ - `served` (the default, recommended; used for the v2.2 measurements): `--prompt-wording served --label-scheme upper --default-temperature 1.65`; up to 255 options per question
29
+ - `native`: `--prompt-wording native --native-system-prompt none --default-temperature 0.85 --temperature-by-type choice=0.85,noul=0.85,score=0.70` (temperatures fitted on v2); at most 26 options per question
30
 
31
  `--temperature-by-type` sets one default temperature per answer type (`choice`, `noul`, `score`); a request's own
32
  `options.temperature` still wins. Let only this adapter talk to the engine port. The recommended values and the full
 
69
 
70
  `--default-temperature T` (환경 변수 `JEV_DEFAULT_TEMPERATURE`)는 요청이 `options.temperature`를 생략했을 때만 적용되는 서버 기본 온도입니다. 요청이 온도를 명시하면 요청 값이 우선하고, `options.temperature_scaling: false`이면 기본값과 무관하게 스케일링하지 않습니다. 지정하지 않으면 기존과 같이 1.0입니다. 실제 적용된 온도는 응답의 `metadata.temperature`로 확인합니다.
71
 
72
+ `--label-scheme {upper,served}` (환경 변수 `JEV_LABEL_SCHEME`)는 선택지가 26개를 넘는 질문의 답 라벨 순서입니다. 기본값 `upper`는 v2.2가 학습한 A–Z 다음 AA, AB, … 순서(한 토큰인 라벨만, 최대 255개)이고, `served`는 v2.2 이전의 A–Z, a–z, 0–9, AA, … 순서입니다. 26개 이하에서는 둘 다 A–Z입니다.
73
+
74
  `SGLANG_API_KEY`는 엔진에 보낼 키, `JEV_API_KEY`는 클라이언트가 어댑터에 보낼 키입니다. 설정했다면 각각 `Authorization: Bearer ...` 인증을 사용합니다. 키를 설정하지 않은 어댑터는 인증 없이 동작합니다.
75
 
76
  시작할 때 엔진 정보를 확인하고, 엔진 토크나이저로 라벨을 검증합니다. 엔진을 먼저 실행해야 합니다. 모델/토크나이저를 교체하면 어댑터도 재시작합니다.
server/jev_adapter/__main__.py CHANGED
@@ -5,6 +5,8 @@ import logging
5
  import math
6
  import os
7
 
 
 
8
  logger = logging.getLogger(__name__)
9
 
10
 
@@ -98,9 +100,25 @@ def main():
98
  "Ministral default is 531 tokens, about 80%% of a typical request). "
99
  "Env: JEV_NATIVE_SYSTEM_PROMPT.",
100
  )
 
 
 
 
 
 
 
 
 
 
 
101
  # argparse runs the type converter on the string default too, so a bad
102
  # JEV_DEFAULT_TEMPERATURE fails at startup with the same message.
103
  args = parser.parse_args()
 
 
 
 
 
104
  if args.timeout <= 0 or args.max_concurrency < 1:
105
  parser.error("timeout and max-concurrency must be positive")
106
  if bool(args.tokenizer_model) != bool(args.tokenizer_revision):
@@ -135,12 +153,13 @@ def main():
135
  "defines none); serving the native wording text without it."
136
  )
137
  logger.info(
138
- "prompt_wording=%s native_system_prompt=%s (%s)",
139
  args.prompt_wording,
140
  args.native_system_prompt,
141
  "system message attached"
142
  if native_system_prompt
143
  else "no system message attached",
 
144
  )
145
  backend = SGLangBackend(
146
  args.engine_url,
@@ -151,6 +170,7 @@ def main():
151
  native_tokenizer=native_tokenizer,
152
  prompt_wording=args.prompt_wording,
153
  native_system_prompt=native_system_prompt,
 
154
  )
155
  app = create_app(
156
  backend,
 
5
  import math
6
  import os
7
 
8
+ from .labels import DEFAULT_LABEL_SCHEME, LABEL_SCHEME_ENV, LABEL_SCHEMES
9
+
10
  logger = logging.getLogger(__name__)
11
 
12
 
 
100
  "Ministral default is 531 tokens, about 80%% of a typical request). "
101
  "Env: JEV_NATIVE_SYSTEM_PROMPT.",
102
  )
103
+ parser.add_argument(
104
+ "--label-scheme",
105
+ choices=LABEL_SCHEMES,
106
+ default=os.environ.get(LABEL_SCHEME_ENV) or DEFAULT_LABEL_SCHEME,
107
+ help="Answer labels for questions with more than 26 options. 'upper' "
108
+ "(default): A-Z, then AA, AB, ... (uppercase only; labels that are not "
109
+ "single tokens are skipped). 'served': the original A-Z, a-z, 0-9, "
110
+ "then AA, ... order (pre-v2.2 serving). Both give A-Z to "
111
+ "questions with at most 26 options. Reported in /v1/models and in "
112
+ "response metadata. Env: JEV_LABEL_SCHEME.",
113
+ )
114
  # argparse runs the type converter on the string default too, so a bad
115
  # JEV_DEFAULT_TEMPERATURE fails at startup with the same message.
116
  args = parser.parse_args()
117
+ if args.label_scheme not in LABEL_SCHEMES:
118
+ parser.error(
119
+ f"JEV_LABEL_SCHEME must be one of {', '.join(LABEL_SCHEMES)}; "
120
+ f"got {args.label_scheme!r}"
121
+ )
122
  if args.timeout <= 0 or args.max_concurrency < 1:
123
  parser.error("timeout and max-concurrency must be positive")
124
  if bool(args.tokenizer_model) != bool(args.tokenizer_revision):
 
153
  "defines none); serving the native wording text without it."
154
  )
155
  logger.info(
156
+ "prompt_wording=%s native_system_prompt=%s (%s) label_scheme=%s",
157
  args.prompt_wording,
158
  args.native_system_prompt,
159
  "system message attached"
160
  if native_system_prompt
161
  else "no system message attached",
162
+ args.label_scheme,
163
  )
164
  backend = SGLangBackend(
165
  args.engine_url,
 
170
  native_tokenizer=native_tokenizer,
171
  prompt_wording=args.prompt_wording,
172
  native_system_prompt=native_system_prompt,
173
+ label_scheme=args.label_scheme,
174
  )
175
  app = create_app(
176
  backend,
server/jev_adapter/labels.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Answer-label schemes shared by serving, readout export, and training data.
2
+
3
+ A scheme is an ordered list of candidate label strings. A model's labels are
4
+ the candidates that its tokenizer encodes as one distinct, non-special token
5
+ which decodes back to exactly the candidate, taken in candidate order, at most
6
+ ``MAX_LABELS``. A question with ``n`` options always uses the first ``n``.
7
+
8
+ ``upper`` (the default): ``A``..``Z``, then two-letter ``AA``, ``AB``, ...,
9
+ ``ZZ``, then three-letter ``AAA``..``ZZZ``; uppercase ASCII only, ordered by
10
+ length and then alphabetically. Three-letter candidates are only consulted
11
+ when the one- and two-letter single tokens number fewer than ``MAX_LABELS``.
12
+
13
+ ``served`` (the original order, kept to reproduce pre-v2.2 serving): ``A``..``Z``, ``a``..``z``,
14
+ ``0``..``9``, ``000``..``999``, ``AA``..``ZZ``, ``aa``..``zz``. No v2 training
15
+ row had more than 26 options, so beyond Z neither order is what v2 learned.
16
+
17
+ Both schemes give ``A``..``Z`` to questions with at most 26 options whenever
18
+ the tokenizer has those 26 single-token letters.
19
+ """
20
+
21
+ import itertools
22
+ import os
23
+ import re
24
+ import string
25
+ from collections.abc import Mapping, Sequence
26
+ from typing import Any
27
+
28
+ LABEL_SCHEMES = ("served", "upper")
29
+ DEFAULT_LABEL_SCHEME = "upper"
30
+ LABEL_SCHEME_ENV = "JEV_LABEL_SCHEME"
31
+ MAX_LABELS = 255
32
+ UPPER_MAX_LENGTH = 3
33
+ _UPPER_LABEL = re.compile(r"[A-Z]{1,3}")
34
+
35
+
36
+ def validate_label_scheme(value: Any) -> str:
37
+ if value not in LABEL_SCHEMES:
38
+ raise ValueError(
39
+ f"label scheme must be one of {', '.join(LABEL_SCHEMES)}; got {value!r}"
40
+ )
41
+ return value
42
+
43
+
44
+ def resolve_label_scheme(
45
+ value: str | None = None, environ: Mapping[str, str] | None = None
46
+ ) -> str:
47
+ """An explicit value, else ``JEV_LABEL_SCHEME``, else the default (upper)."""
48
+ if value is None or value == "":
49
+ environ = os.environ if environ is None else environ
50
+ value = environ.get(LABEL_SCHEME_ENV) or DEFAULT_LABEL_SCHEME
51
+ return validate_label_scheme(value)
52
+
53
+
54
+ def _served_candidates() -> tuple[str, ...]:
55
+ letters = string.ascii_uppercase + string.ascii_lowercase + string.digits
56
+ return tuple(
57
+ list(letters)
58
+ + [f"{value:03d}" for value in range(1000)]
59
+ + [
60
+ first + second
61
+ for alphabet in (string.ascii_uppercase, string.ascii_lowercase)
62
+ for first in alphabet
63
+ for second in alphabet
64
+ ]
65
+ )
66
+
67
+
68
+ def _upper_length(length: int) -> tuple[str, ...]:
69
+ return tuple(
70
+ "".join(chars)
71
+ for chars in itertools.product(string.ascii_uppercase, repeat=length)
72
+ )
73
+
74
+
75
+ def candidate_tiers(scheme: str) -> tuple[tuple[str, ...], ...]:
76
+ """Candidates in order, grouped so a caller may stop after an early group.
77
+
78
+ A later group is consulted only while fewer than MAX_LABELS labels were
79
+ found; the concatenated order is the scheme's order either way.
80
+ """
81
+ validate_label_scheme(scheme)
82
+ if scheme == "served":
83
+ return (_served_candidates(),)
84
+ return (
85
+ _upper_length(1) + _upper_length(2),
86
+ _upper_length(3),
87
+ )
88
+
89
+
90
+ def label_candidates(scheme: str) -> list[str]:
91
+ return [label for tier in candidate_tiers(scheme) for label in tier]
92
+
93
+
94
+ def select_labels(
95
+ tokenizer: Any, scheme: str, *, limit: int = MAX_LABELS
96
+ ) -> tuple[list[str], list[int]]:
97
+ """The scheme's labels for a tokenizer with ``encode`` and ``decode``.
98
+
99
+ A candidate is kept when it encodes (without special tokens) to exactly
100
+ one token that is not special, not already taken, and decodes back to the
101
+ candidate. This is the rule every exporter, backend, and training-data
102
+ writer must share; it needs only the tokenizer files.
103
+ """
104
+ if type(limit) is not int or not 1 <= limit <= MAX_LABELS:
105
+ raise ValueError(f"limit must be an integer from 1 to {MAX_LABELS}")
106
+ special = set(getattr(tokenizer, "all_special_ids", ()) or ())
107
+ labels: list[str] = []
108
+ token_ids: list[int] = []
109
+ seen: set[int] = set()
110
+ for tier in candidate_tiers(scheme):
111
+ for label in tier:
112
+ ids = tokenizer.encode(label, add_special_tokens=False)
113
+ if not isinstance(ids, list) or len(ids) != 1 or type(ids[0]) is not int:
114
+ continue
115
+ token = ids[0]
116
+ if token < 0 or token in seen or token in special:
117
+ continue
118
+ if tokenizer.decode([token], skip_special_tokens=True) != label:
119
+ continue
120
+ labels.append(label)
121
+ token_ids.append(token)
122
+ seen.add(token)
123
+ if len(labels) == limit:
124
+ return labels, token_ids
125
+ return labels, token_ids
126
+
127
+
128
+ def follows_scheme_order(scheme: str, labels: Sequence[str]) -> bool:
129
+ """Whether labels are distinct candidates of the scheme in scheme order.
130
+
131
+ A structural check that needs no tokenizer: it cannot tell whether a
132
+ skipped candidate was a single token, only that nothing is out of order.
133
+ """
134
+ validate_label_scheme(scheme)
135
+ if not labels or any(not isinstance(label, str) for label in labels):
136
+ return False
137
+ if scheme == "upper":
138
+ if any(_UPPER_LABEL.fullmatch(label) is None for label in labels):
139
+ return False
140
+ ranks = [(len(label), label) for label in labels]
141
+ else:
142
+ index = {label: position for position, label in enumerate(_served_candidates())}
143
+ if any(label not in index for label in labels):
144
+ return False
145
+ ranks = [index[label] for label in labels]
146
+ return all(before < after for before, after in itertools.pairwise(ranks))
server/jev_adapter/protocol.py CHANGED
@@ -79,6 +79,9 @@ class NoulQuestion(_SystemOneModel):
79
  @field_validator("criteria")
80
  @classmethod
81
  def validate_boolean_criteria(cls, criteria):
 
 
 
82
  if criteria is not None and set(criteria) != {"true", "false"}:
83
  raise ValueError('noul criteria must contain exactly "true" and "false"')
84
  return criteria
 
79
  @field_validator("criteria")
80
  @classmethod
81
  def validate_boolean_criteria(cls, criteria):
82
+ # An empty map means "no descriptions", same as omitting the field.
83
+ if criteria == {}:
84
+ return None
85
  if criteria is not None and set(criteria) != {"true", "false"}:
86
  raise ValueError('noul criteria must contain exactly "true" and "false"')
87
  return criteria
server/jev_adapter/server.py CHANGED
@@ -95,6 +95,11 @@ def create_app(
95
  "id": backend.model,
96
  "object": "model",
97
  "owned_by": "inference-engine",
 
 
 
 
 
98
  },
99
  ],
100
  }
 
95
  "id": backend.model,
96
  "object": "model",
97
  "owned_by": "inference-engine",
98
+ **(
99
+ {"label_scheme": backend.label_scheme}
100
+ if getattr(backend, "label_scheme", None)
101
+ else {}
102
+ ),
103
  },
104
  ],
105
  }
server/jev_adapter/service.py CHANGED
@@ -166,6 +166,11 @@ class SystemOneService:
166
  else {}
167
  ),
168
  "evaluations": len(jobs),
 
 
 
 
 
169
  "adapter_elapsed_ms": (time.perf_counter() - started) * 1000,
170
  },
171
  }
 
166
  else {}
167
  ),
168
  "evaluations": len(jobs),
169
+ **(
170
+ {"label_scheme": self.backend.label_scheme}
171
+ if getattr(self.backend, "label_scheme", None)
172
+ else {}
173
+ ),
174
  "adapter_elapsed_ms": (time.perf_counter() - started) * 1000,
175
  },
176
  }
server/jev_adapter/sglang.py CHANGED
@@ -7,27 +7,18 @@ No SGLang, CUDA, or model weights are imported into this process.
7
 
8
  import asyncio
9
  import math
10
- import string
11
  import uuid
12
  from typing import Any
13
 
14
  import httpx
15
 
16
  from .backend import AdapterError, ScoringResult
17
-
18
-
19
- def _label_candidates() -> list[str]:
20
- letters = string.ascii_uppercase + string.ascii_lowercase + string.digits
21
- return (
22
- list(letters)
23
- + [f"{value:03d}" for value in range(1000)]
24
- + [
25
- first + second
26
- for alphabet in (string.ascii_uppercase, string.ascii_lowercase)
27
- for first in alphabet
28
- for second in alphabet
29
- ]
30
- )
31
 
32
 
33
  _IMAGE_MODEL_TYPES = {
@@ -76,11 +67,14 @@ class SGLangBackend:
76
  native_tokenizer: Any | None = None,
77
  prompt_wording: str = "served",
78
  native_system_prompt: str | None = None,
 
79
  ):
80
  if not math.isfinite(timeout) or timeout <= 0 or max_connections < 1:
81
  raise ValueError("timeout and max_connections must be positive")
82
  if prompt_wording not in ("served", "native"):
83
  raise ValueError('prompt_wording must be "served" or "native"')
 
 
84
  url = httpx.URL(base_url)
85
  if (
86
  url.scheme not in ("http", "https")
@@ -217,61 +211,13 @@ class SGLangBackend:
217
  if isinstance(arch, str)
218
  )
219
  )
220
- candidates = _label_candidates()
221
- tokenized = await self._request(
222
- "POST",
223
- "/v1/tokenize",
224
- {
225
- "model": self.model,
226
- "prompt": candidates,
227
- "add_special_tokens": False,
228
- },
229
- )
230
- batches = tokenized.get("tokens")
231
- if (
232
- not isinstance(batches, list)
233
- or len(batches) != len(candidates)
234
- or not all(_token_list(ids) for ids in batches)
235
- ):
236
- raise _bad_response(
237
- "The inference engine returned malformed label tokenization."
238
- )
239
- singles = [
240
- (label, ids[0])
241
- for label, ids in zip(candidates, batches)
242
- if len(ids) == 1
243
- ]
244
- if not singles:
245
- raise AdapterError(
246
- "labels_unavailable",
247
- "The tokenizer has no usable single-token labels.",
248
- )
249
- decoded = await self._request(
250
- "POST",
251
- "/v1/detokenize",
252
- {
253
- "model": self.model,
254
- "tokens": [[token] for _, token in singles],
255
- "skip_special_tokens": True,
256
- },
257
- )
258
- texts = decoded.get("text")
259
- if (
260
- not isinstance(texts, list)
261
- or len(texts) != len(singles)
262
- or not all(isinstance(item, str) for item in texts)
263
- ):
264
- raise _bad_response(
265
- "The inference engine returned malformed label detokenization."
266
- )
267
  valid: list[tuple[str, int]] = []
268
  seen: set[int] = set()
269
- for (label, token), decoded_label in zip(singles, texts):
270
- if label == decoded_label and token not in seen:
271
- valid.append((label, token))
272
- seen.add(token)
273
- if len(valid) == 255:
274
- break
275
  if len(valid) < 2:
276
  raise AdapterError(
277
  "labels_unavailable",
@@ -280,6 +226,58 @@ class SGLangBackend:
280
  self._label_strings, self._label_ids = tuple(zip(*valid))
281
  self._ready = True
282
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  async def close(self) -> None:
284
  if self._owns_client:
285
  await self._client.aclose()
 
7
 
8
  import asyncio
9
  import math
 
10
  import uuid
11
  from typing import Any
12
 
13
  import httpx
14
 
15
  from .backend import AdapterError, ScoringResult
16
+ from .labels import (
17
+ DEFAULT_LABEL_SCHEME,
18
+ MAX_LABELS,
19
+ candidate_tiers,
20
+ validate_label_scheme,
21
+ )
 
 
 
 
 
 
 
 
22
 
23
 
24
  _IMAGE_MODEL_TYPES = {
 
67
  native_tokenizer: Any | None = None,
68
  prompt_wording: str = "served",
69
  native_system_prompt: str | None = None,
70
+ label_scheme: str = DEFAULT_LABEL_SCHEME,
71
  ):
72
  if not math.isfinite(timeout) or timeout <= 0 or max_connections < 1:
73
  raise ValueError("timeout and max_connections must be positive")
74
  if prompt_wording not in ("served", "native"):
75
  raise ValueError('prompt_wording must be "served" or "native"')
76
+ # Which candidate order answer labels come from (jev_adapter.labels).
77
+ self.label_scheme = validate_label_scheme(label_scheme)
78
  url = httpx.URL(base_url)
79
  if (
80
  url.scheme not in ("http", "https")
 
211
  if isinstance(arch, str)
212
  )
213
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  valid: list[tuple[str, int]] = []
215
  seen: set[int] = set()
216
+ # A later candidate group is tokenized only while labels are short.
217
+ for candidates in candidate_tiers(self.label_scheme):
218
+ if len(valid) == MAX_LABELS:
219
+ break
220
+ await self._select_tier(list(candidates), valid, seen)
 
221
  if len(valid) < 2:
222
  raise AdapterError(
223
  "labels_unavailable",
 
226
  self._label_strings, self._label_ids = tuple(zip(*valid))
227
  self._ready = True
228
 
229
+ async def _select_tier(
230
+ self, candidates: list[str], valid: list[tuple[str, int]], seen: set[int]
231
+ ) -> None:
232
+ """Append this group's reversible single-token labels, in order."""
233
+ tokenized = await self._request(
234
+ "POST",
235
+ "/v1/tokenize",
236
+ {
237
+ "model": self.model,
238
+ "prompt": candidates,
239
+ "add_special_tokens": False,
240
+ },
241
+ )
242
+ batches = tokenized.get("tokens")
243
+ if (
244
+ not isinstance(batches, list)
245
+ or len(batches) != len(candidates)
246
+ or not all(_token_list(ids) for ids in batches)
247
+ ):
248
+ raise _bad_response(
249
+ "The inference engine returned malformed label tokenization."
250
+ )
251
+ singles = [
252
+ (label, ids[0]) for label, ids in zip(candidates, batches) if len(ids) == 1
253
+ ]
254
+ if not singles:
255
+ return
256
+ decoded = await self._request(
257
+ "POST",
258
+ "/v1/detokenize",
259
+ {
260
+ "model": self.model,
261
+ "tokens": [[token] for _, token in singles],
262
+ "skip_special_tokens": True,
263
+ },
264
+ )
265
+ texts = decoded.get("text")
266
+ if (
267
+ not isinstance(texts, list)
268
+ or len(texts) != len(singles)
269
+ or not all(isinstance(item, str) for item in texts)
270
+ ):
271
+ raise _bad_response(
272
+ "The inference engine returned malformed label detokenization."
273
+ )
274
+ for (label, token), decoded_label in zip(singles, texts):
275
+ if label == decoded_label and token not in seen:
276
+ valid.append((label, token))
277
+ seen.add(token)
278
+ if len(valid) == MAX_LABELS:
279
+ return
280
+
281
  async def close(self) -> None:
282
  if self._owns_client:
283
  await self._client.aclose()
server/tests/conftest.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+
4
+ @pytest.fixture(autouse=True)
5
+ def _no_label_scheme_env(monkeypatch):
6
+ # The label scheme defaults from JEV_LABEL_SCHEME; tests set it explicitly.
7
+ monkeypatch.delenv("JEV_LABEL_SCHEME", raising=False)
server/tests/data/ministral3-label-probe.json ADDED
@@ -0,0 +1,1183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema": "jev-tokenizer-probe-v1",
3
+ "note": "Raw tokenizer behaviour on the one- and two-letter uppercase label candidates, recorded with AutoTokenizer at the pinned revision and cross-checked against tekken.json (mistral_common) and the release tokenizer.json. encode = encode(candidate, add_special_tokens=False); decode = decode([id], skip_special_tokens=True) for every single-token id.",
4
+ "tokenizer": {"id": "mistralai/Ministral-3-8B-Instruct-2512-BF16", "revision": "f6fae9795746f63c9be8344932f01275f3c63734", "class": "MistralCommonBackend", "tekken_json_sha256": "600bb27946565481ecf51ba8aee252e49b9a68507866080ac9c30185bb312843", "tokenizer_config_json_sha256": "f59f7294e4f26383d0ea93840fe21cf197784be0842a8301a0343e8c34ed0d6d", "release_tokenizer_json_sha256": "d5f6046775b112f0e2d456ee9dba450684ab964fe5c4e231599bdc6773028135"},
5
+ "special_id_ranges": [[0, 999]],
6
+ "expected_upper": {
7
+ "source": "upper-labels-ministral3.json",
8
+ "source_sha256": "27119a52ee9a0417809c3a50b5b07e77aa723d4a63266d1065ba8ca82589d512",
9
+ "labels": ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB", "AC", "AD", "AE", "AF", "AG", "AH", "AI", "AK", "AL", "AM", "AN", "AO", "AP", "AR", "AS", "AT", "AU", "AV", "AX", "AY", "AZ", "BA", "BB", "BC", "BD", "BE", "BF", "BG", "BI", "BJ", "BL", "BM", "BN", "BO", "BP", "BR", "BS", "BT", "BU", "BV", "BW", "BY", "CA", "CB", "CC", "CD", "CE", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CP", "CR", "CS", "CT", "CU", "CV", "CX", "CY", "DA", "DB", "DC", "DD", "DE", "DF", "DG", "DH", "DI", "DJ", "DK", "DL", "DM", "DN", "DO", "DP", "DR", "DS", "DT", "DU", "DX", "EA", "EB", "EC", "ED", "EE", "EF", "EG", "EK", "EL", "EM", "EN", "EO", "EP", "EQ", "ER", "ES", "ET", "EU", "EV", "EW", "EX", "FA", "FB", "FC", "FD", "FE", "FF", "FG", "FI", "FK", "FL", "FM", "FN", "FO", "FP", "FR", "FS", "FT", "FW", "FX", "GA", "GB", "GC", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GO", "GP", "GR", "GS", "GT", "GV", "GW", "HA", "HB", "HC", "HD", "HE", "HF", "HG", "HH", "HI", "HK", "HL", "HM", "HO", "HP", "HR", "HS", "HT", "HV", "IA", "IB", "IC", "ID", "IE", "IF", "IG", "II", "IK", "IL", "IM", "IN", "IO", "IP", "IR", "IS", "IT", "IU", "IV", "IX", "IZ", "JA", "JB", "JK", "JO", "JP", "JR", "JS", "KA", "KB", "KC", "KE", "KG", "KM", "KN", "KO", "KR", "KS", "KT", "LA", "LB", "LC", "LD", "LE", "LF", "LG", "LI", "LL", "LM", "LO", "LP", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MB", "MC", "MD", "ME", "MF", "MG", "MH"],
10
+ "token_ids": [1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 14229, 8062, 5193, 5516, 32484, 12763, 6970, 91939, 33613, 26039, 4286, 6758, 5010, 106550, 6292, 3905, 6003, 3313, 117778, 29970, 13339, 25698, 75317, 10574, 28235, 6535, 18704, 34817, 71042, 87478, 17782, 119492, 30631, 20195, 90805, 18412, 22133, 35846, 7814, 54206, 115497, 130196, 114957, 82317, 9467, 43379, 7619, 8584, 9158, 26910, 39176, 7247, 15466, 17131, 8534, 41183, 47271, 9335, 13943, 12540, 13754, 4061, 97829, 34456, 114741, 130282, 10878, 12223, 22561, 27789, 5088, 28562, 92842, 47909, 29951, 52972, 49329, 42559, 28081, 57310, 24151, 16108, 13705, 14249, 46565, 33674, 52885, 26759, 34112, 13275, 7051, 16748, 35971, 17238, 82787, 11213, 15187, 4456, 92580, 20573, 44943, 2794, 5548, 4321, 78963, 66703, 49942, 10164, 6507, 62311, 10858, 61930, 44675, 7762, 63511, 31638, 66480, 15869, 45746, 88826, 15740, 31994, 53744, 21313, 17310, 70351, 75215, 19454, 12737, 16497, 77858, 19527, 10624, 67199, 96633, 74536, 17899, 28533, 123165, 28649, 31714, 32874, 27735, 35985, 109488, 102348, 23303, 68291, 24093, 26205, 7626, 41999, 122632, 79438, 56611, 64447, 46862, 103147, 74873, 13719, 24498, 30316, 8395, 126227, 20501, 16433, 6048, 3084, 20851, 11098, 4351, 3022, 65015, 9387, 10667, 3174, 11364, 9918, 9474, 6673, 4778, 124666, 6217, 10445, 15152, 71794, 124356, 92274, 128992, 72267, 69496, 32452, 39711, 60305, 120387, 12623, 99414, 128745, 49744, 44338, 128762, 110718, 81092, 10265, 72458, 13515, 20008, 3561, 55938, 106747, 34732, 23947, 45623, 7618, 28547, 23356, 18369, 71905, 73275, 51489, 38180, 8900, 21291, 16126, 14534, 5180, 96790, 64937, 91805]
11
+ },
12
+ "encode": {
13
+ "A": [1065],
14
+ "B": [1066],
15
+ "C": [1067],
16
+ "D": [1068],
17
+ "E": [1069],
18
+ "F": [1070],
19
+ "G": [1071],
20
+ "H": [1072],
21
+ "I": [1073],
22
+ "J": [1074],
23
+ "K": [1075],
24
+ "L": [1076],
25
+ "M": [1077],
26
+ "N": [1078],
27
+ "O": [1079],
28
+ "P": [1080],
29
+ "Q": [1081],
30
+ "R": [1082],
31
+ "S": [1083],
32
+ "T": [1084],
33
+ "U": [1085],
34
+ "V": [1086],
35
+ "W": [1087],
36
+ "X": [1088],
37
+ "Y": [1089],
38
+ "Z": [1090],
39
+ "AA": [14229],
40
+ "AB": [8062],
41
+ "AC": [5193],
42
+ "AD": [5516],
43
+ "AE": [32484],
44
+ "AF": [12763],
45
+ "AG": [6970],
46
+ "AH": [91939],
47
+ "AI": [33613],
48
+ "AJ": [1065, 1074],
49
+ "AK": [26039],
50
+ "AL": [4286],
51
+ "AM": [6758],
52
+ "AN": [5010],
53
+ "AO": [106550],
54
+ "AP": [6292],
55
+ "AQ": [1065, 1081],
56
+ "AR": [3905],
57
+ "AS": [6003],
58
+ "AT": [3313],
59
+ "AU": [117778],
60
+ "AV": [29970],
61
+ "AW": [1065, 1087],
62
+ "AX": [13339],
63
+ "AY": [25698],
64
+ "AZ": [75317],
65
+ "BA": [10574],
66
+ "BB": [28235],
67
+ "BC": [6535],
68
+ "BD": [18704],
69
+ "BE": [34817],
70
+ "BF": [71042],
71
+ "BG": [87478],
72
+ "BH": [1066, 1072],
73
+ "BI": [17782],
74
+ "BJ": [119492],
75
+ "BK": [1066, 1075],
76
+ "BL": [30631],
77
+ "BM": [20195],
78
+ "BN": [90805],
79
+ "BO": [18412],
80
+ "BP": [22133],
81
+ "BQ": [1066, 1081],
82
+ "BR": [35846],
83
+ "BS": [7814],
84
+ "BT": [54206],
85
+ "BU": [115497],
86
+ "BV": [130196],
87
+ "BW": [114957],
88
+ "BX": [1066, 1088],
89
+ "BY": [82317],
90
+ "BZ": [1066, 1090],
91
+ "CA": [9467],
92
+ "CB": [43379],
93
+ "CC": [7619],
94
+ "CD": [8584],
95
+ "CE": [9158],
96
+ "CF": [26910],
97
+ "CG": [39176],
98
+ "CH": [7247],
99
+ "CI": [15466],
100
+ "CJ": [1067, 1074],
101
+ "CK": [17131],
102
+ "CL": [8534],
103
+ "CM": [41183],
104
+ "CN": [47271],
105
+ "CO": [9335],
106
+ "CP": [13943],
107
+ "CQ": [1067, 1081],
108
+ "CR": [12540],
109
+ "CS": [13754],
110
+ "CT": [4061],
111
+ "CU": [97829],
112
+ "CV": [34456],
113
+ "CW": [1067, 1087],
114
+ "CX": [114741],
115
+ "CY": [130282],
116
+ "CZ": [1067, 1090],
117
+ "DA": [10878],
118
+ "DB": [12223],
119
+ "DC": [22561],
120
+ "DD": [27789],
121
+ "DE": [5088],
122
+ "DF": [28562],
123
+ "DG": [92842],
124
+ "DH": [47909],
125
+ "DI": [29951],
126
+ "DJ": [52972],
127
+ "DK": [49329],
128
+ "DL": [42559],
129
+ "DM": [28081],
130
+ "DN": [57310],
131
+ "DO": [24151],
132
+ "DP": [16108],
133
+ "DQ": [1068, 1081],
134
+ "DR": [13705],
135
+ "DS": [14249],
136
+ "DT": [46565],
137
+ "DU": [33674],
138
+ "DV": [1068, 1086],
139
+ "DW": [1068, 1087],
140
+ "DX": [52885],
141
+ "DY": [1068, 1089],
142
+ "DZ": [1068, 1090],
143
+ "EA": [26759],
144
+ "EB": [34112],
145
+ "EC": [13275],
146
+ "ED": [7051],
147
+ "EE": [16748],
148
+ "EF": [35971],
149
+ "EG": [17238],
150
+ "EH": [1069, 1072],
151
+ "EI": [1069, 1073],
152
+ "EJ": [1069, 1074],
153
+ "EK": [82787],
154
+ "EL": [11213],
155
+ "EM": [15187],
156
+ "EN": [4456],
157
+ "EO": [92580],
158
+ "EP": [20573],
159
+ "EQ": [44943],
160
+ "ER": [2794],
161
+ "ES": [5548],
162
+ "ET": [4321],
163
+ "EU": [78963],
164
+ "EV": [66703],
165
+ "EW": [49942],
166
+ "EX": [10164],
167
+ "EY": [1069, 1089],
168
+ "EZ": [1069, 1090],
169
+ "FA": [6507],
170
+ "FB": [62311],
171
+ "FC": [10858],
172
+ "FD": [61930],
173
+ "FE": [44675],
174
+ "FF": [7762],
175
+ "FG": [63511],
176
+ "FH": [1070, 1072],
177
+ "FI": [31638],
178
+ "FJ": [1070, 1074],
179
+ "FK": [66480],
180
+ "FL": [15869],
181
+ "FM": [45746],
182
+ "FN": [88826],
183
+ "FO": [15740],
184
+ "FP": [31994],
185
+ "FQ": [1070, 1081],
186
+ "FR": [53744],
187
+ "FS": [21313],
188
+ "FT": [17310],
189
+ "FU": [1070, 1085],
190
+ "FV": [1070, 1086],
191
+ "FW": [70351],
192
+ "FX": [75215],
193
+ "FY": [1070, 1089],
194
+ "FZ": [1070, 1090],
195
+ "GA": [19454],
196
+ "GB": [12737],
197
+ "GC": [16497],
198
+ "GD": [77858],
199
+ "GE": [19527],
200
+ "GF": [10624],
201
+ "GG": [67199],
202
+ "GH": [96633],
203
+ "GI": [74536],
204
+ "GJ": [1071, 1074],
205
+ "GK": [1071, 1075],
206
+ "GL": [17899],
207
+ "GM": [28533],
208
+ "GN": [123165],
209
+ "GO": [28649],
210
+ "GP": [31714],
211
+ "GQ": [1071, 1081],
212
+ "GR": [32874],
213
+ "GS": [27735],
214
+ "GT": [35985],
215
+ "GU": [1071, 1085],
216
+ "GV": [109488],
217
+ "GW": [102348],
218
+ "GX": [1071, 1088],
219
+ "GY": [1071, 1089],
220
+ "GZ": [1071, 1090],
221
+ "HA": [23303],
222
+ "HB": [68291],
223
+ "HC": [24093],
224
+ "HD": [26205],
225
+ "HE": [7626],
226
+ "HF": [41999],
227
+ "HG": [122632],
228
+ "HH": [79438],
229
+ "HI": [56611],
230
+ "HJ": [1072, 1074],
231
+ "HK": [64447],
232
+ "HL": [46862],
233
+ "HM": [103147],
234
+ "HN": [1072, 1078],
235
+ "HO": [74873],
236
+ "HP": [13719],
237
+ "HQ": [1072, 1081],
238
+ "HR": [24498],
239
+ "HS": [30316],
240
+ "HT": [8395],
241
+ "HU": [1072, 1085],
242
+ "HV": [126227],
243
+ "HW": [1072, 1087],
244
+ "HX": [1072, 1088],
245
+ "HY": [1072, 1089],
246
+ "HZ": [1072, 1090],
247
+ "IA": [20501],
248
+ "IB": [16433],
249
+ "IC": [6048],
250
+ "ID": [3084],
251
+ "IE": [20851],
252
+ "IF": [11098],
253
+ "IG": [4351],
254
+ "IH": [1073, 1072],
255
+ "II": [3022],
256
+ "IJ": [1073, 1074],
257
+ "IK": [65015],
258
+ "IL": [9387],
259
+ "IM": [10667],
260
+ "IN": [3174],
261
+ "IO": [11364],
262
+ "IP": [9918],
263
+ "IQ": [1073, 1081],
264
+ "IR": [9474],
265
+ "IS": [6673],
266
+ "IT": [4778],
267
+ "IU": [124666],
268
+ "IV": [6217],
269
+ "IW": [1073, 1087],
270
+ "IX": [10445],
271
+ "IY": [1073, 1089],
272
+ "IZ": [15152],
273
+ "JA": [71794],
274
+ "JB": [124356],
275
+ "JC": [1074, 1067],
276
+ "JD": [1074, 1068],
277
+ "JE": [1074, 1069],
278
+ "JF": [1074, 1070],
279
+ "JG": [1074, 1071],
280
+ "JH": [1074, 1072],
281
+ "JI": [1074, 1073],
282
+ "JJ": [1074, 1074],
283
+ "JK": [92274],
284
+ "JL": [1074, 1076],
285
+ "JM": [1074, 1077],
286
+ "JN": [1074, 1078],
287
+ "JO": [128992],
288
+ "JP": [72267],
289
+ "JQ": [1074, 1081],
290
+ "JR": [69496],
291
+ "JS": [32452],
292
+ "JT": [1074, 1084],
293
+ "JU": [1074, 1085],
294
+ "JV": [1074, 1086],
295
+ "JW": [1074, 1087],
296
+ "JX": [1074, 1088],
297
+ "JY": [1074, 1089],
298
+ "JZ": [1074, 1090],
299
+ "KA": [39711],
300
+ "KB": [60305],
301
+ "KC": [120387],
302
+ "KD": [1075, 1068],
303
+ "KE": [12623],
304
+ "KF": [1075, 1070],
305
+ "KG": [99414],
306
+ "KH": [1075, 1072],
307
+ "KI": [1075, 1073],
308
+ "KJ": [1075, 1074],
309
+ "KK": [1075, 1075],
310
+ "KL": [1075, 1076],
311
+ "KM": [128745],
312
+ "KN": [49744],
313
+ "KO": [44338],
314
+ "KP": [1075, 1080],
315
+ "KQ": [1075, 1081],
316
+ "KR": [128762],
317
+ "KS": [110718],
318
+ "KT": [81092],
319
+ "KU": [1075, 1085],
320
+ "KV": [1075, 1086],
321
+ "KW": [1075, 1087],
322
+ "KX": [1075, 1088],
323
+ "KY": [1075, 1089],
324
+ "KZ": [1075, 1090],
325
+ "LA": [10265],
326
+ "LB": [72458],
327
+ "LC": [13515],
328
+ "LD": [20008],
329
+ "LE": [3561],
330
+ "LF": [55938],
331
+ "LG": [106747],
332
+ "LH": [1076, 1072],
333
+ "LI": [34732],
334
+ "LJ": [1076, 1074],
335
+ "LK": [1076, 1075],
336
+ "LL": [23947],
337
+ "LM": [45623],
338
+ "LN": [1076, 1078],
339
+ "LO": [7618],
340
+ "LP": [28547],
341
+ "LQ": [1076, 1081],
342
+ "LR": [23356],
343
+ "LS": [18369],
344
+ "LT": [71905],
345
+ "LU": [73275],
346
+ "LV": [51489],
347
+ "LW": [1076, 1087],
348
+ "LX": [1076, 1088],
349
+ "LY": [38180],
350
+ "LZ": [1076, 1090],
351
+ "MA": [8900],
352
+ "MB": [21291],
353
+ "MC": [16126],
354
+ "MD": [14534],
355
+ "ME": [5180],
356
+ "MF": [96790],
357
+ "MG": [64937],
358
+ "MH": [91805],
359
+ "MI": [13460],
360
+ "MJ": [1077, 1074],
361
+ "MK": [67375],
362
+ "ML": [6243],
363
+ "MM": [20451],
364
+ "MN": [113464],
365
+ "MO": [28497],
366
+ "MP": [7690],
367
+ "MQ": [94031],
368
+ "MR": [15755],
369
+ "MS": [7985],
370
+ "MT": [15901],
371
+ "MU": [1077, 1085],
372
+ "MV": [54258],
373
+ "MW": [93272],
374
+ "MX": [115032],
375
+ "MY": [61997],
376
+ "MZ": [1077, 1090],
377
+ "NA": [3251],
378
+ "NB": [46713],
379
+ "NC": [27822],
380
+ "ND": [6999],
381
+ "NE": [29156],
382
+ "NF": [17766],
383
+ "NG": [16705],
384
+ "NH": [19644],
385
+ "NI": [42859],
386
+ "NJ": [1078, 1074],
387
+ "NK": [41360],
388
+ "NL": [70818],
389
+ "NM": [99056],
390
+ "NN": [35756],
391
+ "NO": [16071],
392
+ "NP": [26640],
393
+ "NQ": [1078, 1081],
394
+ "NR": [36995],
395
+ "NS": [8772],
396
+ "NT": [15913],
397
+ "NU": [44113],
398
+ "NV": [63081],
399
+ "NW": [1078, 1087],
400
+ "NX": [1078, 1088],
401
+ "NY": [77192],
402
+ "NZ": [88653],
403
+ "OA": [89987],
404
+ "OB": [53045],
405
+ "OC": [11815],
406
+ "OD": [7460],
407
+ "OE": [55184],
408
+ "OF": [37979],
409
+ "OG": [48368],
410
+ "OH": [16724],
411
+ "OI": [1079, 1073],
412
+ "OJ": [1079, 1074],
413
+ "OK": [13257],
414
+ "OL": [8568],
415
+ "OM": [11135],
416
+ "ON": [2918],
417
+ "OO": [82414],
418
+ "OP": [14748],
419
+ "OQ": [1079, 1081],
420
+ "OR": [3749],
421
+ "OS": [6964],
422
+ "OT": [6918],
423
+ "OU": [32913],
424
+ "OV": [108230],
425
+ "OW": [12381],
426
+ "OX": [60261],
427
+ "OY": [1079, 1089],
428
+ "OZ": [1079, 1090],
429
+ "PA": [12118],
430
+ "PB": [76628],
431
+ "PC": [9793],
432
+ "PD": [9315],
433
+ "PE": [7134],
434
+ "PF": [74287],
435
+ "PG": [54406],
436
+ "PH": [45587],
437
+ "PI": [5119],
438
+ "PJ": [1080, 1074],
439
+ "PK": [47053],
440
+ "PL": [24520],
441
+ "PM": [25285],
442
+ "PN": [28666],
443
+ "PO": [7836],
444
+ "PP": [11388],
445
+ "PQ": [1080, 1081],
446
+ "PR": [17554],
447
+ "PS": [8148],
448
+ "PT": [11130],
449
+ "PU": [81208],
450
+ "PV": [61159],
451
+ "PW": [109996],
452
+ "PX": [1080, 1088],
453
+ "PY": [45242],
454
+ "PZ": [1080, 1090],
455
+ "QA": [1081, 1065],
456
+ "QB": [1081, 1066],
457
+ "QC": [1081, 1067],
458
+ "QD": [1081, 1068],
459
+ "QE": [1081, 1069],
460
+ "QF": [1081, 1070],
461
+ "QG": [1081, 1071],
462
+ "QH": [1081, 1072],
463
+ "QI": [1081, 1073],
464
+ "QJ": [1081, 1074],
465
+ "QK": [1081, 1075],
466
+ "QL": [7972],
467
+ "QM": [1081, 1077],
468
+ "QN": [1081, 1078],
469
+ "QO": [1081, 1079],
470
+ "QP": [1081, 1080],
471
+ "QQ": [1081, 1081],
472
+ "QR": [55582],
473
+ "QS": [1081, 1083],
474
+ "QT": [1081, 1084],
475
+ "QU": [27289],
476
+ "QV": [1081, 1086],
477
+ "QW": [1081, 1087],
478
+ "QX": [1081, 1088],
479
+ "QY": [1081, 1089],
480
+ "QZ": [1081, 1090],
481
+ "RA": [9295],
482
+ "RB": [84165],
483
+ "RC": [22805],
484
+ "RD": [50289],
485
+ "RE": [3609],
486
+ "RF": [43456],
487
+ "RG": [94542],
488
+ "RH": [82104],
489
+ "RI": [8707],
490
+ "RJ": [1082, 1074],
491
+ "RK": [105157],
492
+ "RL": [10397],
493
+ "RM": [72554],
494
+ "RN": [109334],
495
+ "RO": [4755],
496
+ "RP": [40773],
497
+ "RQ": [1082, 1081],
498
+ "RR": [60853],
499
+ "RS": [19693],
500
+ "RT": [19891],
501
+ "RU": [1082, 1085],
502
+ "RV": [101080],
503
+ "RW": [1082, 1087],
504
+ "RX": [130864],
505
+ "RY": [58675],
506
+ "RZ": [1082, 1090],
507
+ "SA": [11941],
508
+ "SB": [5038],
509
+ "SC": [7408],
510
+ "SD": [17173],
511
+ "SE": [3932],
512
+ "SF": [28036],
513
+ "SG": [29834],
514
+ "SH": [18731],
515
+ "SI": [27239],
516
+ "SJ": [1083, 1074],
517
+ "SK": [51753],
518
+ "SL": [27893],
519
+ "SM": [23162],
520
+ "SN": [54061],
521
+ "SO": [19124],
522
+ "SP": [8504],
523
+ "SQ": [1083, 1081],
524
+ "SR": [21967],
525
+ "SS": [4290],
526
+ "ST": [3074],
527
+ "SU": [50885],
528
+ "SV": [24592],
529
+ "SW": [74938],
530
+ "SX": [1083, 1088],
531
+ "SY": [101289],
532
+ "SZ": [1083, 1090],
533
+ "TA": [12887],
534
+ "TB": [64833],
535
+ "TC": [10504],
536
+ "TD": [30975],
537
+ "TE": [9265],
538
+ "TF": [34083],
539
+ "TG": [118593],
540
+ "TH": [12001],
541
+ "TI": [100838],
542
+ "TJ": [1084, 1074],
543
+ "TK": [105765],
544
+ "TL": [35570],
545
+ "TM": [40139],
546
+ "TN": [109975],
547
+ "TO": [9197],
548
+ "TP": [8426],
549
+ "TQ": [1084, 1081],
550
+ "TR": [13404],
551
+ "TS": [9983],
552
+ "TT": [26025],
553
+ "TU": [1084, 1085],
554
+ "TV": [11488],
555
+ "TW": [1084, 1087],
556
+ "TX": [100203],
557
+ "TY": [54462],
558
+ "TZ": [1084, 1090],
559
+ "UA": [69940],
560
+ "UB": [17982],
561
+ "UC": [24859],
562
+ "UD": [23855],
563
+ "UE": [8489],
564
+ "UF": [52359],
565
+ "UG": [17869],
566
+ "UH": [1085, 1072],
567
+ "UI": [8442],
568
+ "UJ": [1085, 1074],
569
+ "UK": [35960],
570
+ "UL": [4614],
571
+ "UM": [11458],
572
+ "UN": [8929],
573
+ "UO": [1085, 1079],
574
+ "UP": [15401],
575
+ "UQ": [1085, 1081],
576
+ "UR": [5553],
577
+ "US": [5507],
578
+ "UT": [5489],
579
+ "UU": [1085, 1085],
580
+ "UV": [34483],
581
+ "UW": [1085, 1087],
582
+ "UX": [82838],
583
+ "UY": [1085, 1089],
584
+ "UZ": [1085, 1090],
585
+ "VA": [15725],
586
+ "VB": [103461],
587
+ "VC": [20500],
588
+ "VD": [10566],
589
+ "VE": [16578],
590
+ "VF": [28198],
591
+ "VG": [122406],
592
+ "VH": [1086, 1072],
593
+ "VI": [31416],
594
+ "VJ": [1086, 1074],
595
+ "VK": [1086, 1075],
596
+ "VL": [1086, 1076],
597
+ "VM": [30569],
598
+ "VN": [98832],
599
+ "VO": [25313],
600
+ "VP": [22580],
601
+ "VQ": [1086, 1081],
602
+ "VR": [56994],
603
+ "VS": [73553],
604
+ "VT": [96527],
605
+ "VU": [1086, 1085],
606
+ "VV": [1086, 1086],
607
+ "VW": [1086, 1087],
608
+ "VX": [1086, 1088],
609
+ "VY": [1086, 1089],
610
+ "VZ": [1086, 1090],
611
+ "WA": [32111],
612
+ "WB": [126602],
613
+ "WC": [61778],
614
+ "WD": [51577],
615
+ "WE": [21147],
616
+ "WF": [81310],
617
+ "WG": [1087, 1071],
618
+ "WH": [25841],
619
+ "WI": [107939],
620
+ "WJ": [1087, 1074],
621
+ "WK": [79469],
622
+ "WL": [130049],
623
+ "WM": [52532],
624
+ "WN": [1087, 1078],
625
+ "WO": [69643],
626
+ "WP": [64557],
627
+ "WQ": [1087, 1081],
628
+ "WR": [44407],
629
+ "WS": [20266],
630
+ "WT": [38315],
631
+ "WU": [1087, 1085],
632
+ "WV": [1087, 1086],
633
+ "WW": [118278],
634
+ "WX": [1087, 1088],
635
+ "WY": [1087, 1089],
636
+ "WZ": [1087, 1090],
637
+ "XA": [1088, 1065],
638
+ "XB": [1088, 1066],
639
+ "XC": [1088, 1067],
640
+ "XD": [1088, 1068],
641
+ "XE": [1088, 1069],
642
+ "XF": [1088, 1070],
643
+ "XG": [1088, 1071],
644
+ "XH": [1088, 1072],
645
+ "XI": [107321],
646
+ "XJ": [1088, 1074],
647
+ "XK": [1088, 1075],
648
+ "XL": [113479],
649
+ "XM": [1088, 1077],
650
+ "XN": [1088, 1078],
651
+ "XO": [1088, 1079],
652
+ "XP": [1088, 1080],
653
+ "XQ": [1088, 1081],
654
+ "XR": [1088, 1082],
655
+ "XS": [1088, 1083],
656
+ "XT": [32225],
657
+ "XU": [1088, 1085],
658
+ "XV": [1088, 1086],
659
+ "XW": [1088, 1087],
660
+ "XX": [18860],
661
+ "XY": [50706],
662
+ "XZ": [1088, 1090],
663
+ "YA": [114400],
664
+ "YB": [1089, 1066],
665
+ "YC": [1089, 1067],
666
+ "YD": [1089, 1068],
667
+ "YE": [1089, 1069],
668
+ "YF": [1089, 1070],
669
+ "YG": [1089, 1071],
670
+ "YH": [1089, 1072],
671
+ "YI": [1089, 1073],
672
+ "YJ": [1089, 1074],
673
+ "YK": [1089, 1075],
674
+ "YL": [1089, 1076],
675
+ "YM": [1089, 1077],
676
+ "YN": [1089, 1078],
677
+ "YO": [45370],
678
+ "YP": [31130],
679
+ "YQ": [1089, 1081],
680
+ "YR": [1089, 1082],
681
+ "YS": [61300],
682
+ "YT": [1089, 1084],
683
+ "YU": [1089, 1085],
684
+ "YV": [1089, 1086],
685
+ "YW": [1089, 1087],
686
+ "YX": [1089, 1088],
687
+ "YY": [28131],
688
+ "YZ": [1089, 1090],
689
+ "ZA": [124622],
690
+ "ZB": [1090, 1066],
691
+ "ZC": [1090, 1067],
692
+ "ZD": [1090, 1068],
693
+ "ZE": [90133],
694
+ "ZF": [1090, 1070],
695
+ "ZG": [1090, 1071],
696
+ "ZH": [1090, 1072],
697
+ "ZI": [1090, 1073],
698
+ "ZJ": [1090, 1074],
699
+ "ZK": [1090, 1075],
700
+ "ZL": [1090, 1076],
701
+ "ZM": [1090, 1077],
702
+ "ZN": [1090, 1078],
703
+ "ZO": [1090, 1079],
704
+ "ZP": [1090, 1080],
705
+ "ZQ": [1090, 1081],
706
+ "ZR": [1090, 1082],
707
+ "ZS": [1090, 1083],
708
+ "ZT": [1090, 1084],
709
+ "ZU": [1090, 1085],
710
+ "ZV": [1090, 1086],
711
+ "ZW": [1090, 1087],
712
+ "ZX": [1090, 1088],
713
+ "ZY": [1090, 1089],
714
+ "ZZ": [105156]
715
+ },
716
+ "decode": {
717
+ "1065": "A",
718
+ "1066": "B",
719
+ "1067": "C",
720
+ "1068": "D",
721
+ "1069": "E",
722
+ "1070": "F",
723
+ "1071": "G",
724
+ "1072": "H",
725
+ "1073": "I",
726
+ "1074": "J",
727
+ "1075": "K",
728
+ "1076": "L",
729
+ "1077": "M",
730
+ "1078": "N",
731
+ "1079": "O",
732
+ "1080": "P",
733
+ "1081": "Q",
734
+ "1082": "R",
735
+ "1083": "S",
736
+ "1084": "T",
737
+ "1085": "U",
738
+ "1086": "V",
739
+ "1087": "W",
740
+ "1088": "X",
741
+ "1089": "Y",
742
+ "1090": "Z",
743
+ "2794": "ER",
744
+ "2918": "ON",
745
+ "3022": "II",
746
+ "3074": "ST",
747
+ "3084": "ID",
748
+ "3174": "IN",
749
+ "3251": "NA",
750
+ "3313": "AT",
751
+ "3561": "LE",
752
+ "3609": "RE",
753
+ "3749": "OR",
754
+ "3905": "AR",
755
+ "3932": "SE",
756
+ "4061": "CT",
757
+ "4286": "AL",
758
+ "4290": "SS",
759
+ "4321": "ET",
760
+ "4351": "IG",
761
+ "4456": "EN",
762
+ "4614": "UL",
763
+ "4755": "RO",
764
+ "4778": "IT",
765
+ "5010": "AN",
766
+ "5038": "SB",
767
+ "5088": "DE",
768
+ "5119": "PI",
769
+ "5180": "ME",
770
+ "5193": "AC",
771
+ "5489": "UT",
772
+ "5507": "US",
773
+ "5516": "AD",
774
+ "5548": "ES",
775
+ "5553": "UR",
776
+ "6003": "AS",
777
+ "6048": "IC",
778
+ "6217": "IV",
779
+ "6243": "ML",
780
+ "6292": "AP",
781
+ "6507": "FA",
782
+ "6535": "BC",
783
+ "6673": "IS",
784
+ "6758": "AM",
785
+ "6918": "OT",
786
+ "6964": "OS",
787
+ "6970": "AG",
788
+ "6999": "ND",
789
+ "7051": "ED",
790
+ "7134": "PE",
791
+ "7247": "CH",
792
+ "7408": "SC",
793
+ "7460": "OD",
794
+ "7618": "LO",
795
+ "7619": "CC",
796
+ "7626": "HE",
797
+ "7690": "MP",
798
+ "7762": "FF",
799
+ "7814": "BS",
800
+ "7836": "PO",
801
+ "7972": "QL",
802
+ "7985": "MS",
803
+ "8062": "AB",
804
+ "8148": "PS",
805
+ "8395": "HT",
806
+ "8426": "TP",
807
+ "8442": "UI",
808
+ "8489": "UE",
809
+ "8504": "SP",
810
+ "8534": "CL",
811
+ "8568": "OL",
812
+ "8584": "CD",
813
+ "8707": "RI",
814
+ "8772": "NS",
815
+ "8900": "MA",
816
+ "8929": "UN",
817
+ "9158": "CE",
818
+ "9197": "TO",
819
+ "9265": "TE",
820
+ "9295": "RA",
821
+ "9315": "PD",
822
+ "9335": "CO",
823
+ "9387": "IL",
824
+ "9467": "CA",
825
+ "9474": "IR",
826
+ "9793": "PC",
827
+ "9918": "IP",
828
+ "9983": "TS",
829
+ "10164": "EX",
830
+ "10265": "LA",
831
+ "10397": "RL",
832
+ "10445": "IX",
833
+ "10504": "TC",
834
+ "10566": "VD",
835
+ "10574": "BA",
836
+ "10624": "GF",
837
+ "10667": "IM",
838
+ "10858": "FC",
839
+ "10878": "DA",
840
+ "11098": "IF",
841
+ "11130": "PT",
842
+ "11135": "OM",
843
+ "11213": "EL",
844
+ "11364": "IO",
845
+ "11388": "PP",
846
+ "11458": "UM",
847
+ "11488": "TV",
848
+ "11815": "OC",
849
+ "11941": "SA",
850
+ "12001": "TH",
851
+ "12118": "PA",
852
+ "12223": "DB",
853
+ "12381": "OW",
854
+ "12540": "CR",
855
+ "12623": "KE",
856
+ "12737": "GB",
857
+ "12763": "AF",
858
+ "12887": "TA",
859
+ "13257": "OK",
860
+ "13275": "EC",
861
+ "13339": "AX",
862
+ "13404": "TR",
863
+ "13460": "MI",
864
+ "13515": "LC",
865
+ "13705": "DR",
866
+ "13719": "HP",
867
+ "13754": "CS",
868
+ "13943": "CP",
869
+ "14229": "AA",
870
+ "14249": "DS",
871
+ "14534": "MD",
872
+ "14748": "OP",
873
+ "15152": "IZ",
874
+ "15187": "EM",
875
+ "15401": "UP",
876
+ "15466": "CI",
877
+ "15725": "VA",
878
+ "15740": "FO",
879
+ "15755": "MR",
880
+ "15869": "FL",
881
+ "15901": "MT",
882
+ "15913": "NT",
883
+ "16071": "NO",
884
+ "16108": "DP",
885
+ "16126": "MC",
886
+ "16433": "IB",
887
+ "16497": "GC",
888
+ "16578": "VE",
889
+ "16705": "NG",
890
+ "16724": "OH",
891
+ "16748": "EE",
892
+ "17131": "CK",
893
+ "17173": "SD",
894
+ "17238": "EG",
895
+ "17310": "FT",
896
+ "17554": "PR",
897
+ "17766": "NF",
898
+ "17782": "BI",
899
+ "17869": "UG",
900
+ "17899": "GL",
901
+ "17982": "UB",
902
+ "18369": "LS",
903
+ "18412": "BO",
904
+ "18704": "BD",
905
+ "18731": "SH",
906
+ "18860": "XX",
907
+ "19124": "SO",
908
+ "19454": "GA",
909
+ "19527": "GE",
910
+ "19644": "NH",
911
+ "19693": "RS",
912
+ "19891": "RT",
913
+ "20008": "LD",
914
+ "20195": "BM",
915
+ "20266": "WS",
916
+ "20451": "MM",
917
+ "20500": "VC",
918
+ "20501": "IA",
919
+ "20573": "EP",
920
+ "20851": "IE",
921
+ "21147": "WE",
922
+ "21291": "MB",
923
+ "21313": "FS",
924
+ "21967": "SR",
925
+ "22133": "BP",
926
+ "22561": "DC",
927
+ "22580": "VP",
928
+ "22805": "RC",
929
+ "23162": "SM",
930
+ "23303": "HA",
931
+ "23356": "LR",
932
+ "23855": "UD",
933
+ "23947": "LL",
934
+ "24093": "HC",
935
+ "24151": "DO",
936
+ "24498": "HR",
937
+ "24520": "PL",
938
+ "24592": "SV",
939
+ "24859": "UC",
940
+ "25285": "PM",
941
+ "25313": "VO",
942
+ "25698": "AY",
943
+ "25841": "WH",
944
+ "26025": "TT",
945
+ "26039": "AK",
946
+ "26205": "HD",
947
+ "26640": "NP",
948
+ "26759": "EA",
949
+ "26910": "CF",
950
+ "27239": "SI",
951
+ "27289": "QU",
952
+ "27735": "GS",
953
+ "27789": "DD",
954
+ "27822": "NC",
955
+ "27893": "SL",
956
+ "28036": "SF",
957
+ "28081": "DM",
958
+ "28131": "YY",
959
+ "28198": "VF",
960
+ "28235": "BB",
961
+ "28497": "MO",
962
+ "28533": "GM",
963
+ "28547": "LP",
964
+ "28562": "DF",
965
+ "28649": "GO",
966
+ "28666": "PN",
967
+ "29156": "NE",
968
+ "29834": "SG",
969
+ "29951": "DI",
970
+ "29970": "AV",
971
+ "30316": "HS",
972
+ "30569": "VM",
973
+ "30631": "BL",
974
+ "30975": "TD",
975
+ "31130": "YP",
976
+ "31416": "VI",
977
+ "31638": "FI",
978
+ "31714": "GP",
979
+ "31994": "FP",
980
+ "32111": "WA",
981
+ "32225": "XT",
982
+ "32452": "JS",
983
+ "32484": "AE",
984
+ "32874": "GR",
985
+ "32913": "OU",
986
+ "33613": "AI",
987
+ "33674": "DU",
988
+ "34083": "TF",
989
+ "34112": "EB",
990
+ "34456": "CV",
991
+ "34483": "UV",
992
+ "34732": "LI",
993
+ "34817": "BE",
994
+ "35570": "TL",
995
+ "35756": "NN",
996
+ "35846": "BR",
997
+ "35960": "UK",
998
+ "35971": "EF",
999
+ "35985": "GT",
1000
+ "36995": "NR",
1001
+ "37979": "OF",
1002
+ "38180": "LY",
1003
+ "38315": "WT",
1004
+ "39176": "CG",
1005
+ "39711": "KA",
1006
+ "40139": "TM",
1007
+ "40773": "RP",
1008
+ "41183": "CM",
1009
+ "41360": "NK",
1010
+ "41999": "HF",
1011
+ "42559": "DL",
1012
+ "42859": "NI",
1013
+ "43379": "CB",
1014
+ "43456": "RF",
1015
+ "44113": "NU",
1016
+ "44338": "KO",
1017
+ "44407": "WR",
1018
+ "44675": "FE",
1019
+ "44943": "EQ",
1020
+ "45242": "PY",
1021
+ "45370": "YO",
1022
+ "45587": "PH",
1023
+ "45623": "LM",
1024
+ "45746": "FM",
1025
+ "46565": "DT",
1026
+ "46713": "NB",
1027
+ "46862": "HL",
1028
+ "47053": "PK",
1029
+ "47271": "CN",
1030
+ "47909": "DH",
1031
+ "48368": "OG",
1032
+ "49329": "DK",
1033
+ "49744": "KN",
1034
+ "49942": "EW",
1035
+ "50289": "RD",
1036
+ "50706": "XY",
1037
+ "50885": "SU",
1038
+ "51489": "LV",
1039
+ "51577": "WD",
1040
+ "51753": "SK",
1041
+ "52359": "UF",
1042
+ "52532": "WM",
1043
+ "52885": "DX",
1044
+ "52972": "DJ",
1045
+ "53045": "OB",
1046
+ "53744": "FR",
1047
+ "54061": "SN",
1048
+ "54206": "BT",
1049
+ "54258": "MV",
1050
+ "54406": "PG",
1051
+ "54462": "TY",
1052
+ "55184": "OE",
1053
+ "55582": "QR",
1054
+ "55938": "LF",
1055
+ "56611": "HI",
1056
+ "56994": "VR",
1057
+ "57310": "DN",
1058
+ "58675": "RY",
1059
+ "60261": "OX",
1060
+ "60305": "KB",
1061
+ "60853": "RR",
1062
+ "61159": "PV",
1063
+ "61300": "YS",
1064
+ "61778": "WC",
1065
+ "61930": "FD",
1066
+ "61997": "MY",
1067
+ "62311": "FB",
1068
+ "63081": "NV",
1069
+ "63511": "FG",
1070
+ "64447": "HK",
1071
+ "64557": "WP",
1072
+ "64833": "TB",
1073
+ "64937": "MG",
1074
+ "65015": "IK",
1075
+ "66480": "FK",
1076
+ "66703": "EV",
1077
+ "67199": "GG",
1078
+ "67375": "MK",
1079
+ "68291": "HB",
1080
+ "69496": "JR",
1081
+ "69643": "WO",
1082
+ "69940": "UA",
1083
+ "70351": "FW",
1084
+ "70818": "NL",
1085
+ "71042": "BF",
1086
+ "71794": "JA",
1087
+ "71905": "LT",
1088
+ "72267": "JP",
1089
+ "72458": "LB",
1090
+ "72554": "RM",
1091
+ "73275": "LU",
1092
+ "73553": "VS",
1093
+ "74287": "PF",
1094
+ "74536": "GI",
1095
+ "74873": "HO",
1096
+ "74938": "SW",
1097
+ "75215": "FX",
1098
+ "75317": "AZ",
1099
+ "76628": "PB",
1100
+ "77192": "NY",
1101
+ "77858": "GD",
1102
+ "78963": "EU",
1103
+ "79438": "HH",
1104
+ "79469": "WK",
1105
+ "81092": "KT",
1106
+ "81208": "PU",
1107
+ "81310": "WF",
1108
+ "82104": "RH",
1109
+ "82317": "BY",
1110
+ "82414": "OO",
1111
+ "82787": "EK",
1112
+ "82838": "UX",
1113
+ "84165": "RB",
1114
+ "87478": "BG",
1115
+ "88653": "NZ",
1116
+ "88826": "FN",
1117
+ "89987": "OA",
1118
+ "90133": "ZE",
1119
+ "90805": "BN",
1120
+ "91805": "MH",
1121
+ "91939": "AH",
1122
+ "92274": "JK",
1123
+ "92580": "EO",
1124
+ "92842": "DG",
1125
+ "93272": "MW",
1126
+ "94031": "MQ",
1127
+ "94542": "RG",
1128
+ "96527": "VT",
1129
+ "96633": "GH",
1130
+ "96790": "MF",
1131
+ "97829": "CU",
1132
+ "98832": "VN",
1133
+ "99056": "NM",
1134
+ "99414": "KG",
1135
+ "100203": "TX",
1136
+ "100838": "TI",
1137
+ "101080": "RV",
1138
+ "101289": "SY",
1139
+ "102348": "GW",
1140
+ "103147": "HM",
1141
+ "103461": "VB",
1142
+ "105156": "ZZ",
1143
+ "105157": "RK",
1144
+ "105765": "TK",
1145
+ "106550": "AO",
1146
+ "106747": "LG",
1147
+ "107321": "XI",
1148
+ "107939": "WI",
1149
+ "108230": "OV",
1150
+ "109334": "RN",
1151
+ "109488": "GV",
1152
+ "109975": "TN",
1153
+ "109996": "PW",
1154
+ "110718": "KS",
1155
+ "113464": "MN",
1156
+ "113479": "XL",
1157
+ "114400": "YA",
1158
+ "114741": "CX",
1159
+ "114957": "BW",
1160
+ "115032": "MX",
1161
+ "115497": "BU",
1162
+ "117778": "AU",
1163
+ "118278": "WW",
1164
+ "118593": "TG",
1165
+ "119492": "BJ",
1166
+ "120387": "KC",
1167
+ "122406": "VG",
1168
+ "122632": "HG",
1169
+ "123165": "GN",
1170
+ "124356": "JB",
1171
+ "124622": "ZA",
1172
+ "124666": "IU",
1173
+ "126227": "HV",
1174
+ "126602": "WB",
1175
+ "128745": "KM",
1176
+ "128762": "KR",
1177
+ "128992": "JO",
1178
+ "130049": "WL",
1179
+ "130196": "BV",
1180
+ "130282": "CY",
1181
+ "130864": "RX"
1182
+ }
1183
+ }
server/tests/data/upper-labels-ministral3.json ADDED
@@ -0,0 +1,535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema": "jev-label-list-v1",
3
+ "label_scheme": "upper",
4
+ "tokenizer": {
5
+ "id": "mistralai/Ministral-3-8B-Instruct-2512-BF16",
6
+ "revision": "f6fae9795746f63c9be8344932f01275f3c63734",
7
+ "class": "MistralCommonBackend",
8
+ "tekken_json_sha256": "600bb27946565481ecf51ba8aee252e49b9a68507866080ac9c30185bb312843",
9
+ "tokenizer_config_json_sha256": "f59f7294e4f26383d0ea93840fe21cf197784be0842a8301a0343e8c34ed0d6d",
10
+ "release_tokenizer_json_sha256": "d5f6046775b112f0e2d456ee9dba450684ab964fe5c4e231599bdc6773028135"
11
+ },
12
+ "rule": "Candidates in order: A..Z, then AA..ZZ, then AAA..ZZZ (uppercase ASCII, by length then alphabetically). Keep a candidate when tokenizer.encode(label, add_special_tokens=False) is exactly one token id that is not a special id, not already used, and decode([id], skip_special_tokens=True) == label. Stop at 255. A question with n options (2 <= n <= 255) uses labels[:n] in display order.",
13
+ "single_token_counts": {
14
+ "one_letter": 26,
15
+ "two_letter": 439,
16
+ "three_letter": 425
17
+ },
18
+ "three_letter_used": false,
19
+ "answer_boundary_checked": "all 255 labels continue the native [/INST] chat prompt by exactly their token id",
20
+ "count": 255,
21
+ "labels": [
22
+ "A",
23
+ "B",
24
+ "C",
25
+ "D",
26
+ "E",
27
+ "F",
28
+ "G",
29
+ "H",
30
+ "I",
31
+ "J",
32
+ "K",
33
+ "L",
34
+ "M",
35
+ "N",
36
+ "O",
37
+ "P",
38
+ "Q",
39
+ "R",
40
+ "S",
41
+ "T",
42
+ "U",
43
+ "V",
44
+ "W",
45
+ "X",
46
+ "Y",
47
+ "Z",
48
+ "AA",
49
+ "AB",
50
+ "AC",
51
+ "AD",
52
+ "AE",
53
+ "AF",
54
+ "AG",
55
+ "AH",
56
+ "AI",
57
+ "AK",
58
+ "AL",
59
+ "AM",
60
+ "AN",
61
+ "AO",
62
+ "AP",
63
+ "AR",
64
+ "AS",
65
+ "AT",
66
+ "AU",
67
+ "AV",
68
+ "AX",
69
+ "AY",
70
+ "AZ",
71
+ "BA",
72
+ "BB",
73
+ "BC",
74
+ "BD",
75
+ "BE",
76
+ "BF",
77
+ "BG",
78
+ "BI",
79
+ "BJ",
80
+ "BL",
81
+ "BM",
82
+ "BN",
83
+ "BO",
84
+ "BP",
85
+ "BR",
86
+ "BS",
87
+ "BT",
88
+ "BU",
89
+ "BV",
90
+ "BW",
91
+ "BY",
92
+ "CA",
93
+ "CB",
94
+ "CC",
95
+ "CD",
96
+ "CE",
97
+ "CF",
98
+ "CG",
99
+ "CH",
100
+ "CI",
101
+ "CK",
102
+ "CL",
103
+ "CM",
104
+ "CN",
105
+ "CO",
106
+ "CP",
107
+ "CR",
108
+ "CS",
109
+ "CT",
110
+ "CU",
111
+ "CV",
112
+ "CX",
113
+ "CY",
114
+ "DA",
115
+ "DB",
116
+ "DC",
117
+ "DD",
118
+ "DE",
119
+ "DF",
120
+ "DG",
121
+ "DH",
122
+ "DI",
123
+ "DJ",
124
+ "DK",
125
+ "DL",
126
+ "DM",
127
+ "DN",
128
+ "DO",
129
+ "DP",
130
+ "DR",
131
+ "DS",
132
+ "DT",
133
+ "DU",
134
+ "DX",
135
+ "EA",
136
+ "EB",
137
+ "EC",
138
+ "ED",
139
+ "EE",
140
+ "EF",
141
+ "EG",
142
+ "EK",
143
+ "EL",
144
+ "EM",
145
+ "EN",
146
+ "EO",
147
+ "EP",
148
+ "EQ",
149
+ "ER",
150
+ "ES",
151
+ "ET",
152
+ "EU",
153
+ "EV",
154
+ "EW",
155
+ "EX",
156
+ "FA",
157
+ "FB",
158
+ "FC",
159
+ "FD",
160
+ "FE",
161
+ "FF",
162
+ "FG",
163
+ "FI",
164
+ "FK",
165
+ "FL",
166
+ "FM",
167
+ "FN",
168
+ "FO",
169
+ "FP",
170
+ "FR",
171
+ "FS",
172
+ "FT",
173
+ "FW",
174
+ "FX",
175
+ "GA",
176
+ "GB",
177
+ "GC",
178
+ "GD",
179
+ "GE",
180
+ "GF",
181
+ "GG",
182
+ "GH",
183
+ "GI",
184
+ "GL",
185
+ "GM",
186
+ "GN",
187
+ "GO",
188
+ "GP",
189
+ "GR",
190
+ "GS",
191
+ "GT",
192
+ "GV",
193
+ "GW",
194
+ "HA",
195
+ "HB",
196
+ "HC",
197
+ "HD",
198
+ "HE",
199
+ "HF",
200
+ "HG",
201
+ "HH",
202
+ "HI",
203
+ "HK",
204
+ "HL",
205
+ "HM",
206
+ "HO",
207
+ "HP",
208
+ "HR",
209
+ "HS",
210
+ "HT",
211
+ "HV",
212
+ "IA",
213
+ "IB",
214
+ "IC",
215
+ "ID",
216
+ "IE",
217
+ "IF",
218
+ "IG",
219
+ "II",
220
+ "IK",
221
+ "IL",
222
+ "IM",
223
+ "IN",
224
+ "IO",
225
+ "IP",
226
+ "IR",
227
+ "IS",
228
+ "IT",
229
+ "IU",
230
+ "IV",
231
+ "IX",
232
+ "IZ",
233
+ "JA",
234
+ "JB",
235
+ "JK",
236
+ "JO",
237
+ "JP",
238
+ "JR",
239
+ "JS",
240
+ "KA",
241
+ "KB",
242
+ "KC",
243
+ "KE",
244
+ "KG",
245
+ "KM",
246
+ "KN",
247
+ "KO",
248
+ "KR",
249
+ "KS",
250
+ "KT",
251
+ "LA",
252
+ "LB",
253
+ "LC",
254
+ "LD",
255
+ "LE",
256
+ "LF",
257
+ "LG",
258
+ "LI",
259
+ "LL",
260
+ "LM",
261
+ "LO",
262
+ "LP",
263
+ "LR",
264
+ "LS",
265
+ "LT",
266
+ "LU",
267
+ "LV",
268
+ "LY",
269
+ "MA",
270
+ "MB",
271
+ "MC",
272
+ "MD",
273
+ "ME",
274
+ "MF",
275
+ "MG",
276
+ "MH"
277
+ ],
278
+ "token_ids": [
279
+ 1065,
280
+ 1066,
281
+ 1067,
282
+ 1068,
283
+ 1069,
284
+ 1070,
285
+ 1071,
286
+ 1072,
287
+ 1073,
288
+ 1074,
289
+ 1075,
290
+ 1076,
291
+ 1077,
292
+ 1078,
293
+ 1079,
294
+ 1080,
295
+ 1081,
296
+ 1082,
297
+ 1083,
298
+ 1084,
299
+ 1085,
300
+ 1086,
301
+ 1087,
302
+ 1088,
303
+ 1089,
304
+ 1090,
305
+ 14229,
306
+ 8062,
307
+ 5193,
308
+ 5516,
309
+ 32484,
310
+ 12763,
311
+ 6970,
312
+ 91939,
313
+ 33613,
314
+ 26039,
315
+ 4286,
316
+ 6758,
317
+ 5010,
318
+ 106550,
319
+ 6292,
320
+ 3905,
321
+ 6003,
322
+ 3313,
323
+ 117778,
324
+ 29970,
325
+ 13339,
326
+ 25698,
327
+ 75317,
328
+ 10574,
329
+ 28235,
330
+ 6535,
331
+ 18704,
332
+ 34817,
333
+ 71042,
334
+ 87478,
335
+ 17782,
336
+ 119492,
337
+ 30631,
338
+ 20195,
339
+ 90805,
340
+ 18412,
341
+ 22133,
342
+ 35846,
343
+ 7814,
344
+ 54206,
345
+ 115497,
346
+ 130196,
347
+ 114957,
348
+ 82317,
349
+ 9467,
350
+ 43379,
351
+ 7619,
352
+ 8584,
353
+ 9158,
354
+ 26910,
355
+ 39176,
356
+ 7247,
357
+ 15466,
358
+ 17131,
359
+ 8534,
360
+ 41183,
361
+ 47271,
362
+ 9335,
363
+ 13943,
364
+ 12540,
365
+ 13754,
366
+ 4061,
367
+ 97829,
368
+ 34456,
369
+ 114741,
370
+ 130282,
371
+ 10878,
372
+ 12223,
373
+ 22561,
374
+ 27789,
375
+ 5088,
376
+ 28562,
377
+ 92842,
378
+ 47909,
379
+ 29951,
380
+ 52972,
381
+ 49329,
382
+ 42559,
383
+ 28081,
384
+ 57310,
385
+ 24151,
386
+ 16108,
387
+ 13705,
388
+ 14249,
389
+ 46565,
390
+ 33674,
391
+ 52885,
392
+ 26759,
393
+ 34112,
394
+ 13275,
395
+ 7051,
396
+ 16748,
397
+ 35971,
398
+ 17238,
399
+ 82787,
400
+ 11213,
401
+ 15187,
402
+ 4456,
403
+ 92580,
404
+ 20573,
405
+ 44943,
406
+ 2794,
407
+ 5548,
408
+ 4321,
409
+ 78963,
410
+ 66703,
411
+ 49942,
412
+ 10164,
413
+ 6507,
414
+ 62311,
415
+ 10858,
416
+ 61930,
417
+ 44675,
418
+ 7762,
419
+ 63511,
420
+ 31638,
421
+ 66480,
422
+ 15869,
423
+ 45746,
424
+ 88826,
425
+ 15740,
426
+ 31994,
427
+ 53744,
428
+ 21313,
429
+ 17310,
430
+ 70351,
431
+ 75215,
432
+ 19454,
433
+ 12737,
434
+ 16497,
435
+ 77858,
436
+ 19527,
437
+ 10624,
438
+ 67199,
439
+ 96633,
440
+ 74536,
441
+ 17899,
442
+ 28533,
443
+ 123165,
444
+ 28649,
445
+ 31714,
446
+ 32874,
447
+ 27735,
448
+ 35985,
449
+ 109488,
450
+ 102348,
451
+ 23303,
452
+ 68291,
453
+ 24093,
454
+ 26205,
455
+ 7626,
456
+ 41999,
457
+ 122632,
458
+ 79438,
459
+ 56611,
460
+ 64447,
461
+ 46862,
462
+ 103147,
463
+ 74873,
464
+ 13719,
465
+ 24498,
466
+ 30316,
467
+ 8395,
468
+ 126227,
469
+ 20501,
470
+ 16433,
471
+ 6048,
472
+ 3084,
473
+ 20851,
474
+ 11098,
475
+ 4351,
476
+ 3022,
477
+ 65015,
478
+ 9387,
479
+ 10667,
480
+ 3174,
481
+ 11364,
482
+ 9918,
483
+ 9474,
484
+ 6673,
485
+ 4778,
486
+ 124666,
487
+ 6217,
488
+ 10445,
489
+ 15152,
490
+ 71794,
491
+ 124356,
492
+ 92274,
493
+ 128992,
494
+ 72267,
495
+ 69496,
496
+ 32452,
497
+ 39711,
498
+ 60305,
499
+ 120387,
500
+ 12623,
501
+ 99414,
502
+ 128745,
503
+ 49744,
504
+ 44338,
505
+ 128762,
506
+ 110718,
507
+ 81092,
508
+ 10265,
509
+ 72458,
510
+ 13515,
511
+ 20008,
512
+ 3561,
513
+ 55938,
514
+ 106747,
515
+ 34732,
516
+ 23947,
517
+ 45623,
518
+ 7618,
519
+ 28547,
520
+ 23356,
521
+ 18369,
522
+ 71905,
523
+ 73275,
524
+ 51489,
525
+ 38180,
526
+ 8900,
527
+ 21291,
528
+ 16126,
529
+ 14534,
530
+ 5180,
531
+ 96790,
532
+ 64937,
533
+ 91805
534
+ ]
535
+ }
server/tests/test_labels.py ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Answer-label schemes: exact lists, the <=26 guarantee, and end-to-end mapping."""
2
+
3
+ import hashlib
4
+ import json
5
+ import re
6
+ import string
7
+ from pathlib import Path
8
+
9
+ import httpx
10
+ import pytest
11
+
12
+ from jev_adapter.labels import (
13
+ DEFAULT_LABEL_SCHEME,
14
+ MAX_LABELS,
15
+ follows_scheme_order,
16
+ label_candidates,
17
+ resolve_label_scheme,
18
+ select_labels,
19
+ )
20
+ from jev_adapter.server import create_app
21
+ from jev_adapter.sglang import SGLangBackend
22
+
23
+ GOLDEN_PATH = Path(__file__).parent / "data" / "upper-labels-ministral3.json"
24
+ GOLDEN = json.loads(GOLDEN_PATH.read_text())
25
+ UPPER = GOLDEN["labels"]
26
+ COUNTS = (2, 26, 27, 77, 151, 255)
27
+
28
+
29
+ class VocabularyTokenizer:
30
+ """Every character is one token; listed multi-letter strings are one token.
31
+
32
+ With the golden vocabulary this has exactly the pinned Ministral-3
33
+ single-token pattern for the upper labels (e.g. AJ and AQ are split).
34
+ """
35
+
36
+ all_special_ids = (0,)
37
+
38
+ def __init__(self, words):
39
+ self.word_ids = {word: 100_000 + index for index, word in enumerate(words)}
40
+ self.words = {token: word for word, token in self.word_ids.items()}
41
+
42
+ def encode(self, text, add_special_tokens=False):
43
+ ids, position = [], 0
44
+ while position < len(text):
45
+ for length in (3, 2):
46
+ piece = text[position : position + length]
47
+ if len(piece) == length and piece in self.word_ids:
48
+ ids.append(self.word_ids[piece])
49
+ position += length
50
+ break
51
+ else:
52
+ ids.append(ord(text[position]) + 1000)
53
+ position += 1
54
+ return ids
55
+
56
+ def decode(self, ids, skip_special_tokens=True):
57
+ return "".join(
58
+ self.words[token] if token in self.words else chr(token - 1000)
59
+ for token in ids
60
+ )
61
+
62
+
63
+ def ministral_like():
64
+ return VocabularyTokenizer([label for label in UPPER if len(label) > 1])
65
+
66
+
67
+ def test_default_and_resolution():
68
+ assert DEFAULT_LABEL_SCHEME == "upper"
69
+ assert resolve_label_scheme(environ={}) == "upper"
70
+ assert resolve_label_scheme(environ={"JEV_LABEL_SCHEME": "served"}) == "served"
71
+ assert resolve_label_scheme("upper", {"JEV_LABEL_SCHEME": "served"}) == "upper"
72
+ assert resolve_label_scheme("", {"JEV_LABEL_SCHEME": ""}) == "upper"
73
+ for bad in ("lower", "UPPER", 3):
74
+ with pytest.raises(ValueError, match="label scheme"):
75
+ resolve_label_scheme(bad, {})
76
+ with pytest.raises(ValueError, match="label scheme"):
77
+ resolve_label_scheme(None, {"JEV_LABEL_SCHEME": "lower"})
78
+
79
+
80
+ def test_candidate_orders():
81
+ upper = label_candidates("upper")
82
+ assert upper[:28] == [*string.ascii_uppercase, "AA", "AB"]
83
+ assert upper[26 + 675] == "ZZ" and upper[26 + 676] == "AAA"
84
+ assert upper[-1] == "ZZZ" and len(upper) == 26 + 26**2 + 26**3
85
+ assert all(re.fullmatch(r"[A-Z]{1,3}", label) for label in upper)
86
+ served = label_candidates("served")
87
+ # The served order is unchanged from before the scheme option existed.
88
+ assert served[:62] == list(
89
+ string.ascii_uppercase + string.ascii_lowercase + string.digits
90
+ )
91
+ assert served[62:64] == ["000", "001"] and served[1062:1064] == ["AA", "AB"]
92
+ assert served[-1] == "zz" and len(served) == 62 + 1000 + 2 * 26**2
93
+
94
+
95
+ @pytest.mark.parametrize("count", COUNTS)
96
+ def test_upper_lists_for_each_option_count(count):
97
+ labels, token_ids = select_labels(ministral_like(), "upper", limit=count)
98
+ assert labels == UPPER[:count]
99
+ assert len(set(labels)) == len(set(token_ids)) == count
100
+ assert all(re.fullmatch(r"[A-Z]{1,2}", label) for label in labels)
101
+ assert labels[: min(count, 26)] == list(string.ascii_uppercase[:count])
102
+ if count > 26:
103
+ assert labels[26:29] == ["AA", "AB", "AC"][: count - 26]
104
+ # AJ, AQ and AW are not single Ministral-3 tokens, so they are skipped.
105
+ assert not {"AJ", "AQ", "AW"} & set(labels)
106
+
107
+
108
+ def test_exact_boundary_labels():
109
+ labels, _ = select_labels(ministral_like(), "upper")
110
+ assert len(labels) == MAX_LABELS
111
+ assert labels[26] == "AA"
112
+ assert labels[76] == "CG" # the 77th label: the last of BANKING77's options
113
+ assert labels[150] == "FT" # the 151st: the last of CLINC150 with oos
114
+ assert labels[254] == "MH"
115
+
116
+
117
+ @pytest.mark.parametrize("count", range(2, 27))
118
+ def test_at_most_26_options_keep_a_to_z_in_both_schemes(count):
119
+ tokenizer = ministral_like()
120
+ upper = select_labels(tokenizer, "upper", limit=count)
121
+ served = select_labels(tokenizer, "served", limit=count)
122
+ assert upper == served
123
+ assert upper[0] == list(string.ascii_uppercase[:count])
124
+
125
+
126
+ def test_served_scheme_still_uses_lowercase_after_z():
127
+ labels, _ = select_labels(ministral_like(), "served")
128
+ assert labels[26:30] == ["a", "b", "c", "d"]
129
+ assert labels[52:62] == list(string.digits)
130
+ assert labels[62] == "AA"
131
+
132
+
133
+ @pytest.mark.parametrize("count", COUNTS)
134
+ def test_label_index_round_trip(count):
135
+ labels, token_ids = select_labels(ministral_like(), "upper", limit=count)
136
+ index = {label: position for position, label in enumerate(labels)}
137
+ token_index = {token: position for position, token in enumerate(token_ids)}
138
+ assert len(index) == len(token_index) == count
139
+ for position, (label, token) in enumerate(zip(labels, token_ids)):
140
+ assert index[label] == token_index[token] == position
141
+ assert labels[index[label]] == label
142
+ # Every label is also a prefix-free answer at the list level: a longer label
143
+ # is one token, never the token of a shorter label followed by another.
144
+ tokenizer = ministral_like()
145
+ assert all(
146
+ tokenizer.encode(label) == [token] for label, token in zip(labels, token_ids)
147
+ )
148
+
149
+
150
+ def test_three_letter_continuation_when_two_letters_run_out():
151
+ # Only AA..AJ are two-letter tokens; the list continues with AAA, AAB, ...
152
+ two = [f"A{letter}" for letter in "ABCDEFGHIJ"]
153
+ three = ["AAA", "AAB", "ABA", "ZZZ"]
154
+ tokenizer = VocabularyTokenizer(two + three)
155
+ labels, _ = select_labels(tokenizer, "upper")
156
+ assert labels == list(string.ascii_uppercase) + two + three
157
+
158
+
159
+ def test_special_duplicate_and_lossy_tokens_are_skipped():
160
+ class Tricky(VocabularyTokenizer):
161
+ all_special_ids = (ord("B") + 1000,)
162
+
163
+ def decode(self, ids, skip_special_tokens=True):
164
+ text = super().decode(ids, skip_special_tokens)
165
+ return "ab" if text == "AB" else text
166
+
167
+ def encode(self, text, add_special_tokens=False):
168
+ return [ord("C") + 1000] if text == "AC" else super().encode(text)
169
+
170
+ labels, _ = select_labels(Tricky(["AA", "AB", "AC", "AD"]), "upper")
171
+ assert labels[:3] == ["A", "C", "D"] and "B" not in labels
172
+ assert labels[25:] == ["AA", "AD"]
173
+
174
+
175
+ def test_scheme_order_check():
176
+ assert follows_scheme_order("upper", ["A", "B", "AA", "AB", "MH", "AAA"])
177
+ assert not follows_scheme_order("upper", ["A", "a"])
178
+ assert not follows_scheme_order("upper", ["B", "A"])
179
+ assert not follows_scheme_order("upper", ["AA", "Z"])
180
+ assert follows_scheme_order("served", ["A", "Z", "a", "0", "AA", "aa"])
181
+ assert not follows_scheme_order("served", ["a", "A"])
182
+ assert not follows_scheme_order("served", ["A", "AAA"])
183
+
184
+
185
+ def test_pinned_ministral3_tokenizer_matches_the_golden_list():
186
+ transformers = pytest.importorskip("transformers")
187
+ tokenizer_info = GOLDEN["tokenizer"]
188
+ try:
189
+ tokenizer = transformers.AutoTokenizer.from_pretrained(
190
+ tokenizer_info["id"],
191
+ revision=tokenizer_info["revision"],
192
+ local_files_only=True,
193
+ )
194
+ except Exception: # noqa: BLE001 - absent from this machine's HF cache
195
+ pytest.skip("pinned Ministral-3 tokenizer is not cached locally")
196
+ labels, token_ids = select_labels(tokenizer, "upper")
197
+ assert labels == GOLDEN["labels"]
198
+ assert token_ids == GOLDEN["token_ids"]
199
+ # The recorded probe below is what this tokenizer really does.
200
+ for candidate, ids in PROBE["encode"].items():
201
+ assert tokenizer.encode(candidate, add_special_tokens=False) == ids
202
+ for token, text in PROBE["decode"].items():
203
+ assert tokenizer.decode([int(token)], skip_special_tokens=True) == text
204
+ assert set(tokenizer.all_special_ids) <= ProbeTokenizer().all_special_ids
205
+ from jev_adapter.native_tokenizer import NativeTokenizer
206
+
207
+ # Every label continues the native chat prompt by exactly its token.
208
+ NativeTokenizer(tokenizer).prepare("Choose.", tuple(labels), tuple(token_ids), None)
209
+
210
+
211
+ # Raw encode/decode results of the pinned Ministral-3 tokenizer for every one-
212
+ # and two-letter uppercase candidate, recorded on the GPU host with
213
+ # AutoTokenizer and cross-checked against tekken.json (mistral_common) and the
214
+ # release tokenizer.json. Unlike ministral_like(), which is built from the
215
+ # golden list, this table is tokenizer data only, so the tests below check the
216
+ # selection rule against the real tokenizer on every CI run.
217
+ PROBE_PATH = Path(__file__).parent / "data" / "ministral3-label-probe.json"
218
+ PROBE = json.loads(PROBE_PATH.read_text())
219
+
220
+
221
+ class ProbeTokenizer:
222
+ """Replays the recorded tokenizer; a string outside the table is an error."""
223
+
224
+ def __init__(self):
225
+ self.all_special_ids = {
226
+ token
227
+ for first, last in PROBE["special_id_ranges"]
228
+ for token in range(first, last + 1)
229
+ }
230
+
231
+ def encode(self, text, add_special_tokens=False):
232
+ assert add_special_tokens is False
233
+ return list(PROBE["encode"][text])
234
+
235
+ def decode(self, ids, skip_special_tokens=True):
236
+ assert skip_special_tokens is True
237
+ return "".join(PROBE["decode"][str(token)] for token in ids)
238
+
239
+
240
+ def test_probe_records_the_golden_lists_tokenizer():
241
+ assert PROBE["schema"] == "jev-tokenizer-probe-v1"
242
+ assert PROBE["tokenizer"] == GOLDEN["tokenizer"]
243
+ expected = PROBE["expected_upper"]
244
+ assert expected["labels"] == GOLDEN["labels"]
245
+ assert expected["token_ids"] == GOLDEN["token_ids"]
246
+ assert expected["source_sha256"] == hashlib.sha256(
247
+ GOLDEN_PATH.read_bytes()
248
+ ).hexdigest()
249
+ candidates = [*string.ascii_uppercase] + [
250
+ first + second
251
+ for first in string.ascii_uppercase
252
+ for second in string.ascii_uppercase
253
+ ]
254
+ assert list(PROBE["encode"]) == candidates
255
+ singles = [label for label, ids in PROBE["encode"].items() if len(ids) == 1]
256
+ counts = GOLDEN["single_token_counts"]
257
+ assert sum(len(label) == 1 for label in singles) == counts["one_letter"] == 26
258
+ assert sum(len(label) == 2 for label in singles) == counts["two_letter"] == 439
259
+
260
+
261
+ def test_rule_on_the_recorded_ministral3_tokenizer_gives_the_golden_list():
262
+ # Three-letter candidates are absent from the table, so consulting one
263
+ # would raise: 255 labels must come from one and two letters.
264
+ labels, token_ids = select_labels(ProbeTokenizer(), "upper")
265
+ assert labels == GOLDEN["labels"]
266
+ assert token_ids == GOLDEN["token_ids"]
267
+ for count in COUNTS:
268
+ assert select_labels(ProbeTokenizer(), "upper", limit=count) == (
269
+ GOLDEN["labels"][:count],
270
+ GOLDEN["token_ids"][:count],
271
+ )
272
+
273
+
274
+ @pytest.mark.asyncio
275
+ async def test_http_backend_picks_the_golden_list_from_the_recorded_tokenizer():
276
+ engine = LabelEngine(ProbeTokenizer(), "A")
277
+ async with httpx.AsyncClient(transport=httpx.MockTransport(engine)) as upstream:
278
+ backend = SGLangBackend(
279
+ "http://engine", "standard-one", client=upstream, label_scheme="upper"
280
+ )
281
+ await backend.start()
282
+ try:
283
+ for count in COUNTS:
284
+ assert backend.labels(count) == (
285
+ tuple(GOLDEN["labels"][:count]),
286
+ tuple(GOLDEN["token_ids"][:count]),
287
+ )
288
+ finally:
289
+ await backend.close()
290
+
291
+
292
+ class LabelEngine:
293
+ """A remote engine whose tokenizer has two-letter tokens and prefers one label."""
294
+
295
+ def __init__(self, tokenizer, preferred):
296
+ self.tokenizer = tokenizer
297
+ self.preferred = preferred
298
+ self.prompts = []
299
+
300
+ def __call__(self, request):
301
+ body = json.loads(request.content) if request.content else None
302
+ path = request.url.path
303
+ if path == "/model_info":
304
+ return httpx.Response(
305
+ 200,
306
+ json={
307
+ "served_model_name": "standard-one",
308
+ "model_path": "org/standard-one",
309
+ "is_generation": True,
310
+ },
311
+ )
312
+ if path == "/server_info":
313
+ return httpx.Response(200, json={"speculative_algorithm": None})
314
+ encode = self.tokenizer.encode
315
+ if path == "/v1/tokenize":
316
+ if "messages" in body:
317
+ content = body["messages"][-1]["content"]
318
+ self.prompts.append(content)
319
+ return httpx.Response(
320
+ 200, json={"tokens": encode(f"<u>{content}</u><a>")}
321
+ )
322
+ return httpx.Response(
323
+ 200, json={"tokens": [encode(text) for text in body["prompt"]]}
324
+ )
325
+ if path == "/v1/detokenize":
326
+ tokens = body["tokens"]
327
+ decode = self.tokenizer.decode
328
+ text = (
329
+ [decode(ids) for ids in tokens]
330
+ if isinstance(tokens[0], list)
331
+ else decode(tokens)
332
+ )
333
+ return httpx.Response(200, json={"text": text})
334
+ if path == "/generate":
335
+ preferred = encode(self.preferred)[0]
336
+ entries = [
337
+ [-0.1 if token == preferred else -9.0, token, None]
338
+ for token in body["token_ids_logprob"]
339
+ ]
340
+ return httpx.Response(
341
+ 200,
342
+ json={
343
+ "meta_info": {
344
+ "completion_tokens": 0,
345
+ "prompt_tokens": len(body["input_ids"]),
346
+ "cached_tokens": 0,
347
+ "output_token_ids_logprobs": [entries],
348
+ }
349
+ },
350
+ )
351
+ raise AssertionError(path)
352
+
353
+
354
+ @pytest.mark.asyncio
355
+ @pytest.mark.parametrize(
356
+ "scheme,count,preferred,expected_option",
357
+ [
358
+ ("upper", 77, "CG", 76),
359
+ ("upper", 77, "AA", 26),
360
+ ("upper", 151, "FT", 150),
361
+ ("upper", 26, "Z", 25),
362
+ ("served", 77, "a", 26),
363
+ ("served", 26, "Z", 25),
364
+ ],
365
+ )
366
+ async def test_label_maps_to_its_option_through_the_http_stack(
367
+ scheme, count, preferred, expected_option
368
+ ):
369
+ engine = LabelEngine(ministral_like(), preferred)
370
+ async with httpx.AsyncClient(transport=httpx.MockTransport(engine)) as upstream:
371
+ backend = SGLangBackend(
372
+ "http://engine", "standard-one", client=upstream, label_scheme=scheme
373
+ )
374
+ app = create_app(backend)
375
+ async with (
376
+ app.router.lifespan_context(app),
377
+ httpx.AsyncClient(
378
+ transport=httpx.ASGITransport(app), base_url="http://adapter"
379
+ ) as client,
380
+ ):
381
+ models = (await client.get("/v1/models")).json()
382
+ assert models["data"][0]["label_scheme"] == scheme
383
+ options = {f"intent-{index}": None for index in range(count)}
384
+ response = await client.post(
385
+ "/v1/systemone",
386
+ json={
387
+ "model": "standard-one",
388
+ "state": "I lost my card.",
389
+ "questions": {
390
+ "intent": {
391
+ "type": "choice",
392
+ "instructions": "Which intent?",
393
+ "criteria": options,
394
+ }
395
+ },
396
+ },
397
+ )
398
+ assert response.status_code == 200, response.text
399
+ result = response.json()
400
+ assert result["metadata"]["label_scheme"] == scheme
401
+ assert result["answers"]["intent"]["choice"] == f"intent-{expected_option}"
402
+ lines = engine.prompts[-1].split("Options:\n")[1].splitlines()
403
+ labels = [line.split(":", 1)[0] for line in lines]
404
+ expected, _ = select_labels(ministral_like(), scheme, limit=count)
405
+ assert labels == expected
406
+ assert lines[expected_option] == f"{preferred}: intent-{expected_option}"
server/tests/test_main.py CHANGED
@@ -44,6 +44,7 @@ def run_main(monkeypatch, argv, env=None):
44
  "JEV_PROMPT_WORDING",
45
  "JEV_DEFAULT_TEMPERATURE",
46
  "JEV_NATIVE_SYSTEM_PROMPT",
 
47
  ):
48
  monkeypatch.delenv(key, raising=False)
49
  for key, value in (env or {}).items():
@@ -366,3 +367,34 @@ def test_native_system_prompt_startup_log_line(monkeypatch, capture, caplog):
366
  message = info_records[0].getMessage()
367
  assert "prompt_wording=native" in message
368
  assert "native_system_prompt=none" in message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  "JEV_PROMPT_WORDING",
45
  "JEV_DEFAULT_TEMPERATURE",
46
  "JEV_NATIVE_SYSTEM_PROMPT",
47
+ "JEV_LABEL_SCHEME",
48
  ):
49
  monkeypatch.delenv(key, raising=False)
50
  for key, value in (env or {}).items():
 
367
  message = info_records[0].getMessage()
368
  assert "prompt_wording=native" in message
369
  assert "native_system_prompt=none" in message
370
+ assert "label_scheme=upper" in message
371
+
372
+
373
+ def test_label_scheme_defaults_to_upper(monkeypatch, capture):
374
+ run_main(monkeypatch, ["--model", "decision-model"])
375
+ assert capture.backend_kwargs["label_scheme"] == "upper"
376
+
377
+
378
+ def test_label_scheme_flag_and_env_var(monkeypatch, capture):
379
+ run_main(monkeypatch, ["--model", "m"], env={"JEV_LABEL_SCHEME": "served"})
380
+ assert capture.backend_kwargs["label_scheme"] == "served"
381
+ run_main(
382
+ monkeypatch,
383
+ ["--model", "m", "--label-scheme", "upper"],
384
+ env={"JEV_LABEL_SCHEME": "served"},
385
+ )
386
+ assert capture.backend_kwargs["label_scheme"] == "upper"
387
+
388
+
389
+ @pytest.mark.parametrize(
390
+ "argv,env",
391
+ [
392
+ (["--label-scheme", "lower"], {}),
393
+ ([], {"JEV_LABEL_SCHEME": "lower"}),
394
+ ],
395
+ )
396
+ def test_invalid_label_scheme_rejected(monkeypatch, capture, argv, env):
397
+ with pytest.raises(SystemExit):
398
+ run_main(monkeypatch, ["--model", "m", *argv], env=env)
399
+ assert capture.backend_kwargs is None
400
+
server/tests/test_protocol.py CHANGED
@@ -115,6 +115,22 @@ class TestSystemOneValidation(unittest.TestCase):
115
  with self.subTest(question=question), self.assertRaises(ValidationError):
116
  request_for(question)
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  def test_required_fields_and_request_limits(self):
119
  for updates in (
120
  {"model": " "},
 
115
  with self.subTest(question=question), self.assertRaises(ValidationError):
116
  request_for(question)
117
 
118
+ def test_empty_noul_criteria_mean_no_descriptions(self):
119
+ # {} is the same as omitting criteria (the released server's fix).
120
+ empty = request_for({"type": "noul", "instructions": "Urgent?", "criteria": {}})
121
+ omitted = request_for({"type": "noul", "instructions": "Urgent?"})
122
+ self.assertIsNone(empty.questions["routing"].criteria)
123
+ plan = plan_question(empty.questions["routing"])
124
+ self.assertEqual(plan, plan_question(omitted.questions["routing"]))
125
+ self.assertEqual(plan.descriptions, (None, None))
126
+ self.assertTrue(build_prompt("s", plan, ["A", "B"]).endswith("A: yes\nB: no"))
127
+ # Only an empty map is relaxed; a partial or foreign map still fails.
128
+ for criteria in ({"true": None}, {"yes": None, "no": None}):
129
+ with self.subTest(criteria=criteria), self.assertRaises(ValidationError):
130
+ request_for(
131
+ {"type": "noul", "instructions": "Urgent?", "criteria": criteria}
132
+ )
133
+
134
  def test_required_fields_and_request_limits(self):
135
  for updates in (
136
  {"model": " "},