local-embed 0.2.0__tar.gz → 0.2.2__tar.gz
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.
Potentially problematic release.
This version of local-embed might be problematic. Click here for more details.
- {local_embed-0.2.0 → local_embed-0.2.2}/PKG-INFO +34 -10
- {local_embed-0.2.0 → local_embed-0.2.2}/README.md +32 -9
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/cli.py +33 -13
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/cmd_set.py +16 -5
- {local_embed-0.2.0 → local_embed-0.2.2}/pyproject.toml +2 -1
- {local_embed-0.2.0 → local_embed-0.2.2}/tests/test_set.py +86 -7
- {local_embed-0.2.0 → local_embed-0.2.2}/.gitignore +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/__init__.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/__main__.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/config.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/engine.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/logging.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/server.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/server_utils.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/local_embed/threads.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/tests/test_client.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/tests/test_config.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/tests/test_engine.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/tests/test_logging.py +0 -0
- {local_embed-0.2.0 → local_embed-0.2.2}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: local-embed
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Standalone local embedding server — GGUF / transformer models as an OpenAI-compatible /v1/embeddings service
|
|
5
5
|
Project-URL: Homepage, https://github.com/juzcn/slife
|
|
6
6
|
Project-URL: Repository, https://github.com/juzcn/slife
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Topic :: Terminals
|
|
17
17
|
Requires-Python: >=3.13
|
|
18
18
|
Requires-Dist: fastmcp>=2.0.0
|
|
19
|
+
Requires-Dist: json5>=0.15.0
|
|
19
20
|
Requires-Dist: starlette>=0.37.0
|
|
20
21
|
Provides-Extra: gguf
|
|
21
22
|
Requires-Dist: llama-cpp-python; extra == 'gguf'
|
|
@@ -59,11 +60,22 @@ OpenAI-compatible client works.
|
|
|
59
60
|
|
|
60
61
|
## Install
|
|
61
62
|
|
|
62
|
-
Requires [uv](https://docs.astral.sh/uv)
|
|
63
|
-
|
|
63
|
+
Requires [uv](https://docs.astral.sh/uv). All installs below pin
|
|
64
|
+
**Python 3.13** (`requires-python` is `>=3.13`, but 3.13 is what CI tests,
|
|
65
|
+
and uv would otherwise pick the newest 3.14 it finds). Re-running any of
|
|
66
|
+
them over an **existing** install requires `--reinstall` (`uv tool install`
|
|
67
|
+
is a no-op on an already-installed tool otherwise), e.g. to upgrade to a new
|
|
68
|
+
release or pick up the `json5` dependency fix:
|
|
64
69
|
|
|
65
70
|
```bash
|
|
66
|
-
uv tool install 'local-embed[transformer]'
|
|
71
|
+
uv tool install --python 3.13 --reinstall 'local-embed[gguf,transformer]' \
|
|
72
|
+
--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `transformer` backend installs the same everywhere:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uv tool install --python 3.13 'local-embed[transformer]' # sentence-transformers
|
|
67
79
|
```
|
|
68
80
|
|
|
69
81
|
`gguf` (llama-cpp-python) is the platform-sensitive one: PyPI ships
|
|
@@ -74,10 +86,10 @@ for your platform:
|
|
|
74
86
|
|
|
75
87
|
| Platform | Command |
|
|
76
88
|
|---|---|
|
|
77
|
-
| Linux / macOS, CPU | `uv tool install 'local-embed[gguf]'` (PyPI wheel) |
|
|
78
|
-
| **Windows, CPU** | `uv tool install 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu` |
|
|
79
|
-
| macOS arm64 (Metal) | `uv tool install 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/metal` |
|
|
80
|
-
| CUDA (Linux or Windows) | `uv tool install 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu122` — pick `cu118` / `cu121` / `cu122` / `cu123` / `cu124` / `cu125` / `cu130` / `cu132` to match your CUDA version |
|
|
89
|
+
| Linux / macOS, CPU | `uv tool install --python 3.13 'local-embed[gguf]'` (PyPI wheel) |
|
|
90
|
+
| **Windows, CPU** | `uv tool install --python 3.13 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu` |
|
|
91
|
+
| macOS arm64 (Metal) | `uv tool install --python 3.13 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/metal` |
|
|
92
|
+
| CUDA (Linux or Windows) | `uv tool install --python 3.13 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu122` — pick `cu118` / `cu121` / `cu122` / `cu123` / `cu124` / `cu125` / `cu130` / `cu132` to match your CUDA version |
|
|
81
93
|
|
|
82
94
|
When the index is an *extra*, uv uses its wheel only when PyPI has no
|
|
83
95
|
compatible wheel for the platform (the Windows / Metal cases); on Linux
|
|
@@ -87,6 +99,17 @@ CUDA index. Note the CUDA Linux wheels need **glibc ≥ 2.35** (built for
|
|
|
87
99
|
If the `gguf` backend is missing, the server errors at startup with the
|
|
88
100
|
exact command for your platform (the Windows CPU one on Windows).
|
|
89
101
|
|
|
102
|
+
**Want both backends in one environment?** Install the two extras together
|
|
103
|
+
in a single command — running `uv tool install` twice replaces the first
|
|
104
|
+
environment with the second, so they would not coexist:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# both backends, Windows CPU:
|
|
108
|
+
uv tool install --python 3.13 'local-embed[gguf,transformer]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
|
|
109
|
+
# both backends, Linux / macOS CPU:
|
|
110
|
+
uv tool install --python 3.13 'local-embed[gguf,transformer]'
|
|
111
|
+
```
|
|
112
|
+
|
|
90
113
|
Core deps are `fastmcp` + `starlette`; the model backends are optional
|
|
91
114
|
extras. Installing does **not** fetch a model — get the weights first.
|
|
92
115
|
|
|
@@ -182,14 +205,15 @@ source of truth.
|
|
|
182
205
|
|
|
183
206
|
`local-embed set` (transformer) and `local-embed set-gguf` (gguf) upsert a
|
|
184
207
|
model in the config, make it **active**, and pin port (and, for `set`, the
|
|
185
|
-
HF cache). Both are **idempotent** — re-running yields the
|
|
186
|
-
and leave other models untouched.
|
|
208
|
+
HF cache + offline flag). Both are **idempotent** — re-running yields the
|
|
209
|
+
same config — and leave other models untouched.
|
|
187
210
|
|
|
188
211
|
| | `set` (transformer) | `set-gguf` |
|
|
189
212
|
|---|---|---|
|
|
190
213
|
| `<model_name>` | required — HF repo id (key + repo loaded) | required — config key |
|
|
191
214
|
| weight ref | `--HF_HUB_CACHE <dir>` — cache must contain the repo | `--path <PATH>` required — existing `.gguf` file |
|
|
192
215
|
| cache fallback | env `HF_HUB_CACHE`, else error | — |
|
|
216
|
+
| env written | `HF_HUB_CACHE` + `HF_HUB_OFFLINE: "1"` (offline server) | — |
|
|
193
217
|
| `--port <n>` | default `8000` | default `8000` |
|
|
194
218
|
|
|
195
219
|
```bash
|
|
@@ -34,11 +34,22 @@ OpenAI-compatible client works.
|
|
|
34
34
|
|
|
35
35
|
## Install
|
|
36
36
|
|
|
37
|
-
Requires [uv](https://docs.astral.sh/uv)
|
|
38
|
-
|
|
37
|
+
Requires [uv](https://docs.astral.sh/uv). All installs below pin
|
|
38
|
+
**Python 3.13** (`requires-python` is `>=3.13`, but 3.13 is what CI tests,
|
|
39
|
+
and uv would otherwise pick the newest 3.14 it finds). Re-running any of
|
|
40
|
+
them over an **existing** install requires `--reinstall` (`uv tool install`
|
|
41
|
+
is a no-op on an already-installed tool otherwise), e.g. to upgrade to a new
|
|
42
|
+
release or pick up the `json5` dependency fix:
|
|
39
43
|
|
|
40
44
|
```bash
|
|
41
|
-
uv tool install 'local-embed[transformer]'
|
|
45
|
+
uv tool install --python 3.13 --reinstall 'local-embed[gguf,transformer]' \
|
|
46
|
+
--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The `transformer` backend installs the same everywhere:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv tool install --python 3.13 'local-embed[transformer]' # sentence-transformers
|
|
42
53
|
```
|
|
43
54
|
|
|
44
55
|
`gguf` (llama-cpp-python) is the platform-sensitive one: PyPI ships
|
|
@@ -49,10 +60,10 @@ for your platform:
|
|
|
49
60
|
|
|
50
61
|
| Platform | Command |
|
|
51
62
|
|---|---|
|
|
52
|
-
| Linux / macOS, CPU | `uv tool install 'local-embed[gguf]'` (PyPI wheel) |
|
|
53
|
-
| **Windows, CPU** | `uv tool install 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu` |
|
|
54
|
-
| macOS arm64 (Metal) | `uv tool install 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/metal` |
|
|
55
|
-
| CUDA (Linux or Windows) | `uv tool install 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu122` — pick `cu118` / `cu121` / `cu122` / `cu123` / `cu124` / `cu125` / `cu130` / `cu132` to match your CUDA version |
|
|
63
|
+
| Linux / macOS, CPU | `uv tool install --python 3.13 'local-embed[gguf]'` (PyPI wheel) |
|
|
64
|
+
| **Windows, CPU** | `uv tool install --python 3.13 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu` |
|
|
65
|
+
| macOS arm64 (Metal) | `uv tool install --python 3.13 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/metal` |
|
|
66
|
+
| CUDA (Linux or Windows) | `uv tool install --python 3.13 'local-embed[gguf]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu122` — pick `cu118` / `cu121` / `cu122` / `cu123` / `cu124` / `cu125` / `cu130` / `cu132` to match your CUDA version |
|
|
56
67
|
|
|
57
68
|
When the index is an *extra*, uv uses its wheel only when PyPI has no
|
|
58
69
|
compatible wheel for the platform (the Windows / Metal cases); on Linux
|
|
@@ -62,6 +73,17 @@ CUDA index. Note the CUDA Linux wheels need **glibc ≥ 2.35** (built for
|
|
|
62
73
|
If the `gguf` backend is missing, the server errors at startup with the
|
|
63
74
|
exact command for your platform (the Windows CPU one on Windows).
|
|
64
75
|
|
|
76
|
+
**Want both backends in one environment?** Install the two extras together
|
|
77
|
+
in a single command — running `uv tool install` twice replaces the first
|
|
78
|
+
environment with the second, so they would not coexist:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# both backends, Windows CPU:
|
|
82
|
+
uv tool install --python 3.13 'local-embed[gguf,transformer]' --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
|
|
83
|
+
# both backends, Linux / macOS CPU:
|
|
84
|
+
uv tool install --python 3.13 'local-embed[gguf,transformer]'
|
|
85
|
+
```
|
|
86
|
+
|
|
65
87
|
Core deps are `fastmcp` + `starlette`; the model backends are optional
|
|
66
88
|
extras. Installing does **not** fetch a model — get the weights first.
|
|
67
89
|
|
|
@@ -157,14 +179,15 @@ source of truth.
|
|
|
157
179
|
|
|
158
180
|
`local-embed set` (transformer) and `local-embed set-gguf` (gguf) upsert a
|
|
159
181
|
model in the config, make it **active**, and pin port (and, for `set`, the
|
|
160
|
-
HF cache). Both are **idempotent** — re-running yields the
|
|
161
|
-
and leave other models untouched.
|
|
182
|
+
HF cache + offline flag). Both are **idempotent** — re-running yields the
|
|
183
|
+
same config — and leave other models untouched.
|
|
162
184
|
|
|
163
185
|
| | `set` (transformer) | `set-gguf` |
|
|
164
186
|
|---|---|---|
|
|
165
187
|
| `<model_name>` | required — HF repo id (key + repo loaded) | required — config key |
|
|
166
188
|
| weight ref | `--HF_HUB_CACHE <dir>` — cache must contain the repo | `--path <PATH>` required — existing `.gguf` file |
|
|
167
189
|
| cache fallback | env `HF_HUB_CACHE`, else error | — |
|
|
190
|
+
| env written | `HF_HUB_CACHE` + `HF_HUB_OFFLINE: "1"` (offline server) | — |
|
|
168
191
|
| `--port <n>` | default `8000` | default `8000` |
|
|
169
192
|
|
|
170
193
|
```bash
|
|
@@ -108,26 +108,46 @@ def main(argv: "list[str] | None" = None) -> int:
|
|
|
108
108
|
|
|
109
109
|
# Everything (host/port/backend/model/gguf_path/device) comes from
|
|
110
110
|
# local_embed.json5 — the CLI deliberately takes no model/endpoint flags.
|
|
111
|
-
|
|
111
|
+
# A missing file is fine (defaults); a file that exists but won't parse
|
|
112
|
+
# must fail cleanly, not traceback.
|
|
113
|
+
try:
|
|
114
|
+
settings = resolve_engine_settings()
|
|
115
|
+
except ValueError as e:
|
|
116
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
117
|
+
print("Fix local_embed.json5, or run `local-embed set` to rewrite it.",
|
|
118
|
+
file=sys.stderr)
|
|
119
|
+
return 2
|
|
120
|
+
except OSError as e:
|
|
121
|
+
print(f"Error: cannot read config: {e}", file=sys.stderr)
|
|
122
|
+
return 2
|
|
123
|
+
|
|
112
124
|
engine = Engine(specs=settings["specs"], active=settings["active"])
|
|
113
125
|
|
|
114
|
-
# Validate the
|
|
115
|
-
# `check_backend_runtime`, which never imports) so the answer
|
|
116
|
-
# reality. Only the ACTIVE model
|
|
117
|
-
#
|
|
118
|
-
#
|
|
126
|
+
# Validate the active model can actually run. `resolve_backend_runtime`
|
|
127
|
+
# (not `check_backend_runtime`, which never imports) so the answer
|
|
128
|
+
# reflects reality. Only the ACTIVE model is import-checked at startup:
|
|
129
|
+
# a non-active model's backend import cost (torch!) is only worth paying
|
|
130
|
+
# when the model is actually requested — it fails at load time then,
|
|
131
|
+
# and this CLI must not stall on it (or hang an impatient Ctrl-C during
|
|
132
|
+
# the import). gguf_path is still checked for every model.
|
|
119
133
|
active_name = engine.active_model
|
|
120
134
|
for spec in settings["specs"]:
|
|
121
135
|
problems: list[str] = []
|
|
122
136
|
if spec.backend == "gguf" and not spec.gguf_path:
|
|
123
137
|
problems.append("no gguf_path (set gguf_path in local_embed.json5)")
|
|
124
|
-
if
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
if spec.name == active_name:
|
|
139
|
+
try:
|
|
140
|
+
installed = resolve_backend_runtime(spec.backend)
|
|
141
|
+
except KeyboardInterrupt:
|
|
142
|
+
print("Interrupted — local-embed not started.", file=sys.stderr)
|
|
143
|
+
return 130
|
|
144
|
+
if not installed:
|
|
145
|
+
from local_embed.cmd_set import backend_install_hint
|
|
146
|
+
|
|
147
|
+
problems.append(
|
|
148
|
+
f"{spec.backend} backend not installed "
|
|
149
|
+
f"({backend_install_hint(spec.backend)})"
|
|
150
|
+
)
|
|
131
151
|
if spec.name == active_name and problems:
|
|
132
152
|
print(
|
|
133
153
|
f"Error: active model '{spec.name}' cannot start: {'; '.join(problems)}",
|
|
@@ -24,6 +24,7 @@ from pathlib import Path
|
|
|
24
24
|
from local_embed.config import load_config, write_config
|
|
25
25
|
|
|
26
26
|
ENV_CACHE_KEY = "HF_HUB_CACHE"
|
|
27
|
+
ENV_OFFLINE_KEY = "HF_HUB_OFFLINE"
|
|
27
28
|
|
|
28
29
|
# Official prebuilt-wheel indexes for llama-cpp-python (the upstream llama.cpp
|
|
29
30
|
# project). PyPI carries no Windows wheels for llama-cpp-python, so on
|
|
@@ -42,16 +43,19 @@ def backend_install_hint(backend: str, platform: "str | None" = None) -> str:
|
|
|
42
43
|
must be added or uv falls back to a source build (MSVC + CMake). On
|
|
43
44
|
Linux/macOS the plain PyPI wheel is fine. (CUDA / Metal need their own
|
|
44
45
|
indexes — see the README install matrix.)
|
|
46
|
+
|
|
47
|
+
The command always pins ``--python 3.13``: it is the version CI tests,
|
|
48
|
+
and without it uv would silently pick the newest Python it finds.
|
|
45
49
|
"""
|
|
46
50
|
platform = platform or sys.platform
|
|
47
51
|
if backend == "gguf":
|
|
48
52
|
if platform == "win32":
|
|
49
53
|
return (
|
|
50
|
-
"uv tool install 'local-embed[gguf]' "
|
|
54
|
+
"uv tool install --python 3.13 'local-embed[gguf]' "
|
|
51
55
|
f"--extra-index-url {LLAMA_CPP_INDEXES['cpu']}"
|
|
52
56
|
)
|
|
53
|
-
return "uv tool install 'local-embed[gguf]'"
|
|
54
|
-
return f"uv tool install 'local-embed[{backend}]'"
|
|
57
|
+
return "uv tool install --python 3.13 'local-embed[gguf]'"
|
|
58
|
+
return f"uv tool install --python 3.13 'local-embed[{backend}]'"
|
|
55
59
|
|
|
56
60
|
|
|
57
61
|
def resolve_cache(raw: "str | None", environ: "dict[str, str] | None" = None) -> str:
|
|
@@ -92,11 +96,18 @@ def _upsert_model(cfg: dict, model: str, entry: dict, port: int) -> dict:
|
|
|
92
96
|
|
|
93
97
|
|
|
94
98
|
def set_transformer_model(cfg: dict, model: str, cache: str, port: int) -> dict:
|
|
95
|
-
"""Upsert ``model`` as a transformer model, pin the cache
|
|
99
|
+
"""Upsert ``model`` as a transformer model, pin the pre-downloaded cache.
|
|
100
|
+
|
|
101
|
+
The server is intended to run **offline** — the model is pre-downloaded
|
|
102
|
+
into ``cache`` and ``set`` requires it to already be there — so
|
|
103
|
+
``HF_HUB_OFFLINE=1`` is written alongside the cache (the server resolves
|
|
104
|
+
the repo against this cache and never hits the network).
|
|
105
|
+
"""
|
|
96
106
|
env = dict(cfg.get("env") or {})
|
|
97
107
|
env[ENV_CACHE_KEY] = cache
|
|
108
|
+
env[ENV_OFFLINE_KEY] = "1"
|
|
98
109
|
out = _upsert_model(cfg, model, {"backend": "transformer", "model": model}, port)
|
|
99
|
-
out["env"] = env # existing env keys preserved;
|
|
110
|
+
out["env"] = env # existing env keys preserved; cache + offline (re)written
|
|
100
111
|
return out
|
|
101
112
|
|
|
102
113
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "local-embed"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Standalone local embedding server — GGUF / transformer models as an OpenAI-compatible /v1/embeddings service"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -11,6 +11,7 @@ requires-python = ">=3.13"
|
|
|
11
11
|
dependencies = [
|
|
12
12
|
"fastmcp>=2.0.0",
|
|
13
13
|
"starlette>=0.37.0",
|
|
14
|
+
"json5>=0.15.0",
|
|
14
15
|
]
|
|
15
16
|
classifiers = [
|
|
16
17
|
"Development Status :: 3 - Alpha",
|
|
@@ -10,15 +10,18 @@ import pytest
|
|
|
10
10
|
pytestmark = pytest.mark.unit
|
|
11
11
|
|
|
12
12
|
from local_embed.cli import main
|
|
13
|
+
|
|
14
|
+
import local_embed.cli as cli
|
|
13
15
|
from local_embed.cmd_set import (
|
|
14
16
|
ENV_CACHE_KEY,
|
|
17
|
+
ENV_OFFLINE_KEY,
|
|
15
18
|
backend_install_hint,
|
|
16
19
|
model_in_cache,
|
|
17
20
|
resolve_cache,
|
|
18
21
|
set_gguf_model,
|
|
19
22
|
set_transformer_model,
|
|
20
23
|
)
|
|
21
|
-
from local_embed.config import load_config
|
|
24
|
+
from local_embed.config import load_config, write_config
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
class TestResolveCache:
|
|
@@ -41,9 +44,15 @@ class TestSetTransformerModel:
|
|
|
41
44
|
"models": {"BAAI/bge-m3": {"backend": "transformer", "model": "BAAI/bge-m3"}},
|
|
42
45
|
"active_model": "BAAI/bge-m3",
|
|
43
46
|
"port": 8000,
|
|
44
|
-
"env": {ENV_CACHE_KEY: "C:\\hub"},
|
|
47
|
+
"env": {ENV_CACHE_KEY: "C:\\hub", ENV_OFFLINE_KEY: "1"},
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
def test_offline_flag_defaults_on(self):
|
|
51
|
+
# the server should never silently refetch a missing repo — pin
|
|
52
|
+
# HF_HUB_OFFLINE even when the user only supplied a cache dir
|
|
53
|
+
out = set_transformer_model({}, "BAAI/bge-m3", "C:\\hub", 8000)
|
|
54
|
+
assert out["env"][ENV_OFFLINE_KEY] == "1"
|
|
55
|
+
|
|
47
56
|
def test_preserves_existing_models_and_env(self):
|
|
48
57
|
cfg = {
|
|
49
58
|
"models": {"old": {"backend": "gguf", "gguf_path": "/a.gguf"}},
|
|
@@ -111,18 +120,22 @@ def config_path(tmp_path, monkeypatch):
|
|
|
111
120
|
|
|
112
121
|
class TestBackendInstallHint:
|
|
113
122
|
def test_gguf_linux_macos_plain(self):
|
|
114
|
-
assert backend_install_hint("gguf", "linux") ==
|
|
115
|
-
|
|
123
|
+
assert backend_install_hint("gguf", "linux") == (
|
|
124
|
+
"uv tool install --python 3.13 'local-embed[gguf]'"
|
|
125
|
+
)
|
|
126
|
+
assert backend_install_hint("gguf", "darwin") == (
|
|
127
|
+
"uv tool install --python 3.13 'local-embed[gguf]'"
|
|
128
|
+
)
|
|
116
129
|
|
|
117
130
|
def test_gguf_windows_adds_cpu_index(self):
|
|
118
131
|
assert backend_install_hint("gguf", "win32") == (
|
|
119
|
-
"uv tool install 'local-embed[gguf]' "
|
|
132
|
+
"uv tool install --python 3.13 'local-embed[gguf]' "
|
|
120
133
|
"--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu"
|
|
121
134
|
)
|
|
122
135
|
|
|
123
136
|
def test_transformer_plain(self):
|
|
124
137
|
assert backend_install_hint("transformer", "win32") == (
|
|
125
|
-
"uv tool install 'local-embed[transformer]'"
|
|
138
|
+
"uv tool install --python 3.13 'local-embed[transformer]'"
|
|
126
139
|
)
|
|
127
140
|
|
|
128
141
|
|
|
@@ -206,4 +219,70 @@ class TestRunSetGguf:
|
|
|
206
219
|
assert main(["set-gguf", "bge-m3", "--path", str(gguf)]) == 0
|
|
207
220
|
first = config_path.read_text(encoding="utf-8")
|
|
208
221
|
assert main(["set-gguf", "bge-m3", "--path", str(gguf)]) == 0
|
|
209
|
-
assert config_path.read_text(encoding="utf-8") == first
|
|
222
|
+
assert config_path.read_text(encoding="utf-8") == first
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class TestCliCtrlC:
|
|
226
|
+
"""Ctrl-C during the active backend check must exit 130, never traceback.
|
|
227
|
+
|
|
228
|
+
Regression: the startup validation calls ``resolve_backend_runtime``
|
|
229
|
+
(a REAL import — torch takes seconds), and a Ctrl-C landing mid-import
|
|
230
|
+
used to propagate as a raw KeyboardInterrupt traceback.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def _write_active_transformer(self, config_path):
|
|
234
|
+
write_config(
|
|
235
|
+
{
|
|
236
|
+
"active_model": "bge-m3-transformer",
|
|
237
|
+
"models": {
|
|
238
|
+
"bge-m3-transformer": {
|
|
239
|
+
"backend": "transformer",
|
|
240
|
+
"model": "BAAI/bge-m3",
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
config_path,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def test_interrupt_during_active_check_exits_130(self, monkeypatch, config_path, capsys):
|
|
248
|
+
self._write_active_transformer(config_path)
|
|
249
|
+
|
|
250
|
+
def interrupt(_backend):
|
|
251
|
+
raise KeyboardInterrupt()
|
|
252
|
+
|
|
253
|
+
monkeypatch.setattr(cli, "resolve_backend_runtime", interrupt)
|
|
254
|
+
code = cli.main([])
|
|
255
|
+
assert code == 130
|
|
256
|
+
assert "Interrupted" in capsys.readouterr().err
|
|
257
|
+
|
|
258
|
+
def test_non_active_backend_not_resolved(self, monkeypatch, config_path):
|
|
259
|
+
# active = gguf; the non-active transformer must NOT trigger the
|
|
260
|
+
# torch import at startup (it is only checked when actually loaded).
|
|
261
|
+
write_config(
|
|
262
|
+
{
|
|
263
|
+
"active_model": "bge-m3",
|
|
264
|
+
"models": {
|
|
265
|
+
"bge-m3": {"backend": "gguf", "gguf_path": "/x.gguf"},
|
|
266
|
+
"bge-m3-transformer": {
|
|
267
|
+
"backend": "transformer",
|
|
268
|
+
"model": "BAAI/bge-m3",
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
config_path,
|
|
273
|
+
)
|
|
274
|
+
resolved = []
|
|
275
|
+
|
|
276
|
+
def tracking(backend):
|
|
277
|
+
resolved.append(backend)
|
|
278
|
+
return True
|
|
279
|
+
|
|
280
|
+
monkeypatch.setattr(cli, "resolve_backend_runtime", tracking)
|
|
281
|
+
# stop after the validation loop instead of serving (serve_standalone
|
|
282
|
+
# is imported lazily inside main, so patch its source module)
|
|
283
|
+
monkeypatch.setattr(
|
|
284
|
+
"local_embed.server.serve_standalone", lambda *a, **k: 0
|
|
285
|
+
)
|
|
286
|
+
code = cli.main([])
|
|
287
|
+
assert code == 0
|
|
288
|
+
assert resolved == ["gguf"] # only the active backend was imported
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|