local-embed 0.1.0__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.
@@ -0,0 +1,246 @@
1
+ # Runtime config — personal per-user settings; template is slife.template.json5.
2
+ slife.json5
3
+ mcp-plugin.json5
4
+ local_embed.json5
5
+
6
+ # Playwright MCP browser traces (test artifacts)
7
+ .playwright-mcp/
8
+
9
+ # Credential store encrypted backup
10
+ credentials.crypt
11
+
12
+ # WeChat session files (contain auth tokens)
13
+ wechat_*.json5
14
+
15
+ # Runtime logs
16
+ logs/
17
+
18
+ # User-saved files (memfiles plugin storage — per-agent runtime data)
19
+ *.files/
20
+
21
+ # Byte-compiled / optimized / DLL files
22
+ __pycache__/
23
+ *.py[codz]
24
+ *$py.class
25
+
26
+ # C extensions
27
+ *.so
28
+
29
+ # Distribution / packaging
30
+ .Python
31
+ build/
32
+ develop-eggs/
33
+ dist/
34
+ downloads/
35
+ eggs/
36
+ .eggs/
37
+ lib/
38
+ lib64/
39
+ parts/
40
+ sdist/
41
+ var/
42
+ wheels/
43
+ share/python-wheels/
44
+ *.egg-info/
45
+ .installed.cfg
46
+ *.egg
47
+ MANIFEST
48
+
49
+ # PyInstaller
50
+ # Usually these files are written by a python script from a template
51
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
52
+ *.manifest
53
+ *.spec
54
+
55
+ # Installer logs
56
+ pip-log.txt
57
+ pip-delete-this-directory.txt
58
+
59
+ # Unit test / coverage reports
60
+ htmlcov/
61
+ .tox/
62
+ .nox/
63
+ .coverage
64
+ .coverage.*
65
+ .cache
66
+ nosetests.xml
67
+ coverage.xml
68
+ *.cover
69
+ *.py.cover
70
+ .hypothesis/
71
+ .pytest_cache/
72
+ cover/
73
+
74
+ # Translations
75
+ *.mo
76
+ *.pot
77
+
78
+ # Django stuff:
79
+ *.log
80
+ local_settings.py
81
+ db.sqlite3
82
+ db.sqlite3-journal
83
+
84
+ # Flask stuff:
85
+ instance/
86
+ .webassets-cache
87
+
88
+ # Scrapy stuff:
89
+ .scrapy
90
+
91
+ # Sphinx documentation
92
+ docs/_build/
93
+
94
+ # PyBuilder
95
+ .pybuilder/
96
+ target/
97
+
98
+ # Jupyter Notebook
99
+ .ipynb_checkpoints
100
+
101
+ # IPython
102
+ profile_default/
103
+ ipython_config.py
104
+
105
+ # pyenv
106
+ # For a library or package, you might want to ignore these files since the code is
107
+ # intended to run in multiple environments; otherwise, check them in:
108
+ # .python-version
109
+
110
+ # pipenv
111
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
112
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
113
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
114
+ # install all needed dependencies.
115
+ # Pipfile.lock
116
+
117
+ # UV
118
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
119
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
120
+ # commonly ignored for libraries.
121
+ # uv.lock
122
+
123
+ # poetry
124
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
125
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
126
+ # commonly ignored for libraries.
127
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
128
+ # poetry.lock
129
+ # poetry.toml
130
+
131
+ # pdm
132
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
133
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
134
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
135
+ # pdm.lock
136
+ # pdm.toml
137
+ .pdm-python
138
+ .pdm-build/
139
+
140
+ # pixi
141
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
142
+ # pixi.lock
143
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
144
+ # in the .venv directory. It is recommended not to include this directory in version control.
145
+ .pixi
146
+
147
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
148
+ __pypackages__/
149
+
150
+ # Celery stuff
151
+ celerybeat-schedule
152
+ celerybeat.pid
153
+
154
+ # Redis
155
+ *.rdb
156
+ *.aof
157
+ *.pid
158
+
159
+ # RabbitMQ
160
+ mnesia/
161
+ rabbitmq/
162
+ rabbitmq-data/
163
+
164
+ # ActiveMQ
165
+ activemq-data/
166
+
167
+ # SageMath parsed files
168
+ *.sage.py
169
+
170
+ # Environments
171
+ .env
172
+ .envrc
173
+ .venv
174
+ env/
175
+ venv/
176
+ ENV/
177
+ env.bak/
178
+ venv.bak/
179
+
180
+ # Spyder project settings
181
+ .spyderproject
182
+ .spyproject
183
+
184
+ # Rope project settings
185
+ .ropeproject
186
+
187
+ # mkdocs documentation
188
+ /site
189
+
190
+ # mypy
191
+ .mypy_cache/
192
+ .dmypy.json
193
+ dmypy.json
194
+
195
+ # Pyre type checker
196
+ .pyre/
197
+
198
+ # pytype static type analyzer
199
+ .pytype/
200
+
201
+ # Cython debug symbols
202
+ cython_debug/
203
+
204
+ # PyCharm
205
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
206
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
207
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
208
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
209
+ # .idea/
210
+
211
+ # Abstra
212
+ # Abstra is an AI-powered process automation framework.
213
+ # Ignore directories containing user credentials, local state, and settings.
214
+ # Learn more at https://abstra.io/docs
215
+ .abstra/
216
+
217
+ # Visual Studio Code
218
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
219
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
220
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
221
+ # you could uncomment the following to ignore the entire vscode folder
222
+ # .vscode/
223
+ # Temporary file for partial code execution
224
+ tempCodeRunnerFile.py
225
+
226
+ # Ruff stuff:
227
+ .ruff_cache/
228
+
229
+ # PyPI configuration file
230
+ .pypirc
231
+
232
+ # Marimo
233
+ marimo/_static/
234
+ marimo/_lsp/
235
+ __marimo__/
236
+
237
+ # Streamlit
238
+ .streamlit/secrets.toml
239
+
240
+ # SQLite database files (may appear anywhere in the project)
241
+ *.db
242
+ *.db-shm
243
+ *.db-wal
244
+
245
+ # local DB backups (never committed)
246
+ slife.db.bak*
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.3
2
+ Name: local-embed
3
+ Version: 0.1.0
4
+ Summary: Standalone local embedding server — GGUF / transformer models as an OpenAI-compatible /v1/embeddings service
5
+ Project-URL: Homepage, https://github.com/juzcn/slife
6
+ Project-URL: Repository, https://github.com/juzcn/slife
7
+ Project-URL: Documentation, https://github.com/juzcn/slife/blob/main/local-embed/README.md
8
+ Author-email: juzcn <zhangjun@cueb.edu.cn>
9
+ License: MIT
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Terminals
17
+ Requires-Python: >=3.13
18
+ Requires-Dist: fastmcp>=2.0.0
19
+ Requires-Dist: starlette>=0.37.0
20
+ Provides-Extra: gguf
21
+ Requires-Dist: llama-cpp-python; extra == 'gguf'
22
+ Provides-Extra: transformer
23
+ Requires-Dist: sentence-transformers; extra == 'transformer'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # local-embed
27
+
28
+ **Standalone local embedding server** — loads one GGUF (llama-cpp) or HF
29
+ transformer embedding model **once** and exposes it as an
30
+ [OpenAI-compatible](https://platform.openai.com/docs/api-reference/embeddings)
31
+ `/v1/embeddings` HTTP endpoint **plus** FastMCP tools, on a single port.
32
+
33
+ Built for **slife** (its `memdb` and `memfiles` plugins both call this
34
+ service over HTTP, so the model is never loaded twice in one process
35
+ tree), but it is a fully standalone package — any OpenAI-compatible
36
+ client can use it.
37
+
38
+ ```
39
+ POST http://127.0.0.1:8000/v1/embeddings
40
+ slife memdb ─────────────────────────┐
41
+ slife memfiles ──────────────────────┤
42
+ any OpenAI client ───────────────────┤
43
+
44
+ ┌────────────────────────────┐
45
+ │ local-embed │
46
+ │ /v1/embeddings (HTTP) │
47
+ │ /v1/models (HTTP) │
48
+ │ /health (HTTP) │
49
+ │ /mcp (FastMCP tools) │
50
+ │ ONE loaded model │
51
+ └────────────────────────────┘
52
+ ```
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ # GGUF backend (llama-cpp-python)
58
+ uv tool install 'local-embed[gguf]' # or: uv pip install 'local-embed[gguf]'
59
+ # Transformer backend (sentence-transformers)
60
+ uv tool install 'local-embed[transformer]' # or: uv pip install 'local-embed[transformer]'
61
+ ```
62
+
63
+ Python 3.13+. The server core only depends on `fastmcp` + `starlette`; the
64
+ heavy model backends are optional extras.
65
+
66
+ ## Run
67
+
68
+ ```bash
69
+ # GGUF model (recommended — offline, no HF download)
70
+ local-embed --backend gguf --model bge-m3 --gguf-path /path/to/bge-m3-q4_k_m.gguf --port 8000
71
+
72
+ # HF transformer model (downloads from HF hub on first load)
73
+ local-embed --backend transformer --model BAAI/bge-m3 --port 8000
74
+ ```
75
+
76
+ By default it binds `127.0.0.1:8000` (local only — never exposed to the
77
+ network).
78
+
79
+ ### Transformer models & the `env:` section
80
+
81
+ A `transformer` model is referenced by its HF *repo name* (`BAAI/bge-m3`).
82
+ The HuggingFace hub resolves that name against its cache (default
83
+ `~/.cache/huggingface`), downloading on first load if missing. To keep the
84
+ server self-contained — point it at an existing local cache, or force
85
+ offline — put the env vars in `local_embed.json5`'s `env:` section (injected
86
+ into this process before any model loads; an existing shell env var wins):
87
+
88
+ ```json5
89
+ {
90
+ env: {
91
+ HF_HUB_CACHE: "C:\\Users\\me\\HuggingFace\\hub", // existing cache
92
+ HF_HUB_OFFLINE: "1" // never hit the network
93
+ },
94
+ models: {
95
+ "bge-m3-transformer": { backend: "transformer", model: "BAAI/bge-m3", device: "" }
96
+ }
97
+ }
98
+ ```
99
+
100
+ Without `HF_HUB_CACHE` the model resolves against the default cache — a
101
+ model already downloaded elsewhere would be silently re-fetched.
102
+
103
+ ## Use
104
+
105
+ ```bash
106
+ curl http://127.0.0.1:8000/v1/embeddings \
107
+ -H 'Content-Type: application/json' \
108
+ -d '{"model": "bge-m3", "input": ["hello world", "another text"]}'
109
+ ```
110
+
111
+ Returns the standard OpenAI shape:
112
+
113
+ ```json
114
+ {
115
+ "object": "list",
116
+ "data": [
117
+ {"object": "embedding", "index": 0, "embedding": [0.012, ...]},
118
+ {"object": "embedding", "index": 1, "embedding": [...]}
119
+ ],
120
+ "model": "bge-m3",
121
+ "usage": {"prompt_tokens": 3, "total_tokens": 3}
122
+ }
123
+ ```
124
+
125
+ Any OpenAI-compatible client works — point `base_url` at
126
+ `http://127.0.0.1:8000/v1` (e.g. slife's `memdb.embedding` api backend, or
127
+ the `openai` Python package):
128
+
129
+ ```python
130
+ from openai import OpenAI
131
+
132
+ client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="local")
133
+ vecs = client.embeddings.create(model="bge-m3", input=["hello"])
134
+ ```
135
+
136
+ Other endpoints:
137
+
138
+ - `GET /v1/models` — the loaded model + its real embedding dimension.
139
+ - `GET /health` — `{status, backend, model, dimension, loaded}`.
140
+ - `POST /mcp` — FastMCP streamable-HTTP endpoint (tools `embed_status` and
141
+ `embed`).
142
+
143
+ ## Dimension
144
+
145
+ The real output width is only known once the model is loaded (`n_embd` /
146
+ `get_sentence_embedding_dimension`). `GET /v1/models` reports the real
147
+ dimension, so a client can size its vector table correctly — a wrong width
148
+ silently drops every mis-sized embedding.
149
+
150
+ ## As a slife external plugin
151
+
152
+ slife treats every embedding model as a **remote OpenAI-compatible
153
+ endpoint** — local-embed is just one such endpoint. The model is
154
+ **determined by the plugin's active model**: slife discovers it from
155
+ `GET /v1/models` (the entry flagged `active: true`) on load.
156
+
157
+ Register local-embed as an external plugin (like `mcp-plugin`) so slife
158
+ manages the process, and point slife's embedding config at it with the
159
+ unified OpenAI format:
160
+
161
+ ```json5
162
+ plugins: {
163
+ external: [
164
+ { name: "local-embed", module: "local_embed.server" }
165
+ ]
166
+ },
167
+ memdb: {
168
+ embedding: {
169
+ base_url: "http://127.0.0.1:8000/v1", // stable port from local_embed.json5
170
+ api_key: "local",
171
+ }
172
+ }
173
+ ```
174
+
175
+ The plugin binds the **stable port** from `local_embed.json5` (default
176
+ `8000`), so slife's `base_url` is fixed whether local-embed runs as the
177
+ plugin or standalone. When the service is unreachable, slife degrades
178
+ gracefully to keyword search.
179
+
180
+ ## CLI
181
+
182
+ ```
183
+ local-embed --help
184
+ ```
185
+
186
+ - `--host` / `--port` — bind address (default `127.0.0.1:8000`)
187
+ - `--backend gguf|transformer` — model backend (default `gguf`)
188
+ - `--model` — model name/id (for metadata and dim guessing)
189
+ - `--gguf-path` — path to the GGUF file (required for `backend=gguf`)
190
+ - `--device cpu|cuda` — transformer device (default auto)
191
+ - `--log-level` — logging level (default `INFO`)
192
+
193
+ ## License
194
+
195
+ MIT — see the repository root `LICENSE`.
@@ -0,0 +1,170 @@
1
+ # local-embed
2
+
3
+ **Standalone local embedding server** — loads one GGUF (llama-cpp) or HF
4
+ transformer embedding model **once** and exposes it as an
5
+ [OpenAI-compatible](https://platform.openai.com/docs/api-reference/embeddings)
6
+ `/v1/embeddings` HTTP endpoint **plus** FastMCP tools, on a single port.
7
+
8
+ Built for **slife** (its `memdb` and `memfiles` plugins both call this
9
+ service over HTTP, so the model is never loaded twice in one process
10
+ tree), but it is a fully standalone package — any OpenAI-compatible
11
+ client can use it.
12
+
13
+ ```
14
+ POST http://127.0.0.1:8000/v1/embeddings
15
+ slife memdb ─────────────────────────┐
16
+ slife memfiles ──────────────────────┤
17
+ any OpenAI client ───────────────────┤
18
+
19
+ ┌────────────────────────────┐
20
+ │ local-embed │
21
+ │ /v1/embeddings (HTTP) │
22
+ │ /v1/models (HTTP) │
23
+ │ /health (HTTP) │
24
+ │ /mcp (FastMCP tools) │
25
+ │ ONE loaded model │
26
+ └────────────────────────────┘
27
+ ```
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ # GGUF backend (llama-cpp-python)
33
+ uv tool install 'local-embed[gguf]' # or: uv pip install 'local-embed[gguf]'
34
+ # Transformer backend (sentence-transformers)
35
+ uv tool install 'local-embed[transformer]' # or: uv pip install 'local-embed[transformer]'
36
+ ```
37
+
38
+ Python 3.13+. The server core only depends on `fastmcp` + `starlette`; the
39
+ heavy model backends are optional extras.
40
+
41
+ ## Run
42
+
43
+ ```bash
44
+ # GGUF model (recommended — offline, no HF download)
45
+ local-embed --backend gguf --model bge-m3 --gguf-path /path/to/bge-m3-q4_k_m.gguf --port 8000
46
+
47
+ # HF transformer model (downloads from HF hub on first load)
48
+ local-embed --backend transformer --model BAAI/bge-m3 --port 8000
49
+ ```
50
+
51
+ By default it binds `127.0.0.1:8000` (local only — never exposed to the
52
+ network).
53
+
54
+ ### Transformer models & the `env:` section
55
+
56
+ A `transformer` model is referenced by its HF *repo name* (`BAAI/bge-m3`).
57
+ The HuggingFace hub resolves that name against its cache (default
58
+ `~/.cache/huggingface`), downloading on first load if missing. To keep the
59
+ server self-contained — point it at an existing local cache, or force
60
+ offline — put the env vars in `local_embed.json5`'s `env:` section (injected
61
+ into this process before any model loads; an existing shell env var wins):
62
+
63
+ ```json5
64
+ {
65
+ env: {
66
+ HF_HUB_CACHE: "C:\\Users\\me\\HuggingFace\\hub", // existing cache
67
+ HF_HUB_OFFLINE: "1" // never hit the network
68
+ },
69
+ models: {
70
+ "bge-m3-transformer": { backend: "transformer", model: "BAAI/bge-m3", device: "" }
71
+ }
72
+ }
73
+ ```
74
+
75
+ Without `HF_HUB_CACHE` the model resolves against the default cache — a
76
+ model already downloaded elsewhere would be silently re-fetched.
77
+
78
+ ## Use
79
+
80
+ ```bash
81
+ curl http://127.0.0.1:8000/v1/embeddings \
82
+ -H 'Content-Type: application/json' \
83
+ -d '{"model": "bge-m3", "input": ["hello world", "another text"]}'
84
+ ```
85
+
86
+ Returns the standard OpenAI shape:
87
+
88
+ ```json
89
+ {
90
+ "object": "list",
91
+ "data": [
92
+ {"object": "embedding", "index": 0, "embedding": [0.012, ...]},
93
+ {"object": "embedding", "index": 1, "embedding": [...]}
94
+ ],
95
+ "model": "bge-m3",
96
+ "usage": {"prompt_tokens": 3, "total_tokens": 3}
97
+ }
98
+ ```
99
+
100
+ Any OpenAI-compatible client works — point `base_url` at
101
+ `http://127.0.0.1:8000/v1` (e.g. slife's `memdb.embedding` api backend, or
102
+ the `openai` Python package):
103
+
104
+ ```python
105
+ from openai import OpenAI
106
+
107
+ client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="local")
108
+ vecs = client.embeddings.create(model="bge-m3", input=["hello"])
109
+ ```
110
+
111
+ Other endpoints:
112
+
113
+ - `GET /v1/models` — the loaded model + its real embedding dimension.
114
+ - `GET /health` — `{status, backend, model, dimension, loaded}`.
115
+ - `POST /mcp` — FastMCP streamable-HTTP endpoint (tools `embed_status` and
116
+ `embed`).
117
+
118
+ ## Dimension
119
+
120
+ The real output width is only known once the model is loaded (`n_embd` /
121
+ `get_sentence_embedding_dimension`). `GET /v1/models` reports the real
122
+ dimension, so a client can size its vector table correctly — a wrong width
123
+ silently drops every mis-sized embedding.
124
+
125
+ ## As a slife external plugin
126
+
127
+ slife treats every embedding model as a **remote OpenAI-compatible
128
+ endpoint** — local-embed is just one such endpoint. The model is
129
+ **determined by the plugin's active model**: slife discovers it from
130
+ `GET /v1/models` (the entry flagged `active: true`) on load.
131
+
132
+ Register local-embed as an external plugin (like `mcp-plugin`) so slife
133
+ manages the process, and point slife's embedding config at it with the
134
+ unified OpenAI format:
135
+
136
+ ```json5
137
+ plugins: {
138
+ external: [
139
+ { name: "local-embed", module: "local_embed.server" }
140
+ ]
141
+ },
142
+ memdb: {
143
+ embedding: {
144
+ base_url: "http://127.0.0.1:8000/v1", // stable port from local_embed.json5
145
+ api_key: "local",
146
+ }
147
+ }
148
+ ```
149
+
150
+ The plugin binds the **stable port** from `local_embed.json5` (default
151
+ `8000`), so slife's `base_url` is fixed whether local-embed runs as the
152
+ plugin or standalone. When the service is unreachable, slife degrades
153
+ gracefully to keyword search.
154
+
155
+ ## CLI
156
+
157
+ ```
158
+ local-embed --help
159
+ ```
160
+
161
+ - `--host` / `--port` — bind address (default `127.0.0.1:8000`)
162
+ - `--backend gguf|transformer` — model backend (default `gguf`)
163
+ - `--model` — model name/id (for metadata and dim guessing)
164
+ - `--gguf-path` — path to the GGUF file (required for `backend=gguf`)
165
+ - `--device cpu|cuda` — transformer device (default auto)
166
+ - `--log-level` — logging level (default `INFO`)
167
+
168
+ ## License
169
+
170
+ MIT — see the repository root `LICENSE`.
@@ -0,0 +1,29 @@
1
+ """local-embed — standalone local embedding server.
2
+
3
+ Loads a local GGUF (llama-cpp) or HF transformer embedding model ONCE and
4
+ exposes it as an OpenAI-compatible ``/v1/embeddings`` HTTP endpoint plus
5
+ FastMCP tools. slife's memdb/memfiles plugins both call it over HTTP, so
6
+ the model is never loaded twice in one process tree.
7
+
8
+ Modules::
9
+
10
+ server.py FastMCP plugin — MCP tools + /v1/embeddings custom route
11
+ engine.py Model engine (gguf / transformer), lazy load + encode
12
+ cli.py Console entry point (``local-embed``)
13
+ threads.py run_daemon — daemon-thread offload for blocking model calls
14
+ logging.py Structured logging setup
15
+
16
+ Usage::
17
+
18
+ local-embed --backend gguf --gguf-path /path/to/model.gguf
19
+ local-embed --backend transformer --model BAAI/bge-m3
20
+ """
21
+
22
+ try:
23
+ from importlib.metadata import version as _version
24
+
25
+ __version__ = _version("local-embed")
26
+ except Exception:
27
+ __version__ = "0.0.0"
28
+
29
+ __all__ = ["__version__"]
@@ -0,0 +1,8 @@
1
+ """Entry point for ``python -m local_embed`` — delegates to the CLI."""
2
+
3
+ import sys
4
+
5
+ from local_embed.cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())