speedy-utils 2.0.1__tar.gz → 2.0.2__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 (149) hide show
  1. speedy_utils-2.0.1/src/speedy_utils/common/__init__.py → speedy_utils-2.0.2/.codex +0 -0
  2. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/CLAUDE.md +30 -10
  3. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/PKG-INFO +32 -1
  4. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/README.md +30 -0
  5. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/integration_test.py +13 -13
  6. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/docs/GENERATE_QUICKREF.md +10 -2
  7. speedy_utils-2.0.2/docs/MIGRATION_THINKING.md +73 -0
  8. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/docs/TOKENIZATION.md +31 -4
  9. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/generate_example.py +17 -17
  10. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/pyproject.toml +7 -1
  11. speedy_utils-2.0.2/pyrightconfig.json +8 -0
  12. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/bug_simple.py +1 -1
  13. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/test.py +1 -1
  14. speedy_utils-2.0.2/scripts/test_locals.py +17 -0
  15. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/datasets_utils/viz_chat.py +87 -51
  16. speedy_utils-2.0.2/src/llm_utils/__init__.py +75 -0
  17. speedy_utils-2.0.2/src/llm_utils/lm/llm.py +863 -0
  18. speedy_utils-2.0.2/src/llm_utils/lm/llm_qwen3.py +705 -0
  19. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/llm_signature.py +1 -1
  20. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/lm_base.py +4 -4
  21. speedy_utils-2.0.2/src/llm_utils/lm/utils.py +71 -0
  22. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/scripts/sp_chat.py +4 -1
  23. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/__imports.py +9 -2
  24. speedy_utils-2.0.2/src/speedy_utils/__init__.py +227 -0
  25. {speedy_utils-2.0.1/src/speedy_utils/scripts → speedy_utils-2.0.2/src/speedy_utils/common}/__init__.py +0 -0
  26. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_error.py +2 -0
  27. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_io.py +5 -2
  28. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/_multi_process.py +228 -16
  29. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/common.py +1 -1
  30. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/dataset_sharding.py +1 -1
  31. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/thread.py +208 -55
  32. speedy_utils-2.0.2/src/speedy_utils/scripts/__init__.py +0 -0
  33. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/vision_utils/__init__.py +7 -7
  34. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/vision_utils/io_utils.py +36 -57
  35. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/vision_utils/plot.py +2 -0
  36. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/llm_utils/test_llm_call_contract.py +139 -9
  37. speedy_utils-2.0.2/tests/llm_utils/test_llm_mixins.py +835 -0
  38. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/llm_utils/test_llm_signature.py +2 -1
  39. speedy_utils-2.0.2/tests/llm_utils/test_openai_memoize_completion_cache.py +116 -0
  40. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/llm_utils/test_sp_chat.py +3 -1
  41. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_process.py +37 -0
  42. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_process_update.py +14 -4
  43. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_thread.py +6 -0
  44. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_tokenization.py +48 -4
  45. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_viz_chat.py +101 -5
  46. speedy_utils-2.0.2/tools/uv_test.sh +9 -0
  47. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/uv.lock +35 -1
  48. speedy_utils-2.0.1/docs/MIGRATION_THINKING.md +0 -36
  49. speedy_utils-2.0.1/scripts/test_locals.py +0 -18
  50. speedy_utils-2.0.1/src/llm_utils/__init__.py +0 -26
  51. speedy_utils-2.0.1/src/llm_utils/lm/llm.py +0 -655
  52. speedy_utils-2.0.1/src/llm_utils/lm/llm_qwen3.py +0 -468
  53. speedy_utils-2.0.1/src/llm_utils/lm/utils.py +0 -38
  54. speedy_utils-2.0.1/src/speedy_utils/__init__.py +0 -209
  55. speedy_utils-2.0.1/tests/llm_utils/test_llm_mixins.py +0 -346
  56. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.githooks/pre-commit +0 -0
  57. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.githooks/pre-push +0 -0
  58. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/prompts/improveParallelErrorHandling.prompt.md +0 -0
  59. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/caching-utilities/SKILL.md +0 -0
  60. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/caching-utilities/examples/caching_example.py +0 -0
  61. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/dataset-processing-multiprocessing/CONFIG_REFERENCE.md +0 -0
  62. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/dataset-processing-multiprocessing/SKILL.md +0 -0
  63. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/dataset-processing-multiprocessing/example_tokenize_pack.py +0 -0
  64. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/edit-llm-inference-style/SKILL.md +0 -0
  65. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/io-utilities/SKILL.md +0 -0
  66. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/io-utilities/examples/io_example.py +0 -0
  67. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/llm-integration/SKILL.md +0 -0
  68. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/llm-integration/examples/llm_example.py +0 -0
  69. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/multi-threading-processing/SKILL.md +0 -0
  70. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/ray-distributed-computing/SKILL.md +0 -0
  71. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/skill-creation/SKILL.md +0 -0
  72. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/vision-utilities/SKILL.md +0 -0
  73. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/vision-utilities/examples/vision_example.py +0 -0
  74. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/workflows/publish.yml +0 -0
  75. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.gitignore +0 -0
  76. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.pre-commit-config.yaml +0 -0
  77. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/AGENTS.md +0 -0
  78. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/bumpversion.sh +0 -0
  79. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/chainlit.md +0 -0
  80. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/debug_generate_response.py +0 -0
  81. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/debug_n_param.py +0 -0
  82. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/debug_n_structure.py +0 -0
  83. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/multi_thread.py +0 -0
  84. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_decode_api.py +0 -0
  85. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_endpoints.py +0 -0
  86. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_generate.py +0 -0
  87. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_generate_endpoint.py +0 -0
  88. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_qwen3_generate.md +0 -0
  89. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_qwen3_generate.py +0 -0
  90. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/docs/TOKENIZATION_IMPLEMENTATION.md +0 -0
  91. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/llm_public_api_example.ipynb +0 -0
  92. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/model_specific_thinking_example.py +0 -0
  93. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/tokenization_example.py +0 -0
  94. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/vision_utils_example.py +0 -0
  95. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/experiments/exp1/dockerfile +0 -0
  96. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/experiments/exp1/run_in_docker.sh +0 -0
  97. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/experiments/exp1/test.png +0 -0
  98. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/notebooks/llm_utils/llm_as_a_judge.ipynb +0 -0
  99. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/notebooks/test.ipynb +0 -0
  100. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/notebooks/test_multi_thread.ipynb +0 -0
  101. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/ruff.toml +0 -0
  102. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/debug_import_time.py +0 -0
  103. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/deploy.sh +0 -0
  104. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/imports.sh +0 -0
  105. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/install_hooks.sh +0 -0
  106. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/test_error_handling.py +0 -0
  107. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/test_import_time_vision.py +0 -0
  108. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/test_ray_mp.py +0 -0
  109. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/setup.cfg +0 -0
  110. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/datasets_utils/convert_to_arrow.py +0 -0
  111. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/chat_format/__init__.py +0 -0
  112. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/chat_format/display.py +0 -0
  113. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/chat_format/transform.py +0 -0
  114. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/chat_format/utils.py +0 -0
  115. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/group_messages.py +0 -0
  116. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/__init__.py +0 -0
  117. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/base_prompt_builder.py +0 -0
  118. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/openai_memoize.py +0 -0
  119. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/signature.py +0 -0
  120. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/clock.py +0 -0
  121. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/function_decorator.py +0 -0
  122. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/logger.py +0 -0
  123. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/notebook_utils.py +0 -0
  124. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/patcher.py +0 -0
  125. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/report_manager.py +0 -0
  126. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_cache.py +0 -0
  127. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_misc.py +0 -0
  128. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_print.py +0 -0
  129. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/__init__.py +0 -0
  130. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/process.py +0 -0
  131. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/scripts/kill_mpython.py +0 -0
  132. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/scripts/mpython.py +0 -0
  133. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/scripts/openapi_client_codegen.py +0 -0
  134. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/scripts/prefetch_large_model.py +0 -0
  135. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/vision_utils/README.md +0 -0
  136. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/import_all.py +0 -0
  137. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/import_time_report.py +0 -0
  138. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/llm_utils/test_openai_proxy_localhost.py +0 -0
  139. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/sample_objects.py +0 -0
  140. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test.py +0 -0
  141. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_import_time.py +0 -0
  142. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_logger.py +0 -0
  143. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_logger_format.py +0 -0
  144. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_memoize_typing.py +0 -0
  145. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_mpython.py +0 -0
  146. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_multi_process.py +0 -0
  147. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_multi_thread.py +0 -0
  148. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_multithread_error_trace.py +0 -0
  149. {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_prefetch_large_model.py +0 -0
@@ -9,7 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
9
9
  uv sync
10
10
 
11
11
  # Run tests
12
- uv run pytest
12
+ uv run pytest -n 32
13
13
 
14
14
  # Run a single test file
15
15
  uv run pytest tests/test_thread.py
@@ -60,21 +60,41 @@ def some_function():
60
60
 
61
61
  | Module | Import Time | Strategy |
62
62
  |--------|-------------|----------|
63
- | `torch` | ~5s | Lazy import in GPU functions |
64
- | `matplotlib` | ~3.6s | Lazy import in plotting functions |
65
- | `pandas` | ~1.3s | Lazy import in data functions |
66
- | `IPython` | ~1s | Lazy import in notebook functions |
63
+ | `torch` | ~5s | Lazy import inside functions |
64
+ | `matplotlib` | ~1.3s cumulative | Lazy via `__getattr__` in `__init__.py` |
65
+ | `pandas` | ~1.9s cumulative | Lazy via `__getattr__` in `__init__.py` |
66
+ | `IPython` | ~2.1s cumulative | Lazy via `__getattr__` in `__init__.py` |
67
67
 
68
- ### Using the _LazyModule Pattern
68
+ ### Rule: Never top-level import heavy modules in `__init__.py`
69
+
70
+ Only a short `__getattr__` block (keyed on module name) is allowed for heavy
71
+ modules. All internal `speedy_utils.*` imports must remain direct top-level
72
+ imports. External heavy modules (`pandas`, `matplotlib`, `IPython`, `torch`)
73
+ must **never** appear as top-level imports in any `__init__.py`.
69
74
 
70
75
  ```python
71
- from speedy_utils.__imports import pd, plt
76
+ # BAD — in __init__.py
77
+ import pandas as pd # adds ~1.3 s
78
+
79
+ # GOOD — in __init__.py
80
+ # pd is declared in _HEAVY dict, resolved by __getattr__ on first access
72
81
 
73
- def process_data():
74
- df = pd.DataFrame() # pandas imports here, not at module load
75
- return df
82
+ # GOOD — inside a function anywhere in the package
83
+ def load_dataframe(path):
84
+ import pandas as pd # only pays cost when called
85
+ return pd.read_csv(path)
76
86
  ```
77
87
 
88
+ ### Rule: Heavy lazy names must NOT be in `__all__`
89
+
90
+ `from speedy_utils import *` resolves every name in `__all__`, which triggers
91
+ `__getattr__` for each heavy name and defeats the lazy-load strategy. Only
92
+ include names that are directly importable at module load time in `__all__`.
93
+ Heavy names (`np`, `pd`, `matplotlib`, `plt`, `get_ipython`, `HTML`, `display`,
94
+ `BaseModel`) must be kept out of `__all__`; they remain accessible via explicit
95
+ attribute access (`speedy_utils.pd`) or explicit import
96
+ (`from speedy_utils import pd`).
97
+
78
98
  ## Architecture
79
99
 
80
100
  ### Package Structure
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: speedy-utils
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Fast and easy-to-use package for data science
5
5
  Project-URL: Homepage, https://github.com/anhvth/speedy
6
6
  Project-URL: Repository, https://github.com/anhvth/speedy
@@ -24,6 +24,7 @@ Requires-Dist: cachetools
24
24
  Requires-Dist: chainlit>=2.0
25
25
  Requires-Dist: datasets
26
26
  Requires-Dist: debugpy
27
+ Requires-Dist: dill
27
28
  Requires-Dist: fastcore
28
29
  Requires-Dist: fastprogress
29
30
  Requires-Dist: freezegun
@@ -648,6 +649,36 @@ answer = message.content
648
649
  reasoning = getattr(message, "reasoning_content", None)
649
650
  ```
650
651
 
652
+ For custom staged generation with explicit tags, `Qwen3LLM` also exposes
653
+ `complete_until()`:
654
+
655
+ ```python
656
+ from llm_utils import Qwen3LLM
657
+
658
+ llm = Qwen3LLM(model="Qwen/Qwen3-0.6B")
659
+
660
+ memory_state = llm.complete_until(
661
+ [{"role": "user", "content": "Solve this in stages"}],
662
+ "<memory>",
663
+ stop="</memory>",
664
+ max_tokens=256,
665
+ )
666
+
667
+ think_state = llm.complete_until(
668
+ [{"role": "user", "content": "Solve this in stages"}],
669
+ memory_state.assistant_prompt_prefix + "\n<think_efficient>",
670
+ stop="</think_efficient>",
671
+ max_tokens=512,
672
+ )
673
+
674
+ final_state = llm.complete_until(
675
+ [{"role": "user", "content": "Solve this in stages"}],
676
+ think_state.assistant_prompt_prefix,
677
+ stop="<|im_end|>",
678
+ max_tokens=512,
679
+ )
680
+ ```
681
+
651
682
  #### Conversation History
652
683
 
653
684
  Inspect previous conversations:
@@ -597,6 +597,36 @@ answer = message.content
597
597
  reasoning = getattr(message, "reasoning_content", None)
598
598
  ```
599
599
 
600
+ For custom staged generation with explicit tags, `Qwen3LLM` also exposes
601
+ `complete_until()`:
602
+
603
+ ```python
604
+ from llm_utils import Qwen3LLM
605
+
606
+ llm = Qwen3LLM(model="Qwen/Qwen3-0.6B")
607
+
608
+ memory_state = llm.complete_until(
609
+ [{"role": "user", "content": "Solve this in stages"}],
610
+ "<memory>",
611
+ stop="</memory>",
612
+ max_tokens=256,
613
+ )
614
+
615
+ think_state = llm.complete_until(
616
+ [{"role": "user", "content": "Solve this in stages"}],
617
+ memory_state.assistant_prompt_prefix + "\n<think_efficient>",
618
+ stop="</think_efficient>",
619
+ max_tokens=512,
620
+ )
621
+
622
+ final_state = llm.complete_until(
623
+ [{"role": "user", "content": "Solve this in stages"}],
624
+ think_state.assistant_prompt_prefix,
625
+ stop="<|im_end|>",
626
+ max_tokens=512,
627
+ )
628
+ ```
629
+
600
630
  #### Conversation History
601
631
 
602
632
  Inspect previous conversations:
@@ -23,8 +23,8 @@ print()
23
23
  print('✓ Test 2: Generate from text')
24
24
  result = lm.generate('The answer is', max_tokens=10, temperature=0.5)
25
25
  print(f' Input: "The answer is"')
26
- print(f' Output: {result["text"][:50]}')
27
- assert 'text' in result
26
+ print(f' Output: {result.text[:50]}')
27
+ assert result.text is not None
28
28
  print()
29
29
 
30
30
  # Test 3: Generate from token IDs
@@ -32,10 +32,10 @@ print('✓ Test 3: Generate from token IDs')
32
32
  input_ids = lm.encode('Python is')
33
33
  result = lm.generate(input_ids, max_tokens=15, return_token_ids=True)
34
34
  print(f' Input IDs: {input_ids}')
35
- print(f' Output: {result["text"][:50]}')
36
- print(f' Output IDs: {result.get("token_ids", "N/A")}')
37
- assert 'text' in result
38
- assert 'token_ids' in result
35
+ print(f' Output: {result.text[:50]}')
36
+ print(f' Output IDs: {getattr(result, "token_ids", "N/A")}')
37
+ assert result.text is not None
38
+ assert hasattr(result, "token_ids") and result.token_ids is not None
39
39
  print()
40
40
 
41
41
  # Test 4: Multiple generations
@@ -43,7 +43,7 @@ print('✓ Test 4: Multiple generations (n=3)')
43
43
  results = lm.generate('AI is', max_tokens=10, n=3, temperature=0.8)
44
44
  print(f' Input: "AI is"')
45
45
  for i, r in enumerate(results, 1):
46
- print(f' {i}. {r["text"][:40]}')
46
+ print(f' {i}. {r.text[:40]}')
47
47
  # Note: Server may return fewer choices than requested
48
48
  assert isinstance(results, list)
49
49
  assert len(results) >= 1
@@ -53,8 +53,8 @@ print()
53
53
  print('✓ Test 5: Temperature control')
54
54
  low_temp = lm.generate('1 + 1 =', max_tokens=5, temperature=0.1, seed=42)
55
55
  high_temp = lm.generate('1 + 1 =', max_tokens=5, temperature=1.5, seed=123)
56
- print(f' Low temp (0.1): {low_temp["text"][:30]}')
57
- print(f' High temp (1.5): {high_temp["text"][:30]}')
56
+ print(f' Low temp (0.1): {low_temp.text[:30]}')
57
+ print(f' High temp (1.5): {high_temp.text[:30]}')
58
58
  print()
59
59
 
60
60
  # Test 6: Stop sequences
@@ -66,16 +66,16 @@ result = lm.generate(
66
66
  temperature=0.7,
67
67
  )
68
68
  print(f' Input: "Count: 1, 2, 3,"')
69
- print(f' Output (stop at ", 6"): {result["text"][:50]}')
69
+ print(f' Output (stop at ", 6"): {result.text[:50]}')
70
70
  print()
71
71
 
72
72
  # Test 7: Seed reproducibility
73
73
  print('✓ Test 7: Seed reproducibility')
74
74
  r1 = lm.generate('Test', max_tokens=10, seed=999, temperature=0.8)
75
75
  r2 = lm.generate('Test', max_tokens=10, seed=999, temperature=0.8)
76
- print(f' Run 1: {r1["text"][:40]}')
77
- print(f' Run 2: {r2["text"][:40]}')
78
- print(f' Match: {r1["text"] == r2["text"]}')
76
+ print(f' Run 1: {r1.text[:40]}')
77
+ print(f' Run 2: {r2.text[:40]}')
78
+ print(f' Match: {r1.text == r2.text}')
79
79
  print()
80
80
 
81
81
  print('=' * 60)
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Summary
4
4
 
5
- The `LLM.generate()` method provides a HuggingFace Transformers-style interface for low-level text generation, working directly with token IDs.
5
+ The `LLM.generate()` method provides a HuggingFace Transformers-style interface for prompt continuation via the completions API. For chat assistant-turn generation, use `LLM.chat_completion()`.
6
6
 
7
7
  ## Basic Signature
8
8
 
@@ -16,9 +16,17 @@ lm.generate(
16
16
  ) -> dict | list[dict]
17
17
  ```
18
18
 
19
+ ## Which method to use
20
+
21
+ - `generate()`: Continue a raw prompt through the completions API.
22
+ - `chat_completion()`: Generate the next assistant turn from chat messages.
23
+ - `pydantic_parse()`: Parse structured output from the chat completions API.
24
+
25
+ `enable_thinking` is primarily a chat-path control. `generate()` forwards it only for backends that support the same flag on the completions endpoint.
26
+
19
27
  ## Common Use Cases
20
28
 
21
- ### 1. Simple Generation
29
+ ### 1. Simple Prompt Continuation
22
30
  ```python
23
31
  result = lm.generate('Hello world', max_tokens=50)
24
32
  print(result['text'])
@@ -0,0 +1,73 @@
1
+ # Qwen3LLM Thinking Guide
2
+
3
+ `Qwen3LLM` is the only model-specific thinking helper kept in this repository.
4
+ It supports prefix continuation for Qwen3-style reasoning traces.
5
+ It also includes a raw prefix-conditioned helper for custom staged tag flows.
6
+
7
+ ## Usage
8
+
9
+ ```python
10
+ from llm_utils import Qwen3LLM
11
+
12
+ llm = Qwen3LLM(client=8000)
13
+
14
+ message = llm.chat_completion(
15
+ [{"role": "user", "content": "Solve x^2 + 2x + 1 = 0"}],
16
+ thinking_max_tokens=32,
17
+ content_max_tokens=128,
18
+ )
19
+ print(message)
20
+ ```
21
+
22
+ ## Custom Prefix Flows
23
+
24
+ Use `complete_until()` when you want to generate a custom tagged block and stop
25
+ at a specific closing tag instead of the built-in `<think>...</think>` flow.
26
+
27
+ ```python
28
+ from llm_utils import Qwen3LLM
29
+
30
+ llm = Qwen3LLM(client=8000)
31
+
32
+ memory_state = llm.complete_until(
33
+ [{"role": "user", "content": "Plan the answer in stages"}],
34
+ "<memory>",
35
+ stop="</memory>",
36
+ max_tokens=128,
37
+ )
38
+
39
+ think_state = llm.complete_until(
40
+ [{"role": "user", "content": "Plan the answer in stages"}],
41
+ memory_state.assistant_prompt_prefix + "\n<think_efficient>",
42
+ stop="</think_efficient>",
43
+ max_tokens=256,
44
+ )
45
+
46
+ final_state = llm.complete_until(
47
+ [{"role": "user", "content": "Plan the answer in stages"}],
48
+ think_state.assistant_prompt_prefix,
49
+ stop="<|im_end|>",
50
+ max_tokens=256,
51
+ )
52
+ ```
53
+
54
+ ## Behavior
55
+
56
+ - `chat_completion()` continues from a partial assistant prefix.
57
+ - The default prefix starts a `<think>` block.
58
+ - `thinking_max_tokens` caps the reasoning phase.
59
+ - `content_max_tokens` caps the final answer phase.
60
+ - `complete_until()` continues from a raw assistant prefix and stops on custom
61
+ tag boundaries.
62
+ - The returned `assistant_prompt_prefix` includes the generated text and the
63
+ matched stop token when generation ends on a stop sequence.
64
+
65
+ ## Notes
66
+
67
+ - Use `n=1`; prefix continuation is single-path only.
68
+ - The returned object is an OpenAI `ChatCompletionMessage`.
69
+ - The intermediate prefix state is internal; the returned value is an OpenAI
70
+ `ChatCompletionMessage`.
71
+ - The returned message includes `call_count`, which is `1` when the model
72
+ finishes in one staged call and `2` when it needs a reasoning call plus a
73
+ follow-up content call.
@@ -1,12 +1,16 @@
1
1
  # Tokenization and Generation Support in LLM
2
2
 
3
- The `LLM` class now includes built-in tokenization and low-level generation support through the `TokenizationMixin`, providing methods similar to HuggingFace Transformers.
3
+ The `LLM` class includes built-in tokenization plus two distinct generation styles:
4
+ - `generate()` for prompt continuation via the completions API
5
+ - `chat_completion()` for assistant-turn generation via the chat completions API
4
6
 
5
7
  ## Features
6
8
 
7
9
  - **encode()**: Convert text to token IDs
8
10
  - **decode()**: Convert token IDs back to text
9
- - **generate()**: HuggingFace-style generation with token-level control
11
+ - **generate()**: HuggingFace-style prompt continuation with token-level control
12
+ - **chat_completion()**: Chat-based assistant-turn generation from messages
13
+ - **pydantic_parse()**: Structured chat output parsing
10
14
  - Support for special tokens (BOS, EOS, etc.)
11
15
  - Optional token string output for debugging
12
16
 
@@ -94,6 +98,14 @@ def generate(
94
98
  - `dict`: Generation result with 'text', 'token_ids', 'finish_reason' (if n=1)
95
99
  - `list[dict]`: List of generation results (if n>1)
96
100
 
101
+ ### Which method to use
102
+
103
+ - Use `generate()` when you already have a raw prompt string and want the completions API to continue it with more tokens.
104
+ - Use `chat_completion()` when you want the chat completions API to produce the next assistant turn from messages.
105
+ - Use `pydantic_parse()` when you want structured output from the chat completions API.
106
+
107
+ `enable_thinking` is mainly meaningful on the chat-based paths. `generate()` passes it through for backends that honor `extra_body.chat_template_kwargs.enable_thinking` on the completions endpoint, but that support is backend-specific.
108
+
97
109
  ## Usage Examples
98
110
 
99
111
  ### Basic Encoding/Decoding
@@ -169,9 +181,9 @@ result = lm.decode(combined)
169
181
  print(result) # 'Helloworld'
170
182
  ```
171
183
 
172
- ## HuggingFace-Style Generation
184
+ ## Prompt Continuation with `generate()`
173
185
 
174
- The `generate()` method provides a low-level interface similar to HuggingFace Transformers:
186
+ The `generate()` method provides a low-level prompt-continuation interface similar to HuggingFace Transformers. It uses the completions API rather than the chat completions API.
175
187
 
176
188
  ### Basic Text Generation
177
189
 
@@ -184,6 +196,21 @@ result = lm.generate(
184
196
  print(result['text']) # ' Paris.'
185
197
  ```
186
198
 
199
+ ## Chat Assistant-Turn Generation
200
+
201
+ Use `chat_completion()` when you want the next assistant message from a list of chat messages:
202
+
203
+ ```python
204
+ message = lm.chat_completion(
205
+ [
206
+ {"role": "system", "content": "Be concise."},
207
+ {"role": "user", "content": "What is the capital of France?"},
208
+ ],
209
+ temperature=0.2,
210
+ )
211
+ print(message.content) # 'Paris.'
212
+ ```
213
+
187
214
  ### Working with Token IDs
188
215
 
189
216
  ```python
@@ -1,7 +1,7 @@
1
- """Example: Using the HuggingFace-style generate() method.
1
+ """Example: Using the prompt-continuation `generate()` method.
2
2
 
3
- This demonstrates the low-level generation interface that works
4
- directly with token IDs, similar to HuggingFace Transformers.
3
+ This demonstrates the low-level completions-style generation interface
4
+ for continuing a raw prompt, similar to HuggingFace Transformers.
5
5
  """
6
6
 
7
7
  from llm_utils.lm import LLM
@@ -10,7 +10,7 @@ from llm_utils.lm import LLM
10
10
  lm = LLM(client=8000)
11
11
 
12
12
  print('=' * 70)
13
- print('HuggingFace-Style Generation Examples')
13
+ print('Prompt Continuation Examples')
14
14
  print('=' * 70)
15
15
  print()
16
16
 
@@ -23,7 +23,7 @@ result = lm.generate(
23
23
  temperature=0.8,
24
24
  )
25
25
  print(f"Prompt: 'Write a haiku about coding:'")
26
- print(f"Generated:\n{result['text']}")
26
+ print(f"Generated:\n{result.text}")
27
27
  print()
28
28
 
29
29
  # Example 2: Working with token IDs
@@ -40,8 +40,8 @@ result = lm.generate(
40
40
  temperature=0.7,
41
41
  return_token_ids=True, # Get token IDs back
42
42
  )
43
- print(f'Output IDs: {result["token_ids"]}')
44
- print(f'Output text: {result["text"]}')
43
+ print(f'Output IDs: {result.token_ids}')
44
+ print(f'Output text: {result.text}')
45
45
  print()
46
46
 
47
47
  # Example 3: Temperature sampling
@@ -52,12 +52,12 @@ prompt = 'The best way to learn programming is'
52
52
  # Low temperature (more deterministic)
53
53
  result_low = lm.generate(prompt, max_tokens=40, temperature=0.1)
54
54
  print(f'Temperature 0.1 (deterministic):')
55
- print(f' {result_low["text"][:100]}...')
55
+ print(f' {result_low.text[:100]}...')
56
56
 
57
57
  # High temperature (more creative)
58
58
  result_high = lm.generate(prompt, max_tokens=40, temperature=1.5)
59
59
  print(f'Temperature 1.5 (creative):')
60
- print(f' {result_high["text"][:100]}...')
60
+ print(f' {result_high.text[:100]}...')
61
61
  print()
62
62
 
63
63
  # Example 4: Top-k and Top-p sampling
@@ -71,7 +71,7 @@ result = lm.generate(
71
71
  top_p=0.95, # Nucleus sampling
72
72
  repetition_penalty=1.2, # Reduce repetition
73
73
  )
74
- print(f'Generated: {result["text"]}')
74
+ print(f'Generated: {result.text}')
75
75
  print()
76
76
 
77
77
  # Example 5: Multiple generations (like num_return_sequences)
@@ -84,7 +84,7 @@ results = lm.generate(
84
84
  n=4, # Generate 4 different completions
85
85
  )
86
86
  for i, result in enumerate(results, 1):
87
- print(f'{i}. {result["text"]}')
87
+ print(f'{i}. {result.text}')
88
88
  print()
89
89
 
90
90
  # Example 6: Controlled generation with stop sequences
@@ -96,7 +96,7 @@ result = lm.generate(
96
96
  temperature=0.7,
97
97
  stop=['\n\n', 'Instructions:'], # Stop at these sequences
98
98
  )
99
- print(f'Generated:\n{result["text"]}')
99
+ print(f'Generated:\n{result.text}')
100
100
  print()
101
101
 
102
102
  # Example 7: Reproducible generation with seed
@@ -109,10 +109,10 @@ result1 = lm.generate(prompt, max_tokens=10, temperature=0.8, seed=42)
109
109
  result2 = lm.generate(prompt, max_tokens=10, temperature=0.8, seed=42)
110
110
  result3 = lm.generate(prompt, max_tokens=10, temperature=0.8, seed=123)
111
111
 
112
- print(f'Seed 42 (run 1): {result1["text"]}')
113
- print(f'Seed 42 (run 2): {result2["text"]}')
114
- print(f'Seed 123: {result3["text"]}')
115
- print(f'Results match: {result1["text"] == result2["text"]}')
112
+ print(f'Seed 42 (run 1): {result1.text}')
113
+ print(f'Seed 42 (run 2): {result2.text}')
114
+ print(f'Seed 123: {result3.text}')
115
+ print(f'Results match: {result1.text == result2.text}')
116
116
  print()
117
117
 
118
118
  # Example 8: Token count estimation
@@ -132,7 +132,7 @@ result = lm.generate(
132
132
  max_tokens=min(100, available_tokens),
133
133
  temperature=0.7,
134
134
  )
135
- generated_tokens = lm.encode(result['text'])
135
+ generated_tokens = lm.encode(result.text)
136
136
  print(f'Generated tokens: {len(generated_tokens)}')
137
137
  print(f'Total tokens used: {len(prompt_tokens) + len(generated_tokens)}')
138
138
  print()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "speedy-utils"
3
- version = "2.0.1"
3
+ version = "2.0.2"
4
4
  description = "Fast and easy-to-use package for data science"
5
5
  authors = [{ name = "AnhVTH", email = "anhvth.226@gmail.com" }]
6
6
  readme = "README.md"
@@ -23,6 +23,7 @@ dependencies = [
23
23
  "pydantic",
24
24
  "tqdm",
25
25
  "cachetools",
26
+ "dill",
26
27
  "ruff",
27
28
  "bump2version",
28
29
  "json-repair",
@@ -143,3 +144,8 @@ python_classes = ["Test*"]
143
144
  python_functions = ["test_*"]
144
145
  # Exclude debug and example directories from test discovery
145
146
  norecursedirs = ["debug", "examples", "experiments", "notebooks", "scripts", ".git", "__pycache__"]
147
+
148
+ [dependency-groups]
149
+ dev = [
150
+ "pytest-xdist>=3.8.0",
151
+ ]
@@ -0,0 +1,8 @@
1
+ {
2
+ "include": ["src", "tests"],
3
+ "exclude": ["debug", "examples", "scripts", "**/node_modules", "**/__pycache__", "**/.*", ".venv"],
4
+ "diagnosticSeverityOverrides": {
5
+ "reportUnsupportedDunderAll": "none"
6
+ },
7
+ "pythonVersion": "3.11"
8
+ }
@@ -4,7 +4,7 @@ from speedy_utils import *
4
4
  def do_something(x):
5
5
  x = 10
6
6
  y = 0
7
- x/y
7
+ x/y # type: ignore[operator]
8
8
 
9
9
 
10
10
  do_something(1)
@@ -10,7 +10,7 @@ def demo_1_simple_error():
10
10
  def process_item(x):
11
11
  # Intentional error - calling a list
12
12
  my_list = [1, 2, 3]
13
- return my_list(x)
13
+ return my_list(x) # type: ignore[operator]
14
14
 
15
15
  # try:
16
16
  multi_thread(process_item, range(300), workers=100, progress=True)
@@ -0,0 +1,17 @@
1
+ from speedy_utils import multi_process
2
+
3
+
4
+ def process_data(item):
5
+ # Mix of user variables and imported modules
6
+ data = {"value": item}
7
+ multiplier = 2
8
+
9
+ # This will cause an error
10
+ denominator = 0
11
+ final = data["value"] * multiplier / denominator
12
+ return final
13
+
14
+
15
+ if __name__ == "__main__":
16
+ inputs = range(5)
17
+ multi_process(process_data, inputs, backend="mp")