ltcai 11.2.0 → 11.4.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 +46 -53
- package/docs/CHANGELOG.md +61 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/MULTI_AGENT_RUNTIME.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -2
- package/docs/PERMISSION_MODE.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +2 -2
- package/docs/v11.3.0_PLAN.md +202 -0
- package/docs/v11.4.0_RUST_FOUNDATION_PLAN.md +176 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/_kg_common/__init__.py +287 -0
- package/lattice_brain/graph/_kg_common/extraction.py +516 -0
- package/lattice_brain/graph/_kg_common/relations.py +161 -0
- package/lattice_brain/graph/_kg_common/text.py +479 -0
- package/lattice_brain/graph/discovery_index/__init__.py +35 -0
- package/lattice_brain/graph/discovery_index/cleanup.py +182 -0
- package/lattice_brain/graph/discovery_index/extract.py +137 -0
- package/lattice_brain/graph/discovery_index/scan.py +411 -0
- package/lattice_brain/graph/discovery_index/upsert.py +495 -0
- package/lattice_brain/graph/projection/__init__.py +42 -0
- package/lattice_brain/graph/projection/curation.py +500 -0
- package/lattice_brain/graph/{projection.py → projection/v2_schema.py} +15 -477
- package/lattice_brain/graph/retrieval/__init__.py +54 -0
- package/lattice_brain/graph/retrieval/context.py +197 -0
- package/lattice_brain/graph/retrieval/graph_view.py +319 -0
- package/lattice_brain/graph/retrieval/hybrid.py +488 -0
- package/lattice_brain/graph/retrieval/maintenance.py +121 -0
- package/lattice_brain/graph/retrieval/signals.py +95 -0
- package/lattice_brain/graph/retrieval_vector/__init__.py +42 -0
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +97 -0
- package/lattice_brain/graph/retrieval_vector/indexing.py +347 -0
- package/lattice_brain/graph/retrieval_vector/search.py +560 -0
- package/lattice_brain/graph/retrieval_vector/status.py +374 -0
- package/lattice_brain/ingestion/__init__.py +130 -0
- package/lattice_brain/ingestion/_contract.py +90 -0
- package/lattice_brain/ingestion/constants.py +127 -0
- package/lattice_brain/ingestion/folder_scan.py +57 -0
- package/lattice_brain/ingestion/folders.py +258 -0
- package/lattice_brain/ingestion/hashing.py +26 -0
- package/lattice_brain/ingestion/jobs_api.py +107 -0
- package/lattice_brain/ingestion/models.py +80 -0
- package/lattice_brain/ingestion/pipeline.py +486 -0
- package/lattice_brain/ingestion/quality.py +209 -0
- package/lattice_brain/ingestion/routing.py +295 -0
- package/lattice_brain/multimodal/__init__.py +164 -0
- package/lattice_brain/multimodal/audio.py +77 -0
- package/lattice_brain/multimodal/common.py +118 -0
- package/lattice_brain/multimodal/images.py +498 -0
- package/lattice_brain/multimodal/ports.py +169 -0
- package/lattice_brain/multimodal/video.py +410 -0
- package/lattice_brain/portability/__init__.py +90 -0
- package/lattice_brain/portability/_contract.py +42 -0
- package/lattice_brain/portability/backups.py +338 -0
- package/lattice_brain/portability/bundles.py +136 -0
- package/lattice_brain/portability/constants.py +93 -0
- package/lattice_brain/portability/fsops.py +138 -0
- package/lattice_brain/portability/service.py +41 -0
- package/lattice_brain/{portability.py → portability/sharing.py} +44 -677
- package/lattice_brain/runtime/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/chronicle.py +63 -0
- package/latticeai/core/agent/__init__.py +93 -0
- package/latticeai/core/agent/_contract.py +79 -0
- package/latticeai/core/agent/context.py +57 -0
- package/latticeai/core/agent/deps.py +125 -0
- package/latticeai/core/agent/execution.py +622 -0
- package/latticeai/core/agent/planning.py +145 -0
- package/latticeai/core/agent/recovery.py +157 -0
- package/latticeai/core/agent/runtime.py +210 -0
- package/latticeai/core/agent/verification.py +231 -0
- package/latticeai/core/embedding_providers/__init__.py +151 -0
- package/latticeai/core/embedding_providers/base.py +199 -0
- package/latticeai/core/embedding_providers/captions.py +162 -0
- package/latticeai/core/embedding_providers/profiles.py +126 -0
- package/latticeai/core/embedding_providers/text.py +350 -0
- package/latticeai/core/embedding_providers/vision.py +352 -0
- package/latticeai/core/file_generation/__init__.py +115 -0
- package/latticeai/core/file_generation/bundles.py +76 -0
- package/latticeai/core/file_generation/extraction.py +154 -0
- package/latticeai/core/file_generation/inference.py +235 -0
- package/latticeai/core/file_generation/orchestration.py +152 -0
- package/latticeai/core/file_generation/prompting.py +117 -0
- package/latticeai/core/file_generation/repair.py +114 -0
- package/latticeai/core/file_generation/sanitize.py +61 -0
- package/latticeai/core/file_generation/validation.py +201 -0
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/messages.py +9 -0
- package/latticeai/core/workspace_os_constants.py +1 -1
- package/latticeai/integrations/telegram_bot/__init__.py +123 -0
- package/latticeai/integrations/telegram_bot/__main__.py +17 -0
- package/latticeai/integrations/telegram_bot/config.py +86 -0
- package/latticeai/integrations/telegram_bot/dispatch.py +311 -0
- package/latticeai/integrations/telegram_bot/flows.py +478 -0
- package/latticeai/integrations/telegram_bot/helpers.py +322 -0
- package/latticeai/integrations/telegram_bot/screens.py +394 -0
- package/latticeai/models/router/__init__.py +88 -0
- package/latticeai/models/router/_contract.py +66 -0
- package/latticeai/models/router/branding.py +56 -0
- package/latticeai/models/router/catalog.py +69 -0
- package/latticeai/models/router/documents.py +199 -0
- package/latticeai/models/router/errors.py +37 -0
- package/latticeai/models/router/generation.py +258 -0
- package/latticeai/models/router/loading.py +291 -0
- package/latticeai/models/router/local_models.py +85 -0
- package/latticeai/models/router/registry.py +147 -0
- package/latticeai/runtime/build_phases/__init__.py +82 -0
- package/latticeai/runtime/build_phases/features.py +407 -0
- package/latticeai/runtime/build_phases/foundation.py +555 -0
- package/latticeai/runtime/build_phases/web.py +492 -0
- package/latticeai/runtime/runtime_context.py +1 -0
- package/latticeai/services/architecture_readiness.py +48 -19
- package/latticeai/services/brain_intelligence/__init__.py +58 -0
- package/latticeai/services/brain_intelligence/_contract.py +71 -0
- package/latticeai/services/brain_intelligence/consistency.py +193 -0
- package/latticeai/services/brain_intelligence/constants.py +47 -0
- package/latticeai/services/brain_intelligence/digest.py +258 -0
- package/latticeai/services/brain_intelligence/health.py +331 -0
- package/latticeai/services/brain_intelligence/proposals.py +264 -0
- package/latticeai/services/brain_intelligence/sampling.py +84 -0
- package/latticeai/services/brain_intelligence/service.py +48 -0
- package/latticeai/services/chronicle.py +557 -0
- package/latticeai/services/memory_service/__init__.py +52 -0
- package/latticeai/services/memory_service/_contract.py +100 -0
- package/latticeai/services/memory_service/brief.py +431 -0
- package/latticeai/services/memory_service/constants.py +57 -0
- package/latticeai/services/memory_service/maintenance.py +138 -0
- package/latticeai/services/memory_service/manager.py +186 -0
- package/latticeai/services/memory_service/proof.py +136 -0
- package/latticeai/services/memory_service/recall.py +225 -0
- package/latticeai/services/memory_service/service.py +48 -0
- package/latticeai/services/memory_service/stores.py +110 -0
- package/latticeai/services/model_runtime/__init__.py +322 -0
- package/latticeai/services/model_runtime/cloud.py +87 -0
- package/latticeai/services/model_runtime/download.py +282 -0
- package/latticeai/services/model_runtime/engines.py +341 -0
- package/latticeai/services/model_runtime/loading.py +178 -0
- package/latticeai/services/model_runtime/service.py +129 -0
- package/latticeai/services/model_runtime/state.py +131 -0
- package/latticeai/services/model_runtime/status.py +255 -0
- package/latticeai/services/product_readiness.py +15 -7
- package/latticeai/setup/wizard/__init__.py +126 -0
- package/latticeai/setup/wizard/catalog.py +172 -0
- package/latticeai/setup/wizard/detect.py +323 -0
- package/latticeai/setup/wizard/install.py +348 -0
- package/latticeai/setup/wizard/paths.py +168 -0
- package/latticeai/setup/wizard/plans.py +74 -0
- package/latticeai/setup/wizard/recommend.py +320 -0
- package/package.json +6 -2
- package/scripts/bump_version.py +14 -0
- package/scripts/capture_release_evidence.mjs +33 -21
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_i18n_namespace_coverage.mjs +41 -4
- package/scripts/check_max_file_lines.mjs +102 -0
- package/scripts/check_release_evidence_bound.mjs +30 -15
- package/scripts/check_screenshot_pixel_delta.py +34 -4
- package/scripts/check_server_i18n.mjs +1 -0
- package/scripts/generate_rust_parity_fixtures.py +562 -0
- package/scripts/lib/mock_server_fingerprint.mjs +94 -0
- package/scripts/release_screen_claims.json +31 -2
- package/src-tauri/Cargo.lock +361 -3
- package/src-tauri/Cargo.toml +6 -1
- package/src-tauri/src/backend.rs +349 -0
- package/src-tauri/src/folder.rs +33 -0
- package/src-tauri/src/main.rs +97 -399
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +41 -37
- package/static/app/assets/Act-yYpYnn0v.js +1 -0
- package/static/app/assets/AdminConsole-DL3Cr5pL.js +1 -0
- package/static/app/assets/{Brain-tuhI4sOC.js → Brain-C1HBN0Wf.js} +2 -2
- package/static/app/assets/BrainHome-DoXRhUUC.js +2 -0
- package/static/app/assets/BrainSignals-6yR6ir5t.js +1 -0
- package/static/app/assets/Capture-CFIRsFNE.js +1 -0
- package/static/app/assets/Chronicle-BZbEgiwN.js +1 -0
- package/static/app/assets/CommandPalette-D2pMxC2I.js +1 -0
- package/static/app/assets/Library-DwO3yZST.js +1 -0
- package/static/app/assets/{LivingBrain-DBwhto14.js → LivingBrain-Jn1GK0-S.js} +1 -1
- package/static/app/assets/ProductFlow-B-w1R4Oo.js +1 -0
- package/static/app/assets/ReviewCard-6B27X8Vg.js +3 -0
- package/static/app/assets/System-DW8F-2xL.js +1 -0
- package/static/app/assets/arrow-left-DXvKg9U6.js +1 -0
- package/static/app/assets/{bot-Cia42c2h.js → bot-IM_E_Y12.js} +1 -1
- package/static/app/assets/brain-Ci1CkWjM.js +1 -0
- package/static/app/assets/{button-2j2Ijzgq.js → button-COwyqfHM.js} +1 -1
- package/static/app/assets/circle-check-DfInj-qD.js +1 -0
- package/static/app/assets/{circle-pause-BEFeWpVW.js → circle-pause-DEM4A1Y5.js} +1 -1
- package/static/app/assets/{circle-play-ujXMcHxl.js → circle-play-C9djDuLd.js} +1 -1
- package/static/app/assets/{cpu-k4awryFq.js → cpu-DFdo1gw-.js} +1 -1
- package/static/app/assets/{download-DFbLJ_ig.js → download-SnJL6oqk.js} +1 -1
- package/static/app/assets/{folder-open-7y_b6xkM.js → folder-open-CqZeDkjE.js} +1 -1
- package/static/app/assets/{hard-drive-Bidh02Kr.js → hard-drive-j1jJXYYf.js} +1 -1
- package/static/app/assets/{index-DwDl9-8Y.css → index-BLPb5lmE.css} +1 -1
- package/static/app/assets/index-_u5iUHDr.js +10 -0
- package/static/app/assets/input-B0lPdRQZ.js +1 -0
- package/static/app/assets/link-2-CoFbooHS.js +1 -0
- package/static/app/assets/{permissionCopy-Bpb83Hx9.js → permissionCopy-BsyLxtao.js} +1 -1
- package/static/app/assets/primitives-DEbN-d6p.js +1 -0
- package/static/app/assets/search-BybIWPNd.js +1 -0
- package/static/app/assets/{share-2-BH1M-WNi.js → share-2-CVtZ_ewX.js} +1 -1
- package/static/app/assets/{shield-alert-BlKdBXcG.js → shield-alert-CBi2GNWM.js} +1 -1
- package/static/app/assets/{textarea-CCWbUfFB.js → textarea-DNMpB5ih.js} +1 -1
- package/static/app/assets/{useFocusTrap-YdHQ7pJ1.js → useFocusTrap-C83t3GXF.js} +1 -1
- package/static/app/assets/useMutation-DtbJDoyz.js +1 -0
- package/static/app/assets/{useQuery-CXQiwbVT.js → useQuery-Dcp1OChy.js} +1 -1
- package/static/app/assets/utils-BlZr7Pd4.js +4 -0
- package/static/app/assets/workspace-jJY4RuAV.js +1 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/graph/_kg_common.py +0 -1331
- package/lattice_brain/graph/discovery_index.py +0 -1141
- package/lattice_brain/graph/retrieval.py +0 -1120
- package/lattice_brain/graph/retrieval_vector.py +0 -1293
- package/lattice_brain/ingestion.py +0 -1525
- package/lattice_brain/multimodal.py +0 -1258
- package/latticeai/core/agent.py +0 -1465
- package/latticeai/core/embedding_providers.py +0 -1196
- package/latticeai/core/file_generation.py +0 -1047
- package/latticeai/integrations/telegram_bot.py +0 -1390
- package/latticeai/models/router.py +0 -1007
- package/latticeai/runtime/build_phases.py +0 -1450
- package/latticeai/services/brain_intelligence.py +0 -1083
- package/latticeai/services/memory_service.py +0 -1177
- package/latticeai/services/model_runtime.py +0 -1281
- package/latticeai/setup/wizard.py +0 -1310
- package/static/app/assets/Act-AWf0SAKp.js +0 -1
- package/static/app/assets/AdminConsole-D0u8Tiyj.js +0 -1
- package/static/app/assets/BrainHome-Ts7G_Ila.js +0 -2
- package/static/app/assets/BrainSignals-jMYgQ2Ar.js +0 -1
- package/static/app/assets/Capture-CqOSzyPr.js +0 -1
- package/static/app/assets/CommandPalette-DC0Bzh-I.js +0 -1
- package/static/app/assets/Library-CX-bbhmK.js +0 -1
- package/static/app/assets/ProductFlow-BHA2cfKI.js +0 -1
- package/static/app/assets/ReviewCard-BUhCKRNM.js +0 -3
- package/static/app/assets/System-Bu2t5hn1.js +0 -1
- package/static/app/assets/arrow-left-Dzwa5zRb.js +0 -1
- package/static/app/assets/brain-DJMoqrwx.js +0 -1
- package/static/app/assets/index-BpYkzcVm.js +0 -10
- package/static/app/assets/input-DSlJJxRs.js +0 -1
- package/static/app/assets/primitives-BCx6TvfG.js +0 -1
- package/static/app/assets/search-Cgy8cCFJ.js +0 -1
- package/static/app/assets/utils-zqPZJxdx.js +0 -4
- package/static/app/assets/workspace-DXTihhfU.js +0 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""The wizard's model catalogue and the per-engine "best model" tiers.
|
|
2
|
+
|
|
3
|
+
Data plus the two pure decisions that read it: hide superseded model
|
|
4
|
+
*generations* (:func:`_filter_lower_family_versions`) and pick the best model a
|
|
5
|
+
machine with this much RAM can actually hold (:func:`_best_model_for_engine`).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Dict, List, Tuple
|
|
12
|
+
|
|
13
|
+
# ── Model Catalog ─────────────────────────────────────────────────────────────
|
|
14
|
+
# (model_id, display_name, size_gb, tag, description, min_ram_gb)
|
|
15
|
+
# 11.2.0: 모든 repo id 를 2026-08-10 Hugging Face API 로 확인했다 — 존재 여부,
|
|
16
|
+
# gated 여부, 정확한 대소문자, siblings 합계 크기. 크기 값은 측정값이다.
|
|
17
|
+
# 삭제: Qwen3-VL 전 라인업(2025-10, Qwen3.5/3.6 이 상위 세대), Llama 4 Scout
|
|
18
|
+
# (vLLM/LM Studio 경로가 gated 인 meta-llama 저장소를 가리켰다).
|
|
19
|
+
_MODEL_CATALOG = [
|
|
20
|
+
("mlx-community/LFM2.5-2.6B-4bit", "LFM2.5 2.6B", 1.5, "LLM", "가장 가벼움 · 한국어 대화 (사진은 못 읽음)", 4),
|
|
21
|
+
("mlx-community/gemma-4-e2b-it-4bit", "Gemma 4 E2B", 3.6, "VLM", "사진을 읽는 가장 작은 모델", 8),
|
|
22
|
+
("mlx-community/gemma-4-e4b-it-4bit", "Gemma 4 E4B", 5.2, "VLM", "E2B 보다 한 단계 위 · 여전히 가벼움", 10),
|
|
23
|
+
("mlx-community/Qwen3.5-9B-MLX-4bit", "Qwen3.5 9B", 6.0, "VLM", "중형 멀티모달 · 균형 추천", 12),
|
|
24
|
+
("mlx-community/gemma-4-12B-it-4bit", "Gemma 4 12B", 6.8, "VLM", "Gemma 4 기본 추천 · 4bit", 16),
|
|
25
|
+
("mlx-community/gpt-oss-20b-MXFP4-Q8", "GPT-OSS 20B", 12.1, "LLM", "범용 · 다운로드 최다 (사진은 못 읽음)", 24),
|
|
26
|
+
("mlx-community/gemma-4-26b-a4b-it-4bit", "Gemma 4 26B A4B", 15.4, "VLM", "MoE · 대형 추천", 32),
|
|
27
|
+
("mlx-community/Qwen3.6-27B-4bit", "Qwen3.6 27B", 16.1, "VLM+", "dense 최상위 · 느리지만 일정함", 48),
|
|
28
|
+
("mlx-community/gemma-4-31b-it-4bit", "Gemma 4 31B", 18.4, "VLM+", "Gemma 4 최대 모델", 48),
|
|
29
|
+
("mlx-community/Qwen3.6-35B-A3B-4bit", "Qwen3.6 35B A3B", 20.4, "VLM+", "MoE 최상위", 48),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
_CROSS_PLATFORM_MODEL_CATALOG: Dict[str, List[Tuple[str, str, float, str, str, int]]] = {
|
|
33
|
+
"ollama": [
|
|
34
|
+
("ollama:hf.co/LiquidAI/LFM2.5-2.6B-GGUF:Q4_K_M", "LFM2.5 2.6B Q4", 1.7, "LLM", "가장 가벼움 · 한국어 대화", 4),
|
|
35
|
+
("ollama:hf.co/ggml-org/gemma-4-E2B-it-GGUF:Q4_K_M", "Gemma 4 E2B Q4", 3.8, "VLM", "Hugging Face GGUF 기반 Gemma 4", 8),
|
|
36
|
+
("ollama:hf.co/ggml-org/gemma-4-E4B-it-GGUF:Q4_K_M", "Gemma 4 E4B Q4", 5.4, "VLM", "Hugging Face GGUF 기반 Gemma 4", 10),
|
|
37
|
+
("ollama:hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M", "Gemma 4 12B Q4", 7.9, "VLM", "Hugging Face GGUF 기반 Gemma 4", 16),
|
|
38
|
+
("ollama:hf.co/ggml-org/gpt-oss-20b-GGUF:Q4_K_M", "GPT-OSS 20B Q4", 12.5, "LLM", "범용 · 다운로드 최다", 24),
|
|
39
|
+
("ollama:hf.co/ggml-org/gemma-4-26B-A4B-it-GGUF:Q4_K_M", "Gemma 4 26B Q4", 16.0, "VLM", "MoE · 대형 추천", 32),
|
|
40
|
+
("ollama:hf.co/ggml-org/Qwen3.6-27B-GGUF:Q4_K_M", "Qwen3.6 27B Q4", 16.6, "VLM+", "dense 최상위", 48),
|
|
41
|
+
("ollama:hf.co/ggml-org/gemma-4-31B-it-GGUF:Q4_K_M", "Gemma 4 31B Q4", 18.7, "VLM+", "Gemma 4 최대 모델", 48),
|
|
42
|
+
],
|
|
43
|
+
"lmstudio": [
|
|
44
|
+
("lmstudio:LiquidAI/LFM2.5-2.6B-GGUF", "LFM2.5 2.6B", 1.7, "LLM", "가장 가벼움 · 한국어 대화", 4),
|
|
45
|
+
("lmstudio:ggml-org/gemma-4-E2B-it-GGUF", "Gemma 4 E2B", 3.8, "VLM", "LM Studio GGUF Gemma 4", 8),
|
|
46
|
+
("lmstudio:ggml-org/gemma-4-E4B-it-GGUF", "Gemma 4 E4B", 5.4, "VLM", "LM Studio GGUF Gemma 4", 10),
|
|
47
|
+
("lmstudio:ggml-org/gemma-4-12B-it-GGUF", "Gemma 4 12B", 7.9, "VLM", "LM Studio GGUF Gemma 4", 16),
|
|
48
|
+
("lmstudio:ggml-org/gpt-oss-20b-GGUF", "GPT-OSS 20B", 12.5, "LLM", "범용 · 다운로드 최다", 24),
|
|
49
|
+
("lmstudio:ggml-org/gemma-4-26B-A4B-it-GGUF", "Gemma 4 26B", 16.0, "VLM", "MoE · 대형 추천", 32),
|
|
50
|
+
("lmstudio:ggml-org/Qwen3.6-27B-GGUF", "Qwen3.6 27B", 16.6, "VLM+", "dense 최상위", 48),
|
|
51
|
+
("lmstudio:ggml-org/gemma-4-31B-it-GGUF", "Gemma 4 31B", 18.7, "VLM+", "Gemma 4 최대 모델", 48),
|
|
52
|
+
],
|
|
53
|
+
"vllm": [
|
|
54
|
+
("vllm:LiquidAI/LFM2.5-2.6B", "LFM2.5 2.6B", 5.2, "LLM", "가장 가벼움 · 한국어 대화", 8),
|
|
55
|
+
("vllm:google/gemma-4-E2B-it", "Gemma 4 E2B", 6.0, "VLM", "내 컴퓨터 GPU 실행 도구 권장", 12),
|
|
56
|
+
("vllm:google/gemma-4-E4B-it", "Gemma 4 E4B", 9.0, "VLM", "내 컴퓨터 GPU 실행 도구 권장", 16),
|
|
57
|
+
("vllm:Qwen/Qwen3.5-9B", "Qwen3.5 9B", 18.0, "VLM", "중형 멀티모달 · 균형 추천", 24),
|
|
58
|
+
("vllm:google/gemma-4-12B-it", "Gemma 4 12B", 24.0, "VLM", "Gemma 4 기본 추천", 32),
|
|
59
|
+
("vllm:openai/gpt-oss-20b", "GPT-OSS 20B", 13.5, "LLM", "범용 · 다운로드 최다", 24),
|
|
60
|
+
("vllm:Qwen/Qwen3.6-27B", "Qwen3.6 27B", 54.0, "VLM+", "dense 최상위 · 24GB+ VRAM 권장", 64),
|
|
61
|
+
("vllm:Qwen/Qwen3.6-35B-A3B", "Qwen3.6 35B A3B", 70.0, "VLM+", "MoE 최상위 · 24GB+ VRAM 권장", 80),
|
|
62
|
+
],
|
|
63
|
+
"llamacpp": [
|
|
64
|
+
("llamacpp:LiquidAI/LFM2.5-2.6B-GGUF", "LFM2.5 2.6B GGUF", 1.7, "GGUF", "CPU/Vulkan 백업 · 가장 가벼움", 4),
|
|
65
|
+
("llamacpp:ggml-org/gemma-4-E2B-it-GGUF", "Gemma 4 E2B GGUF", 3.8, "GGUF", "Gemma 4 E2B Q4_K_M", 8),
|
|
66
|
+
("llamacpp:ggml-org/gemma-4-E4B-it-GGUF", "Gemma 4 E4B GGUF", 5.4, "GGUF", "Gemma 4 E4B Q4_K_M", 10),
|
|
67
|
+
("llamacpp:ggml-org/gemma-4-12B-it-GGUF", "Gemma 4 12B GGUF", 7.9, "GGUF", "Gemma 4 12B Q4_K_M", 16),
|
|
68
|
+
("llamacpp:ggml-org/gpt-oss-20b-GGUF", "GPT-OSS 20B GGUF", 12.5, "GGUF", "범용 · 다운로드 최다", 24),
|
|
69
|
+
("llamacpp:ggml-org/gemma-4-26B-A4B-it-GGUF", "Gemma 4 26B GGUF", 16.0, "GGUF", "MoE · 대형 추천", 32),
|
|
70
|
+
("llamacpp:ggml-org/Qwen3.6-27B-GGUF", "Qwen3.6 27B GGUF", 16.6, "GGUF", "dense 최상위", 48),
|
|
71
|
+
("llamacpp:ggml-org/gemma-4-31B-it-GGUF", "Gemma 4 31B GGUF", 18.7, "GGUF", "Gemma 4 31B Q4_K_M", 48),
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_VERSIONED_MODEL_PATTERNS = (
|
|
76
|
+
("gemma", re.compile(r"\bgemma[-\s]?(\d+(?:\.\d+)?)", re.IGNORECASE)),
|
|
77
|
+
("qwen", re.compile(r"\bqwen[-\s]?(\d+(?:\.\d+)?)", re.IGNORECASE)),
|
|
78
|
+
("llama", re.compile(r"\bllama[-\s]?(\d+(?:\.\d+)?)", re.IGNORECASE)),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# RAM(GB) 내림차순 티어. 첫 번째로 조건을 만족하는 항목이 "이 컴퓨터에서 가장
|
|
82
|
+
# 좋은 모델"이 된다. 초경량(≤8) / 경량(16) / 중형(24) / MoE(32) / 대형(48).
|
|
83
|
+
_BEST_MODEL_TIERS: Dict[str, List[Tuple[int, str]]] = {
|
|
84
|
+
"local_mlx": [
|
|
85
|
+
(48, "mlx-community/gemma-4-31b-it-4bit"),
|
|
86
|
+
(32, "mlx-community/gemma-4-26b-a4b-it-4bit"),
|
|
87
|
+
(24, "mlx-community/gemma-4-12B-it-4bit"),
|
|
88
|
+
(16, "mlx-community/Qwen3.5-9B-MLX-4bit"),
|
|
89
|
+
(8, "mlx-community/gemma-4-e2b-it-4bit"),
|
|
90
|
+
(4, "mlx-community/LFM2.5-2.6B-4bit"),
|
|
91
|
+
],
|
|
92
|
+
"ollama": [
|
|
93
|
+
(48, "ollama:hf.co/ggml-org/gemma-4-31B-it-GGUF:Q4_K_M"),
|
|
94
|
+
(32, "ollama:hf.co/ggml-org/gemma-4-26B-A4B-it-GGUF:Q4_K_M"),
|
|
95
|
+
(24, "ollama:hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M"),
|
|
96
|
+
(16, "ollama:hf.co/ggml-org/gemma-4-E4B-it-GGUF:Q4_K_M"),
|
|
97
|
+
(8, "ollama:hf.co/ggml-org/gemma-4-E2B-it-GGUF:Q4_K_M"),
|
|
98
|
+
(4, "ollama:hf.co/LiquidAI/LFM2.5-2.6B-GGUF:Q4_K_M"),
|
|
99
|
+
],
|
|
100
|
+
"lmstudio": [
|
|
101
|
+
(48, "lmstudio:ggml-org/gemma-4-31B-it-GGUF"),
|
|
102
|
+
(32, "lmstudio:ggml-org/gemma-4-26B-A4B-it-GGUF"),
|
|
103
|
+
(24, "lmstudio:ggml-org/gemma-4-12B-it-GGUF"),
|
|
104
|
+
(16, "lmstudio:ggml-org/gemma-4-E4B-it-GGUF"),
|
|
105
|
+
(8, "lmstudio:ggml-org/gemma-4-E2B-it-GGUF"),
|
|
106
|
+
(4, "lmstudio:LiquidAI/LFM2.5-2.6B-GGUF"),
|
|
107
|
+
],
|
|
108
|
+
# vLLM serves the upstream bf16 repos, which are 3-4x the MLX 4-bit builds
|
|
109
|
+
# (Qwen3.6-27B is 54GB, not 16GB), so its thresholds sit far higher than the
|
|
110
|
+
# local_mlx ones. Picking by RAM alone would nominate a model no consumer
|
|
111
|
+
# GPU can hold.
|
|
112
|
+
"vllm": [
|
|
113
|
+
(96, "vllm:Qwen/Qwen3.6-27B"),
|
|
114
|
+
(32, "vllm:google/gemma-4-12B-it"),
|
|
115
|
+
(24, "vllm:Qwen/Qwen3.5-9B"),
|
|
116
|
+
(16, "vllm:google/gemma-4-E4B-it"),
|
|
117
|
+
(8, "vllm:LiquidAI/LFM2.5-2.6B"),
|
|
118
|
+
],
|
|
119
|
+
"llamacpp": [
|
|
120
|
+
(48, "llamacpp:ggml-org/gemma-4-31B-it-GGUF"),
|
|
121
|
+
(32, "llamacpp:ggml-org/gemma-4-26B-A4B-it-GGUF"),
|
|
122
|
+
(24, "llamacpp:ggml-org/gemma-4-12B-it-GGUF"),
|
|
123
|
+
(16, "llamacpp:ggml-org/gemma-4-E4B-it-GGUF"),
|
|
124
|
+
(8, "llamacpp:ggml-org/gemma-4-E2B-it-GGUF"),
|
|
125
|
+
(4, "llamacpp:LiquidAI/LFM2.5-2.6B-GGUF"),
|
|
126
|
+
],
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _version_tuple(raw: str) -> Tuple[int, ...]:
|
|
131
|
+
return tuple(int(part) for part in raw.split(".") if part.isdigit())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _catalog_row_family_version(row: Tuple[str, str, float, str, str, int]) -> Tuple[str, Tuple[int, ...]] | None:
|
|
135
|
+
text = f"{row[0]} {row[1]}"
|
|
136
|
+
for family, pattern in _VERSIONED_MODEL_PATTERNS:
|
|
137
|
+
match = pattern.search(text)
|
|
138
|
+
if match:
|
|
139
|
+
# Major version only — see model_catalog._model_family_version. The
|
|
140
|
+
# filter hides superseded *generations*; Qwen3.5 and Qwen3.6 are one
|
|
141
|
+
# generation filling two different RAM tiers and must coexist.
|
|
142
|
+
version = _version_tuple(match.group(1))[:1]
|
|
143
|
+
if version:
|
|
144
|
+
return family, version
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _filter_lower_family_versions(
|
|
149
|
+
rows: List[Tuple[str, str, float, str, str, int]],
|
|
150
|
+
) -> List[Tuple[str, str, float, str, str, int]]:
|
|
151
|
+
max_versions: Dict[str, Tuple[int, ...]] = {}
|
|
152
|
+
detected: List[Tuple[Tuple[str, str, float, str, str, int], Tuple[str, Tuple[int, ...]] | None]] = []
|
|
153
|
+
for row in rows:
|
|
154
|
+
version_info = _catalog_row_family_version(row)
|
|
155
|
+
detected.append((row, version_info))
|
|
156
|
+
if not version_info:
|
|
157
|
+
continue
|
|
158
|
+
family, version = version_info
|
|
159
|
+
if version > max_versions.get(family, (0,)):
|
|
160
|
+
max_versions[family] = version
|
|
161
|
+
return [
|
|
162
|
+
row for row, version_info in detected
|
|
163
|
+
if not version_info or version_info[1] >= max_versions.get(version_info[0], version_info[1])
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _best_model_for_engine(engine: str, ram_gb: float, rows: List[Tuple[str, str, float, str, str, int]]) -> str:
|
|
168
|
+
available_ids = {row[0] for row in rows}
|
|
169
|
+
for min_ram, model_id in _BEST_MODEL_TIERS.get(engine, []):
|
|
170
|
+
if ram_gb >= min_ram and model_id in available_ids:
|
|
171
|
+
return model_id
|
|
172
|
+
return rows[0][0] if rows else ""
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Hardware, OS, toolchain and API-key detection for the setup wizard.
|
|
2
|
+
|
|
3
|
+
Everything the recommender needs to know about *this* machine: chip, CPU
|
|
4
|
+
features, RAM, free disk, GPU/CUDA/WSL, which CLIs are on PATH, whether MLX
|
|
5
|
+
imports, and which provider keys are in the environment. Every probe is
|
|
6
|
+
best-effort — a failed probe answers "unknown", never raises.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import platform
|
|
13
|
+
import re
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, List
|
|
18
|
+
|
|
19
|
+
from latticeai.core.quiet import quiet
|
|
20
|
+
from latticeai.services.setup_detection import (
|
|
21
|
+
detect_cuda,
|
|
22
|
+
detect_tools,
|
|
23
|
+
detect_wsl_from_text,
|
|
24
|
+
)
|
|
25
|
+
from latticeai.services.setup_detection import (
|
|
26
|
+
parse_windows_video_controllers as _parse_windows_video_controllers,
|
|
27
|
+
)
|
|
28
|
+
from latticeai.setup.wizard.paths import (
|
|
29
|
+
_component_detail,
|
|
30
|
+
_module_available,
|
|
31
|
+
_which_any,
|
|
32
|
+
repair_path_for,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _cmd(args: List[str], timeout: int = 10) -> str:
|
|
37
|
+
try:
|
|
38
|
+
r = subprocess.run(args, capture_output=True, text=True, timeout=timeout, check=False)
|
|
39
|
+
return (r.stdout or r.stderr or "").strip()
|
|
40
|
+
except Exception:
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
# ── Environment Detection ─────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
def _detect_chip() -> Dict[str, Any]:
|
|
46
|
+
arch = platform.machine()
|
|
47
|
+
is_apple = arch == "arm64" and platform.system() == "Darwin"
|
|
48
|
+
name = "Unknown CPU"
|
|
49
|
+
gen: Any = None
|
|
50
|
+
|
|
51
|
+
if is_apple:
|
|
52
|
+
profiler = _cmd(["system_profiler", "SPHardwareDataType"], timeout=8)
|
|
53
|
+
m = re.search(r"Chip:\s+(Apple M\S+)", profiler)
|
|
54
|
+
name = m.group(1) if m else "Apple Silicon"
|
|
55
|
+
gm = re.search(r"M(\d+)", name)
|
|
56
|
+
gen = int(gm.group(1)) if gm else 1
|
|
57
|
+
else:
|
|
58
|
+
brand = ""
|
|
59
|
+
if platform.system() == "Darwin":
|
|
60
|
+
brand = _cmd(["sysctl", "-n", "machdep.cpu.brand_string"])
|
|
61
|
+
elif platform.system() == "Windows":
|
|
62
|
+
raw = _cmd(["wmic", "cpu", "get", "Name", "/value"], timeout=5)
|
|
63
|
+
if "Name=" in raw:
|
|
64
|
+
brand = raw.split("Name=", 1)[-1].splitlines()[0].strip()
|
|
65
|
+
elif platform.system() == "Linux":
|
|
66
|
+
try:
|
|
67
|
+
for line in Path("/proc/cpuinfo").read_text(encoding="utf-8", errors="replace").splitlines():
|
|
68
|
+
if line.lower().startswith("model name"):
|
|
69
|
+
brand = line.split(":", 1)[-1].strip()
|
|
70
|
+
break
|
|
71
|
+
except Exception:
|
|
72
|
+
quiet()
|
|
73
|
+
name = brand or platform.processor() or "Unknown CPU"
|
|
74
|
+
|
|
75
|
+
return {"name": name, "arch": arch, "is_apple_silicon": is_apple, "gen": gen}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _detect_cpu() -> Dict[str, Any]:
|
|
79
|
+
flags: List[str] = []
|
|
80
|
+
physical_cores = os.cpu_count() or 0
|
|
81
|
+
logical_cores = os.cpu_count() or 0
|
|
82
|
+
model = _detect_chip()["name"]
|
|
83
|
+
if platform.system() == "Darwin":
|
|
84
|
+
flags = [item.lower() for item in _cmd(["sysctl", "-n", "machdep.cpu.features"], timeout=5).split()]
|
|
85
|
+
try:
|
|
86
|
+
physical_cores = int(_cmd(["sysctl", "-n", "hw.physicalcpu"], timeout=5) or physical_cores)
|
|
87
|
+
logical_cores = int(_cmd(["sysctl", "-n", "hw.logicalcpu"], timeout=5) or logical_cores)
|
|
88
|
+
except ValueError:
|
|
89
|
+
quiet()
|
|
90
|
+
elif platform.system() == "Linux":
|
|
91
|
+
try:
|
|
92
|
+
text = Path("/proc/cpuinfo").read_text(encoding="utf-8", errors="replace")
|
|
93
|
+
for line in text.splitlines():
|
|
94
|
+
if line.lower().startswith(("flags", "features")):
|
|
95
|
+
flags = line.split(":", 1)[-1].strip().lower().split()
|
|
96
|
+
break
|
|
97
|
+
except Exception:
|
|
98
|
+
quiet()
|
|
99
|
+
elif platform.system() == "Windows":
|
|
100
|
+
raw = _cmd(["wmic", "cpu", "get", "Name,NumberOfCores,NumberOfLogicalProcessors", "/format:list"], timeout=5)
|
|
101
|
+
for line in raw.splitlines():
|
|
102
|
+
key, _, value = line.partition("=")
|
|
103
|
+
if key == "Name" and value.strip():
|
|
104
|
+
model = value.strip()
|
|
105
|
+
elif key == "NumberOfCores" and value.strip():
|
|
106
|
+
try:
|
|
107
|
+
physical_cores = int(value.strip())
|
|
108
|
+
except ValueError:
|
|
109
|
+
quiet()
|
|
110
|
+
elif key == "NumberOfLogicalProcessors" and value.strip():
|
|
111
|
+
try:
|
|
112
|
+
logical_cores = int(value.strip())
|
|
113
|
+
except ValueError:
|
|
114
|
+
quiet()
|
|
115
|
+
try:
|
|
116
|
+
import ctypes
|
|
117
|
+
kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined] # Windows-only
|
|
118
|
+
feature_map = {
|
|
119
|
+
6: "sse",
|
|
120
|
+
10: "sse2",
|
|
121
|
+
13: "sse3",
|
|
122
|
+
19: "neon",
|
|
123
|
+
28: "rdrand",
|
|
124
|
+
}
|
|
125
|
+
flags.extend(name for code, name in feature_map.items() if kernel32.IsProcessorFeaturePresent(code))
|
|
126
|
+
except Exception:
|
|
127
|
+
quiet()
|
|
128
|
+
interesting = {"avx", "avx2", "avx512f", "fma", "neon", "sse4_2"}
|
|
129
|
+
if platform.system() == "Windows":
|
|
130
|
+
interesting.update({"sse", "sse2", "sse3", "rdrand"})
|
|
131
|
+
return {
|
|
132
|
+
"model": model,
|
|
133
|
+
"physical_cores": physical_cores,
|
|
134
|
+
"logical_cores": logical_cores,
|
|
135
|
+
"instructions": sorted({flag for flag in flags if flag in interesting}),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
def _detect_ram_gb() -> float:
|
|
139
|
+
if platform.system() == "Windows":
|
|
140
|
+
raw = _cmd(["wmic", "ComputerSystem", "get", "TotalPhysicalMemory", "/format:list"], timeout=5)
|
|
141
|
+
for line in raw.splitlines():
|
|
142
|
+
if line.startswith("TotalPhysicalMemory="):
|
|
143
|
+
try:
|
|
144
|
+
return round(int(line.split("=", 1)[-1].strip()) / 1_073_741_824, 1)
|
|
145
|
+
except ValueError:
|
|
146
|
+
break
|
|
147
|
+
raw = _cmd(["sysctl", "-n", "hw.memsize"])
|
|
148
|
+
if raw:
|
|
149
|
+
try:
|
|
150
|
+
return round(int(raw) / 1_073_741_824, 1)
|
|
151
|
+
except ValueError:
|
|
152
|
+
quiet()
|
|
153
|
+
if platform.system() == "Darwin":
|
|
154
|
+
profiler = _cmd(["system_profiler", "SPHardwareDataType"], timeout=8)
|
|
155
|
+
m = re.search(r"Memory:\s+([\d.]+)\s*(TB|GB|MB)", profiler, re.IGNORECASE)
|
|
156
|
+
if m:
|
|
157
|
+
value = float(m.group(1))
|
|
158
|
+
unit = m.group(2).lower()
|
|
159
|
+
if unit == "tb":
|
|
160
|
+
return round(value * 1024, 1)
|
|
161
|
+
if unit == "gb":
|
|
162
|
+
return round(value, 1)
|
|
163
|
+
return round(value / 1024, 1)
|
|
164
|
+
hostinfo = _cmd(["hostinfo"], timeout=5)
|
|
165
|
+
m = re.search(r"Primary memory available:\s+([\d.]+)\s+gigabytes", hostinfo, re.IGNORECASE)
|
|
166
|
+
if m:
|
|
167
|
+
return round(float(m.group(1)), 1)
|
|
168
|
+
try:
|
|
169
|
+
with open("/proc/meminfo") as f:
|
|
170
|
+
for line in f:
|
|
171
|
+
if line.startswith("MemTotal:"):
|
|
172
|
+
return round(int(line.split()[1]) / 1_048_576, 1)
|
|
173
|
+
except Exception:
|
|
174
|
+
quiet()
|
|
175
|
+
return 0.0
|
|
176
|
+
|
|
177
|
+
def _detect_disk_free_gb() -> float:
|
|
178
|
+
try:
|
|
179
|
+
path = "C:\\" if platform.system() == "Windows" else "/"
|
|
180
|
+
return round(shutil.disk_usage(path).free / 1_073_741_824, 1)
|
|
181
|
+
except Exception:
|
|
182
|
+
return 0.0
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _detect_gpu() -> Dict[str, Any]:
|
|
186
|
+
devices: List[Dict[str, Any]] = []
|
|
187
|
+
nvidia_smi = _which_any("nvidia-smi")
|
|
188
|
+
if nvidia_smi:
|
|
189
|
+
info = _cmd([nvidia_smi, "--query-gpu=name,memory.total", "--format=csv,noheader,nounits"], timeout=8)
|
|
190
|
+
for line in [item.strip() for item in info.splitlines() if item.strip()]:
|
|
191
|
+
try:
|
|
192
|
+
name, mem = [part.strip() for part in line.split(",", 1)]
|
|
193
|
+
devices.append({"vendor": "nvidia", "name": name, "vram_mb": int(float(mem)), "backend": "cuda"})
|
|
194
|
+
except Exception:
|
|
195
|
+
quiet()
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
if platform.system() == "Windows":
|
|
199
|
+
shell = _which_any("powershell") or _which_any("pwsh")
|
|
200
|
+
raw = ""
|
|
201
|
+
if shell:
|
|
202
|
+
raw = _cmd([
|
|
203
|
+
shell, "-NoProfile", "-Command",
|
|
204
|
+
"Get-CimInstance Win32_VideoController | Select-Object Name,AdapterRAM | ConvertTo-Json -Compress",
|
|
205
|
+
], timeout=8)
|
|
206
|
+
if not raw:
|
|
207
|
+
raw = _cmd(["wmic", "path", "win32_VideoController", "get", "Name,AdapterRAM", "/format:list"], timeout=8)
|
|
208
|
+
for item in _parse_windows_video_controllers(raw):
|
|
209
|
+
if any(existing.get("name") == item["name"] for existing in devices):
|
|
210
|
+
continue
|
|
211
|
+
low = item["name"].lower()
|
|
212
|
+
vendor, backend = "unknown", "cpu"
|
|
213
|
+
if "nvidia" in low or "geforce" in low or "rtx" in low:
|
|
214
|
+
vendor, backend = "nvidia", "cuda"
|
|
215
|
+
elif "amd" in low or "radeon" in low:
|
|
216
|
+
vendor, backend = "amd", "directml/vulkan"
|
|
217
|
+
elif "intel" in low or "arc" in low or "iris" in low:
|
|
218
|
+
vendor, backend = "intel", "directml/vulkan"
|
|
219
|
+
devices.append({"vendor": vendor, "name": item["name"], "vram_mb": item["vram_mb"], "backend": backend})
|
|
220
|
+
elif platform.system() == "Darwin":
|
|
221
|
+
sp = _cmd(["system_profiler", "SPDisplaysDataType"], timeout=8)
|
|
222
|
+
for line in sp.splitlines():
|
|
223
|
+
if "Chipset Model" in line:
|
|
224
|
+
devices.append({"vendor": "apple", "name": line.split(":", 1)[-1].strip(), "vram_mb": 0, "backend": "metal/mlx"})
|
|
225
|
+
break
|
|
226
|
+
elif platform.system() == "Linux" and not devices:
|
|
227
|
+
info = _cmd(["lspci"], timeout=5)
|
|
228
|
+
for line in info.splitlines():
|
|
229
|
+
low = line.lower()
|
|
230
|
+
if not any(token in low for token in ("vga", "3d controller", "display")):
|
|
231
|
+
continue
|
|
232
|
+
if "nvidia" in low:
|
|
233
|
+
devices.append({"vendor": "nvidia", "name": line.strip(), "vram_mb": 0, "backend": "cuda"})
|
|
234
|
+
elif "amd" in low or "advanced micro devices" in low or "radeon" in low:
|
|
235
|
+
devices.append({"vendor": "amd", "name": line.strip(), "vram_mb": 0, "backend": "rocm/vulkan"})
|
|
236
|
+
elif "intel" in low:
|
|
237
|
+
devices.append({"vendor": "intel", "name": line.strip(), "vram_mb": 0, "backend": "vulkan"})
|
|
238
|
+
|
|
239
|
+
primary = max(devices, key=lambda item: int(item.get("vram_mb") or 0), default={})
|
|
240
|
+
vram_mb = int(primary.get("vram_mb") or 0)
|
|
241
|
+
return {
|
|
242
|
+
"devices": devices,
|
|
243
|
+
"vendor": primary.get("vendor", "none"),
|
|
244
|
+
"name": primary.get("name", ""),
|
|
245
|
+
"vram_mb": vram_mb,
|
|
246
|
+
"vram_gb": round(vram_mb / 1024, 1),
|
|
247
|
+
"backend": primary.get("backend", "cpu"),
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _detect_cuda() -> Dict[str, Any]:
|
|
252
|
+
available, version, nvidia_smi, nvcc = detect_cuda(_which_any, lambda args: _cmd(args, timeout=5))
|
|
253
|
+
return {"available": available, "nvidia_smi": nvidia_smi, "nvcc": nvcc, "version": version}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _detect_wsl() -> Dict[str, Any]:
|
|
257
|
+
raw = ""
|
|
258
|
+
try:
|
|
259
|
+
raw = Path("/proc/version").read_text(encoding="utf-8", errors="replace")
|
|
260
|
+
except Exception:
|
|
261
|
+
quiet()
|
|
262
|
+
is_wsl, version = detect_wsl_from_text(platform.system().lower(), raw)
|
|
263
|
+
return {"is_wsl": is_wsl, "version": version}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _detect_tools() -> Dict[str, bool]:
|
|
267
|
+
repair_path_for()
|
|
268
|
+
detected = detect_tools(_which_any, ["brew", "ollama", "python3", "python", "node", "npm", "git", "tesseract", "lms", "nvidia-smi", "nvcc"])
|
|
269
|
+
return {tool: path is not None for tool, path in detected.items()}
|
|
270
|
+
|
|
271
|
+
def _detect_mlx() -> Dict[str, Any]:
|
|
272
|
+
return {
|
|
273
|
+
"available": _module_available("mlx"),
|
|
274
|
+
"mlx_vlm": _module_available("mlx_vlm"),
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
def _detect_api_keys() -> Dict[str, bool]:
|
|
278
|
+
return {
|
|
279
|
+
"openai": bool(os.getenv("OPENAI_API_KEY")),
|
|
280
|
+
"openrouter": bool(os.getenv("OPENROUTER_API_KEY")),
|
|
281
|
+
"groq": bool(os.getenv("GROQ_API_KEY")),
|
|
282
|
+
"together": bool(os.getenv("TOGETHER_API_KEY")),
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
def scan_environment() -> Dict[str, Any]:
|
|
286
|
+
chip = _detect_chip()
|
|
287
|
+
cpu = _detect_cpu()
|
|
288
|
+
gpu = _detect_gpu()
|
|
289
|
+
cuda = _detect_cuda()
|
|
290
|
+
wsl = _detect_wsl()
|
|
291
|
+
tools = _detect_tools()
|
|
292
|
+
python_binary = "python3" if tools.get("python3") else "python"
|
|
293
|
+
return {
|
|
294
|
+
"os": platform.system(),
|
|
295
|
+
"os_version": platform.mac_ver()[0] if platform.system() == "Darwin" else platform.version(),
|
|
296
|
+
"chip": chip,
|
|
297
|
+
"cpu": cpu,
|
|
298
|
+
"gpu": gpu,
|
|
299
|
+
"cuda": cuda,
|
|
300
|
+
"wsl": wsl,
|
|
301
|
+
"ram_gb": _detect_ram_gb(),
|
|
302
|
+
"disk_free_gb": _detect_disk_free_gb(),
|
|
303
|
+
"tools": tools,
|
|
304
|
+
"components": {
|
|
305
|
+
"homebrew": _component_detail("homebrew", "brew"),
|
|
306
|
+
"python": {**_component_detail("python", python_binary), "version": platform.python_version()},
|
|
307
|
+
"node": _component_detail("node", "node"),
|
|
308
|
+
"npm": _component_detail("node", "npm"),
|
|
309
|
+
"git": _component_detail("git", "git"),
|
|
310
|
+
"ollama": _component_detail("ollama", "ollama"),
|
|
311
|
+
"lmstudio": _component_detail("lmstudio", "lms"),
|
|
312
|
+
"cuda": {**_component_detail("cuda", "nvcc"), **cuda},
|
|
313
|
+
"tesseract": _component_detail("tesseract", "tesseract"),
|
|
314
|
+
"mlx": _component_detail("mlx", module="mlx"),
|
|
315
|
+
"mlx_vlm": _component_detail("mlx", module="mlx_vlm"),
|
|
316
|
+
},
|
|
317
|
+
"path": {
|
|
318
|
+
"active": os.environ.get("PATH", ""),
|
|
319
|
+
"extra": os.environ.get("LATTICEAI_EXTRA_PATH", ""),
|
|
320
|
+
},
|
|
321
|
+
"mlx": _detect_mlx(),
|
|
322
|
+
"api_keys": _detect_api_keys(),
|
|
323
|
+
}
|