@modular-prompt/driver 0.14.0 → 0.15.0

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 (205) hide show
  1. package/README.md +58 -5
  2. package/dist/driver-registry/ai-service.d.ts +23 -1
  3. package/dist/driver-registry/ai-service.d.ts.map +1 -1
  4. package/dist/driver-registry/ai-service.js +44 -10
  5. package/dist/driver-registry/ai-service.js.map +1 -1
  6. package/dist/driver-registry/config-based-factory.d.ts.map +1 -1
  7. package/dist/driver-registry/config-based-factory.js +16 -0
  8. package/dist/driver-registry/config-based-factory.js.map +1 -1
  9. package/dist/driver-registry/factory-helper.d.ts +2 -0
  10. package/dist/driver-registry/factory-helper.d.ts.map +1 -1
  11. package/dist/driver-registry/factory-helper.js +21 -3
  12. package/dist/driver-registry/factory-helper.js.map +1 -1
  13. package/dist/driver-registry/index.d.ts +2 -2
  14. package/dist/driver-registry/index.d.ts.map +1 -1
  15. package/dist/driver-registry/index.js +1 -1
  16. package/dist/driver-registry/index.js.map +1 -1
  17. package/dist/driver-registry/registry.d.ts.map +1 -1
  18. package/dist/driver-registry/registry.js +3 -1
  19. package/dist/driver-registry/registry.js.map +1 -1
  20. package/dist/driver-registry/types.d.ts +8 -2
  21. package/dist/driver-registry/types.d.ts.map +1 -1
  22. package/dist/index.d.ts +10 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +9 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/local-inference/adapters.d.ts +66 -0
  27. package/dist/local-inference/adapters.d.ts.map +1 -0
  28. package/dist/local-inference/adapters.js +2 -0
  29. package/dist/local-inference/adapters.js.map +1 -0
  30. package/dist/local-inference/driver.d.ts +51 -0
  31. package/dist/local-inference/driver.d.ts.map +1 -0
  32. package/dist/local-inference/driver.js +309 -0
  33. package/dist/local-inference/driver.js.map +1 -0
  34. package/dist/local-inference/index.d.ts +22 -0
  35. package/dist/local-inference/index.d.ts.map +1 -0
  36. package/dist/local-inference/index.js +17 -0
  37. package/dist/local-inference/index.js.map +1 -0
  38. package/dist/local-inference/process-client.d.ts +50 -0
  39. package/dist/local-inference/process-client.d.ts.map +1 -0
  40. package/dist/local-inference/process-client.js +92 -0
  41. package/dist/local-inference/process-client.js.map +1 -0
  42. package/dist/local-inference/process-communication.d.ts +41 -0
  43. package/dist/local-inference/process-communication.d.ts.map +1 -0
  44. package/dist/{mlx-ml/process → local-inference}/process-communication.js +25 -61
  45. package/dist/local-inference/process-communication.js.map +1 -0
  46. package/dist/local-inference/process-port.d.ts +12 -0
  47. package/dist/local-inference/process-port.d.ts.map +1 -0
  48. package/dist/local-inference/process-port.js +2 -0
  49. package/dist/local-inference/process-port.js.map +1 -0
  50. package/dist/local-inference/prompt-utils.d.ts +6 -0
  51. package/dist/local-inference/prompt-utils.d.ts.map +1 -0
  52. package/dist/local-inference/prompt-utils.js +17 -0
  53. package/dist/local-inference/prompt-utils.js.map +1 -0
  54. package/dist/local-inference/protocol.d.ts +192 -0
  55. package/dist/local-inference/protocol.d.ts.map +1 -0
  56. package/dist/local-inference/protocol.js +2 -0
  57. package/dist/local-inference/protocol.js.map +1 -0
  58. package/dist/local-inference/queue-types.d.ts +54 -0
  59. package/dist/local-inference/queue-types.d.ts.map +1 -0
  60. package/dist/local-inference/queue-types.js +2 -0
  61. package/dist/local-inference/queue-types.js.map +1 -0
  62. package/dist/local-inference/request-queue.d.ts +36 -0
  63. package/dist/local-inference/request-queue.d.ts.map +1 -0
  64. package/dist/{mlx-ml/process/queue.js → local-inference/request-queue.js} +83 -56
  65. package/dist/local-inference/request-queue.js.map +1 -0
  66. package/dist/local-inference/stream-utils.d.ts +19 -0
  67. package/dist/local-inference/stream-utils.d.ts.map +1 -0
  68. package/dist/local-inference/stream-utils.js +76 -0
  69. package/dist/local-inference/stream-utils.js.map +1 -0
  70. package/dist/mlx-ml/mlx-cache-support.d.ts +23 -0
  71. package/dist/mlx-ml/mlx-cache-support.d.ts.map +1 -0
  72. package/dist/mlx-ml/mlx-cache-support.js +45 -0
  73. package/dist/mlx-ml/mlx-cache-support.js.map +1 -0
  74. package/dist/mlx-ml/mlx-driver.d.ts +20 -59
  75. package/dist/mlx-ml/mlx-driver.d.ts.map +1 -1
  76. package/dist/mlx-ml/mlx-driver.js +86 -460
  77. package/dist/mlx-ml/mlx-driver.js.map +1 -1
  78. package/dist/mlx-ml/mlx-local-inference-adapters.d.ts +3 -0
  79. package/dist/mlx-ml/mlx-local-inference-adapters.d.ts.map +1 -0
  80. package/dist/mlx-ml/mlx-local-inference-adapters.js +19 -0
  81. package/dist/mlx-ml/mlx-local-inference-adapters.js.map +1 -0
  82. package/dist/mlx-ml/mlx-options.d.ts +19 -0
  83. package/dist/mlx-ml/mlx-options.d.ts.map +1 -0
  84. package/dist/mlx-ml/mlx-options.js +30 -0
  85. package/dist/mlx-ml/mlx-options.js.map +1 -0
  86. package/dist/mlx-ml/process/index.d.ts +10 -8
  87. package/dist/mlx-ml/process/index.d.ts.map +1 -1
  88. package/dist/mlx-ml/process/index.js +73 -54
  89. package/dist/mlx-ml/process/index.js.map +1 -1
  90. package/dist/mlx-ml/process/model-specific.d.ts +2 -1
  91. package/dist/mlx-ml/process/model-specific.d.ts.map +1 -1
  92. package/dist/mlx-ml/process/model-specific.js.map +1 -1
  93. package/dist/mlx-ml/process/prompt-builder.d.ts +11 -0
  94. package/dist/mlx-ml/process/prompt-builder.d.ts.map +1 -0
  95. package/dist/mlx-ml/process/prompt-builder.js +51 -0
  96. package/dist/mlx-ml/process/prompt-builder.js.map +1 -0
  97. package/dist/mlx-ml/process/types.d.ts +15 -183
  98. package/dist/mlx-ml/process/types.d.ts.map +1 -1
  99. package/dist/mlx-ml/types.d.ts +2 -45
  100. package/dist/mlx-ml/types.d.ts.map +1 -1
  101. package/dist/models-config/index.d.ts +8 -0
  102. package/dist/models-config/index.d.ts.map +1 -0
  103. package/dist/models-config/index.js +7 -0
  104. package/dist/models-config/index.js.map +1 -0
  105. package/dist/models-config/loader.d.ts +20 -0
  106. package/dist/models-config/loader.d.ts.map +1 -0
  107. package/dist/models-config/loader.js +85 -0
  108. package/dist/models-config/loader.js.map +1 -0
  109. package/dist/models-config/paths.d.ts +7 -0
  110. package/dist/models-config/paths.d.ts.map +1 -0
  111. package/dist/models-config/paths.js +11 -0
  112. package/dist/models-config/paths.js.map +1 -0
  113. package/dist/models-config/resolve.d.ts +57 -0
  114. package/dist/models-config/resolve.d.ts.map +1 -0
  115. package/dist/models-config/resolve.js +187 -0
  116. package/dist/models-config/resolve.js.map +1 -0
  117. package/dist/models-config/types.d.ts +60 -0
  118. package/dist/models-config/types.d.ts.map +1 -0
  119. package/dist/models-config/types.js +5 -0
  120. package/dist/models-config/types.js.map +1 -0
  121. package/dist/pytorch/process/index.d.ts +35 -0
  122. package/dist/pytorch/process/index.d.ts.map +1 -0
  123. package/dist/pytorch/process/index.js +69 -0
  124. package/dist/pytorch/process/index.js.map +1 -0
  125. package/dist/pytorch/pytorch-driver.d.ts +35 -0
  126. package/dist/pytorch/pytorch-driver.d.ts.map +1 -0
  127. package/dist/pytorch/pytorch-driver.js +48 -0
  128. package/dist/pytorch/pytorch-driver.js.map +1 -0
  129. package/dist/pytorch/pytorch-local-inference-adapters.d.ts +3 -0
  130. package/dist/pytorch/pytorch-local-inference-adapters.d.ts.map +1 -0
  131. package/dist/pytorch/pytorch-local-inference-adapters.js +19 -0
  132. package/dist/pytorch/pytorch-local-inference-adapters.js.map +1 -0
  133. package/dist/pytorch/pytorch-options.d.ts +8 -0
  134. package/dist/pytorch/pytorch-options.d.ts.map +1 -0
  135. package/dist/pytorch/pytorch-options.js +21 -0
  136. package/dist/pytorch/pytorch-options.js.map +1 -0
  137. package/dist/query-logger.js +1 -1
  138. package/dist/query-logger.js.map +1 -1
  139. package/dist/runtime/check.d.ts +10 -0
  140. package/dist/runtime/check.d.ts.map +1 -0
  141. package/dist/runtime/check.js +24 -0
  142. package/dist/runtime/check.js.map +1 -0
  143. package/dist/runtime/index.d.ts +4 -0
  144. package/dist/runtime/index.d.ts.map +1 -0
  145. package/dist/runtime/index.js +4 -0
  146. package/dist/runtime/index.js.map +1 -0
  147. package/dist/runtime/manifest-core.d.mts +27 -0
  148. package/dist/runtime/manifest-core.d.mts.map +1 -0
  149. package/dist/runtime/manifest-core.mjs +68 -0
  150. package/dist/runtime/manifest-core.mjs.map +1 -0
  151. package/dist/runtime/manifest.d.ts +18 -0
  152. package/dist/runtime/manifest.d.ts.map +1 -0
  153. package/dist/runtime/manifest.js +9 -0
  154. package/dist/runtime/manifest.js.map +1 -0
  155. package/dist/runtime/paths-core.d.mts +17 -0
  156. package/dist/runtime/paths-core.d.mts.map +1 -0
  157. package/dist/runtime/paths-core.mjs +67 -0
  158. package/dist/runtime/paths-core.mjs.map +1 -0
  159. package/dist/runtime/paths.d.ts +12 -0
  160. package/dist/runtime/paths.d.ts.map +1 -0
  161. package/dist/runtime/paths.js +17 -0
  162. package/dist/runtime/paths.js.map +1 -0
  163. package/dist/types.d.ts +11 -1
  164. package/dist/types.d.ts.map +1 -1
  165. package/dist/types.js.map +1 -1
  166. package/package.json +10 -7
  167. package/scripts/download-model.js +25 -9
  168. package/scripts/runtime-cli.js +315 -0
  169. package/src/mlx-ml/python/__main__.py +43 -4
  170. package/src/mlx-ml/python/handlers/__init__.py +2 -1
  171. package/src/mlx-ml/python/handlers/completion.py +3 -27
  172. package/src/mlx-ml/python/handlers/{chat.py → generate.py} +33 -106
  173. package/src/mlx-ml/python/handlers/render.py +40 -0
  174. package/src/mlx-ml/python/pyproject.toml +3 -2
  175. package/src/mlx-ml/python/server.py +28 -8
  176. package/src/mlx-ml/python/utils/template_render.py +80 -0
  177. package/src/mlx-ml/python/utils/token_utils.py +2 -2
  178. package/src/mlx-ml/python/uv.lock +549 -454
  179. package/src/pytorch/python/__main__.py +19 -0
  180. package/src/pytorch/python/backends/__init__.py +3 -0
  181. package/src/pytorch/python/backends/base.py +84 -0
  182. package/src/pytorch/python/backends/transformers_lm.py +127 -0
  183. package/src/pytorch/python/handlers/__init__.py +6 -0
  184. package/src/pytorch/python/handlers/cancel.py +53 -0
  185. package/src/pytorch/python/handlers/capabilities.py +6 -0
  186. package/src/pytorch/python/handlers/completion.py +15 -0
  187. package/src/pytorch/python/handlers/format_test.py +70 -0
  188. package/src/pytorch/python/handlers/generate.py +68 -0
  189. package/src/pytorch/python/handlers/render.py +40 -0
  190. package/src/pytorch/python/handlers/tokenize.py +63 -0
  191. package/src/pytorch/python/pyproject.toml +36 -0
  192. package/src/pytorch/python/server.py +140 -0
  193. package/src/pytorch/python/utils/__init__.py +0 -0
  194. package/src/pytorch/python/utils/chat_template_constraints.py +164 -0
  195. package/src/pytorch/python/utils/prompt_builder.py +54 -0
  196. package/src/pytorch/python/utils/template_render.py +80 -0
  197. package/src/pytorch/python/utils/token_utils.py +376 -0
  198. package/src/pytorch/python/uv.lock +694 -0
  199. package/dist/mlx-ml/process/process-communication.d.ts +0 -45
  200. package/dist/mlx-ml/process/process-communication.d.ts.map +0 -1
  201. package/dist/mlx-ml/process/process-communication.js.map +0 -1
  202. package/dist/mlx-ml/process/queue.d.ts +0 -35
  203. package/dist/mlx-ml/process/queue.d.ts.map +0 -1
  204. package/dist/mlx-ml/process/queue.js.map +0 -1
  205. package/scripts/setup-mlx.js +0 -53
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Python runtime 管理 CLI
5
+ *
6
+ * setup mlx — ~/.modular-prompt/runtimes/mlx に venv を作成
7
+ * setup pytorch — ~/.modular-prompt/runtimes/pytorch に cpu-minimal venv を作成
8
+ * setup --status
9
+ * cleanup mlx [--yes]
10
+ * cleanup --all [--yes]
11
+ */
12
+
13
+ import { execSync } from 'child_process';
14
+ import { existsSync, readFileSync, rmSync, mkdirSync } from 'fs';
15
+ import { createInterface } from 'readline';
16
+ import { dirname, join } from 'path';
17
+ import { fileURLToPath, pathToFileURL } from 'url';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const packageRoot = join(__dirname, '..');
21
+ const driverVersion = readPackageVersion();
22
+
23
+ function runtimeModuleUrl(name) {
24
+ const distPath = join(packageRoot, 'dist', 'runtime', name);
25
+ const srcPath = join(packageRoot, 'src', 'runtime', name);
26
+ return pathToFileURL(existsSync(distPath) ? distPath : srcPath).href;
27
+ }
28
+
29
+ const {
30
+ RUNTIME_PROFILES,
31
+ getModularPromptHome,
32
+ getRuntimeDir,
33
+ getVenvPath,
34
+ getMlxPythonDir,
35
+ getPytorchPythonDir,
36
+ isRuntimeReady,
37
+ } = await import(runtimeModuleUrl('paths-core.mjs'));
38
+
39
+ const {
40
+ collectInstalledPackages,
41
+ readManifest,
42
+ writeManifest,
43
+ } = await import(runtimeModuleUrl('manifest-core.mjs'));
44
+
45
+ function readPackageVersion() {
46
+ try {
47
+ const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
48
+ return pkg.version ?? 'unknown';
49
+ } catch {
50
+ return 'unknown';
51
+ }
52
+ }
53
+
54
+ function ensureUv() {
55
+ try {
56
+ execSync('uv --version', { stdio: 'ignore' });
57
+ return;
58
+ } catch {
59
+ console.log('⚠️ uv is not installed. Installing uv...');
60
+ execSync('curl -LsSf https://astral.sh/uv/install.sh | sh', { stdio: 'inherit' });
61
+ }
62
+ }
63
+
64
+ function setupMlx() {
65
+ if (process.platform !== 'darwin') {
66
+ console.error('❌ MLX runtime is only available on macOS (Apple Silicon).');
67
+ console.error(' For local inference on this platform, use setup-pytorch or vLLM.');
68
+ process.exit(1);
69
+ }
70
+
71
+ const pythonDir = getMlxPythonDir(packageRoot);
72
+ if (!existsSync(pythonDir)) {
73
+ console.error(`❌ MLX Python project not found: ${pythonDir}`);
74
+ process.exit(1);
75
+ }
76
+
77
+ const venvPath = getVenvPath('mlx');
78
+ const runtimeDir = getRuntimeDir('mlx');
79
+
80
+ console.log('🚀 Setting up MLX runtime...\n');
81
+ console.log(`📁 Python project: ${pythonDir}`);
82
+ console.log(`📁 Runtime venv: ${venvPath}\n`);
83
+
84
+ ensureUv();
85
+ mkdirSync(runtimeDir, { recursive: true });
86
+
87
+ const env = {
88
+ ...process.env,
89
+ UV_PROJECT_ENVIRONMENT: venvPath,
90
+ };
91
+
92
+ try {
93
+ execSync('uv venv --python 3.13', { cwd: pythonDir, stdio: 'inherit', env });
94
+ execSync('uv pip install -e .', { cwd: pythonDir, stdio: 'inherit', env });
95
+
96
+ writeManifest('mlx', {
97
+ profile: 'mlx',
98
+ driverVersion,
99
+ platform: process.platform,
100
+ pythonVersion: '3.13',
101
+ createdAt: new Date().toISOString(),
102
+ packages: collectInstalledPackages(pythonDir, venvPath),
103
+ });
104
+
105
+ console.log('\n✅ MLX runtime setup completed.');
106
+ console.log(` Home: ${getModularPromptHome()}`);
107
+ console.log(' You can now use MlxDriver from @modular-prompt/driver');
108
+ } catch (error) {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ console.error('❌ Failed to setup MLX runtime:', message);
111
+ process.exit(1);
112
+ }
113
+ }
114
+
115
+ const PYTORCH_CPU_INDEX = 'https://download.pytorch.org/whl/cpu';
116
+ const PYTORCH_PYTHON_VERSION = '3.12';
117
+
118
+ function setupPytorch() {
119
+ const pythonDir = getPytorchPythonDir(packageRoot);
120
+ if (!existsSync(pythonDir)) {
121
+ console.error(`❌ PyTorch Python project not found: ${pythonDir}`);
122
+ process.exit(1);
123
+ }
124
+
125
+ const venvPath = getVenvPath('pytorch');
126
+ const runtimeDir = getRuntimeDir('pytorch');
127
+
128
+ console.log('🚀 Setting up PyTorch runtime (cpu-minimal)...\n');
129
+ console.log(`📁 Python project: ${pythonDir}`);
130
+ console.log(`📁 Runtime venv: ${venvPath}`);
131
+ console.log(`📦 torch index: ${PYTORCH_CPU_INDEX}\n`);
132
+
133
+ ensureUv();
134
+ mkdirSync(runtimeDir, { recursive: true });
135
+
136
+ const env = {
137
+ ...process.env,
138
+ UV_PROJECT_ENVIRONMENT: venvPath,
139
+ };
140
+
141
+ try {
142
+ execSync(`uv venv --python ${PYTORCH_PYTHON_VERSION}`, { cwd: pythonDir, stdio: 'inherit', env });
143
+ const venvPython =
144
+ process.platform === 'win32'
145
+ ? join(venvPath, 'Scripts', 'python.exe')
146
+ : join(venvPath, 'bin', 'python');
147
+ execSync(`uv pip install --python "${venvPython}" "torch==2.9.1" --index-url ${PYTORCH_CPU_INDEX}`, {
148
+ cwd: pythonDir,
149
+ stdio: 'inherit',
150
+ env,
151
+ });
152
+ execSync(`uv pip install --python "${venvPython}" -e .`, { cwd: pythonDir, stdio: 'inherit', env });
153
+
154
+ const packages = collectInstalledPackages(pythonDir, venvPath);
155
+ writeManifest('pytorch', {
156
+ profile: 'pytorch',
157
+ variant: 'cpu-minimal',
158
+ driverVersion,
159
+ platform: process.platform,
160
+ pythonVersion: PYTORCH_PYTHON_VERSION,
161
+ torchVersion: packages?.torch,
162
+ createdAt: new Date().toISOString(),
163
+ packages,
164
+ });
165
+
166
+ console.log('\n✅ PyTorch runtime setup completed (cpu-minimal).');
167
+ console.log(` Home: ${getModularPromptHome()}`);
168
+ console.log(' You can now use PyTorchDriver from @modular-prompt/driver');
169
+ console.log(' For CUDA / custom environments, see docs/LOCAL_MODEL_SETUP.md');
170
+ } catch (error) {
171
+ const message = error instanceof Error ? error.message : String(error);
172
+ console.error('❌ Failed to setup PyTorch runtime:', message);
173
+ process.exit(1);
174
+ }
175
+ }
176
+
177
+ function formatManifestDetail(manifest) {
178
+ const parts = [`driver ${manifest.driverVersion}`];
179
+ if (manifest.variant) {
180
+ parts.push(`variant ${manifest.variant}`);
181
+ }
182
+ const torchVersion = manifest.torchVersion ?? manifest.packages?.torch;
183
+ if (torchVersion) {
184
+ parts.push(`torch ${torchVersion}`);
185
+ }
186
+ parts.push(manifest.createdAt);
187
+ return ` (${parts.join(', ')})`;
188
+ }
189
+
190
+ function printStatus() {
191
+ console.log(`modular-prompt home: ${getModularPromptHome()}\n`);
192
+ for (const profile of RUNTIME_PROFILES) {
193
+ const ready = isRuntimeReady(profile);
194
+ const manifest = ready ? readManifest(profile) : null;
195
+ const detail = manifest ? formatManifestDetail(manifest) : '';
196
+ const icon = ready ? '✅' : '❌';
197
+ const runtimePath = getRuntimeDir(profile);
198
+ console.log(`${icon} ${profile}: ${ready ? 'ready' : 'not installed'}${detail}`);
199
+ console.log(` ${runtimePath}`);
200
+ }
201
+ const setupHints = [];
202
+ if (!isRuntimeReady('mlx') && process.platform === 'darwin') {
203
+ setupHints.push('pnpm run setup-mlx -w @modular-prompt/driver');
204
+ }
205
+ if (!isRuntimeReady('pytorch')) {
206
+ setupHints.push('pnpm run setup-pytorch -w @modular-prompt/driver');
207
+ }
208
+ if (setupHints.length > 0) {
209
+ console.log(`\nRun: ${setupHints.join(' or ')}`);
210
+ }
211
+ }
212
+
213
+ async function confirm(message) {
214
+ if (process.argv.includes('--yes') || process.argv.includes('-y')) {
215
+ return true;
216
+ }
217
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
218
+ const answer = await new Promise((resolve) => {
219
+ rl.question(`${message} [y/N] `, resolve);
220
+ });
221
+ rl.close();
222
+ return String(answer).toLowerCase() === 'y' || String(answer).toLowerCase() === 'yes';
223
+ }
224
+
225
+ async function cleanupProfile(profile) {
226
+ if (!existsSync(getRuntimeDir(profile))) {
227
+ console.log(`ℹ️ ${profile}: nothing to clean (${getRuntimeDir(profile)} does not exist)`);
228
+ return;
229
+ }
230
+ const ok = await confirm(`Delete ${profile} runtime at ${getRuntimeDir(profile)}?`);
231
+ if (!ok) {
232
+ console.log('Cancelled.');
233
+ return;
234
+ }
235
+ rmSync(getRuntimeDir(profile), { recursive: true, force: true });
236
+ console.log(`✅ Removed ${profile} runtime.`);
237
+ }
238
+
239
+ async function cleanupAll() {
240
+ const home = getModularPromptHome();
241
+ if (!existsSync(home)) {
242
+ console.log(`ℹ️ Nothing to clean (${home} does not exist)`);
243
+ return;
244
+ }
245
+ const ok = await confirm(`Delete entire modular-prompt home at ${home}?`);
246
+ if (!ok) {
247
+ console.log('Cancelled.');
248
+ return;
249
+ }
250
+ rmSync(home, { recursive: true, force: true });
251
+ console.log(`✅ Removed ${home}`);
252
+ }
253
+
254
+ function printUsage() {
255
+ console.log(`Usage:
256
+ node scripts/runtime-cli.js setup mlx Set up MLX Python runtime (macOS only)
257
+ node scripts/runtime-cli.js setup pytorch Set up PyTorch runtime (cpu-minimal)
258
+ node scripts/runtime-cli.js setup --status Show runtime status
259
+ node scripts/runtime-cli.js cleanup mlx Remove MLX runtime
260
+ node scripts/runtime-cli.js cleanup pytorch Remove PyTorch runtime
261
+ node scripts/runtime-cli.js cleanup --all Remove entire ~/.modular-prompt
262
+ node scripts/runtime-cli.js cleanup ... --yes Skip confirmation
263
+
264
+ npm scripts: setup-mlx, setup-pytorch, runtime:status, runtime:cleanup`);
265
+ }
266
+
267
+ async function main() {
268
+ const [command, target] = process.argv.slice(2);
269
+
270
+ if (!command || command === '--help' || command === '-h') {
271
+ printUsage();
272
+ process.exit(0);
273
+ }
274
+
275
+ if (command === 'setup') {
276
+ if (target === '--status' || target === 'status') {
277
+ printStatus();
278
+ return;
279
+ }
280
+ if (target === 'mlx') {
281
+ setupMlx();
282
+ return;
283
+ }
284
+ if (target === 'pytorch') {
285
+ setupPytorch();
286
+ return;
287
+ }
288
+ console.error(`Unknown setup target: ${target ?? '(none)'}`);
289
+ printUsage();
290
+ process.exit(1);
291
+ }
292
+
293
+ if (command === 'cleanup') {
294
+ if (target === '--all' || target === 'all') {
295
+ await cleanupAll();
296
+ return;
297
+ }
298
+ if (target === 'mlx' || target === 'pytorch') {
299
+ await cleanupProfile(target);
300
+ return;
301
+ }
302
+ console.error(`Unknown cleanup target: ${target ?? '(none)'}`);
303
+ printUsage();
304
+ process.exit(1);
305
+ }
306
+
307
+ console.error(`Unknown command: ${command}`);
308
+ printUsage();
309
+ process.exit(1);
310
+ }
311
+
312
+ main().catch((error) => {
313
+ console.error(error);
314
+ process.exit(1);
315
+ });
@@ -6,7 +6,28 @@ from utils.vlm_utils import detect_model_kind
6
6
  from server import Server
7
7
 
8
8
  model_name = sys.argv[1] if len(sys.argv) > 1 else "mlx-community/gemma-3-270m-it-qat-4bit"
9
- text_only = "--text-only" in sys.argv
9
+
10
+ VALID_BACKENDS = {"auto", "lm", "vlm", "optiq"}
11
+
12
+
13
+ def parse_backend_mode() -> str:
14
+ if "--backend" in sys.argv:
15
+ idx = sys.argv.index("--backend")
16
+ if idx + 1 < len(sys.argv):
17
+ mode = sys.argv[idx + 1]
18
+ if mode not in VALID_BACKENDS:
19
+ sys.stderr.write(
20
+ f"Invalid --backend value: {mode}. "
21
+ f"Use one of: {', '.join(sorted(VALID_BACKENDS))}\n"
22
+ )
23
+ sys.exit(1)
24
+ return mode
25
+ if "--text-only" in sys.argv:
26
+ return "lm"
27
+ return "auto"
28
+
29
+
30
+ backend_mode = parse_backend_mode()
10
31
 
11
32
  drafter_model = None
12
33
  if "--drafter" in sys.argv:
@@ -25,8 +46,26 @@ if "--draft-block-size" in sys.argv:
25
46
  sys.exit(1)
26
47
 
27
48
 
28
- def create_backend(model_name: str, text_only: bool = False):
29
- model_kind = "lm" if text_only else detect_model_kind(model_name)
49
+ def ensure_optiq_registered() -> None:
50
+ import optiq # noqa: F401 - registers vendored architectures into mlx-lm
51
+
52
+
53
+ def create_backend(model_name: str, mode: str = "auto"):
54
+ if mode == "optiq":
55
+ ensure_optiq_registered()
56
+ mode = "lm"
57
+
58
+ if mode == "lm":
59
+ backend = MlxLmBackend()
60
+ backend.load(model_name)
61
+ return backend, "lm"
62
+
63
+ if mode == "vlm":
64
+ backend = MlxVlmBackend()
65
+ backend.load(model_name)
66
+ return backend, "vlm"
67
+
68
+ model_kind = detect_model_kind(model_name)
30
69
 
31
70
  if model_kind == "vlm":
32
71
  backend = MlxVlmBackend()
@@ -42,7 +81,7 @@ def create_backend(model_name: str, text_only: bool = False):
42
81
 
43
82
 
44
83
  if __name__ == "__main__":
45
- backend, model_kind = create_backend(model_name, text_only)
84
+ backend, model_kind = create_backend(model_name, backend_mode)
46
85
 
47
86
  if drafter_model:
48
87
  backend.load_drafter(drafter_model)
@@ -1,6 +1,7 @@
1
1
  from handlers.cache import handle_cache_prefill
2
2
  from handlers.capabilities import handle_capabilities
3
- from handlers.chat import handle_chat
4
3
  from handlers.completion import handle_completion
5
4
  from handlers.format_test import handle_format_test
5
+ from handlers.generate import handle_generate
6
+ from handlers.render import handle_render
6
7
  from handlers.tokenize import handle_tokenize
@@ -1,11 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
- import re
5
- import sys
6
-
7
3
  from backends.base import ModelBackend
8
- from handlers.cancel import poll_cancel
4
+ from handlers.generate import handle_generate
9
5
 
10
6
 
11
7
  def handle_completion(
@@ -15,25 +11,5 @@ def handle_completion(
15
11
  images: list | None = None,
16
12
  max_image_size: int = 768,
17
13
  ) -> None:
18
- """completion API の処理"""
19
- if options is None:
20
- options = {}
21
-
22
- final_options = dict(options)
23
- if images:
24
- final_options["max_image_size"] = max_image_size
25
- if os.getenv('MLX_DEBUG'):
26
- display_prompt = re.sub(r'(<\|image_pad\|>)+', '<|image_pad|>...', prompt)
27
- sys.stderr.write(f"--- vlm completion (images: {len(images)}, max_size: {max_image_size})\n{display_prompt}\n")
28
- elif os.getenv('MLX_DEBUG'):
29
- if isinstance(prompt, list):
30
- sys.stderr.write(f"--- prompt: len={len(prompt)}\n")
31
- else:
32
- sys.stderr.write(f"--- prompt\n{prompt}\n")
33
-
34
- for response in backend.stream_generate(prompt, final_options, images):
35
- if poll_cancel():
36
- break
37
- print(response.text.replace("\0", ""), end="", flush=True)
38
-
39
- print("\n", end="\0", flush=True)
14
+ """completion API(後方互換)— generate に委譲"""
15
+ handle_generate(backend, prompt, options, images, max_image_size)
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import os
4
5
  import re
5
6
  import sys
6
7
 
7
8
  from backends.base import ModelBackend
8
9
  from mlx_lm.models.cache import trim_prompt_cache
9
- from utils.prompt_builder import generate_merged_prompt, supports_chat_template
10
10
  from handlers.cancel import poll_cancel
11
11
 
12
12
 
@@ -53,65 +53,42 @@ def _stream_to_stdout(
53
53
  print("", end="\0", flush=True)
54
54
 
55
55
 
56
- def handle_chat(
56
+ def handle_generate(
57
57
  backend: ModelBackend,
58
- capabilities: dict,
59
- messages: list,
60
- primer: str | None = None,
58
+ prompt: str | list[int],
61
59
  options: dict | None = None,
62
- tools: list | None = None,
63
60
  images: list | None = None,
64
61
  max_image_size: int = 768,
65
- reasoning_effort: str | None = None,
62
+ primer: str | None = None,
66
63
  cache_path: str | None = None,
67
64
  cache_trim_tokens: int | None = None,
68
65
  ) -> None:
69
- """chat API の処理"""
66
+ """LIP generate: 整形済み prompt のストリーム推論"""
70
67
  if options is None:
71
68
  options = {}
72
69
 
73
- tokenizer = backend.get_tokenizer()
74
-
75
- if backend.supports_vision():
76
- add_generation_prompt = True
77
- fmt_messages = list(messages)
78
- if primer is not None:
79
- fmt_messages.append({"role": "assistant", "content": primer})
80
- add_generation_prompt = False
81
-
82
- try:
83
- prompt = tokenizer.apply_chat_template(
84
- fmt_messages,
85
- tools=tools,
86
- add_generation_prompt=add_generation_prompt,
87
- tokenize=False,
88
- )
89
- except TypeError:
90
- prompt = tokenizer.apply_chat_template(
91
- fmt_messages,
92
- add_generation_prompt=add_generation_prompt,
93
- tokenize=False,
94
- )
95
-
96
- if primer is not None:
97
- prompt = primer.join(prompt.split(primer)[0:-1]) + primer
98
-
99
- display_prompt = re.sub(r'(<\|image_pad\|>)+', '<|image_pad|>...', prompt)
100
- sys.stderr.write(f"--- vlm prompt (images: {len(images) if images else 0}, max_size: {max_image_size})\n{display_prompt}\n")
101
-
102
- final_options = dict(options)
70
+ final_options = dict(options)
71
+ if images:
103
72
  final_options["max_image_size"] = max_image_size
104
- _stream_to_stdout(
105
- backend,
106
- prompt,
107
- final_options,
108
- images=images,
109
- primer=primer,
110
- )
111
- return
73
+ if os.getenv('MLX_DEBUG'):
74
+ if isinstance(prompt, str):
75
+ display_prompt = re.sub(r'(<\|image_pad\|>)+', '<|image_pad|>...', prompt)
76
+ else:
77
+ display_prompt = f"<token ids: len={len(prompt)}>"
78
+ sys.stderr.write(
79
+ f"--- vlm generate (images: {len(images)}, max_size: {max_image_size})\n{display_prompt}\n"
80
+ )
81
+ elif os.getenv('MLX_DEBUG'):
82
+ if isinstance(prompt, list):
83
+ sys.stderr.write(f"--- prompt: len={len(prompt)}\n")
84
+ else:
85
+ sys.stderr.write(f"--- prompt\n{prompt}\n")
112
86
 
113
- prompt_cache = backend.load_cache_from_file(cache_path) if cache_path else None
87
+ # VLM 経路では KV キャッシュを使わない(旧 chat ハンドラと同様)
88
+ prompt_cache = None
114
89
  cache_tokens = 0
90
+ if cache_path and not images:
91
+ prompt_cache = backend.load_cache_from_file(cache_path)
115
92
  if prompt_cache is not None:
116
93
  if cache_trim_tokens is not None:
117
94
  current_offset = backend.get_cache_offset(prompt_cache)
@@ -128,7 +105,6 @@ def handle_chat(
128
105
  if meta_count is not None:
129
106
  cache_tokens = meta_count
130
107
  else:
131
- # Legacy cache without meta file - skip it for safety
132
108
  sys.stderr.write(
133
109
  f"WARNING: Cache file exists but no .meta.json found at {cache_path}. "
134
110
  "Ignoring cache for safety (may be from old implementation).\n"
@@ -142,67 +118,11 @@ def handle_chat(
142
118
  elif cache_path:
143
119
  sys.stderr.write(f"KV cache load FAILED: {cache_path}\n")
144
120
 
145
- if not supports_chat_template(tokenizer):
146
- prompt = generate_merged_prompt(messages, capabilities)
147
- if prompt_cache is not None:
148
- sys.stderr.write("KV cache ignored: model does not support chat template\n")
149
- _stream_to_stdout(backend, prompt, options, primer=primer)
150
- return
151
-
152
- add_generation_prompt = True
153
- fmt_messages = list(messages)
154
- if primer is not None:
155
- fmt_messages.append({"role": "assistant", "content": primer})
156
- add_generation_prompt = False
157
-
158
- extra_kwargs = {}
159
- if tools is not None:
160
- extra_kwargs["tools"] = tools
161
- if reasoning_effort is not None:
162
- extra_kwargs["reasoning_effort"] = reasoning_effort
163
-
164
- trust_remote_code = options.get("trust_remote_code")
165
- if trust_remote_code is not None:
166
- extra_kwargs["trust_remote_code"] = trust_remote_code
167
-
168
- try:
169
- prompt = tokenizer.apply_chat_template(
170
- fmt_messages,
171
- add_generation_prompt=add_generation_prompt,
172
- tokenize=False,
173
- **extra_kwargs,
174
- )
175
- except TypeError:
176
- try:
177
- fallback_kwargs = {}
178
- if tools is not None:
179
- fallback_kwargs["tools"] = tools
180
- prompt = tokenizer.apply_chat_template(
181
- fmt_messages,
182
- add_generation_prompt=add_generation_prompt,
183
- tokenize=False,
184
- **fallback_kwargs,
185
- )
186
- except TypeError:
187
- prompt = tokenizer.apply_chat_template(
188
- fmt_messages,
189
- add_generation_prompt=add_generation_prompt,
190
- tokenize=False,
191
- )
192
-
193
- if primer is not None:
194
- prompt = primer.join(prompt.split(primer)[0:-1]) + primer
195
-
196
- if isinstance(prompt, list):
197
- sys.stderr.write(f"--- prompt: len={len(prompt)}\n")
198
- else:
199
- sys.stderr.write(f"--- prompt\n{prompt}\n")
200
-
201
- final_options = dict(options)
202
121
  final_options.pop("trust_remote_code", None)
203
122
 
204
123
  effective_prompt = prompt
205
124
  if prompt_cache is not None and cache_tokens > 0 and isinstance(prompt, str):
125
+ tokenizer = backend.get_tokenizer()
206
126
  add_special = tokenizer.bos_token is None or not prompt.startswith(
207
127
  tokenizer.bos_token
208
128
  )
@@ -221,4 +141,11 @@ def handle_chat(
221
141
  )
222
142
  prompt_cache = None
223
143
 
224
- _stream_to_stdout(backend, effective_prompt, final_options, primer=primer, prompt_cache=prompt_cache)
144
+ _stream_to_stdout(
145
+ backend,
146
+ effective_prompt,
147
+ final_options,
148
+ images=images,
149
+ primer=primer,
150
+ prompt_cache=prompt_cache,
151
+ )
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from backends.base import ModelBackend
6
+ from utils.template_render import apply_chat_template_prompt
7
+
8
+
9
+ def handle_render(
10
+ backend: ModelBackend,
11
+ messages: list,
12
+ options: dict | None = None,
13
+ tools: list | None = None,
14
+ reasoning_effort: str | None = None,
15
+ ) -> None:
16
+ """LIP render: apply_chat_template のみ(推論しない)"""
17
+ if options is None:
18
+ options = {}
19
+
20
+ result: dict = {
21
+ "formatted_prompt": None,
22
+ "error": None,
23
+ }
24
+
25
+ try:
26
+ trust_remote_code = options.get("trust_remote_code")
27
+ primer = options.get("primer")
28
+ prompt = apply_chat_template_prompt(
29
+ backend,
30
+ messages,
31
+ primer=primer,
32
+ tools=tools,
33
+ reasoning_effort=reasoning_effort,
34
+ trust_remote_code=trust_remote_code,
35
+ )
36
+ result["formatted_prompt"] = prompt
37
+ except Exception as e:
38
+ result["error"] = str(e)
39
+
40
+ print(json.dumps(result), end="\0", flush=True)
@@ -2,14 +2,15 @@
2
2
  name = "mlx_driver"
3
3
  version = "0.1.0"
4
4
  description = "MLX driver for moduler-prompt"
5
- requires-python = ">=3.10,<3.14"
5
+ requires-python = ">=3.11,<3.14"
6
6
  dependencies = [
7
7
  "flex==6.14.1",
8
8
  "hf-xet==1.2.0",
9
9
  "jinja2==3.1.6",
10
10
  "mlx>=0.31.2; sys_platform == 'darwin'",
11
11
  "mlx-lm==0.31.3; sys_platform == 'darwin'",
12
- "mlx-vlm==0.6.3; sys_platform == 'darwin'",
12
+ "mlx-optiq>=0.4.4; sys_platform == 'darwin'",
13
+ "mlx-vlm==0.6.4; sys_platform == 'darwin'",
13
14
  "tokenizers==0.22.2",
14
15
  "torch==2.9.1",
15
16
  "torchvision==0.24.1",