@minimalcorp/tsunagi 0.1.29 → 0.1.31
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.
- package/.next/standalone/apps/web/.next/BUILD_ID +1 -1
- package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
- package/.next/standalone/apps/web/.next/prerender-manifest.json +3 -3
- package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
- package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
- package/.next/standalone/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/dist/server/lib/llm-process.js +9 -5
- package/dist/server/lib/python-venv.d.ts +4 -0
- package/dist/server/lib/python-venv.js +66 -0
- package/dist/server/lib/whisper-process.js +9 -5
- package/llm-server/run.sh +31 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/whisper-server/run.sh +31 -4
- /package/.next/standalone/apps/web/.next/static/{I7sHo3zSfusvVo0mGg-dC → 66COjNvXaXarcvgB4nt9B}/_buildManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{I7sHo3zSfusvVo0mGg-dC → 66COjNvXaXarcvgB4nt9B}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{I7sHo3zSfusvVo0mGg-dC → 66COjNvXaXarcvgB4nt9B}/_ssgManifest.js +0 -0
|
@@ -4,6 +4,7 @@ import * as os from 'node:os';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { killProcessOnPort } from './process-port.js';
|
|
7
|
+
import { findPython, isVenvUpToDate, PYTHON_NOT_FOUND_MESSAGE, writeVenvMarker, } from './python-venv.js';
|
|
7
8
|
const MODEL = 'mlx-community/Qwen3-30B-A3B-Instruct-2507-4bit';
|
|
8
9
|
const LLM_SERVER_URL = process.env.TSUNAGI_LLM_SERVER_URL || 'http://127.0.0.1:8766';
|
|
9
10
|
// run.shが待受けるポート固定値(host.docker.internal経由URLと違い、停止処理は
|
|
@@ -40,9 +41,6 @@ export function findLlmServerDir() {
|
|
|
40
41
|
function venvPython() {
|
|
41
42
|
return path.join(VENV_DIR, 'bin', 'python3');
|
|
42
43
|
}
|
|
43
|
-
function isVenvReady() {
|
|
44
|
-
return fs.existsSync(venvPython());
|
|
45
|
-
}
|
|
46
44
|
function isModelReady() {
|
|
47
45
|
const snapshotsDir = path.join(MODEL_CACHE_DIR, 'snapshots');
|
|
48
46
|
if (!fs.existsSync(snapshotsDir))
|
|
@@ -135,11 +133,17 @@ async function trackModelDownload(child) {
|
|
|
135
133
|
}
|
|
136
134
|
async function runSetupAndStart(dir) {
|
|
137
135
|
const hfEnv = { ...process.env, HF_HOME: HF_CACHE_DIR, HF_HUB_DISABLE_XET: '1' };
|
|
138
|
-
|
|
136
|
+
const requirementsFile = path.join(dir, 'requirements.txt');
|
|
137
|
+
if (!isVenvUpToDate(VENV_DIR, requirementsFile)) {
|
|
139
138
|
currentStep = 'installing_deps';
|
|
139
|
+
const pythonBin = findPython();
|
|
140
|
+
if (!pythonBin)
|
|
141
|
+
throw new Error(PYTHON_NOT_FOUND_MESSAGE);
|
|
140
142
|
fs.mkdirSync(TSUNAGI_LLM_DIR, { recursive: true });
|
|
141
|
-
|
|
143
|
+
fs.rmSync(VENV_DIR, { recursive: true, force: true });
|
|
144
|
+
await runStep(pythonBin, ['-m', 'venv', VENV_DIR], dir);
|
|
142
145
|
await runStep(venvPython(), ['-m', 'pip', 'install', '-r', 'requirements.txt'], dir);
|
|
146
|
+
writeVenvMarker(VENV_DIR, requirementsFile);
|
|
143
147
|
}
|
|
144
148
|
if (!isModelReady()) {
|
|
145
149
|
currentStep = 'downloading_model';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function findPython(): string | null;
|
|
2
|
+
export declare const PYTHON_NOT_FOUND_MESSAGE = "Python 3.10\u4EE5\u4E0A\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002'brew install python@3.12' \u7B49\u3067\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
|
|
3
|
+
export declare function isVenvUpToDate(venvDir: string, requirementsFile: string): boolean;
|
|
4
|
+
export declare function writeVenvMarker(venvDir: string, requirementsFile: string): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import * as crypto from 'node:crypto';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
// mlx/mlx-lm/mlx-whisper の固定バージョンが requires-python >=3.10 のため、venv作成に
|
|
6
|
+
// 使う python3 自体も 3.10 以上でなければ pip install がそもそも失敗する。`python3` という
|
|
7
|
+
// 名前が PATH 上のどのバージョンを指すかは環境依存(古い macOS 付属 Python 等)なため、
|
|
8
|
+
// 候補を新しい順に探して最初に見つかった 3.10 以上を使う。
|
|
9
|
+
const MIN_PYTHON_VERSION = '3.10';
|
|
10
|
+
const PYTHON_CANDIDATES = [
|
|
11
|
+
'python3.14',
|
|
12
|
+
'python3.13',
|
|
13
|
+
'python3.12',
|
|
14
|
+
'python3.11',
|
|
15
|
+
'python3.10',
|
|
16
|
+
'python3',
|
|
17
|
+
];
|
|
18
|
+
function pythonVersion(bin) {
|
|
19
|
+
try {
|
|
20
|
+
return execFileSync(bin, ['-c', 'import sys; print("%d.%d" % sys.version_info[:2])'], {
|
|
21
|
+
encoding: 'utf8',
|
|
22
|
+
}).trim();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function versionAtLeast(version, min) {
|
|
29
|
+
const [vMajor, vMinor] = version.split('.').map(Number);
|
|
30
|
+
const [mMajor, mMinor] = min.split('.').map(Number);
|
|
31
|
+
return vMajor !== mMajor ? vMajor > mMajor : vMinor >= mMinor;
|
|
32
|
+
}
|
|
33
|
+
export function findPython() {
|
|
34
|
+
for (const cand of PYTHON_CANDIDATES) {
|
|
35
|
+
const version = pythonVersion(cand);
|
|
36
|
+
if (version && versionAtLeast(version, MIN_PYTHON_VERSION))
|
|
37
|
+
return cand;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
export const PYTHON_NOT_FOUND_MESSAGE = `Python ${MIN_PYTHON_VERSION}以上が見つかりません。'brew install python@3.12' 等でインストールしてください。`;
|
|
42
|
+
function venvMarkerPath(venvDir) {
|
|
43
|
+
return path.join(venvDir, '.requirements.sha256');
|
|
44
|
+
}
|
|
45
|
+
function requirementsHash(requirementsFile) {
|
|
46
|
+
return crypto.createHash('sha256').update(fs.readFileSync(requirementsFile)).digest('hex');
|
|
47
|
+
}
|
|
48
|
+
// venvが「存在するだけ」でなく、実際に使えるかを検証する。
|
|
49
|
+
// - venv作成に使われた python が 3.10 未満だと mlx 系のインストールに失敗するバージョンのまま放置される
|
|
50
|
+
// - requirements.txt のバージョン指定を後から変更しても、既存venvには反映されない
|
|
51
|
+
// のいずれかが起きていないかを、venv自身のpythonバージョンと記録済みハッシュから判定する。
|
|
52
|
+
export function isVenvUpToDate(venvDir, requirementsFile) {
|
|
53
|
+
const venvPython = path.join(venvDir, 'bin', 'python3');
|
|
54
|
+
if (!fs.existsSync(venvPython))
|
|
55
|
+
return false;
|
|
56
|
+
const version = pythonVersion(venvPython);
|
|
57
|
+
if (!version || !versionAtLeast(version, MIN_PYTHON_VERSION))
|
|
58
|
+
return false;
|
|
59
|
+
const marker = venvMarkerPath(venvDir);
|
|
60
|
+
if (!fs.existsSync(marker))
|
|
61
|
+
return false;
|
|
62
|
+
return fs.readFileSync(marker, 'utf8').trim() === requirementsHash(requirementsFile);
|
|
63
|
+
}
|
|
64
|
+
export function writeVenvMarker(venvDir, requirementsFile) {
|
|
65
|
+
fs.writeFileSync(venvMarkerPath(venvDir), requirementsHash(requirementsFile));
|
|
66
|
+
}
|
|
@@ -4,6 +4,7 @@ import * as os from 'node:os';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { killProcessOnPort } from './process-port.js';
|
|
7
|
+
import { findPython, isVenvUpToDate, PYTHON_NOT_FOUND_MESSAGE, writeVenvMarker, } from './python-venv.js';
|
|
7
8
|
const WHISPER_SERVER_URL = process.env.TSUNAGI_WHISPER_SERVER_URL || 'http://127.0.0.1:8765';
|
|
8
9
|
// run.shが待受けるポート固定値(host.docker.internal経由URLと違い、停止処理は
|
|
9
10
|
// 必ずこのNodeプロセスと同じホスト上のポートを対象にする必要があるため分けて持つ)。
|
|
@@ -34,9 +35,6 @@ export function findWhisperServerDir() {
|
|
|
34
35
|
function venvPython() {
|
|
35
36
|
return path.join(VENV_DIR, 'bin', 'python3');
|
|
36
37
|
}
|
|
37
|
-
function isVenvReady() {
|
|
38
|
-
return fs.existsSync(venvPython());
|
|
39
|
-
}
|
|
40
38
|
function isModelReady() {
|
|
41
39
|
const snapshotsDir = path.join(MODEL_CACHE_DIR, 'snapshots');
|
|
42
40
|
if (!fs.existsSync(snapshotsDir))
|
|
@@ -129,11 +127,17 @@ async function trackModelDownload(child) {
|
|
|
129
127
|
}
|
|
130
128
|
async function runSetupAndStart(dir) {
|
|
131
129
|
const hfEnv = { ...process.env, HF_HOME: HF_CACHE_DIR, HF_HUB_DISABLE_XET: '1' };
|
|
132
|
-
|
|
130
|
+
const requirementsFile = path.join(dir, 'requirements.txt');
|
|
131
|
+
if (!isVenvUpToDate(VENV_DIR, requirementsFile)) {
|
|
133
132
|
currentStep = 'installing_deps';
|
|
133
|
+
const pythonBin = findPython();
|
|
134
|
+
if (!pythonBin)
|
|
135
|
+
throw new Error(PYTHON_NOT_FOUND_MESSAGE);
|
|
134
136
|
fs.mkdirSync(TSUNAGI_WHISPER_DIR, { recursive: true });
|
|
135
|
-
|
|
137
|
+
fs.rmSync(VENV_DIR, { recursive: true, force: true });
|
|
138
|
+
await runStep(pythonBin, ['-m', 'venv', VENV_DIR], dir);
|
|
136
139
|
await runStep(venvPython(), ['-m', 'pip', 'install', '-r', 'requirements.txt'], dir);
|
|
140
|
+
writeVenvMarker(VENV_DIR, requirementsFile);
|
|
137
141
|
}
|
|
138
142
|
if (!isModelReady()) {
|
|
139
143
|
currentStep = 'downloading_model';
|
package/llm-server/run.sh
CHANGED
|
@@ -12,14 +12,41 @@ export HF_HUB_DISABLE_XET=1
|
|
|
12
12
|
|
|
13
13
|
MODEL="mlx-community/Qwen3-30B-A3B-Instruct-2507-4bit"
|
|
14
14
|
|
|
15
|
-
# requirements.txt
|
|
16
|
-
#
|
|
15
|
+
# requirements.txtでpinしているmlxがrequires-python >=3.10のため、venv作成に使うpython3自体も
|
|
16
|
+
# 3.10以上でなければpip installがそもそも失敗する。`python3`という名前がPATH上のどのバージョンを
|
|
17
|
+
# 指すかは環境依存(古いmacOS付属Python等)なため、候補を新しい順に探して最初に見つかった
|
|
18
|
+
# 3.10以上を使う。
|
|
19
|
+
MIN_PYTHON_VERSION="3.10"
|
|
20
|
+
|
|
21
|
+
find_python() {
|
|
22
|
+
for cand in python3.14 python3.13 python3.12 python3.11 python3.10 python3; do
|
|
23
|
+
if command -v "$cand" >/dev/null 2>&1; then
|
|
24
|
+
ver="$("$cand" -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null || true)"
|
|
25
|
+
[ -z "$ver" ] && continue
|
|
26
|
+
if [ "$(printf '%s\n%s\n' "$MIN_PYTHON_VERSION" "$ver" | sort -V | head -n1)" = "$MIN_PYTHON_VERSION" ]; then
|
|
27
|
+
echo "$cand"
|
|
28
|
+
return 0
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
done
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ! PYTHON_BIN="$(find_python)"; then
|
|
36
|
+
echo "音声整形(LLM整形)サーバーの起動に失敗: Python ${MIN_PYTHON_VERSION}以上が見つかりません。" >&2
|
|
37
|
+
echo "'brew install python@3.12' 等でインストールしてください。" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# requirements.txtの内容、または使用するpython3のバージョンが変わった場合は既存venvごと作り直す。
|
|
42
|
+
# venvは作成時のpythonバイナリに紐づくため、pip installだけでは古いバージョンのまま更新できない。
|
|
17
43
|
REQUIREMENTS_HASH_FILE="${VENV_DIR}/.requirements.sha256"
|
|
18
|
-
CURRENT_HASH="$(
|
|
44
|
+
CURRENT_HASH="$( { cat requirements.txt; "$PYTHON_BIN" --version; } | shasum -a 256 | awk '{print $1}')"
|
|
19
45
|
|
|
20
46
|
if [ ! -d "$VENV_DIR" ] || [ "$(cat "$REQUIREMENTS_HASH_FILE" 2>/dev/null)" != "$CURRENT_HASH" ]; then
|
|
21
47
|
mkdir -p "$TSUNAGI_LLM_DIR"
|
|
22
|
-
|
|
48
|
+
rm -rf "$VENV_DIR"
|
|
49
|
+
"$PYTHON_BIN" -m venv "$VENV_DIR"
|
|
23
50
|
"$VENV_DIR/bin/python3" -m pip install -r requirements.txt
|
|
24
51
|
echo "$CURRENT_HASH" > "$REQUIREMENTS_HASH_FILE"
|
|
25
52
|
fi
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minimalcorp/tsunagi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@minimalcorp/tsunagi",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.31",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"os": [
|
|
12
12
|
"darwin",
|
package/package.json
CHANGED
package/whisper-server/run.sh
CHANGED
|
@@ -10,14 +10,41 @@ export HF_HOME="${TSUNAGI_WHISPER_DIR}/cache"
|
|
|
10
10
|
# xet転送は進捗の観測が難しい独自プロトコルのため無効化し、素直なHTTPダウンロードに固定する。
|
|
11
11
|
export HF_HUB_DISABLE_XET=1
|
|
12
12
|
|
|
13
|
-
# requirements.txt
|
|
14
|
-
#
|
|
13
|
+
# requirements.txtでpinしているmlxがrequires-python >=3.10のため、venv作成に使うpython3自体も
|
|
14
|
+
# 3.10以上でなければpip installがそもそも失敗する。`python3`という名前がPATH上のどのバージョンを
|
|
15
|
+
# 指すかは環境依存(古いmacOS付属Python等)なため、候補を新しい順に探して最初に見つかった
|
|
16
|
+
# 3.10以上を使う。
|
|
17
|
+
MIN_PYTHON_VERSION="3.10"
|
|
18
|
+
|
|
19
|
+
find_python() {
|
|
20
|
+
for cand in python3.14 python3.13 python3.12 python3.11 python3.10 python3; do
|
|
21
|
+
if command -v "$cand" >/dev/null 2>&1; then
|
|
22
|
+
ver="$("$cand" -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null || true)"
|
|
23
|
+
[ -z "$ver" ] && continue
|
|
24
|
+
if [ "$(printf '%s\n%s\n' "$MIN_PYTHON_VERSION" "$ver" | sort -V | head -n1)" = "$MIN_PYTHON_VERSION" ]; then
|
|
25
|
+
echo "$cand"
|
|
26
|
+
return 0
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
done
|
|
30
|
+
return 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if ! PYTHON_BIN="$(find_python)"; then
|
|
34
|
+
echo "音声入力(Whisper)サーバーの起動に失敗: Python ${MIN_PYTHON_VERSION}以上が見つかりません。" >&2
|
|
35
|
+
echo "'brew install python@3.12' 等でインストールしてください。" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# requirements.txtの内容、または使用するpython3のバージョンが変わった場合は既存venvごと作り直す。
|
|
40
|
+
# venvは作成時のpythonバイナリに紐づくため、pip installだけでは古いバージョンのまま更新できない。
|
|
15
41
|
REQUIREMENTS_HASH_FILE="${VENV_DIR}/.requirements.sha256"
|
|
16
|
-
CURRENT_HASH="$(
|
|
42
|
+
CURRENT_HASH="$( { cat requirements.txt; "$PYTHON_BIN" --version; } | shasum -a 256 | awk '{print $1}')"
|
|
17
43
|
|
|
18
44
|
if [ ! -d "$VENV_DIR" ] || [ "$(cat "$REQUIREMENTS_HASH_FILE" 2>/dev/null)" != "$CURRENT_HASH" ]; then
|
|
19
45
|
mkdir -p "$TSUNAGI_WHISPER_DIR"
|
|
20
|
-
|
|
46
|
+
rm -rf "$VENV_DIR"
|
|
47
|
+
"$PYTHON_BIN" -m venv "$VENV_DIR"
|
|
21
48
|
"$VENV_DIR/bin/python3" -m pip install -r requirements.txt
|
|
22
49
|
echo "$CURRENT_HASH" > "$REQUIREMENTS_HASH_FILE"
|
|
23
50
|
fi
|
|
File without changes
|
|
File without changes
|
|
File without changes
|