@modular-prompt/driver 0.16.0 → 0.17.1

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 (202) hide show
  1. package/README.md +98 -10
  2. package/dist/cache-controller.d.ts +4 -0
  3. package/dist/cache-controller.d.ts.map +1 -1
  4. package/dist/driver-registry/config-based-factory.d.ts +3 -0
  5. package/dist/driver-registry/config-based-factory.d.ts.map +1 -1
  6. package/dist/driver-registry/config-based-factory.js +8 -1
  7. package/dist/driver-registry/config-based-factory.js.map +1 -1
  8. package/dist/driver-registry/factory-helper.d.ts.map +1 -1
  9. package/dist/driver-registry/factory-helper.js +9 -2
  10. package/dist/driver-registry/factory-helper.js.map +1 -1
  11. package/dist/driver-registry/index.d.ts +1 -1
  12. package/dist/driver-registry/index.d.ts.map +1 -1
  13. package/dist/driver-registry/types.d.ts +15 -1
  14. package/dist/driver-registry/types.d.ts.map +1 -1
  15. package/dist/formatter/converter.d.ts.map +1 -1
  16. package/dist/formatter/converter.js +31 -2
  17. package/dist/formatter/converter.js.map +1 -1
  18. package/dist/google-genai/google-genai-driver.d.ts +1 -0
  19. package/dist/google-genai/google-genai-driver.d.ts.map +1 -1
  20. package/dist/google-genai/google-genai-driver.js +36 -27
  21. package/dist/google-genai/google-genai-driver.js.map +1 -1
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +5 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/local-inference/adapters.d.ts +6 -0
  27. package/dist/local-inference/adapters.d.ts.map +1 -1
  28. package/dist/local-inference/driver.d.ts.map +1 -1
  29. package/dist/local-inference/driver.js +45 -24
  30. package/dist/local-inference/driver.js.map +1 -1
  31. package/dist/local-inference/process-client.d.ts +4 -2
  32. package/dist/local-inference/process-client.d.ts.map +1 -1
  33. package/dist/local-inference/process-client.js +24 -8
  34. package/dist/local-inference/process-client.js.map +1 -1
  35. package/dist/local-inference/process-communication.d.ts +9 -2
  36. package/dist/local-inference/process-communication.d.ts.map +1 -1
  37. package/dist/local-inference/process-communication.js +37 -5
  38. package/dist/local-inference/process-communication.js.map +1 -1
  39. package/dist/local-inference/protocol.d.ts +4 -0
  40. package/dist/local-inference/protocol.d.ts.map +1 -1
  41. package/dist/local-inference/request-queue.d.ts +1 -1
  42. package/dist/local-inference/request-queue.d.ts.map +1 -1
  43. package/dist/local-inference/request-queue.js +26 -7
  44. package/dist/local-inference/request-queue.js.map +1 -1
  45. package/dist/local-inference/stream-utils.d.ts +6 -0
  46. package/dist/local-inference/stream-utils.d.ts.map +1 -1
  47. package/dist/local-inference/stream-utils.js.map +1 -1
  48. package/dist/mlx-ml/mlx-cache-controller.d.ts +9 -0
  49. package/dist/mlx-ml/mlx-cache-controller.d.ts.map +1 -1
  50. package/dist/mlx-ml/mlx-cache-controller.js +158 -33
  51. package/dist/mlx-ml/mlx-cache-controller.js.map +1 -1
  52. package/dist/mlx-ml/mlx-cache-support.d.ts +2 -2
  53. package/dist/mlx-ml/mlx-cache-support.d.ts.map +1 -1
  54. package/dist/mlx-ml/mlx-cache-support.js +8 -3
  55. package/dist/mlx-ml/mlx-cache-support.js.map +1 -1
  56. package/dist/mlx-ml/mlx-driver.d.ts +0 -1
  57. package/dist/mlx-ml/mlx-driver.d.ts.map +1 -1
  58. package/dist/mlx-ml/mlx-driver.js +1 -8
  59. package/dist/mlx-ml/mlx-driver.js.map +1 -1
  60. package/dist/mlx-ml/process/index.d.ts +1 -1
  61. package/dist/mlx-ml/process/index.d.ts.map +1 -1
  62. package/dist/mlx-ml/process/index.js +2 -2
  63. package/dist/mlx-ml/process/index.js.map +1 -1
  64. package/dist/models-config/index.d.ts +1 -1
  65. package/dist/models-config/index.d.ts.map +1 -1
  66. package/dist/models-config/index.js +1 -1
  67. package/dist/models-config/index.js.map +1 -1
  68. package/dist/models-config/resolve.d.ts +9 -1
  69. package/dist/models-config/resolve.d.ts.map +1 -1
  70. package/dist/models-config/resolve.js +94 -2
  71. package/dist/models-config/resolve.js.map +1 -1
  72. package/dist/models-config/types.d.ts +3 -1
  73. package/dist/models-config/types.d.ts.map +1 -1
  74. package/dist/pytorch/process/index.d.ts +4 -2
  75. package/dist/pytorch/process/index.d.ts.map +1 -1
  76. package/dist/pytorch/process/index.js +24 -7
  77. package/dist/pytorch/process/index.js.map +1 -1
  78. package/dist/pytorch/pytorch-cache-controller.d.ts +84 -0
  79. package/dist/pytorch/pytorch-cache-controller.d.ts.map +1 -0
  80. package/dist/pytorch/pytorch-cache-controller.js +742 -0
  81. package/dist/pytorch/pytorch-cache-controller.js.map +1 -0
  82. package/dist/pytorch/pytorch-cache-support.d.ts +23 -0
  83. package/dist/pytorch/pytorch-cache-support.d.ts.map +1 -0
  84. package/dist/pytorch/pytorch-cache-support.js +47 -0
  85. package/dist/pytorch/pytorch-cache-support.js.map +1 -0
  86. package/dist/pytorch/pytorch-driver.d.ts +8 -1
  87. package/dist/pytorch/pytorch-driver.d.ts.map +1 -1
  88. package/dist/pytorch/pytorch-driver.js +40 -0
  89. package/dist/pytorch/pytorch-driver.js.map +1 -1
  90. package/dist/runtime/check.d.ts.map +1 -1
  91. package/dist/runtime/check.js +8 -6
  92. package/dist/runtime/check.js.map +1 -1
  93. package/dist/runtime/index.d.ts +2 -2
  94. package/dist/runtime/index.d.ts.map +1 -1
  95. package/dist/runtime/index.js +2 -2
  96. package/dist/runtime/index.js.map +1 -1
  97. package/dist/runtime/manifest-core.d.mts +1 -0
  98. package/dist/runtime/manifest-core.mjs +1 -0
  99. package/dist/runtime/manifest-core.mjs.map +1 -1
  100. package/dist/runtime/manifest.d.ts +2 -0
  101. package/dist/runtime/manifest.d.ts.map +1 -1
  102. package/dist/runtime/manifest.js.map +1 -1
  103. package/dist/runtime/paths-core.d.mts +15 -1
  104. package/dist/runtime/paths-core.d.mts.map +1 -1
  105. package/dist/runtime/paths-core.mjs +50 -5
  106. package/dist/runtime/paths-core.mjs.map +1 -1
  107. package/dist/runtime/paths.d.ts +2 -2
  108. package/dist/runtime/paths.d.ts.map +1 -1
  109. package/dist/runtime/paths.js +2 -2
  110. package/dist/runtime/paths.js.map +1 -1
  111. package/dist/runtime/pytorch-template-core.d.mts +11 -0
  112. package/dist/runtime/pytorch-template-core.d.mts.map +1 -0
  113. package/dist/runtime/pytorch-template-core.mjs +54 -0
  114. package/dist/runtime/pytorch-template-core.mjs.map +1 -0
  115. package/dist/runtime/setup-commands-core.d.mts +3 -0
  116. package/dist/runtime/setup-commands-core.d.mts.map +1 -1
  117. package/dist/runtime/setup-commands-core.mjs +4 -0
  118. package/dist/runtime/setup-commands-core.mjs.map +1 -1
  119. package/dist/runtime/setup-commands.d.ts +1 -1
  120. package/dist/runtime/setup-commands.d.ts.map +1 -1
  121. package/dist/runtime/setup-commands.js +1 -1
  122. package/dist/runtime/setup-commands.js.map +1 -1
  123. package/dist/vertexai/vertexai-driver.d.ts +6 -0
  124. package/dist/vertexai/vertexai-driver.d.ts.map +1 -1
  125. package/dist/vertexai/vertexai-driver.js +106 -36
  126. package/dist/vertexai/vertexai-driver.js.map +1 -1
  127. package/docs/DRIVER_API.md +455 -0
  128. package/docs/LOCAL_MODEL_SETUP.md +765 -0
  129. package/docs/mlx-api-selection.md +301 -0
  130. package/package.json +10 -6
  131. package/scripts/runtime-cli.bin.test.ts +142 -0
  132. package/scripts/runtime-cli.js +305 -35
  133. package/scripts/runtime-cli.test.ts +163 -0
  134. package/skills/driver-usage/SKILL.md +29 -0
  135. package/src/mlx-ml/python/__main__.py +1 -1
  136. package/src/mlx-ml/python/backends/base.py +88 -18
  137. package/src/mlx-ml/python/backends/mlx_lm.py +28 -3
  138. package/src/mlx-ml/python/backends/mlx_vlm.py +679 -2
  139. package/src/mlx-ml/python/handlers/cache.py +4 -0
  140. package/src/mlx-ml/python/handlers/generate.py +33 -10
  141. package/src/mlx-ml/python/handlers/tokenize.py +1 -4
  142. package/src/mlx-ml/python/pyproject.toml +2 -2
  143. package/src/mlx-ml/python/server.py +2 -0
  144. package/src/mlx-ml/python/uv.lock +12 -12
  145. package/src/pytorch/templates/cpu-minimal/backends/base.py +139 -0
  146. package/src/pytorch/templates/cpu-minimal/backends/transformers_lm.py +1167 -0
  147. package/src/pytorch/{python → templates/cpu-minimal}/handlers/__init__.py +1 -0
  148. package/src/pytorch/templates/cpu-minimal/handlers/cache.py +88 -0
  149. package/src/pytorch/templates/cpu-minimal/handlers/generate.py +157 -0
  150. package/src/pytorch/{python → templates/cpu-minimal}/pyproject.toml +2 -2
  151. package/src/pytorch/{python → templates/cpu-minimal}/server.py +20 -2
  152. package/src/pytorch/templates/cpu-minimal/tests/test_cache_handler.py +284 -0
  153. package/src/pytorch/templates/cpu-minimal/tests/test_capabilities.py +14 -0
  154. package/src/pytorch/templates/cpu-minimal/tests/test_server.py +141 -0
  155. package/src/pytorch/templates/cpu-minimal/tests/test_transformers_errors.py +89 -0
  156. package/src/pytorch/templates/cpu-minimal/tests/test_transformers_lm_cache.py +554 -0
  157. package/src/pytorch/templates/cpu-minimal/utils/__init__.py +0 -0
  158. package/src/pytorch/{python → templates/cpu-minimal}/utils/token_utils.py +2 -2
  159. package/src/pytorch/templates/cpu-minimal/utils/transformers_errors.py +54 -0
  160. package/src/pytorch/{python → templates/cpu-minimal}/uv.lock +149 -109
  161. package/src/pytorch/templates/cuda/__main__.py +19 -0
  162. package/src/pytorch/templates/cuda/backends/__init__.py +3 -0
  163. package/src/pytorch/{python → templates/cuda}/backends/base.py +54 -6
  164. package/src/pytorch/templates/cuda/backends/transformers_lm.py +379 -0
  165. package/src/pytorch/templates/cuda/handlers/__init__.py +7 -0
  166. package/src/pytorch/templates/cuda/handlers/cache.py +93 -0
  167. package/src/pytorch/templates/cuda/handlers/cancel.py +53 -0
  168. package/src/pytorch/templates/cuda/handlers/capabilities.py +6 -0
  169. package/src/pytorch/templates/cuda/handlers/completion.py +15 -0
  170. package/src/pytorch/templates/cuda/handlers/format_test.py +70 -0
  171. package/src/pytorch/templates/cuda/handlers/generate.py +152 -0
  172. package/src/pytorch/templates/cuda/handlers/render.py +40 -0
  173. package/src/pytorch/templates/cuda/handlers/tokenize.py +63 -0
  174. package/src/pytorch/templates/cuda/pyproject.toml +37 -0
  175. package/src/pytorch/templates/cuda/server.py +158 -0
  176. package/src/pytorch/templates/cuda/tests/__init__.py +0 -0
  177. package/src/pytorch/templates/cuda/tests/test_cache_handler.py +207 -0
  178. package/src/pytorch/templates/cuda/tests/test_capabilities.py +14 -0
  179. package/src/pytorch/templates/cuda/tests/test_server.py +145 -0
  180. package/src/pytorch/templates/cuda/tests/test_transformers_errors.py +89 -0
  181. package/src/pytorch/templates/cuda/tests/test_transformers_lm_cache.py +288 -0
  182. package/src/pytorch/templates/cuda/utils/__init__.py +0 -0
  183. package/src/pytorch/templates/cuda/utils/chat_template_constraints.py +164 -0
  184. package/src/pytorch/templates/cuda/utils/prompt_builder.py +54 -0
  185. package/src/pytorch/templates/cuda/utils/template_render.py +80 -0
  186. package/src/pytorch/templates/cuda/utils/token_utils.py +376 -0
  187. package/src/pytorch/templates/cuda/utils/transformers_errors.py +54 -0
  188. package/src/pytorch/templates/cuda/uv.lock +734 -0
  189. package/src/pytorch/python/backends/transformers_lm.py +0 -127
  190. package/src/pytorch/python/handlers/generate.py +0 -68
  191. /package/src/pytorch/{python → templates/cpu-minimal}/__main__.py +0 -0
  192. /package/src/pytorch/{python → templates/cpu-minimal}/backends/__init__.py +0 -0
  193. /package/src/pytorch/{python → templates/cpu-minimal}/handlers/cancel.py +0 -0
  194. /package/src/pytorch/{python → templates/cpu-minimal}/handlers/capabilities.py +0 -0
  195. /package/src/pytorch/{python → templates/cpu-minimal}/handlers/completion.py +0 -0
  196. /package/src/pytorch/{python → templates/cpu-minimal}/handlers/format_test.py +0 -0
  197. /package/src/pytorch/{python → templates/cpu-minimal}/handlers/render.py +0 -0
  198. /package/src/pytorch/{python → templates/cpu-minimal}/handlers/tokenize.py +0 -0
  199. /package/src/pytorch/{python/utils → templates/cpu-minimal/tests}/__init__.py +0 -0
  200. /package/src/pytorch/{python → templates/cpu-minimal}/utils/chat_template_constraints.py +0 -0
  201. /package/src/pytorch/{python → templates/cpu-minimal}/utils/prompt_builder.py +0 -0
  202. /package/src/pytorch/{python → templates/cpu-minimal}/utils/template_render.py +0 -0
@@ -18,6 +18,8 @@ def handle_cache_prefill(
18
18
  prefix_hashes: list[str] | None = None,
19
19
  tools: list | None = None,
20
20
  reasoning_effort: str | None = None,
21
+ images: list | None = None,
22
+ max_image_size: int = 768,
21
23
  ) -> None:
22
24
  tokenizer = backend.get_tokenizer()
23
25
 
@@ -74,6 +76,8 @@ def handle_cache_prefill(
74
76
  trim_to_tokens=trim_to_tokens,
75
77
  prefix_offsets=prefix_offsets,
76
78
  prefix_hashes=prefix_hashes,
79
+ images=images,
80
+ max_image_size=max_image_size,
77
81
  )
78
82
  if prefix_offsets and prefix_hashes:
79
83
  result["prefix_offsets"] = prefix_offsets
@@ -6,7 +6,6 @@ import re
6
6
  import sys
7
7
 
8
8
  from backends.base import ModelBackend
9
- from mlx_lm.models.cache import trim_prompt_cache
10
9
  from handlers.cancel import poll_cancel
11
10
 
12
11
 
@@ -29,6 +28,7 @@ def _stream_to_stdout(
29
28
  images: list | None = None,
30
29
  primer: str | None = None,
31
30
  prompt_cache: list | None = None,
31
+ cache_loaded: bool | None = None,
32
32
  ) -> None:
33
33
  if primer is not None:
34
34
  print(primer, end="", flush=True)
@@ -46,6 +46,8 @@ def _stream_to_stdout(
46
46
  meta["prompt_tokens"] = last_response.prompt_tokens
47
47
  if hasattr(last_response, "generation_tokens"):
48
48
  meta["generation_tokens"] = last_response.generation_tokens
49
+ if cache_loaded is not None:
50
+ meta["cache_loaded"] = cache_loaded
49
51
 
50
52
  if meta:
51
53
  print(f"\x1e__META__:{json.dumps(meta)}", end="\0", flush=True)
@@ -84,16 +86,22 @@ def handle_generate(
84
86
  else:
85
87
  sys.stderr.write(f"--- prompt\n{prompt}\n")
86
88
 
87
- # VLM 経路では KV キャッシュを使わない(旧 chat ハンドラと同様)
88
89
  prompt_cache = None
89
90
  cache_tokens = 0
90
- if cache_path and not images:
91
- prompt_cache = backend.load_cache_from_file(cache_path)
91
+ cache_loaded = None
92
+ if cache_path:
93
+ prompt_cache = backend.load_cache_from_file(
94
+ cache_path,
95
+ images=images,
96
+ max_image_size=max_image_size,
97
+ prompt=prompt,
98
+ )
99
+ cache_loaded = prompt_cache is not None
92
100
  if prompt_cache is not None:
93
101
  if cache_trim_tokens is not None:
94
102
  current_offset = backend.get_cache_offset(prompt_cache)
95
103
  if current_offset > cache_trim_tokens:
96
- trim_prompt_cache(prompt_cache, current_offset - cache_trim_tokens)
104
+ backend.trim_cache(prompt_cache, current_offset - cache_trim_tokens)
97
105
  sys.stderr.write(
98
106
  f"KV cache trimmed: {current_offset} → {cache_trim_tokens} tokens\n"
99
107
  )
@@ -101,9 +109,21 @@ def handle_generate(
101
109
  else:
102
110
  cache_tokens = current_offset
103
111
  else:
104
- meta_count = _read_cache_token_count(cache_path) if cache_path else None
112
+ meta_count = (
113
+ _read_cache_token_count(cache_path)
114
+ if cache_path
115
+ else None
116
+ )
105
117
  if meta_count is not None:
106
118
  cache_tokens = meta_count
119
+ elif (
120
+ backend.model_kind == "vlm"
121
+ and cache_path is not None
122
+ and cache_path.startswith("mlx-vlm-memory://")
123
+ ):
124
+ # Keep the Phase 1 in-process ref as a compatibility fallback.
125
+ # Persistent VLM refs always have the sidecar above.
126
+ cache_tokens = backend.get_cache_offset(prompt_cache)
107
127
  else:
108
128
  sys.stderr.write(
109
129
  f"WARNING: Cache file exists but no .meta.json found at {cache_path}. "
@@ -111,6 +131,7 @@ def handle_generate(
111
131
  )
112
132
  prompt_cache = None
113
133
  cache_tokens = 0
134
+ cache_loaded = False
114
135
  if prompt_cache is not None:
115
136
  sys.stderr.write(
116
137
  f"KV cache loaded: {len(prompt_cache)} layers, {cache_tokens} cached tokens\n"
@@ -122,11 +143,11 @@ def handle_generate(
122
143
 
123
144
  effective_prompt = prompt
124
145
  if prompt_cache is not None and cache_tokens > 0 and isinstance(prompt, str):
125
- tokenizer = backend.get_tokenizer()
126
- add_special = tokenizer.bos_token is None or not prompt.startswith(
127
- tokenizer.bos_token
146
+ full_tokens = backend.tokenize_prompt(
147
+ prompt,
148
+ images=images,
149
+ max_image_size=max_image_size,
128
150
  )
129
- full_tokens = tokenizer.encode(prompt, add_special_tokens=add_special)
130
151
 
131
152
  if cache_tokens < len(full_tokens):
132
153
  effective_prompt = full_tokens[cache_tokens:]
@@ -140,6 +161,7 @@ def handle_generate(
140
161
  f"ignoring prefill state\n"
141
162
  )
142
163
  prompt_cache = None
164
+ cache_loaded = False
143
165
 
144
166
  _stream_to_stdout(
145
167
  backend,
@@ -148,4 +170,5 @@ def handle_generate(
148
170
  images=images,
149
171
  primer=primer,
150
172
  prompt_cache=prompt_cache,
173
+ cache_loaded=cache_loaded,
151
174
  )
@@ -50,10 +50,7 @@ def handle_tokenize(
50
50
  prompt = generate_merged_prompt(messages, capabilities)
51
51
 
52
52
  # トークン化
53
- add_special = tokenizer.bos_token is None or not prompt.startswith(
54
- tokenizer.bos_token or ""
55
- )
56
- token_ids = tokenizer.encode(prompt, add_special_tokens=add_special)
53
+ token_ids = backend.tokenize_prompt(prompt)
57
54
 
58
55
  result["token_ids"] = token_ids
59
56
  result["token_count"] = len(token_ids)
@@ -8,9 +8,9 @@ dependencies = [
8
8
  "hf-xet==1.2.0",
9
9
  "jinja2==3.1.6",
10
10
  "mlx>=0.32.2; sys_platform == 'darwin'",
11
- "mlx-lm==0.31.3; sys_platform == 'darwin'",
11
+ "mlx-lm==0.32.0; sys_platform == 'darwin'",
12
12
  "mlx-optiq>=0.4.4; sys_platform == 'darwin'",
13
- "mlx-vlm==0.6.17; sys_platform == 'darwin'",
13
+ "mlx-vlm==0.7.4; sys_platform == 'darwin'",
14
14
  "tokenizers==0.22.2",
15
15
  "torch==2.9.1",
16
16
  "torchvision==0.24.1",
@@ -100,6 +100,8 @@ class Server:
100
100
  prefix_hashes=req.get('prefix_hashes'),
101
101
  tools=req.get('tools'),
102
102
  reasoning_effort=req.get('reasoning_effort'),
103
+ images=req.get('images'),
104
+ max_image_size=req.get('maxImageSize', 768),
103
105
  )
104
106
 
105
107
  elif method == 'render':
@@ -724,22 +724,21 @@ wheels = [
724
724
 
725
725
  [[package]]
726
726
  name = "mlx-audio"
727
- version = "0.4.3"
727
+ version = "0.5.3"
728
728
  source = { registry = "https://pypi.org/simple" }
729
729
  dependencies = [
730
730
  { name = "huggingface-hub", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
731
731
  { name = "miniaudio", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
732
732
  { name = "mlx", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
733
- { name = "mlx-lm", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
734
733
  { name = "numpy", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
735
734
  { name = "scipy", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
736
735
  { name = "sounddevice", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
737
736
  { name = "tqdm", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
738
737
  { name = "transformers", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
739
738
  ]
740
- sdist = { url = "https://files.pythonhosted.org/packages/95/db/a9f95e3794eca373d681220c8b9f8f84451a0d14959f85cc341ca592394c/mlx_audio-0.4.3.tar.gz", hash = "sha256:8e87badf56a0f73bf91e3797b1195c01440a181cf0b64a2a08dc1bda4b037f54", size = 1144947, upload-time = "2026-04-28T20:18:12.09Z" }
739
+ sdist = { url = "https://files.pythonhosted.org/packages/60/e8/54d02c8bb50fc5b2cae0fbcb459acf78f1fbb316f20f758a8f56824aa407/mlx_audio-0.5.3.tar.gz", hash = "sha256:9e96f1ee68bc81e7ba74d85b8eab213384c8cd8c6f66ccd12034ded7cb5cd167", size = 1709447, upload-time = "2026-09-07T19:44:40.285Z" }
741
740
  wheels = [
742
- { url = "https://files.pythonhosted.org/packages/e5/25/0a89073ed7b7cdf34299042bd03d867c12c0c8b43f597be61bea7f146793/mlx_audio-0.4.3-py3-none-any.whl", hash = "sha256:6b87bf42d79d9ceb6b9310a77656b9b76429c2d6ddd89f634b2786c58a2e4721", size = 1373582, upload-time = "2026-04-28T20:18:10.512Z" },
741
+ { url = "https://files.pythonhosted.org/packages/84/c2/2876e6ff4811b1d6a43e1480947dd6580d02dba16f34724b2e3645090997/mlx_audio-0.5.3-py3-none-any.whl", hash = "sha256:8d920b2dcbcf37b5fd2bbb070e21a9a33442bf231a754e0d39387e65986bd830", size = 2057098, upload-time = "2026-09-07T19:44:38.581Z" },
743
742
  ]
744
743
 
745
744
  [[package]]
@@ -771,9 +770,9 @@ requires-dist = [
771
770
  { name = "hf-xet", specifier = "==1.2.0" },
772
771
  { name = "jinja2", specifier = "==3.1.6" },
773
772
  { name = "mlx", marker = "sys_platform == 'darwin'", specifier = ">=0.32.2" },
774
- { name = "mlx-lm", marker = "sys_platform == 'darwin'", specifier = "==0.31.3" },
773
+ { name = "mlx-lm", marker = "sys_platform == 'darwin'", specifier = "==0.32.0" },
775
774
  { name = "mlx-optiq", marker = "sys_platform == 'darwin'", specifier = ">=0.4.4" },
776
- { name = "mlx-vlm", marker = "sys_platform == 'darwin'", specifier = "==0.6.17" },
775
+ { name = "mlx-vlm", marker = "sys_platform == 'darwin'", specifier = "==0.7.4" },
777
776
  { name = "tokenizers", specifier = "==0.22.2" },
778
777
  { name = "torch", specifier = "==2.9.1" },
779
778
  { name = "torchvision", specifier = "==0.24.1" },
@@ -785,7 +784,7 @@ dev = [{ name = "pytest", specifier = ">=9.0" }]
785
784
 
786
785
  [[package]]
787
786
  name = "mlx-lm"
788
- version = "0.31.3"
787
+ version = "0.32.0"
789
788
  source = { registry = "https://pypi.org/simple" }
790
789
  dependencies = [
791
790
  { name = "jinja2", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
@@ -796,9 +795,9 @@ dependencies = [
796
795
  { name = "sentencepiece", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
797
796
  { name = "transformers", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
798
797
  ]
799
- sdist = { url = "https://files.pythonhosted.org/packages/84/94/9a38d6b0c6fcca995b9136c94eb7da1e9c5165652edf228b96b29960fa7a/mlx_lm-0.31.3.tar.gz", hash = "sha256:61eb0e3ba09444f77f874aff295401d7ccd20b39495cbbce0c782a15474ce733", size = 304318, upload-time = "2026-04-22T07:37:27.922Z" }
798
+ sdist = { url = "https://files.pythonhosted.org/packages/96/6f/c87a4e092194c77b39e65de7979147314e4486d9451e873b4124ba2d6dcd/mlx_lm-0.32.0.tar.gz", hash = "sha256:035ca44b08c3476630a205d2c9bdea26825a6a8359f974944d4985230e41859f", size = 412082, upload-time = "2026-10-01T04:44:42.681Z" }
800
799
  wheels = [
801
- { url = "https://files.pythonhosted.org/packages/90/02/9a67b8e4f87e3e2e5cd7b1ad79304b93c09a0db6af34bee75e6551c06c60/mlx_lm-0.31.3-py3-none-any.whl", hash = "sha256:758cfddf1180053b7613db76fad3d246a331a2a905808e1164a275621fc983b8", size = 408890, upload-time = "2026-04-22T07:37:25.965Z" },
800
+ { url = "https://files.pythonhosted.org/packages/88/ed/99cd7d36a1671851b41df06b59b73825bb95b8ab8b388ac871ddf9ae1523/mlx_lm-0.32.0-py3-none-any.whl", hash = "sha256:9a0f3843135fd88d36ad791f0d82210cdc4130c5c1cb5416f5f0c4b56a135778", size = 513667, upload-time = "2026-10-01T04:44:40.74Z" },
802
801
  ]
803
802
 
804
803
  [[package]]
@@ -846,10 +845,11 @@ wheels = [
846
845
 
847
846
  [[package]]
848
847
  name = "mlx-vlm"
849
- version = "0.6.17"
848
+ version = "0.7.4"
850
849
  source = { registry = "https://pypi.org/simple" }
851
850
  dependencies = [
852
851
  { name = "fastapi", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
852
+ { name = "jinja2", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
853
853
  { name = "llguidance", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
854
854
  { name = "miniaudio", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
855
855
  { name = "mlx", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
@@ -866,9 +866,9 @@ dependencies = [
866
866
  { name = "uvicorn", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
867
867
  { name = "websockets", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
868
868
  ]
869
- sdist = { url = "https://files.pythonhosted.org/packages/c5/a1/4b21ea85918e5223144bbe80cb649f5953c105ea440f0eba45dd00a5b863/mlx_vlm-0.6.17.tar.gz", hash = "sha256:5e4ff5426af093b3cf41b4e5e33c272db82fa33000cb3c36a1aab9e218ad7ee7", size = 2043597, upload-time = "2026-08-26T17:08:38.01Z" }
869
+ sdist = { url = "https://files.pythonhosted.org/packages/70/f0/66a399de592460882ff598ca6b398fdda7aad7155a84c74cd4b5b4a42e87/mlx_vlm-0.7.4.tar.gz", hash = "sha256:a35ca38c5be3a061496cc9bb5b3ada20413ceda9317ec67ba0b0fff1c2885e04", size = 2500846, upload-time = "2026-09-28T19:08:27.464Z" }
870
870
  wheels = [
871
- { url = "https://files.pythonhosted.org/packages/f8/42/f275566dba66b54c21e039344c4fb90fd3a3749fc251754545249b45dc8d/mlx_vlm-0.6.17-py3-none-any.whl", hash = "sha256:d24723244ef89b2a6140bec45e38c078689ea89450edfc0e348ad149f5af3b1c", size = 2590205, upload-time = "2026-08-26T17:08:36.148Z" },
871
+ { url = "https://files.pythonhosted.org/packages/2a/7a/b94b11f2c738ac9890c1cdbd9ca2c3396755efefc6af683f54b6a861b71a/mlx_vlm-0.7.4-py3-none-any.whl", hash = "sha256:7a3e8e288b21115630c4eab75e9ffc38e4b1770f9d2ce4992d29ecffe75a628c", size = 3138891, upload-time = "2026-09-28T19:08:25.594Z" },
872
872
  ]
873
873
 
874
874
  [[package]]
@@ -0,0 +1,139 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Iterator
3
+
4
+
5
+ class ModelBackend(ABC):
6
+ """Abstract base class for model backends."""
7
+
8
+ @abstractmethod
9
+ def load(self, model_name: str) -> None:
10
+ """Load the target model."""
11
+ raise NotImplementedError
12
+
13
+ @abstractmethod
14
+ def get_tokenizer(self) -> Any:
15
+ """Return the tokenizer or processor."""
16
+ raise NotImplementedError
17
+
18
+ @abstractmethod
19
+ def stream_generate(
20
+ self, prompt: str | list[int], options: dict, images: list | None = None,
21
+ prompt_cache: Any | None = None,
22
+ ) -> Iterator[Any]:
23
+ """Stream generation results."""
24
+ raise NotImplementedError
25
+
26
+ @abstractmethod
27
+ def supports_vision(self) -> bool:
28
+ """Return whether image input is supported."""
29
+ raise NotImplementedError
30
+
31
+ @property
32
+ @abstractmethod
33
+ def model_kind(self) -> str:
34
+ """Return "lm" or "vlm"."""
35
+ raise NotImplementedError
36
+
37
+ def load_drafter(self, drafter_model: str) -> None:
38
+ """Load a drafter model for speculative decoding."""
39
+ raise NotImplementedError(
40
+ f"{type(self).__name__} does not support drafter models"
41
+ )
42
+
43
+ def has_drafter(self) -> bool:
44
+ """Return whether a drafter model is loaded."""
45
+ return False
46
+
47
+ def cache_prefill(
48
+ self,
49
+ cache_path: str,
50
+ prompt: str,
51
+ base_cache_path: str | None = None,
52
+ trim_to_tokens: int | None = None,
53
+ prefix_offsets: list[int] | None = None,
54
+ prefix_hashes: list[str] | None = None,
55
+ images: list | None = None,
56
+ max_image_size: int = 768,
57
+ ) -> dict:
58
+ """Build a KV cache from a prompt prefix."""
59
+ raise NotImplementedError(
60
+ f"{type(self).__name__} does not support prompt caching"
61
+ )
62
+
63
+ def consume_cache_write_tokens(self, cache_path: str) -> int:
64
+ """Return and clear write usage pending for a cache reference."""
65
+ return 0
66
+
67
+ def trim_cache(self, prompt_cache: Any, tokens: int) -> Any:
68
+ """Remove trailing tokens from a backend-owned prompt cache."""
69
+ raise NotImplementedError(
70
+ f"{type(self).__name__} does not support prompt cache trimming"
71
+ )
72
+
73
+ def tokenize_prompt(
74
+ self,
75
+ prompt: str,
76
+ images: list | None = None,
77
+ max_image_size: int = 768,
78
+ ) -> list[int]:
79
+ """Tokenize a rendered prompt using the backend's prompt rules."""
80
+ tokenizer = self.get_tokenizer()
81
+ bos_token = getattr(tokenizer, "bos_token", None)
82
+ add_special = bos_token is None or not prompt.startswith(bos_token or "")
83
+ token_ids = tokenizer.encode(prompt, add_special_tokens=add_special)
84
+ if hasattr(token_ids, "flatten"):
85
+ token_ids = token_ids.flatten().tolist()
86
+ return [int(token_id) for token_id in token_ids]
87
+
88
+ def load_cache_from_file(
89
+ self,
90
+ cache_path: str,
91
+ images: list | None = None,
92
+ max_image_size: int = 768,
93
+ prompt: str | list[int] | None = None,
94
+ prefix_token_count: int | None = None,
95
+ ) -> Any | None:
96
+ """Load a prompt cache, optionally validating only a prompt prefix."""
97
+ return None
98
+
99
+ def get_cache_offset(self, prompt_cache: Any) -> int:
100
+ """Get the number of tokens stored in a loaded prompt cache."""
101
+ if not prompt_cache:
102
+ return 0
103
+
104
+ get_seq_length = getattr(prompt_cache, "get_seq_length", None)
105
+ if callable(get_seq_length):
106
+ try:
107
+ return int(get_seq_length())
108
+ except Exception:
109
+ pass
110
+
111
+ keys = getattr(prompt_cache, "keys", None)
112
+ if keys is not None:
113
+ try:
114
+ return int(keys.shape[-2])
115
+ except Exception:
116
+ pass
117
+
118
+ layers = getattr(prompt_cache, "layers", None)
119
+ if layers:
120
+ return self.get_cache_offset(layers[0])
121
+
122
+ layer0 = prompt_cache[0]
123
+ if hasattr(layer0, 'offset'):
124
+ off = layer0.offset
125
+ return int(off.item() if hasattr(off, 'item') else off)
126
+ if hasattr(layer0, 'caches'):
127
+ for c in layer0.caches:
128
+ if hasattr(c, 'offset'):
129
+ off = c.offset
130
+ return int(off.item() if hasattr(off, 'item') else off)
131
+ try:
132
+ key = layer0[0] if isinstance(layer0, (list, tuple)) else layer0
133
+ shape = key.shape
134
+ return int(shape[-2] if len(shape) >= 2 else shape[0])
135
+ except Exception:
136
+ pass
137
+ if hasattr(layer0, 'keys') and layer0.keys is not None:
138
+ return int(layer0.keys.shape[-2])
139
+ return 0