@minimalcorp/tsunagi 0.1.26 → 0.1.27

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 +24 -7
  40. package/dist/server/lib/whisper-process.js +22 -5
  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 → pTO1DUFCsjrTTtmSPyfRJ}/_buildManifest.js +0 -0
  45. /package/.next/standalone/apps/web/.next/static/{7jfNq7gWy6LmFypqM6QZf → pTO1DUFCsjrTTtmSPyfRJ}/_clientMiddlewareManifest.js +0 -0
  46. /package/.next/standalone/apps/web/.next/static/{7jfNq7gWy6LmFypqM6QZf → pTO1DUFCsjrTTtmSPyfRJ}/_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
  // 兄弟ディレクトリとして存在するようレイアウトを揃えている
@@ -150,25 +158,34 @@ async function runSetupAndStart(dir) {
150
158
  downloadProgress = undefined;
151
159
  }
152
160
  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', () => {
161
+ 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 });
162
+ let stderrTail = '';
163
+ child.stderr?.on('data', (chunk) => {
164
+ stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_CHARS);
165
+ });
166
+ // healthポーリングとは別に、プロセスが起動途中で終了したことをタイムアウトを待たず検知する。
167
+ let exitInfo = null;
168
+ child.on('exit', (code, signal) => {
169
+ exitInfo = `exit code=${code} signal=${signal}`;
155
170
  if (managedProcess === child)
156
171
  managedProcess = null;
157
172
  });
158
- child.on('error', () => {
173
+ child.on('error', (err) => {
174
+ exitInfo = `spawn error: ${err.message}`;
159
175
  if (managedProcess === child)
160
176
  managedProcess = null;
161
177
  });
162
178
  managedProcess = child;
163
- // MoEで実計算はアクティブパラメータ(~3B)分のみだが、4bit量子化でも~17GBの
164
- // 重みファイル自体はメモリへ展開する必要があるため、whisperより長めに待つ。
165
179
  const start = Date.now();
166
- while (Date.now() - start < 90000) {
180
+ while (Date.now() - start < STARTUP_TIMEOUT_MS) {
181
+ if (exitInfo) {
182
+ throw new Error(`llm-server crashed while starting (${exitInfo})${stderrTail ? `\n${stderrTail}` : ''}`);
183
+ }
167
184
  if (await checkHealth())
168
185
  return;
169
186
  await sleep(1000);
170
187
  }
171
- throw new Error('Server did not become healthy within 90s of starting');
188
+ throw new Error(`Server did not become healthy within ${STARTUP_TIMEOUT_MS / 1000}s of starting`);
172
189
  }
173
190
  export function startLlmServer() {
174
191
  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
  // 兄弟ディレクトリとして存在するようレイアウトを揃えている
@@ -146,23 +152,34 @@ async function runSetupAndStart(dir) {
146
152
  downloadProgress = undefined;
147
153
  }
148
154
  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', () => {
155
+ const child = spawn(venvPython(), ['-m', 'uvicorn', 'server:app', '--host', '127.0.0.1', '--port', '8765'], { cwd: dir, stdio: ['ignore', 'ignore', 'pipe'], env: hfEnv });
156
+ let stderrTail = '';
157
+ child.stderr?.on('data', (chunk) => {
158
+ stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_CHARS);
159
+ });
160
+ // healthポーリングとは別に、プロセスが起動途中で終了したことをタイムアウトを待たず検知する。
161
+ let exitInfo = null;
162
+ child.on('exit', (code, signal) => {
163
+ exitInfo = `exit code=${code} signal=${signal}`;
151
164
  if (managedProcess === child)
152
165
  managedProcess = null;
153
166
  });
154
- child.on('error', () => {
167
+ child.on('error', (err) => {
168
+ exitInfo = `spawn error: ${err.message}`;
155
169
  if (managedProcess === child)
156
170
  managedProcess = null;
157
171
  });
158
172
  managedProcess = child;
159
173
  const start = Date.now();
160
- while (Date.now() - start < 30000) {
174
+ while (Date.now() - start < STARTUP_TIMEOUT_MS) {
175
+ if (exitInfo) {
176
+ throw new Error(`whisper-server crashed while starting (${exitInfo})${stderrTail ? `\n${stderrTail}` : ''}`);
177
+ }
161
178
  if (await checkHealth())
162
179
  return;
163
180
  await sleep(1000);
164
181
  }
165
- throw new Error('Server did not become healthy within 30s of starting');
182
+ throw new Error(`Server did not become healthy within ${STARTUP_TIMEOUT_MS / 1000}s of starting`);
166
183
  }
167
184
  export function startWhisperServer() {
168
185
  if (setupPromise || managedProcess) {