ltcai 12.4.0 → 12.5.0
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/README.md +23 -37
- package/docs/CHANGELOG.md +13 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +9 -9
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/models.py +21 -1
- package/latticeai/api/search.py +8 -1
- package/latticeai/api/worker_compute.py +9 -1
- package/latticeai/core/embedding_providers/__init__.py +14 -0
- package/latticeai/core/embedding_providers/autodetect.py +91 -24
- package/latticeai/core/embedding_providers/base.py +10 -2
- package/latticeai/core/embedding_providers/profiles.py +44 -0
- package/latticeai/core/embedding_providers/text.py +325 -2
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/embedding_recommendation.py +229 -0
- package/latticeai/services/model_capability_registry.py +37 -0
- package/latticeai/services/model_catalog.py +130 -0
- package/latticeai/services/product_readiness.py +1 -1
- package/package.json +1 -1
- package/scripts/bump_version.py +106 -6
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_screenshot_pixel_delta.py +6 -2
- package/src-tauri/Cargo.lock +10 -10
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +44 -44
- package/static/app/assets/{Act-inLiZfDE.js → Act-DQ2Ldwo8.js} +2 -2
- package/static/app/assets/{AdminConsole-iLFwSJSg.js → AdminConsole-y6EjiXeH.js} +1 -1
- package/static/app/assets/{Brain-Cb7noiNc.js → Brain-CRWWw-h9.js} +2 -2
- package/static/app/assets/{BrainHome-CfiJ4tau.js → BrainHome-cmvTu7Ea.js} +1 -1
- package/static/app/assets/{BrainSignals-ShxKNNqr.js → BrainSignals-gaNn5E4-.js} +1 -1
- package/static/app/assets/{Capture-Cajj1Trf.js → Capture-BJK7in1j.js} +1 -1
- package/static/app/assets/{Chronicle-CPd8GP_O.js → Chronicle-BuYZ9lMt.js} +1 -1
- package/static/app/assets/{CommandPalette-Cb_GWoYC.js → CommandPalette-BjFwuKaA.js} +1 -1
- package/static/app/assets/{DigitalBrainExplorer-Df6KVrBz.js → DigitalBrainExplorer-DTBRPYJ3.js} +1 -1
- package/static/app/assets/Library-BsmZ-YVh.js +1 -0
- package/static/app/assets/{LivingBrain-u9X_XGC2.js → LivingBrain-aT9hgkPV.js} +1 -1
- package/static/app/assets/ProductFlow-Ct0QLLY5.js +1 -0
- package/static/app/assets/{ReviewCard-C64YFHOR.js → ReviewCard-CdooYtWr.js} +1 -1
- package/static/app/assets/{RunsListPanel-DKZEJcxF.js → RunsListPanel-Btf2DIrG.js} +1 -1
- package/static/app/assets/{System-Dretrknf.js → System-B77M4t-M.js} +1 -1
- package/static/app/assets/{WorkflowGraph-CE6XgWjJ.js → WorkflowGraph-u3ZKLvZe.js} +1 -1
- package/static/app/assets/{WorkflowsPanel-MKU05J7_.js → WorkflowsPanel--nTFq3Cs.js} +2 -2
- package/static/app/assets/{actHelpers-BjMnKMCI.js → actHelpers-xNnCfUeC.js} +1 -1
- package/static/app/assets/arrow-left-Cp4NHbfX.js +1 -0
- package/static/app/assets/{bot-Qgvc2PGU.js → bot-CXwvpODC.js} +1 -1
- package/static/app/assets/{brain-BwGnubn8.js → brain-BzjoGgor.js} +1 -1
- package/static/app/assets/{button-DEloiIsj.js → button-_hHOhp7g.js} +1 -1
- package/static/app/assets/circle-check-qG5dMahn.js +1 -0
- package/static/app/assets/{circle-pause-CkkmXxA9.js → circle-pause-DBOqYYMh.js} +1 -1
- package/static/app/assets/{circle-play-rnb6A1zq.js → circle-play-DA0Vf2NH.js} +1 -1
- package/static/app/assets/{cpu-CzcGFQQq.js → cpu-DwsE0_o3.js} +1 -1
- package/static/app/assets/{download-BTQigJvp.js → download-iCoe2f2Q.js} +1 -1
- package/static/app/assets/{folder-open-CIXPPhhm.js → folder-open-C1vNOMhy.js} +1 -1
- package/static/app/assets/{hard-drive-CZXxdIcT.js → hard-drive-a73KaYth.js} +1 -1
- package/static/app/assets/index-Dbb2YhYw.css +2 -0
- package/static/app/assets/{index-CHQbWj9a.js → index-DpuEeYmP.js} +2 -2
- package/static/app/assets/{input-CTZHuOXe.js → input-BclQNHyj.js} +1 -1
- package/static/app/assets/{link-2-BsG1NQUA.js → link-2-D23wihBe.js} +1 -1
- package/static/app/assets/{permissionCopy-klyWL3ai.js → permissionCopy-DiGZRKAY.js} +1 -1
- package/static/app/assets/{primitives-ZmN4jzq-.js → primitives-1S-XCgmz.js} +1 -1
- package/static/app/assets/search-Cl2k5uhD.js +1 -0
- package/static/app/assets/{share-2-Cuv9rj7A.js → share-2-47_av_aF.js} +1 -1
- package/static/app/assets/{shield-alert-KKySFd03.js → shield-alert-BvKq2P-G.js} +1 -1
- package/static/app/assets/{textarea-CBwqgGSA.js → textarea-DcLO52bu.js} +1 -1
- package/static/app/assets/{useFocusTrap-D93vds_Z.js → useFocusTrap-CpqVPzJ_.js} +1 -1
- package/static/app/assets/{useMutation-B9hHPD0p.js → useMutation-DhI3jqCN.js} +1 -1
- package/static/app/assets/workspace-DwNHbqaX.js +1 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/Library-Dk5Mefl_.js +0 -1
- package/static/app/assets/ProductFlow-CpX0BMr9.js +0 -1
- package/static/app/assets/arrow-left-Cbck4Ig2.js +0 -1
- package/static/app/assets/circle-check-CDygnRMn.js +0 -1
- package/static/app/assets/index-C_dThdD6.css +0 -2
- package/static/app/assets/search-xjKVb4Nh.js +0 -1
- package/static/app/assets/workspace-JGzrc0D8.js +0 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://github.com/TaeSooPark-PTS/LatticeAI/actions/workflows/publish.yml)
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
|
|
15
|
-

|
|
16
16
|
|
|
17
17
|
Lattice AI is a Digital Brain that lives on your computer. Chat, files,
|
|
18
18
|
folders, notes, and web pages all land in one knowledge graph you own.
|
|
@@ -42,19 +42,19 @@ LTCAI # then open http://127.0.0.1:4825/app
|
|
|
42
42
|
|
|
43
43
|
| | | |
|
|
44
44
|
| --- | --- | --- |
|
|
45
|
-
|  |  |  |
|
|
46
46
|
|
|
47
47
|
Screenshot index:
|
|
48
|
-
[output/release/v12.
|
|
48
|
+
[output/release/v12.5.0/SCREENSHOT_INDEX.md](output/release/v12.5.0/SCREENSHOT_INDEX.md)
|
|
49
49
|
|
|
50
50
|
## What you can do
|
|
51
51
|
|
|
52
52
|
| | |
|
|
53
53
|
| --- | --- |
|
|
54
|
-
| **See your Brain's story in time** — a growth curve, an activity heatmap, and each day's story  | **Chat with a Brain that remembers** — every conversation grows durable, source-linked memory  |
|
|
55
|
+
| **See how knowledge connects** — a real relationship graph, not a file list  | **Capture anything** — files, whole folders, notes, screenshots, web pages  |
|
|
56
|
+
| **Automate with review** — agent changes become proposals you approve first  | **Pick a model in one click** — recommended local models for your hardware  |
|
|
57
|
+
| **Watch a file become memory** — three named steps, not a pipeline diagram  | **Say how much it may do alone** — one dial in plain words  |
|
|
58
58
|
|
|
59
59
|
## Why this is yours
|
|
60
60
|
|
|
@@ -70,41 +70,27 @@ Screenshot index:
|
|
|
70
70
|
|
|
71
71
|
## Current Release
|
|
72
72
|
|
|
73
|
-
The current release is **12.
|
|
74
|
-
|
|
75
|
-
The
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
and
|
|
81
|
-
|
|
82
|
-
- **Qwen 3.8 27B 4-bit local & cloud integration.** Added
|
|
83
|
-
`mlx-community/Qwen3.8-27B-4bit` (`16.1GB`, `qwen3_5`), wired GGUF/HF routes
|
|
84
|
-
(`ggml-org/Qwen3.8-27B-GGUF:Q4_K_M`, `Qwen/Qwen3.8-27B`) across all 5 local
|
|
85
|
-
engines, and added `Qwen/Qwen3.8-27B` / `qwen/qwen3.8-27b` to `together` and
|
|
86
|
-
`openrouter`.
|
|
87
|
-
- **Gemma 4 26B A4B QAT & QAT-OptiQ 4-bit builds.** Added
|
|
88
|
-
`mlx-community/gemma-4-26B-A4B-it-qat-4bit` (`15.6GB`, routed to Google's
|
|
89
|
-
official QAT GGUF `google/gemma-4-26B-A4B-it-qat-q4_0-gguf:Q4_0`) and
|
|
90
|
-
`mlx-community/gemma-4-26B-A4B-it-qat-OptiQ-4bit` (`21.9GB`, sensitivity-guided
|
|
91
|
-
mixed 4-bit/8-bit `mlx-optiq` build).
|
|
92
|
-
- **Dependency security audit closure & runtime hardening.** Upgraded `pillow`
|
|
93
|
-
to `>=13,<14`, resolved all npm lockfile advisories in root and
|
|
94
|
-
`vscode-extension`, enforced `http(s)://` scheme checks in `model_engines`,
|
|
95
|
-
and scoped `@import "tailwindcss" source("..");` in `frontend/src/styles.css`.
|
|
73
|
+
The current release is **12.5.0 — Deep Vector**:
|
|
74
|
+
|
|
75
|
+
The embedding pipeline adds native **Google EmbeddingGemma 2** (`google/embeddinggemma-2`, 768-dim) support with Hugging Face mean-pooling and L2 normalization, adds **hardware-aware embedding model recommendations** that calculate host RAM/VRAM reservations before recommending LLMs, migrates screenshot pixel diffing to Pillow 13+ `get_flattened_data()`, upgrades GitHub Actions CI/CD to Node 24 native major versions, and automates full OpenAPI specification and fragment synchronization in `scripts/bump_version.py`. The door is still **422 operations / 41 families** over a **20-route worker**. Cloud is still optional and the default is still local.
|
|
76
|
+
|
|
77
|
+
- **Google EmbeddingGemma 2 native integration.** Native Hugging Face embedding provider support (`google/embeddinggemma-2`, `embeddinggemma-2`, `embeddinggemma-2-4bit`, `embeddinggemma-2-8bit`, `embeddinggemma-300m`, 768-dimensional vectors) in `latticeai/core/embedding_providers/text.py` with mean pooling, L2 normalization, task instruction prefixes, and automatic semantic activation. Hash embedding fallback remains preserved when offline.
|
|
78
|
+
- **Hardware-aware PC-tailored embedding recommendations.** Modularized `recommend_embedding_for_host` in `lattice-platform` and `latticeai/services/embedding_recommendation.py`. Systems with >=8GB RAM receive `google/embeddinggemma-2` with a 0.5GB–1.5GB reserved RAM budget; smaller hosts receive `google/embeddinggemma-300m`. The host probe calculates `effective_ram_gb` before LLM tier selection.
|
|
79
|
+
- **Onboarding and Model Library embedding UI.** Added the PC-tailored EmbeddingGemma 2 recommendation card in `ProductFlow.tsx` with full i18n support, and surfaced Google EmbeddingGemma 2 in the Model Library (`Library.tsx`) embedding section with 1-click install and activation.
|
|
80
|
+
- **Pillow 13+ flattened data & CI/CD modernisation.** Replaced Pillow 14-deprecated `diff.getdata()` calls in `scripts/check_screenshot_pixel_delta.py` with `diff.get_flattened_data()`, and pinned GitHub Actions to Node 24 native versions with `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"`.
|
|
81
|
+
- **OpenAPI specification and fixture version automation.** Extended `scripts/bump_version.py` to synchronize `frontend/openapi.json`, `rust/fixtures/openapi/_envelope.json`, all 13 OpenAPI fragment files, and `rust/fixtures/openapi/_index.json` with recomputed SHA-256 digests across 49 targets.
|
|
96
82
|
|
|
97
83
|
Release notes live in [docs/releases/](docs/releases/). This version:
|
|
98
|
-
[docs/releases/RELEASE_NOTES_v12.
|
|
84
|
+
[docs/releases/RELEASE_NOTES_v12.5.0.md](docs/releases/RELEASE_NOTES_v12.5.0.md).
|
|
99
85
|
Guide: [RELEASE.md](RELEASE.md) · History: [docs/CHANGELOG.md](docs/CHANGELOG.md).
|
|
100
86
|
|
|
101
|
-
Expected artifacts for 12.
|
|
87
|
+
Expected artifacts for 12.5.0 must use exact filenames:
|
|
102
88
|
|
|
103
|
-
- `dist/ltcai-12.
|
|
104
|
-
- `dist/ltcai-12.
|
|
105
|
-
- `ltcai-12.
|
|
106
|
-
- `dist/ltcai-12.
|
|
107
|
-
- `src-tauri/target/release/bundle/dmg/Lattice AI_12.
|
|
89
|
+
- `dist/ltcai-12.5.0-py3-none-any.whl`
|
|
90
|
+
- `dist/ltcai-12.5.0.tar.gz`
|
|
91
|
+
- `ltcai-12.5.0.tgz`
|
|
92
|
+
- `dist/ltcai-12.5.0.vsix`
|
|
93
|
+
- `src-tauri/target/release/bundle/dmg/Lattice AI_12.5.0_aarch64.dmg`
|
|
108
94
|
|
|
109
95
|
Do not use wildcard artifact uploads. Package registry publishing remains owner-run.
|
|
110
96
|
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ The top entry is either the current unreleased main-branch work or the current
|
|
|
4
4
|
release line. Older entries are historical and may describe behavior as it
|
|
5
5
|
existed at that release.
|
|
6
6
|
|
|
7
|
+
## [12.5.0] - 2026-11-11 — Deep Vector
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Added native Hugging Face embedding provider for Google EmbeddingGemma 2 (`google/embeddinggemma-2`, `embeddinggemma-2`, `embeddinggemma-2-4bit`, `embeddinggemma-2-8bit`, `embeddinggemma-300m`, 768-dim) with mean-pooling, L2 normalization, task prefixes, and fallback protection in `latticeai/core/embedding_providers/text.py`.
|
|
11
|
+
- Added hardware-aware PC-tailored embedding recommendations (`recommend_embedding_for_host`) in `rust/lattice-platform/src/modelops/models_catalog/embedding.rs` and `latticeai/services/embedding_recommendation.py` with memory reservation (0.5GB–1.5GB for Gemma 2, 0.3GB for 300M) before LLM selection.
|
|
12
|
+
- Added PC-tailored EmbeddingGemma 2 recommendation card in `ProductFlow.tsx` (i18n supported) and surfaced Google EmbeddingGemma 2 in `Library.tsx` with 1-click prepare/download.
|
|
13
|
+
- Added comprehensive visual E2E test suite `tests/visual/v3.all_buttons_inspection.spec.js` asserting all routes, controls, tabs, and buttons operate with 0 errors.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Migrated `scripts/check_screenshot_pixel_delta.py` from Pillow 14-deprecated `diff.getdata()` to `diff.get_flattened_data()`.
|
|
17
|
+
- Upgraded all GitHub Actions workflows (`.github/workflows/*.yml`) to Node 24 native major versions with `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"`.
|
|
18
|
+
- Automated OpenAPI specification and fixture version synchronization across 49 targets in `scripts/bump_version.py`.
|
|
19
|
+
|
|
7
20
|
## [12.4.0] - 2026-11-11 — Sharp Weight
|
|
8
21
|
|
|
9
22
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Community And Plugins
|
|
2
2
|
|
|
3
|
-
Current release: **12.
|
|
3
|
+
Current release: **12.5.0 — Deep Vector**.
|
|
4
4
|
|
|
5
5
|
LatticeAI defines the path from a strong local-first framework (8.4.0
|
|
6
6
|
action-aware baseline, 8.5.0 registry+DI hardening, 8.6.0 capture/navigation
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **Status: canonical** — current contributor guidance, kept in sync with the
|
|
4
4
|
> current release.
|
|
5
5
|
|
|
6
|
-
Current release: **12.
|
|
6
|
+
Current release: **12.5.0 — Deep Vector**.
|
|
7
7
|
|
|
8
8
|
This document is the contributor onboarding path: how to boot the tree, where
|
|
9
9
|
a change belongs, which gates will go red, and the house rules that keep the
|
|
@@ -237,7 +237,7 @@ in `rust/`.
|
|
|
237
237
|
Markdown) and `scripts/check_doc_status.mjs` (every relative link under the
|
|
238
238
|
root canonical docs and `docs/**`). A red line is a broken relative path —
|
|
239
239
|
fix the href or put the file where the href says. Canonical docs (this one
|
|
240
|
-
included) must carry `Current release: **12.
|
|
240
|
+
included) must carry `Current release: **12.5.0 — …**` matching
|
|
241
241
|
`package.json`; do not bump that marker here ahead of the release lane.
|
|
242
242
|
`npm run docs:check-current` is the release-set version pin
|
|
243
243
|
([`scripts/check_current_release_docs.mjs`](../scripts/check_current_release_docs.mjs)).
|
|
@@ -317,19 +317,19 @@ For user-facing, API, runtime, release, or packaging changes, check:
|
|
|
317
317
|
appears
|
|
318
318
|
|
|
319
319
|
The release-docs lane owns README / ARCHITECTURE / FEATURE_STATUS version
|
|
320
|
-
bumps. Do not move this file's `Current release` marker off 12.
|
|
320
|
+
bumps. Do not move this file's `Current release` marker off 12.5.0 ahead of
|
|
321
321
|
that lane.
|
|
322
322
|
|
|
323
323
|
Release/publish examples must use exact target-version filenames. Do not
|
|
324
324
|
document wildcard artifact upload commands.
|
|
325
325
|
|
|
326
|
-
For 12.
|
|
326
|
+
For 12.5.0 release work, exact artifacts are:
|
|
327
327
|
|
|
328
|
-
- `dist/ltcai-12.
|
|
329
|
-
- `dist/ltcai-12.
|
|
330
|
-
- `ltcai-12.
|
|
331
|
-
- `dist/ltcai-12.
|
|
332
|
-
- `src-tauri/target/release/bundle/dmg/Lattice AI_12.
|
|
328
|
+
- `dist/ltcai-12.5.0-py3-none-any.whl`
|
|
329
|
+
- `dist/ltcai-12.5.0.tar.gz`
|
|
330
|
+
- `ltcai-12.5.0.tgz`
|
|
331
|
+
- `dist/ltcai-12.5.0.vsix`
|
|
332
|
+
- `src-tauri/target/release/bundle/dmg/Lattice AI_12.5.0_aarch64.dmg`
|
|
333
333
|
|
|
334
334
|
The dmg is ad-hoc signed (effectively unsigned); `npm run release:validate`
|
|
335
335
|
checks the names and presence, not a Developer ID signature.
|
package/docs/ONBOARDING.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Lattice AI Onboarding
|
|
2
2
|
|
|
3
|
-
Current release: **12.
|
|
3
|
+
Current release: **12.5.0 — Deep Vector**.
|
|
4
4
|
|
|
5
5
|
The first-run goal is a five-minute path from "I opened the app" to "my Brain
|
|
6
6
|
has a source, a question, and proof." This page is the product contract behind
|
package/docs/OPERATIONS.md
CHANGED
package/docs/TRUST_MODEL.md
CHANGED
package/docs/WHY_LATTICE.md
CHANGED
package/docs/kg-schema.md
CHANGED
package/latticeai/__init__.py
CHANGED
package/latticeai/api/models.py
CHANGED
|
@@ -264,6 +264,11 @@ def create_models_router(
|
|
|
264
264
|
"size": item["size"],
|
|
265
265
|
"display_name": item.get("name") or item.get("id"),
|
|
266
266
|
"modality": item.get("modality") or "multimodal",
|
|
267
|
+
"category": item.get("category") or ("embedding" if item.get("task") == "embedding" else "chat"),
|
|
268
|
+
"task": item.get("task") or ("embedding" if item.get("category") == "embedding" else "chat"),
|
|
269
|
+
"role": item.get("role") or ("embedding" if item.get("category") == "embedding" else "chat"),
|
|
270
|
+
"dimensions": item.get("dimensions"),
|
|
271
|
+
"hf_repo_id": item.get("hf_repo_id") or item["id"],
|
|
267
272
|
"source_country": item.get("source_country"),
|
|
268
273
|
"source_company": item.get("source_company"),
|
|
269
274
|
"execution_method": item.get("execution_method"),
|
|
@@ -356,12 +361,27 @@ def create_models_router(
|
|
|
356
361
|
)
|
|
357
362
|
# 5.2.0: surface structured registry info (verified status, hf, hardware, strategies) for UX
|
|
358
363
|
try:
|
|
359
|
-
from latticeai.services.model_catalog import
|
|
364
|
+
from latticeai.services.model_catalog import (
|
|
365
|
+
get_verified_models,
|
|
366
|
+
recommend_embedding_for_hardware,
|
|
367
|
+
)
|
|
360
368
|
verified = get_verified_models()
|
|
369
|
+
embedding_rec = recommend_embedding_for_hardware()
|
|
361
370
|
except Exception:
|
|
362
371
|
verified = []
|
|
372
|
+
embedding_rec = {
|
|
373
|
+
"id": "google/embeddinggemma-2",
|
|
374
|
+
"hf_repo_id": "google/embeddinggemma-2",
|
|
375
|
+
"name": "Google EmbeddingGemma 2",
|
|
376
|
+
"category": "embedding",
|
|
377
|
+
"task": "embedding",
|
|
378
|
+
"role": "embedding",
|
|
379
|
+
"dimensions": 768,
|
|
380
|
+
}
|
|
363
381
|
return {
|
|
364
382
|
"recommended": recommended,
|
|
383
|
+
"embedding_model": embedding_rec.get("id") or "google/embeddinggemma-2",
|
|
384
|
+
"embedding_recommendation": embedding_rec,
|
|
365
385
|
"cloud": _router.detected_cloud_models(),
|
|
366
386
|
"engines": engines,
|
|
367
387
|
"loaded": _router.loaded_model_ids,
|
package/latticeai/api/search.py
CHANGED
|
@@ -23,6 +23,10 @@ from typing import Any, Callable, Dict, NoReturn, Optional
|
|
|
23
23
|
|
|
24
24
|
from fastapi import APIRouter, HTTPException, Request
|
|
25
25
|
|
|
26
|
+
from latticeai.core.embedding_providers.profiles import (
|
|
27
|
+
RECOMMENDED_EMBEDDING_MODEL,
|
|
28
|
+
RECOMMENDED_EMBEDDING_PROFILE,
|
|
29
|
+
)
|
|
26
30
|
from latticeai.services.search_service import SearchService
|
|
27
31
|
|
|
28
32
|
|
|
@@ -44,8 +48,11 @@ def create_search_router(
|
|
|
44
48
|
require_user(request)
|
|
45
49
|
resolved = embedding_info() if embedding_info else {}
|
|
46
50
|
try:
|
|
47
|
-
|
|
51
|
+
payload = dict(service.embeddings_status(resolved=resolved, refresh=refresh))
|
|
48
52
|
except ValueError as exc:
|
|
49
53
|
_raise_embedder_error(exc)
|
|
54
|
+
payload.setdefault("recommended_model", RECOMMENDED_EMBEDDING_MODEL)
|
|
55
|
+
payload.setdefault("recommended_profile", RECOMMENDED_EMBEDDING_PROFILE)
|
|
56
|
+
return payload
|
|
50
57
|
|
|
51
58
|
return router
|
|
@@ -85,6 +85,10 @@ from pydantic import BaseModel, Field
|
|
|
85
85
|
|
|
86
86
|
from latticeai.api.agent_worker_seam import SEAM_RATE_BUCKET
|
|
87
87
|
from latticeai.api.agent_worker_seam import _seam_open as seam_open
|
|
88
|
+
from latticeai.core.embedding_providers.profiles import (
|
|
89
|
+
RECOMMENDED_EMBEDDING_MODEL,
|
|
90
|
+
RECOMMENDED_EMBEDDING_PROFILE,
|
|
91
|
+
)
|
|
88
92
|
from latticeai.core.messages import MESSAGES, http_error, resolve_language
|
|
89
93
|
from latticeai.core.quiet import quiet
|
|
90
94
|
from latticeai.services.voice_capture import (
|
|
@@ -633,6 +637,7 @@ def create_worker_compute_router(
|
|
|
633
637
|
if callable(role_aware)
|
|
634
638
|
else await asyncio.to_thread(provider.embed_batch, texts)
|
|
635
639
|
)
|
|
640
|
+
grade = getattr(provider, "grade", "fallback")
|
|
636
641
|
return {
|
|
637
642
|
"vectors": vectors,
|
|
638
643
|
"dim": provider.dim,
|
|
@@ -641,7 +646,10 @@ def create_worker_compute_router(
|
|
|
641
646
|
"kind": kind,
|
|
642
647
|
# Additive (v12.0.0): `fallback` means these vectors are feature
|
|
643
648
|
# hashes, not meaning. A caller that stores them can say so.
|
|
644
|
-
"grade":
|
|
649
|
+
"grade": grade,
|
|
650
|
+
"semantic": grade != "fallback",
|
|
651
|
+
"recommended_model": RECOMMENDED_EMBEDDING_MODEL,
|
|
652
|
+
"recommended_profile": RECOMMENDED_EMBEDDING_PROFILE,
|
|
645
653
|
}
|
|
646
654
|
|
|
647
655
|
@router.post("/worker/parse")
|
|
@@ -70,9 +70,11 @@ from lattice_brain.embeddings import LocalEmbeddingModel as LocalEmbeddingModel
|
|
|
70
70
|
|
|
71
71
|
from .autodetect import AUTO_PROVIDER as AUTO_PROVIDER
|
|
72
72
|
from .autodetect import AUTODETECT_ENV as AUTODETECT_ENV
|
|
73
|
+
from .autodetect import LOCAL_HF_MODELS as LOCAL_HF_MODELS
|
|
73
74
|
from .autodetect import LOCAL_MLX_MODELS as LOCAL_MLX_MODELS
|
|
74
75
|
from .autodetect import Detection as Detection
|
|
75
76
|
from .autodetect import detect_embedder as detect_embedder
|
|
77
|
+
from .autodetect import detect_local_hf as detect_local_hf
|
|
76
78
|
from .autodetect import detect_local_mlx as detect_local_mlx
|
|
77
79
|
from .autodetect import detect_ollama as detect_ollama
|
|
78
80
|
from .autodetect import resolve_auto_provider as resolve_auto_provider
|
|
@@ -92,11 +94,16 @@ from .captions import VisionCaptioner as VisionCaptioner
|
|
|
92
94
|
from .captions import resolve_vision_captioner as resolve_vision_captioner
|
|
93
95
|
from .captions import vision_caption_port as vision_caption_port
|
|
94
96
|
from .profiles import PRODUCTION_PROVIDER_PROFILES as PRODUCTION_PROVIDER_PROFILES
|
|
97
|
+
from .profiles import RECOMMENDED_EMBEDDING_MODEL as RECOMMENDED_EMBEDDING_MODEL
|
|
98
|
+
from .profiles import RECOMMENDED_EMBEDDING_PROFILE as RECOMMENDED_EMBEDDING_PROFILE
|
|
95
99
|
from .profiles import embedding_provider_profiles as embedding_provider_profiles
|
|
96
100
|
from .profiles import resolve_embedding_profile as resolve_embedding_profile
|
|
101
|
+
from .text import DEFAULT_HF_EMBEDDING_MODEL as DEFAULT_HF_EMBEDDING_MODEL
|
|
102
|
+
from .text import EMBEDDINGGEMMA_PREFIXES as EMBEDDINGGEMMA_PREFIXES
|
|
97
103
|
from .text import PROVIDER_TYPES as PROVIDER_TYPES
|
|
98
104
|
from .text import CustomEmbeddingProvider as CustomEmbeddingProvider
|
|
99
105
|
from .text import HashEmbeddingProvider as HashEmbeddingProvider
|
|
106
|
+
from .text import HuggingFaceEmbeddingProvider as HuggingFaceEmbeddingProvider
|
|
100
107
|
from .text import MLXEmbeddingProvider as MLXEmbeddingProvider
|
|
101
108
|
from .text import OllamaEmbeddingProvider as OllamaEmbeddingProvider
|
|
102
109
|
from .text import OpenAICompatibleEmbeddingProvider as OpenAICompatibleEmbeddingProvider
|
|
@@ -123,9 +130,15 @@ from .vision import resolve_vision_embedder as resolve_vision_embedder
|
|
|
123
130
|
__all__ = [
|
|
124
131
|
"AUTODETECT_ENV",
|
|
125
132
|
"AUTO_PROVIDER",
|
|
133
|
+
"DEFAULT_HF_EMBEDDING_MODEL",
|
|
134
|
+
"EMBEDDINGGEMMA_PREFIXES",
|
|
135
|
+
"LOCAL_HF_MODELS",
|
|
126
136
|
"LOCAL_MLX_MODELS",
|
|
137
|
+
"RECOMMENDED_EMBEDDING_MODEL",
|
|
138
|
+
"RECOMMENDED_EMBEDDING_PROFILE",
|
|
127
139
|
"Detection",
|
|
128
140
|
"detect_embedder",
|
|
141
|
+
"detect_local_hf",
|
|
129
142
|
"detect_local_mlx",
|
|
130
143
|
"detect_ollama",
|
|
131
144
|
"resolve_auto_provider",
|
|
@@ -151,6 +164,7 @@ __all__ = [
|
|
|
151
164
|
"resolve_vision_embedder",
|
|
152
165
|
"vision_caption_port",
|
|
153
166
|
"HashEmbeddingProvider",
|
|
167
|
+
"HuggingFaceEmbeddingProvider",
|
|
154
168
|
"MLXEmbeddingProvider",
|
|
155
169
|
"OllamaEmbeddingProvider",
|
|
156
170
|
"OpenAICompatibleEmbeddingProvider",
|
|
@@ -49,15 +49,22 @@ AUTODETECT_ENV = "LATTICEAI_EMBEDDING_AUTODETECT"
|
|
|
49
49
|
OLLAMA_BASE_ENV = "LATTICEAI_EMBEDDING_BASE_URL"
|
|
50
50
|
DEFAULT_OLLAMA_BASE = "http://127.0.0.1:11434"
|
|
51
51
|
|
|
52
|
+
#: Local Hugging Face / sentence-transformers embedding models, best first.
|
|
53
|
+
LOCAL_HF_MODELS: Tuple[Tuple[str, int, bool], ...] = (
|
|
54
|
+
("google/embeddinggemma-2", 768, False),
|
|
55
|
+
("google/embeddinggemma-300m", 768, False),
|
|
56
|
+
)
|
|
57
|
+
|
|
52
58
|
#: Local MLX embedding models this build knows how to drive, best first.
|
|
53
59
|
#: ``dim`` is the model's true output width; ``prefix`` marks the E5 family,
|
|
54
60
|
#: whose quality depends on the ``query:`` / ``passage:`` instruction.
|
|
55
61
|
LOCAL_MLX_MODELS: Tuple[Tuple[str, int, bool], ...] = (
|
|
62
|
+
("mlx-community/embeddinggemma-2-4bit", 768, False),
|
|
63
|
+
("mlx-community/embeddinggemma-300m-4bit", 768, False),
|
|
56
64
|
("mlx-community/multilingual-e5-small-mlx", 384, True),
|
|
57
65
|
("mlx-community/multilingual-e5-base-mlx", 768, True),
|
|
58
66
|
("mlx-community/multilingual-e5-large-mlx", 1024, True),
|
|
59
67
|
("mlx-community/snowflake-arctic-embed-l-v2.0-8bit", 1024, False),
|
|
60
|
-
("mlx-community/embeddinggemma-300m-4bit", 768, False),
|
|
61
68
|
)
|
|
62
69
|
|
|
63
70
|
#: Ollama model names that are embedders rather than chat models.
|
|
@@ -107,16 +114,19 @@ def autodetect_enabled(env: Optional[Dict[str, str]] = None) -> bool:
|
|
|
107
114
|
|
|
108
115
|
def hf_cache_roots(env: Optional[Dict[str, str]] = None) -> List[Path]:
|
|
109
116
|
"""Every directory a Hugging Face snapshot could be under, in order."""
|
|
110
|
-
values = env
|
|
117
|
+
values = env if env is not None else dict(os.environ)
|
|
111
118
|
roots: List[Path] = []
|
|
112
|
-
for key in ("HF_HUB_CACHE", "HUGGINGFACE_HUB_CACHE"):
|
|
119
|
+
for key in ("HF_HUB_CACHE", "HUGGINGFACE_HUB_CACHE", "LATTICEAI_MODELS_DIR"):
|
|
113
120
|
raw = values.get(key, "").strip()
|
|
114
121
|
if raw:
|
|
115
122
|
roots.append(Path(raw))
|
|
116
123
|
home = values.get("HF_HOME", "").strip()
|
|
117
124
|
if home:
|
|
118
125
|
roots.append(Path(home) / "hub")
|
|
119
|
-
|
|
126
|
+
if env is None or "HOME" in values or not roots:
|
|
127
|
+
home_dir = Path(values.get("HOME", "~")).expanduser()
|
|
128
|
+
roots.append(home_dir / ".cache/huggingface/hub")
|
|
129
|
+
roots.append(home_dir / ".latticeai/models")
|
|
120
130
|
seen: List[Path] = []
|
|
121
131
|
for root in roots:
|
|
122
132
|
if root not in seen:
|
|
@@ -124,6 +134,21 @@ def hf_cache_roots(env: Optional[Dict[str, str]] = None) -> List[Path]:
|
|
|
124
134
|
return seen
|
|
125
135
|
|
|
126
136
|
|
|
137
|
+
def _has_model_files(directory: Path) -> bool:
|
|
138
|
+
if not directory.is_dir():
|
|
139
|
+
return False
|
|
140
|
+
if not (directory / "config.json").exists():
|
|
141
|
+
return False
|
|
142
|
+
return (
|
|
143
|
+
any(
|
|
144
|
+
(directory / name).exists()
|
|
145
|
+
for name in ("model.safetensors", "weights.safetensors", "pytorch_model.bin")
|
|
146
|
+
)
|
|
147
|
+
or any(directory.glob("*.safetensors"))
|
|
148
|
+
or any(directory.glob("*.bin"))
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
127
152
|
def _snapshot_dir(repo_id: str, roots: List[Path]) -> Optional[Path]:
|
|
128
153
|
"""The snapshot directory holding this repo's weights, if it is on disk.
|
|
129
154
|
|
|
@@ -135,22 +160,48 @@ def _snapshot_dir(repo_id: str, roots: List[Path]) -> Optional[Path]:
|
|
|
135
160
|
flattened = "models--" + repo_id.replace("/", "--")
|
|
136
161
|
for root in roots:
|
|
137
162
|
snapshots = root / flattened / "snapshots"
|
|
138
|
-
if
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if
|
|
144
|
-
|
|
145
|
-
weights = any(
|
|
146
|
-
(snapshot / name).exists()
|
|
147
|
-
for name in ("model.safetensors", "weights.safetensors")
|
|
148
|
-
) or any(snapshot.glob("*.safetensors"))
|
|
149
|
-
if weights:
|
|
150
|
-
return snapshot
|
|
163
|
+
if snapshots.is_dir():
|
|
164
|
+
for snapshot in sorted(snapshots.iterdir()):
|
|
165
|
+
if _has_model_files(snapshot):
|
|
166
|
+
return snapshot
|
|
167
|
+
for direct in (root / repo_id, root / flattened):
|
|
168
|
+
if _has_model_files(direct):
|
|
169
|
+
return direct
|
|
151
170
|
return None
|
|
152
171
|
|
|
153
172
|
|
|
173
|
+
def detect_local_hf(env: Optional[Dict[str, str]] = None) -> Detection:
|
|
174
|
+
"""The best already-downloaded Hugging Face embedding model, or an empty finding."""
|
|
175
|
+
roots = hf_cache_roots(env)
|
|
176
|
+
candidates: List[Dict[str, Any]] = []
|
|
177
|
+
for repo_id, dim, prefixed in LOCAL_HF_MODELS:
|
|
178
|
+
snapshot = _snapshot_dir(repo_id, roots)
|
|
179
|
+
candidates.append(
|
|
180
|
+
{
|
|
181
|
+
"provider": "huggingface",
|
|
182
|
+
"model": repo_id,
|
|
183
|
+
"dim": dim,
|
|
184
|
+
"downloaded": snapshot is not None,
|
|
185
|
+
"e5_prefixes": prefixed,
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
for candidate in candidates:
|
|
189
|
+
if candidate["downloaded"]:
|
|
190
|
+
return Detection(
|
|
191
|
+
provider="huggingface",
|
|
192
|
+
model=str(candidate["model"]),
|
|
193
|
+
dim=int(candidate["dim"]),
|
|
194
|
+
source="local_model",
|
|
195
|
+
detail=f"{candidate['model']} is already in the local model cache",
|
|
196
|
+
candidates=candidates,
|
|
197
|
+
)
|
|
198
|
+
return Detection(
|
|
199
|
+
source="none",
|
|
200
|
+
detail="no known Hugging Face embedding model is downloaded yet",
|
|
201
|
+
candidates=candidates,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
154
205
|
def detect_local_mlx(env: Optional[Dict[str, str]] = None) -> Detection:
|
|
155
206
|
"""The best already-downloaded MLX embedding model, or an empty finding."""
|
|
156
207
|
roots = hf_cache_roots(env)
|
|
@@ -237,27 +288,41 @@ def detect_embedder(
|
|
|
237
288
|
A configured provider is reported back as ``source="configured"`` and no
|
|
238
289
|
probing happens: the operator already answered the question.
|
|
239
290
|
"""
|
|
240
|
-
values = env
|
|
291
|
+
values = env if env is not None else dict(os.environ)
|
|
241
292
|
configured = str(configured_provider or "").strip().lower()
|
|
293
|
+
if configured in {"hf", "sentence-transformers", "sentence_transformers"}:
|
|
294
|
+
configured = "huggingface"
|
|
242
295
|
if configured and configured not in {"hash", "local", "fallback", AUTO_PROVIDER}:
|
|
296
|
+
resolved_model = configured_model or (
|
|
297
|
+
"google/embeddinggemma-2" if configured == "huggingface" else ""
|
|
298
|
+
)
|
|
299
|
+
dim = 768 if configured == "huggingface" and "embeddinggemma" in resolved_model.lower() else 0
|
|
243
300
|
return Detection(
|
|
244
301
|
provider=configured,
|
|
245
|
-
model=
|
|
302
|
+
model=resolved_model,
|
|
303
|
+
dim=dim,
|
|
246
304
|
source="configured",
|
|
247
305
|
detail=f"{configured} is configured explicitly",
|
|
248
306
|
)
|
|
249
307
|
if not autodetect_enabled(values):
|
|
250
308
|
return Detection(source="none", detail=f"{AUTODETECT_ENV} is off")
|
|
251
309
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
310
|
+
local_hf = detect_local_hf(values)
|
|
311
|
+
local_mlx = detect_local_mlx(values)
|
|
312
|
+
combined_candidates = list(local_hf.candidates) + list(local_mlx.candidates)
|
|
313
|
+
if local_hf.found:
|
|
314
|
+
local_hf.candidates = combined_candidates
|
|
315
|
+
return local_hf
|
|
316
|
+
if local_mlx.found:
|
|
317
|
+
local_mlx.candidates = combined_candidates
|
|
318
|
+
return local_mlx
|
|
255
319
|
if probe_ollama:
|
|
256
320
|
ollama = detect_ollama(env=values)
|
|
257
321
|
if ollama.found:
|
|
258
|
-
ollama.candidates =
|
|
322
|
+
ollama.candidates = combined_candidates
|
|
259
323
|
return ollama
|
|
260
|
-
|
|
324
|
+
local_mlx.candidates = combined_candidates
|
|
325
|
+
return local_mlx
|
|
261
326
|
|
|
262
327
|
|
|
263
328
|
def resolve_auto_provider(
|
|
@@ -289,11 +354,13 @@ __all__ = [
|
|
|
289
354
|
"AUTODETECT_ENV",
|
|
290
355
|
"AUTO_PROVIDER",
|
|
291
356
|
"DEFAULT_OLLAMA_BASE",
|
|
357
|
+
"LOCAL_HF_MODELS",
|
|
292
358
|
"LOCAL_MLX_MODELS",
|
|
293
359
|
"OLLAMA_EMBEDDING_MODELS",
|
|
294
360
|
"Detection",
|
|
295
361
|
"autodetect_enabled",
|
|
296
362
|
"detect_embedder",
|
|
363
|
+
"detect_local_hf",
|
|
297
364
|
"detect_local_mlx",
|
|
298
365
|
"detect_ollama",
|
|
299
366
|
"hf_cache_roots",
|
|
@@ -55,6 +55,12 @@ _KNOWN_DIMS = {
|
|
|
55
55
|
"multilingual-e5-large": 1024,
|
|
56
56
|
"multilingual-e5-large-mlx": 1024,
|
|
57
57
|
"snowflake-arctic-embed-l-v2.0-8bit": 1024,
|
|
58
|
+
"embeddinggemma-2": 768,
|
|
59
|
+
"google/embeddinggemma-2": 768,
|
|
60
|
+
"embeddinggemma-2-4bit": 768,
|
|
61
|
+
"embeddinggemma-2-8bit": 768,
|
|
62
|
+
"embeddinggemma-300m": 768,
|
|
63
|
+
"google/embeddinggemma-300m": 768,
|
|
58
64
|
"embeddinggemma-300m-4bit": 768,
|
|
59
65
|
"embeddinggemma-300m-8bit": 768,
|
|
60
66
|
"text-embedding-3-small": 1536,
|
|
@@ -64,8 +70,10 @@ _KNOWN_DIMS = {
|
|
|
64
70
|
|
|
65
71
|
|
|
66
72
|
def _guess_dim(model: str, default: int) -> int:
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
raw = str(model or "").strip().lower().split(":")[0]
|
|
74
|
+
if raw in _KNOWN_DIMS:
|
|
75
|
+
return _KNOWN_DIMS[raw]
|
|
76
|
+
key = raw.split("/")[-1].strip()
|
|
69
77
|
return _KNOWN_DIMS.get(key, default)
|
|
70
78
|
|
|
71
79
|
|