@minimalcorp/tsunagi 0.1.26 → 0.1.28

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 (46) hide show
  1. package/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  2. package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
  3. package/.next/standalone/apps/web/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
  5. package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
  12. package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +1 -1
  13. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  14. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  15. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  16. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  18. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
  20. package/.next/standalone/apps/web/.next/server/app/index.rsc +1 -1
  21. package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  22. package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  23. package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  24. package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  25. package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  26. package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
  27. package/.next/standalone/apps/web/.next/server/app/settings.rsc +1 -1
  28. package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  29. package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  30. package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  31. package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  32. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
  33. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  34. package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
  35. package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
  36. package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  37. package/.next/standalone/apps/web/.next/server/server-reference-manifest.js +1 -1
  38. package/.next/standalone/apps/web/.next/server/server-reference-manifest.json +1 -1
  39. package/dist/server/lib/llm-process.js +29 -8
  40. package/dist/server/lib/whisper-process.js +27 -6
  41. package/npm-shrinkwrap.json +505 -119
  42. package/package.json +1 -1
  43. package/whisper-server/server.py +2 -1
  44. /package/.next/standalone/apps/web/.next/static/{7jfNq7gWy6LmFypqM6QZf → ryH59GWZw_ooyW3FOb1yO}/_buildManifest.js +0 -0
  45. /package/.next/standalone/apps/web/.next/static/{7jfNq7gWy6LmFypqM6QZf → ryH59GWZw_ooyW3FOb1yO}/_clientMiddlewareManifest.js +0 -0
  46. /package/.next/standalone/apps/web/.next/static/{7jfNq7gWy6LmFypqM6QZf → ryH59GWZw_ooyW3FOb1yO}/_ssgManifest.js +0 -0
@@ -19,6 +19,14 @@ const HF_CACHE_DIR = path.join(TSUNAGI_LLM_DIR, 'cache');
19
19
  const MODEL_CACHE_DIR = path.join(HF_CACHE_DIR, 'hub', 'models--mlx-community--Qwen3-30B-A3B-Instruct-2507-4bit');
20
20
  // 実測値(2026-07時点、4bit量子化された重み一式の合計)。多少の変動はあるがETA計算の目安として使う。
21
21
  const EXPECTED_MODEL_BYTES = 17_200_000_000;
22
+ // starting_serverフェーズ(spawn後にhealthyになるまで待つ時間)の上限。
23
+ // mlx_lm.serverは/healthをモデル本体のロード完了を待たずに返す(ロードはバックグラウンド
24
+ // スレッドで進む)ため、この待ち時間の大半はPython起動/import初期化のばらつきに対する
25
+ // マージン。whisperより依存関係が重いぶん長めの90秒を維持する。
26
+ // プロセスが起動途中でクラッシュした場合はこのタイムアウトを待たず即座に検知する。
27
+ const STARTUP_TIMEOUT_MS = 90_000;
28
+ // クラッシュ時にエラーメッセージへ含めるstderrの上限文字数。
29
+ const STDERR_TAIL_MAX_CHARS = 4000;
22
30
  // このファイルは apps/server/src/lib (dev) または apps/cli/dist/server/lib
23
31
  // (npm配布物) のいずれかにいる。どちらの場合も3階層上に llm-server が
24
32
  // 兄弟ディレクトリとして存在するようレイアウトを揃えている
@@ -111,7 +119,11 @@ async function trackModelDownload(child) {
111
119
  const elapsedSec = (now - lastTime) / 1000;
112
120
  const bytesPerSec = elapsedSec > 0 ? (downloaded - lastBytes) / elapsedSec : 0;
113
121
  const remaining = EXPECTED_MODEL_BYTES - downloaded;
114
- const etaSeconds = bytesPerSec > 0 ? Math.max(0, Math.round(remaining / bytesPerSec)) : null;
122
+ // 転送が一時的に止まって見える(ポーリング間隔とディスク書き込みのタイミングがずれる)だけで
123
+ // 速度0になることがあるため、その場合は直前のETAを維持し「計算中...」への逆戻りを防ぐ。
124
+ const etaSeconds = bytesPerSec > 0
125
+ ? Math.max(0, Math.round(remaining / bytesPerSec))
126
+ : (downloadProgress?.etaSeconds ?? null);
115
127
  downloadProgress = {
116
128
  downloadedBytes: downloaded,
117
129
  totalBytes: EXPECTED_MODEL_BYTES,
@@ -150,25 +162,34 @@ async function runSetupAndStart(dir) {
150
162
  downloadProgress = undefined;
151
163
  }
152
164
  currentStep = 'starting_server';
153
- const child = spawn(venvPython(), ['-m', 'mlx_lm.server', '--model', MODEL, '--host', '127.0.0.1', '--port', String(LLM_PORT)], { cwd: dir, stdio: 'ignore', env: hfEnv });
154
- child.on('exit', () => {
165
+ const child = spawn(venvPython(), ['-m', 'mlx_lm.server', '--model', MODEL, '--host', '127.0.0.1', '--port', String(LLM_PORT)], { cwd: dir, stdio: ['ignore', 'ignore', 'pipe'], env: hfEnv });
166
+ let stderrTail = '';
167
+ child.stderr?.on('data', (chunk) => {
168
+ stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_CHARS);
169
+ });
170
+ // healthポーリングとは別に、プロセスが起動途中で終了したことをタイムアウトを待たず検知する。
171
+ let exitInfo = null;
172
+ child.on('exit', (code, signal) => {
173
+ exitInfo = `exit code=${code} signal=${signal}`;
155
174
  if (managedProcess === child)
156
175
  managedProcess = null;
157
176
  });
158
- child.on('error', () => {
177
+ child.on('error', (err) => {
178
+ exitInfo = `spawn error: ${err.message}`;
159
179
  if (managedProcess === child)
160
180
  managedProcess = null;
161
181
  });
162
182
  managedProcess = child;
163
- // MoEで実計算はアクティブパラメータ(~3B)分のみだが、4bit量子化でも~17GBの
164
- // 重みファイル自体はメモリへ展開する必要があるため、whisperより長めに待つ。
165
183
  const start = Date.now();
166
- while (Date.now() - start < 90000) {
184
+ while (Date.now() - start < STARTUP_TIMEOUT_MS) {
185
+ if (exitInfo) {
186
+ throw new Error(`llm-server crashed while starting (${exitInfo})${stderrTail ? `\n${stderrTail}` : ''}`);
187
+ }
167
188
  if (await checkHealth())
168
189
  return;
169
190
  await sleep(1000);
170
191
  }
171
- throw new Error('Server did not become healthy within 90s of starting');
192
+ throw new Error(`Server did not become healthy within ${STARTUP_TIMEOUT_MS / 1000}s of starting`);
172
193
  }
173
194
  export function startLlmServer() {
174
195
  if (setupPromise || managedProcess) {
@@ -15,6 +15,12 @@ const HF_CACHE_DIR = path.join(TSUNAGI_WHISPER_DIR, 'cache');
15
15
  const MODEL_CACHE_DIR = path.join(HF_CACHE_DIR, 'hub', 'models--mlx-community--whisper-large-v3-turbo');
16
16
  // 実測値(2026-07時点、encoder+decoder等の合計)。多少の変動はあるがETA計算の目安として使う。
17
17
  const EXPECTED_MODEL_BYTES = 1_614_000_000;
18
+ // starting_serverフェーズ(spawn後にhealthyになるまで待つ時間)の上限。
19
+ // 遅いマシンでのPython起動/import/Metal初期化のばらつきを吸収するため60秒に設定。
20
+ // プロセスが起動途中でクラッシュした場合はこのタイムアウトを待たず即座に検知する。
21
+ const STARTUP_TIMEOUT_MS = 60_000;
22
+ // クラッシュ時にエラーメッセージへ含めるstderrの上限文字数。
23
+ const STDERR_TAIL_MAX_CHARS = 4000;
18
24
  // このファイルは apps/server/src/lib (dev) または apps/cli/dist/server/lib
19
25
  // (npm配布物) のいずれかにいる。どちらの場合も3階層上に whisper-server が
20
26
  // 兄弟ディレクトリとして存在するようレイアウトを揃えている
@@ -107,7 +113,11 @@ async function trackModelDownload(child) {
107
113
  const elapsedSec = (now - lastTime) / 1000;
108
114
  const bytesPerSec = elapsedSec > 0 ? (downloaded - lastBytes) / elapsedSec : 0;
109
115
  const remaining = EXPECTED_MODEL_BYTES - downloaded;
110
- const etaSeconds = bytesPerSec > 0 ? Math.max(0, Math.round(remaining / bytesPerSec)) : null;
116
+ // 転送が一時的に止まって見える(ポーリング間隔とディスク書き込みのタイミングがずれる)だけで
117
+ // 速度0になることがあるため、その場合は直前のETAを維持し「計算中...」への逆戻りを防ぐ。
118
+ const etaSeconds = bytesPerSec > 0
119
+ ? Math.max(0, Math.round(remaining / bytesPerSec))
120
+ : (downloadProgress?.etaSeconds ?? null);
111
121
  downloadProgress = {
112
122
  downloadedBytes: downloaded,
113
123
  totalBytes: EXPECTED_MODEL_BYTES,
@@ -146,23 +156,34 @@ async function runSetupAndStart(dir) {
146
156
  downloadProgress = undefined;
147
157
  }
148
158
  currentStep = 'starting_server';
149
- const child = spawn(venvPython(), ['-m', 'uvicorn', 'server:app', '--host', '127.0.0.1', '--port', '8765'], { cwd: dir, stdio: 'ignore', env: hfEnv });
150
- child.on('exit', () => {
159
+ const child = spawn(venvPython(), ['-m', 'uvicorn', 'server:app', '--host', '127.0.0.1', '--port', '8765'], { cwd: dir, stdio: ['ignore', 'ignore', 'pipe'], env: hfEnv });
160
+ let stderrTail = '';
161
+ child.stderr?.on('data', (chunk) => {
162
+ stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_CHARS);
163
+ });
164
+ // healthポーリングとは別に、プロセスが起動途中で終了したことをタイムアウトを待たず検知する。
165
+ let exitInfo = null;
166
+ child.on('exit', (code, signal) => {
167
+ exitInfo = `exit code=${code} signal=${signal}`;
151
168
  if (managedProcess === child)
152
169
  managedProcess = null;
153
170
  });
154
- child.on('error', () => {
171
+ child.on('error', (err) => {
172
+ exitInfo = `spawn error: ${err.message}`;
155
173
  if (managedProcess === child)
156
174
  managedProcess = null;
157
175
  });
158
176
  managedProcess = child;
159
177
  const start = Date.now();
160
- while (Date.now() - start < 30000) {
178
+ while (Date.now() - start < STARTUP_TIMEOUT_MS) {
179
+ if (exitInfo) {
180
+ throw new Error(`whisper-server crashed while starting (${exitInfo})${stderrTail ? `\n${stderrTail}` : ''}`);
181
+ }
161
182
  if (await checkHealth())
162
183
  return;
163
184
  await sleep(1000);
164
185
  }
165
- throw new Error('Server did not become healthy within 30s of starting');
186
+ throw new Error(`Server did not become healthy within ${STARTUP_TIMEOUT_MS / 1000}s of starting`);
166
187
  }
167
188
  export function startWhisperServer() {
168
189
  if (setupPromise || managedProcess) {