llguidance 0.7.12__tar.gz → 0.7.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. {llguidance-0.7.12 → llguidance-0.7.13}/CHANGELOG.md +5 -0
  2. {llguidance-0.7.12 → llguidance-0.7.13}/Cargo.lock +7 -7
  3. {llguidance-0.7.12 → llguidance-0.7.13}/PKG-INFO +1 -1
  4. {llguidance-0.7.12 → llguidance-0.7.13}/json_stats/src/json_stats.rs +3 -11
  5. {llguidance-0.7.12 → llguidance-0.7.13}/parser/Cargo.toml +2 -2
  6. {llguidance-0.7.12 → llguidance-0.7.13}/parser/llguidance.h +8 -0
  7. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/api.rs +8 -0
  8. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/lexer.rs +33 -1
  9. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/lexerspec.rs +2 -1
  10. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/parser.rs +33 -9
  11. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/perf.rs +3 -0
  12. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/regexvec.rs +5 -0
  13. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/factory.rs +8 -1
  14. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/tokenparser.rs +6 -1
  15. {llguidance-0.7.12 → llguidance-0.7.13}/pyproject.toml +1 -1
  16. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/__init__.py +2 -0
  17. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/_lib.pyi +86 -5
  18. {llguidance-0.7.12 → llguidance-0.7.13}/python/torch_tests/test_matcher.py +31 -7
  19. {llguidance-0.7.12 → llguidance-0.7.13}/python_ext/Cargo.toml +1 -1
  20. {llguidance-0.7.12 → llguidance-0.7.13}/python_ext/src/lib.rs +2 -0
  21. {llguidance-0.7.12 → llguidance-0.7.13}/python_ext/src/llinterpreter.rs +6 -3
  22. {llguidance-0.7.12 → llguidance-0.7.13}/python_ext/src/llmatcher.rs +10 -6
  23. llguidance-0.7.13/python_ext/src/parserlimits.rs +106 -0
  24. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/src/sample_parser.rs +19 -0
  25. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/Cargo.toml +1 -1
  26. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie_hf_downloader/Cargo.toml +1 -1
  27. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie_hf_tokenizers/Cargo.toml +1 -1
  28. {llguidance-0.7.12 → llguidance-0.7.13}/.github/workflows/rust.yml +0 -0
  29. {llguidance-0.7.12 → llguidance-0.7.13}/.github/workflows/wheels.yml +0 -0
  30. {llguidance-0.7.12 → llguidance-0.7.13}/.gitignore +0 -0
  31. {llguidance-0.7.12 → llguidance-0.7.13}/CODE_OF_CONDUCT.md +0 -0
  32. {llguidance-0.7.12 → llguidance-0.7.13}/Cargo.toml +0 -0
  33. {llguidance-0.7.12 → llguidance-0.7.13}/LICENSE +0 -0
  34. {llguidance-0.7.12 → llguidance-0.7.13}/README.md +0 -0
  35. {llguidance-0.7.12 → llguidance-0.7.13}/SECURITY.md +0 -0
  36. {llguidance-0.7.12 → llguidance-0.7.13}/SUPPORT.md +0 -0
  37. {llguidance-0.7.12 → llguidance-0.7.13}/c_sample/Makefile +0 -0
  38. {llguidance-0.7.12 → llguidance-0.7.13}/c_sample/README.md +0 -0
  39. {llguidance-0.7.12 → llguidance-0.7.13}/c_sample/c_sample.cpp +0 -0
  40. {llguidance-0.7.12 → llguidance-0.7.13}/docs/fast_forward.md +0 -0
  41. {llguidance-0.7.12 → llguidance-0.7.13}/docs/json_schema.md +0 -0
  42. {llguidance-0.7.12 → llguidance-0.7.13}/docs/mask_plot.png +0 -0
  43. {llguidance-0.7.12 → llguidance-0.7.13}/docs/optimizations.md +0 -0
  44. {llguidance-0.7.12 → llguidance-0.7.13}/docs/special_tokens.md +0 -0
  45. {llguidance-0.7.12 → llguidance-0.7.13}/docs/syntax.md +0 -0
  46. {llguidance-0.7.12 → llguidance-0.7.13}/docs/toktrie.md +0 -0
  47. {llguidance-0.7.12 → llguidance-0.7.13}/json_stats/Cargo.toml +0 -0
  48. {llguidance-0.7.12 → llguidance-0.7.13}/json_stats/expected_maskbench.json +0 -0
  49. {llguidance-0.7.12 → llguidance-0.7.13}/json_stats/jstats.sh +0 -0
  50. {llguidance-0.7.12 → llguidance-0.7.13}/json_stats/scripts/split-stats.sh +0 -0
  51. {llguidance-0.7.12 → llguidance-0.7.13}/json_stats/scripts/split_plot.py +0 -0
  52. {llguidance-0.7.12 → llguidance-0.7.13}/json_stats/src/lib.rs +0 -0
  53. {llguidance-0.7.12 → llguidance-0.7.13}/json_stats/src/stats.rs +0 -0
  54. {llguidance-0.7.12 → llguidance-0.7.13}/parser/LICENSE +0 -0
  55. {llguidance-0.7.12 → llguidance-0.7.13}/parser/README.md +0 -0
  56. {llguidance-0.7.12 → llguidance-0.7.13}/parser/build.rs +0 -0
  57. {llguidance-0.7.12 → llguidance-0.7.13}/parser/cbindgen.toml +0 -0
  58. {llguidance-0.7.12 → llguidance-0.7.13}/parser/grammars/character.json +0 -0
  59. {llguidance-0.7.12 → llguidance-0.7.13}/parser/grammars/json.json +0 -0
  60. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/constraint.rs +0 -0
  61. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/from_guidance.rs +0 -0
  62. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/grammar.rs +0 -0
  63. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/mod.rs +0 -0
  64. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/earley/slicer.rs +0 -0
  65. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/ffi.rs +0 -0
  66. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/ffi_par.rs +0 -0
  67. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/grammar_builder.rs +0 -0
  68. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/README.md +0 -0
  69. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/compiler.rs +0 -0
  70. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/context_ref.rs +0 -0
  71. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/context_simple/context.rs +0 -0
  72. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/context_simple/draft.rs +0 -0
  73. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/context_simple/mod.rs +0 -0
  74. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/formats.rs +0 -0
  75. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/mod.rs +0 -0
  76. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/numeric.rs +0 -0
  77. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/schema.rs +0 -0
  78. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json/shared_context.rs +0 -0
  79. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/json_validation.rs +0 -0
  80. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/lark/README.md +0 -0
  81. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/lark/ast.rs +0 -0
  82. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/lark/common.rs +0 -0
  83. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/lark/compiler.rs +0 -0
  84. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/lark/lexer.rs +0 -0
  85. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/lark/mod.rs +0 -0
  86. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/lark/parser.rs +0 -0
  87. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/lib.rs +0 -0
  88. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/logging.rs +0 -0
  89. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/matcher.rs +0 -0
  90. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/output.rs +0 -0
  91. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/panic_utils.rs +0 -0
  92. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/stop_controller.rs +0 -0
  93. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/substring.rs +0 -0
  94. {llguidance-0.7.12 → llguidance-0.7.13}/parser/src/tokenizer_json.rs +0 -0
  95. {llguidance-0.7.12 → llguidance-0.7.13}/plan.md +0 -0
  96. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/_grammar_from.py +0 -0
  97. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/_struct_tag.py +0 -0
  98. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/_tokenizer.py +0 -0
  99. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/_util.py +0 -0
  100. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/cli.py +0 -0
  101. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/gbnf_to_lark.py +0 -0
  102. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/hf.py +0 -0
  103. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/mlx.py +0 -0
  104. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/numpy.py +0 -0
  105. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/py.typed +0 -0
  106. {llguidance-0.7.12 → llguidance-0.7.13}/python/llguidance/torch.py +0 -0
  107. {llguidance-0.7.12 → llguidance-0.7.13}/python/mypy.ini +0 -0
  108. {llguidance-0.7.12 → llguidance-0.7.13}/python/torch_tests/__init__.py +0 -0
  109. {llguidance-0.7.12 → llguidance-0.7.13}/python/torch_tests/test_bitmask.py +0 -0
  110. {llguidance-0.7.12 → llguidance-0.7.13}/python/torch_tests/test_hf.py +0 -0
  111. {llguidance-0.7.12 → llguidance-0.7.13}/python_ext/src/py.rs +0 -0
  112. {llguidance-0.7.12 → llguidance-0.7.13}/python_ext/src/pyjson.rs +0 -0
  113. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/Cargo.toml +0 -0
  114. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/README.md +0 -0
  115. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/cli.sh +0 -0
  116. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/blog.sample.json +0 -0
  117. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/blog.schema.json +0 -0
  118. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/blog.schema.ll.json +0 -0
  119. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/README.md +0 -0
  120. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/arithmetic.gbnf +0 -0
  121. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/c.gbnf +0 -0
  122. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/chess.gbnf +0 -0
  123. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/english.gbnf +0 -0
  124. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/japanese.gbnf +0 -0
  125. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/json.gbnf +0 -0
  126. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/json_arr.gbnf +0 -0
  127. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/list.gbnf +0 -0
  128. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/from-llama.cpp/vllm-sql.gbnf +0 -0
  129. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/lark.lark +0 -0
  130. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/rfc.lark +0 -0
  131. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/data/rfc.xml +0 -0
  132. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/gtest.sh +0 -0
  133. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/lark.sh +0 -0
  134. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/run.sh +0 -0
  135. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/src/lib.rs +0 -0
  136. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/src/minimal.rs +0 -0
  137. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/tests/test_lark.rs +0 -0
  138. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/tests/test_ll.rs +0 -0
  139. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/tests/test_raw_parser.rs +0 -0
  140. {llguidance-0.7.12 → llguidance-0.7.13}/sample_parser/tests/test_stop.rs +0 -0
  141. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/annotate_asm.js +0 -0
  142. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/bump.py +0 -0
  143. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/cbindgen.sh +0 -0
  144. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/checklinks.py +0 -0
  145. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/checklinks.sh +0 -0
  146. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/ci-publish.py +0 -0
  147. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/disasm.sh +0 -0
  148. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/gbnf_to_lark.py +0 -0
  149. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/gen-testcase.py +0 -0
  150. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/git-version.sh +0 -0
  151. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/install-deps.sh +0 -0
  152. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/jsonschema-stats.js +0 -0
  153. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/remote-guidance-test.sh +0 -0
  154. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/rust-size.js +0 -0
  155. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/rust_size.py +0 -0
  156. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/test-guidance.sh +0 -0
  157. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/tokenizer_test.py +0 -0
  158. {llguidance-0.7.12 → llguidance-0.7.13}/scripts/update-git.py +0 -0
  159. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/LICENSE +0 -0
  160. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/README.md +0 -0
  161. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/src/bytes.rs +0 -0
  162. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/src/lib.rs +0 -0
  163. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/src/recognizer.rs +0 -0
  164. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/src/rng.rs +0 -0
  165. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/src/svob.rs +0 -0
  166. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/src/tokenv.rs +0 -0
  167. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/src/toktree.rs +0 -0
  168. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie/tests/test_svob.rs +0 -0
  169. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie_hf_downloader/LICENSE +0 -0
  170. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie_hf_downloader/src/lib.rs +0 -0
  171. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie_hf_tokenizers/LICENSE +0 -0
  172. {llguidance-0.7.12 → llguidance-0.7.13}/toktrie_hf_tokenizers/src/lib.rs +0 -0
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  If a release doesn't introduce any interesting changes (build fixes etc.), it's skipped.
6
6
 
7
+ #### [0.7.13](https://github.com/guidance-ai/llguidance/compare/v0.7.12...0.7.13) 2025-04-05
8
+
9
+ - expose LLParserLimits in Python API [`598dc8f`](https://github.com/guidance-ai/llguidance/commit/598dc8f37f69f51244e54d9885445abf02a515a7)
10
+ - pre-compute lexer states for particularly large regexes (can be disabled in ParserLimits)
11
+
7
12
  #### [0.7.12](https://github.com/guidance-ai/llguidance/compare/v0.7.11...0.7.12) 2025-04-04
8
13
 
9
14
  - performance optimizations
@@ -401,9 +401,9 @@ dependencies = [
401
401
 
402
402
  [[package]]
403
403
  name = "derivre"
404
- version = "0.3.4"
404
+ version = "0.3.5"
405
405
  source = "registry+https://github.com/rust-lang/crates.io-index"
406
- checksum = "310c9990c5a531352e274c8c929ca667a84b6bbaceb1e095c177e6a979807f57"
406
+ checksum = "2c15da97393af596fc344dc746f804c6742f1ddfd2e88fd7b75232a989c85c4e"
407
407
  dependencies = [
408
408
  "ahash",
409
409
  "anyhow",
@@ -1177,7 +1177,7 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
1177
1177
 
1178
1178
  [[package]]
1179
1179
  name = "llguidance"
1180
- version = "0.7.12"
1180
+ version = "0.7.13"
1181
1181
  dependencies = [
1182
1182
  "anyhow",
1183
1183
  "derivre",
@@ -1196,7 +1196,7 @@ dependencies = [
1196
1196
 
1197
1197
  [[package]]
1198
1198
  name = "llguidance_py"
1199
- version = "0.7.12"
1199
+ version = "0.7.13"
1200
1200
  dependencies = [
1201
1201
  "anyhow",
1202
1202
  "bytemuck",
@@ -2356,7 +2356,7 @@ dependencies = [
2356
2356
 
2357
2357
  [[package]]
2358
2358
  name = "toktrie"
2359
- version = "0.7.12"
2359
+ version = "0.7.13"
2360
2360
  dependencies = [
2361
2361
  "anyhow",
2362
2362
  "bytemuck",
@@ -2367,7 +2367,7 @@ dependencies = [
2367
2367
 
2368
2368
  [[package]]
2369
2369
  name = "toktrie_hf_downloader"
2370
- version = "0.7.12"
2370
+ version = "0.7.13"
2371
2371
  dependencies = [
2372
2372
  "anyhow",
2373
2373
  "hf-hub",
@@ -2378,7 +2378,7 @@ dependencies = [
2378
2378
 
2379
2379
  [[package]]
2380
2380
  name = "toktrie_hf_tokenizers"
2381
- version = "0.7.12"
2381
+ version = "0.7.13"
2382
2382
  dependencies = [
2383
2383
  "anyhow",
2384
2384
  "log",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llguidance
3
- Version: 0.7.12
3
+ Version: 0.7.13
4
4
  License-File: LICENSE
5
5
  Summary: Bindings for the Low-level Guidance (llguidance) Rust library for use within Guidance
6
6
  Author: Michal Moskal
@@ -5,11 +5,7 @@ use json_stats::SchemaStats;
5
5
  use jsonschema::Validator;
6
6
  use llguidance::{
7
7
  api::{GrammarInit, StopReason, TopLevelGrammar},
8
- earley::{
9
- perf::{num_with_commas, ParserPerfCounters},
10
- regexvec::LexerStats,
11
- XorShift,
12
- },
8
+ earley::{perf::num_with_commas, regexvec::LexerStats, XorShift},
13
9
  toktrie::{InferenceCapabilities, SimpleVob, TokEnv},
14
10
  Constraint, HashMap, JsonCompileOptions, ParserFactory, TokenParser,
15
11
  };
@@ -290,7 +286,6 @@ struct TestEnv {
290
286
  factory: Arc<ParserFactory>,
291
287
  ref_factory: Arc<ParserFactory>,
292
288
  file_name: String,
293
- perf_counters: Arc<ParserPerfCounters>,
294
289
  hash_rnd: Arc<ahash::RandomState>,
295
290
  }
296
291
 
@@ -739,8 +734,7 @@ impl TestEnv {
739
734
 
740
735
  let t2 = std::time::Instant::now();
741
736
  let parser = match parser {
742
- Ok(mut parser) => {
743
- parser.parser.set_perf_counters(self.perf_counters.clone());
737
+ Ok(parser) => {
744
738
  let mut constraint = Constraint::new(parser.clone());
745
739
  constraint.compute_mask().unwrap();
746
740
  res.first_mask_us = t2.elapsed().as_micros() as usize;
@@ -996,7 +990,6 @@ fn main() {
996
990
 
997
991
  let t0 = std::time::Instant::now();
998
992
  let par = num_threads > 1;
999
- let perf_counters = Arc::new(ParserPerfCounters::new());
1000
993
  let hash_rnd = Arc::new(ahash::RandomState::new());
1001
994
  let do_file = |file: &String| {
1002
995
  let env = TestEnv {
@@ -1005,7 +998,6 @@ fn main() {
1005
998
  ref_factory: ref_factory.clone(),
1006
999
  file_name: file.to_string(),
1007
1000
  cli: options.clone(),
1008
- perf_counters: perf_counters.clone(),
1009
1001
  hash_rnd: hash_rnd.clone(),
1010
1002
  };
1011
1003
  env.run_test()
@@ -1155,7 +1147,7 @@ fn main() {
1155
1147
  eprintln!(
1156
1148
  "{}\n{}",
1157
1149
  serde_json::to_string_pretty(&total).unwrap(),
1158
- perf_counters
1150
+ &factory.perf_counters(),
1159
1151
  );
1160
1152
  }
1161
1153
  eprintln!(
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "llguidance"
3
- version = "0.7.12"
3
+ version = "0.7.13"
4
4
  edition = "2021"
5
5
  license = "MIT"
6
6
  description = "Super-fast Structured Outputs"
@@ -8,7 +8,7 @@ repository = "https://github.com/guidance-ai/llguidance"
8
8
 
9
9
  [dependencies]
10
10
  toktrie = { workspace = true }
11
- derivre = { version = "=0.3.4", default-features = false, features = ["compress"] }
11
+ derivre = { version = "=0.3.5", default-features = false, features = ["compress"] }
12
12
  serde = { version = "1.0.217", features = ["derive"] }
13
13
  serde_json = { version = "1.0.138", features = ["preserve_order"] }
14
14
  anyhow = "1.0.95"
@@ -70,6 +70,14 @@ typedef struct LlgParserLimits {
70
70
  * Default: 500_000 (a few megabytes of JSON)
71
71
  */
72
72
  size_t max_grammar_size;
73
+ /**
74
+ * If true, we'll run any extremely large regexes against the whole
75
+ * trie of the tokenizer while constructing the lexer.
76
+ * This reduces future mask computation time, but increases
77
+ * the time it takes to construct the lexer.
78
+ * Default: true
79
+ */
80
+ bool precompute_large_lexemes;
73
81
  } LlgParserLimits;
74
82
 
75
83
  typedef struct LlgConstraintInit {
@@ -249,6 +249,13 @@ pub struct ParserLimits {
249
249
  /// Maximum size of the grammar (symbols in productions)
250
250
  /// Default: 500_000 (a few megabytes of JSON)
251
251
  pub max_grammar_size: usize,
252
+
253
+ /// If true, we'll run any extremely large regexes against the whole
254
+ /// trie of the tokenizer while constructing the lexer.
255
+ /// This reduces future mask computation time, but increases
256
+ /// the time it takes to construct the lexer.
257
+ /// Default: true
258
+ pub precompute_large_lexemes: bool,
252
259
  }
253
260
 
254
261
  impl Default for ParserLimits {
@@ -260,6 +267,7 @@ impl Default for ParserLimits {
260
267
  max_lexer_states: 250_000, //
261
268
  max_grammar_size: 500_000, // fhir schema => 200k
262
269
  step_max_items: 50_000, //
270
+ precompute_large_lexemes: true,
263
271
  }
264
272
  }
265
273
  }
@@ -1,6 +1,6 @@
1
1
  use anyhow::Result;
2
2
  use std::fmt::Debug;
3
- use toktrie::SimpleVob;
3
+ use toktrie::{Recognizer, SimpleVob, TokTrie};
4
4
 
5
5
  use crate::api::ParserLimits;
6
6
 
@@ -59,6 +59,29 @@ pub enum LexerResult {
59
59
  Error,
60
60
  }
61
61
 
62
+ struct LexerPrecomputer<'a> {
63
+ states: Vec<StateID>,
64
+ lex: &'a mut Lexer,
65
+ }
66
+
67
+ impl Recognizer for LexerPrecomputer<'_> {
68
+ fn collapse(&mut self) {}
69
+ fn trie_finished(&mut self) {}
70
+ fn pop_bytes(&mut self, num: usize) {
71
+ self.states.truncate(self.states.len() - num);
72
+ }
73
+ fn try_push_byte(&mut self, byte: u8) -> bool {
74
+ let state = *self.states.last().unwrap();
75
+ match self.lex.advance(state, byte, false) {
76
+ LexerResult::State(next_state, _) => {
77
+ self.states.push(next_state);
78
+ true
79
+ }
80
+ _ => false,
81
+ }
82
+ }
83
+ }
84
+
62
85
  impl Lexer {
63
86
  pub fn from(spec: &LexerSpec, limits: &mut ParserLimits, dbg: bool) -> Result<Self> {
64
87
  let mut dfa = spec.to_regex_vec(limits)?;
@@ -92,6 +115,15 @@ impl Lexer {
92
115
  self.dfa.initial_state(allowed_lexemes)
93
116
  }
94
117
 
118
+ pub fn precompute_for(&mut self, trie: &TokTrie, allowed_lexemes: &LexemeSet) {
119
+ let state = self.start_state(allowed_lexemes);
120
+ let mut states = Vec::with_capacity(300);
121
+ states.push(state);
122
+ let mut pre = LexerPrecomputer { states, lex: self };
123
+ let mut toks = trie.alloc_token_set();
124
+ trie.add_bias(&mut pre, &mut toks, &[]);
125
+ }
126
+
95
127
  pub fn transition_start_state(&mut self, s: StateID, first_byte: Option<u8>) -> StateID {
96
128
  first_byte.map(|b| self.dfa.transition(s, b)).unwrap_or(s)
97
129
  }
@@ -48,7 +48,7 @@ pub struct LexemeSpec {
48
48
  pub(crate) name: String,
49
49
  pub(crate) rx: RegexAst,
50
50
  class: LexemeClass,
51
- compiled_rx: ExprRef,
51
+ pub(crate) compiled_rx: ExprRef,
52
52
  ends_at_eos: bool,
53
53
  lazy: bool,
54
54
  contextual: bool,
@@ -299,6 +299,7 @@ impl LexerSpec {
299
299
  } else {
300
300
  compiled
301
301
  };
302
+
302
303
  if let Some(idx) = self.lexemes.iter().position(|lex| {
303
304
  lex.compiled_rx == compiled
304
305
  && lex.class == spec.class
@@ -5,7 +5,7 @@
5
5
  // (Retrieved 18 Sep 2024).
6
6
 
7
7
  use std::{
8
- fmt::Debug,
8
+ fmt::{Debug, Display},
9
9
  hash::Hash,
10
10
  ops::Range,
11
11
  sync::{Arc, Mutex},
@@ -207,6 +207,12 @@ impl ParserStats {
207
207
  }
208
208
  }
209
209
 
210
+ impl Display for ParserStats {
211
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212
+ write!(f, "{}", serde_json::to_string_pretty(self).unwrap())
213
+ }
214
+ }
215
+
210
216
  id32_type!(GrammarStackPtr);
211
217
 
212
218
  #[derive(Clone, Debug)]
@@ -580,9 +586,26 @@ impl ParserState {
580
586
  tok_env: TokEnv,
581
587
  grammar: Arc<CGrammar>,
582
588
  mut limits: ParserLimits,
589
+ perf_counters: Arc<ParserPerfCounters>,
583
590
  ) -> Result<(Self, Lexer)> {
584
591
  let start = grammar.start();
585
- let lexer = Lexer::from(grammar.lexer_spec(), &mut limits, true)?;
592
+ let mut lexer = Lexer::from(grammar.lexer_spec(), &mut limits, true)?;
593
+ if limits.precompute_large_lexemes {
594
+ let t0 = crate::Instant::now();
595
+ for spec in &grammar.lexer_spec().lexemes {
596
+ let w = lexer.dfa.lexeme_weight(spec.idx);
597
+ if w > 1000 {
598
+ // println!(
599
+ // "precomputing lexeme {} (w={w})",
600
+ // lexer.lexer_spec().lexeme_def_to_string(spec.idx)
601
+ // );
602
+ let mut allowed = grammar.lexer_spec().alloc_lexeme_set();
603
+ allowed.add(spec.idx);
604
+ lexer.precompute_for(tok_env.tok_trie(), &allowed);
605
+ }
606
+ }
607
+ perf_counters.precompute.record(t0.elapsed());
608
+ }
586
609
  let scratch = Scratch::new(Arc::clone(&grammar));
587
610
  let lexer_state = lexer.a_dead_state(); // placeholder
588
611
  let spec_tok = tok_env
@@ -626,7 +649,7 @@ impl ParserState {
626
649
  shared_box: Box::new(SharedState {
627
650
  lexer_opt: Some(lexer),
628
651
  }),
629
- perf_counters: Arc::new(ParserPerfCounters::new()),
652
+ perf_counters,
630
653
  };
631
654
 
632
655
  r.scratch.grammar_stack.push(GrammarStackNode {
@@ -2576,8 +2599,13 @@ impl ParserError {
2576
2599
  }
2577
2600
 
2578
2601
  impl Parser {
2579
- pub fn new(tok_env: TokEnv, grammar: Arc<CGrammar>, limits: ParserLimits) -> Result<Self> {
2580
- let (state, lexer) = ParserState::new(tok_env, grammar, limits)?;
2602
+ pub fn new(
2603
+ tok_env: TokEnv,
2604
+ grammar: Arc<CGrammar>,
2605
+ limits: ParserLimits,
2606
+ perf_counters: Arc<ParserPerfCounters>,
2607
+ ) -> Result<Self> {
2608
+ let (state, lexer) = ParserState::new(tok_env, grammar, limits, perf_counters)?;
2581
2609
  let shared = Arc::new(Mutex::new(Box::new(SharedState {
2582
2610
  lexer_opt: Some(lexer),
2583
2611
  })));
@@ -2603,10 +2631,6 @@ impl Parser {
2603
2631
  &self.state.stats
2604
2632
  }
2605
2633
 
2606
- pub fn set_perf_counters(&mut self, counters: Arc<ParserPerfCounters>) {
2607
- self.state.perf_counters = counters;
2608
- }
2609
-
2610
2634
  #[inline(always)]
2611
2635
  pub fn perf_counters(&self) -> &ParserPerfCounters {
2612
2636
  &self.state.perf_counters
@@ -87,6 +87,7 @@ pub struct ParserPerfCounters {
87
87
  pub tokenize_ff: PerfTimer,
88
88
  pub compute_bias: PerfTimer,
89
89
  pub compute_mask: PerfTimer,
90
+ pub precompute: PerfTimer,
90
91
  }
91
92
 
92
93
  impl Default for ParserPerfCounters {
@@ -104,6 +105,7 @@ impl ParserPerfCounters {
104
105
  tokenize_ff: PerfTimer::new("tokenize_ff"),
105
106
  compute_bias: PerfTimer::new("compute_bias"),
106
107
  compute_mask: PerfTimer::new("compute_mask"),
108
+ precompute: PerfTimer::new("precompute"),
107
109
  }
108
110
  }
109
111
 
@@ -115,6 +117,7 @@ impl ParserPerfCounters {
115
117
  &self.compute_bias,
116
118
  &self.compute_mask,
117
119
  &self.tmp_counter,
120
+ &self.precompute,
118
121
  ]
119
122
  }
120
123
  }
@@ -490,6 +490,11 @@ impl RegexVec {
490
490
  self.exprs.cost()
491
491
  }
492
492
 
493
+ pub fn lexeme_weight(&mut self, lexeme_idx: LexemeIdx) -> u32 {
494
+ let e = self.rx_list[lexeme_idx.as_usize()];
495
+ self.exprs.get_weight(e)
496
+ }
497
+
493
498
  pub fn set_max_states(&mut self, max_states: usize) {
494
499
  if !self.has_error() {
495
500
  self.max_states = max_states;
@@ -5,7 +5,7 @@ use toktrie::{InferenceCapabilities, TokEnv};
5
5
 
6
6
  use crate::{
7
7
  api::{GrammarInit, ParserLimits, TopLevelGrammar},
8
- earley::{SlicedBiasComputer, XorShift},
8
+ earley::{perf::ParserPerfCounters, SlicedBiasComputer, XorShift},
9
9
  Logger, TokenParser,
10
10
  };
11
11
 
@@ -17,6 +17,7 @@ pub struct ParserFactory {
17
17
  buffer_log_level: u32,
18
18
  limits: ParserLimits,
19
19
  seed: Mutex<XorShift>,
20
+ perf_counters: Arc<ParserPerfCounters>,
20
21
  }
21
22
 
22
23
  impl ParserFactory {
@@ -34,9 +35,14 @@ impl ParserFactory {
34
35
  buffer_log_level: 0,
35
36
  seed: Mutex::new(XorShift::default()),
36
37
  limits: ParserLimits::default(),
38
+ perf_counters: Arc::new(ParserPerfCounters::default()),
37
39
  })
38
40
  }
39
41
 
42
+ pub fn perf_counters(&self) -> Arc<ParserPerfCounters> {
43
+ self.perf_counters.clone()
44
+ }
45
+
40
46
  pub fn new_simple(tok_env: &TokEnv) -> Result<Self> {
41
47
  Self::new(
42
48
  tok_env,
@@ -55,6 +61,7 @@ impl ParserFactory {
55
61
  buffer_log_level: self.buffer_log_level,
56
62
  seed: Mutex::new(XorShift::default()),
57
63
  limits: self.limits.clone(),
64
+ perf_counters: self.perf_counters.clone(),
58
65
  })
59
66
  }
60
67
 
@@ -80,7 +80,12 @@ impl TokenParser {
80
80
  limits.clone(),
81
81
  factory.extra_lexemes(),
82
82
  )?;
83
- let mut parser = Parser::new(token_env.clone(), compiled_grammar, limits.clone())?;
83
+ let mut parser = Parser::new(
84
+ token_env.clone(),
85
+ compiled_grammar,
86
+ limits.clone(),
87
+ factory.perf_counters(),
88
+ )?;
84
89
  parser.metrics_mut().rand = factory.next_rng();
85
90
  let eos_token = token_env.tok_trie().eos_token();
86
91
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "llguidance"
3
- version = "0.7.12"
3
+ version = "0.7.13"
4
4
  description = "Bindings for the Low-level Guidance (llguidance) Rust library for use within Guidance"
5
5
  requires-python = ">=3.9"
6
6
  license = "MIT"
@@ -6,6 +6,7 @@ from ._lib import (
6
6
  RegexCompiler,
7
7
  LLExecutor,
8
8
  LLMatcher,
9
+ LLParserLimits,
9
10
  )
10
11
  from ._tokenizer import TokenizerWrapper
11
12
  from ._grammar_from import GrammarFormat, grammar_from
@@ -16,6 +17,7 @@ __all__ = [
16
17
  "LLMatcher",
17
18
  "LLInterpreter",
18
19
  "LLExecutor",
20
+ "LLParserLimits",
19
21
  "JsonCompiler",
20
22
  "LarkCompiler",
21
23
  "RegexCompiler",
@@ -121,9 +121,11 @@ class LLInterpreter:
121
121
  cls,
122
122
  tokenizer: LLTokenizer,
123
123
  grammar: str,
124
+ /,
124
125
  enable_backtrack: bool = True,
125
126
  enable_ff_tokens: bool = True,
126
127
  log_level: int = 1,
128
+ limits: Optional[LLParserLimits] = None,
127
129
  ) -> "LLInterpreter":
128
130
  """
129
131
  Create a new interpreter.
@@ -218,10 +220,14 @@ class LLInterpreter:
218
220
 
219
221
  class LLMatcher:
220
222
 
221
- def __new__(cls,
222
- tokenizer: LLTokenizer,
223
- grammar: str,
224
- log_level: int = 1) -> "LLMatcher":
223
+ def __new__(
224
+ cls,
225
+ tokenizer: LLTokenizer,
226
+ grammar: str,
227
+ /,
228
+ log_level: int = 1,
229
+ limits: Optional[LLParserLimits] = None,
230
+ ) -> "LLMatcher":
225
231
  """
226
232
  Create a new LLMatcher.
227
233
  Args:
@@ -246,7 +252,8 @@ class LLMatcher:
246
252
  """
247
253
 
248
254
  @staticmethod
249
- def validate_grammar(grammar: str, tokenizer: Optional[LLTokenizer] = None) -> str:
255
+ def validate_grammar(grammar: str,
256
+ tokenizer: Optional[LLTokenizer] = None) -> str:
250
257
  """
251
258
  Validate the grammar, for example one returned by LLMatcher.grammar_from_*().
252
259
  Returns empty string if the grammar is valid, otherwise an error message.
@@ -504,3 +511,77 @@ class JsonCompileOptions(TypedDict, total=False):
504
511
  whitespace_flexible: Optional[bool]
505
512
  # defaults to false
506
513
  coerce_one_of: Optional[bool]
514
+
515
+
516
+ class LLParserLimits:
517
+
518
+ def __init__(
519
+ self,
520
+ max_items_in_row: Optional[int] = None,
521
+ initial_lexer_fuel: Optional[int] = None,
522
+ step_lexer_fuel: Optional[int] = None,
523
+ step_max_items: Optional[int] = None,
524
+ max_lexer_states: Optional[int] = None,
525
+ max_grammar_size: Optional[int] = None,
526
+ precompute_large_lexemes: Optional[bool] = None,
527
+ ) -> None:
528
+ """
529
+ ParserLimits configuration for controlling parser and lexer resource usage.
530
+
531
+ Args:
532
+ max_items_in_row (Optional[int]):
533
+ Maximum branching factor for a single production row in the grammar.
534
+ Affects ambiguity and parsing explosion risk. Default: 2000.
535
+
536
+ initial_lexer_fuel (Optional[int]):
537
+ Fuel for building the initial regex ASTs in the lexer.
538
+ Limits complexity of regex analysis. Speed: ~50k/ms. Default: 1_000_000.
539
+
540
+ step_lexer_fuel (Optional[int]):
541
+ Maximum fuel used during a single lexer mask computation step.
542
+ Controls performance per token analysis phase. Speed: ~14k/ms. Default: 200_000.
543
+
544
+ step_max_items (Optional[int]):
545
+ Cap on the number of Earley items generated per mask step.
546
+ Controls parsing granularity and performance. Speed: ~20k/ms. Default: 50_000.
547
+
548
+ max_lexer_states (Optional[int]):
549
+ Maximum number of distinct states the lexer can construct.
550
+ Affects memory use (approx. 1–2kB per state). Default: 250_000.
551
+
552
+ max_grammar_size (Optional[int]):
553
+ Maximum number of symbols in grammar productions.
554
+ Acts as a limit on total grammar complexity and size. Default: 500_000.
555
+
556
+ precompute_large_lexemes (Optional[bool]):
557
+ Whether to run large regexes eagerly on the entire token trie during lexer build.
558
+ Increases lexer construction time, but speeds up mask computation. Default: True.
559
+ """
560
+
561
+ @property
562
+ def max_items_in_row(self) -> int:
563
+ """Maximum branching factor for a grammar row. Default: 2000"""
564
+
565
+ @property
566
+ def initial_lexer_fuel(self) -> int:
567
+ """Fuel used to build initial lexer regex ASTs. Default: 1_000_000"""
568
+
569
+ @property
570
+ def step_lexer_fuel(self) -> int:
571
+ """Lexer fuel for mask computation steps. Default: 200_000"""
572
+
573
+ @property
574
+ def step_max_items(self) -> int:
575
+ """Maximum Earley items per step. Default: 50_000"""
576
+
577
+ @property
578
+ def max_lexer_states(self) -> int:
579
+ """Maximum lexer states (affects memory). Default: 250_000"""
580
+
581
+ @property
582
+ def max_grammar_size(self) -> int:
583
+ """Maximum grammar size (symbols in productions). Default: 500_000"""
584
+
585
+ @property
586
+ def precompute_large_lexemes(self) -> bool:
587
+ """Precompute large regexes during lexer construction. Default: True"""
@@ -1,7 +1,7 @@
1
1
  from typing import Any, Dict, List, Tuple, Union
2
2
  import llguidance
3
3
  from llguidance.numpy import fill_next_token_bitmask_par, allocate_token_bitmask
4
- from llguidance import LLMatcher, LLTokenizer, StructTag
4
+ from llguidance import LLMatcher, LLTokenizer, StructTag, LLParserLimits
5
5
  import pytest
6
6
  from numpy.typing import NDArray
7
7
  import numpy as np
@@ -42,7 +42,7 @@ def check_one_grammar(grm: str, s: str, passing: bool) -> None:
42
42
  del tokens[-1]
43
43
  else:
44
44
  tokens = tokenizer().tokenize_str(s)
45
-
45
+
46
46
  print("Check: " + tokenizer().dbg_tokens(tokens))
47
47
 
48
48
  for i, t in enumerate(tokens):
@@ -74,8 +74,7 @@ def check_one_grammar(grm: str, s: str, passing: bool) -> None:
74
74
  assert interp.is_accepting()
75
75
 
76
76
 
77
- def check_grammar(grm: str, passing: List[str],
78
- failing: List[str]) -> None:
77
+ def check_grammar(grm: str, passing: List[str], failing: List[str]) -> None:
79
78
  for s in passing:
80
79
  check_one_grammar(grm, s, True)
81
80
  for s in failing:
@@ -391,6 +390,31 @@ def test_struct_tag_0() -> None:
391
390
  # this should pass since it doesn't actually use special tokens:
392
391
  '<|tool|>qux(1)',
393
392
  ],
394
- [
395
- "⁂<|tool|>qux(11)"
396
- ])
393
+ ["⁂<|tool|>qux(11)"])
394
+
395
+
396
+ def test_parser_limits() -> None:
397
+ # this one needs more than initial_lexer_fuel=20
398
+ m = LLMatcher(tokenizer(),
399
+ "start: /(foo[12]23|bar)/",
400
+ limits=LLParserLimits(initial_lexer_fuel=5))
401
+ assert "initial lexer configuration (grammar) too big" in m.get_error()
402
+
403
+ # m = LLMatcher(tokenizer(),
404
+ # "start: /(foo[12]23|bar|qux|mux)/",
405
+ # limits=LLParserLimits(
406
+ # step_lexer_fuel=1,
407
+ # precompute_large_lexemes=False,
408
+ # ),
409
+ # log_level=2)
410
+ # toks = tokenizer().tokenize_str("foo223")
411
+ # assert not m.get_error()
412
+ # m.consume_token(toks[0])
413
+ # mask = m.compute_logit_bias()
414
+ # assert not m.get_error()
415
+ # m.consume_token(toks[1])
416
+ # mask = m.compute_logit_bias()
417
+ # assert not m.get_error()
418
+ # m.consume_token(toks[2])
419
+ # mask = m.compute_logit_bias()
420
+ # assert not m.get_error()
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "llguidance_py"
3
- version = "0.7.12"
3
+ version = "0.7.13"
4
4
  edition = "2021"
5
5
  license = "MIT"
6
6
  description = "Super-fast Structured Outputs"
@@ -2,6 +2,7 @@ use pyo3::prelude::*;
2
2
 
3
3
  mod llinterpreter;
4
4
  mod llmatcher;
5
+ mod parserlimits;
5
6
  mod py;
6
7
  mod pyjson;
7
8
 
@@ -9,6 +10,7 @@ mod pyjson;
9
10
  #[pymodule]
10
11
  fn _lib(_py: Python<'_>, m: &Bound<PyModule>) -> PyResult<()> {
11
12
  py::init(m)?;
13
+ parserlimits::init(m)?;
12
14
  llinterpreter::init(m)?;
13
15
  llmatcher::init(m)?;
14
16
  Ok(())