ltcai 11.1.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 +47 -54
- package/docs/CHANGELOG.md +94 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/FEATURE_AUDIT_v11.2.0.md +393 -0
- package/docs/LAYOUT_REBUILD_SPEC.md +9 -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/architecture.md +6 -2
- 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/gates.py +125 -0
- 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/fusion.py +35 -4
- 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} +81 -485
- 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/graph/schema.py +9 -0
- package/lattice_brain/graph/store.py +9 -0
- package/lattice_brain/graph/vector_index/selector.py +32 -2
- 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.py → multimodal/images.py} +22 -262
- 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/sharing.py +714 -0
- package/lattice_brain/runtime/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/sealed_box.py +244 -0
- package/lattice_brain/synthesis.py +24 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/brain_intelligence.py +4 -0
- package/latticeai/api/chat.py +11 -0
- package/latticeai/api/chat_helpers.py +16 -3
- package/latticeai/api/chat_hybrid.py +32 -1
- package/latticeai/api/chronicle.py +63 -0
- package/latticeai/api/features.py +70 -0
- package/latticeai/api/local_files.py +102 -0
- package/latticeai/api/portability.py +39 -4
- package/latticeai/api/review_queue.py +126 -0
- package/latticeai/api/search.py +16 -2
- 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/config.py +4 -1
- package/latticeai/core/context_builder.py +6 -3
- 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 +152 -0
- package/latticeai/core/model_compat.py +73 -2
- 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/model_providers.py +12 -4
- 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/chat_wiring.py +4 -0
- package/latticeai/runtime/feature_toggle_wiring.py +163 -0
- package/latticeai/runtime/router_registration.py +11 -0
- package/latticeai/runtime/runtime_context.py +1 -0
- package/latticeai/services/app_context.py +8 -0
- package/latticeai/services/architecture_readiness.py +48 -19
- package/latticeai/services/automation_intelligence.py +22 -2
- 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/command_center.py +10 -4
- package/latticeai/services/feature_toggles.py +502 -0
- package/latticeai/services/folder_watch.py +122 -1
- package/latticeai/services/hybrid_chat.py +56 -5
- package/latticeai/services/interop_bridges.py +978 -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_capability_registry.py +434 -261
- package/latticeai/services/model_catalog.py +95 -61
- package/latticeai/services/model_recommendation.py +18 -11
- 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/multimodal_ports.py +26 -1
- package/latticeai/services/obsidian_bridge.py +16 -25
- package/latticeai/services/product_readiness.py +15 -7
- package/latticeai/services/search_service.py +149 -2
- package/latticeai/services/tool_dispatch.py +4 -0
- package/latticeai/setup/auto_setup.py +27 -30
- 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 +2 -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 +44 -2
- package/scripts/verify_hf_model_registry.py +253 -218
- 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-CzCsI1mi.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-BXMWIK_2.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-4BvN07ux.js → bot-IM_E_Y12.js} +1 -1
- package/static/app/assets/brain-Ci1CkWjM.js +1 -0
- package/static/app/assets/{button-CDjtnAoU.js → button-COwyqfHM.js} +1 -1
- package/static/app/assets/circle-check-DfInj-qD.js +1 -0
- package/static/app/assets/{circle-pause-D_RMn7tp.js → circle-pause-DEM4A1Y5.js} +1 -1
- package/static/app/assets/{circle-play-B5OpB8ae.js → circle-play-C9djDuLd.js} +1 -1
- package/static/app/assets/{cpu-BIlWInHf.js → cpu-DFdo1gw-.js} +1 -1
- package/static/app/assets/{download-BtjXfL3z.js → download-SnJL6oqk.js} +1 -1
- package/static/app/assets/{folder-open-DefMpxI2.js → folder-open-CqZeDkjE.js} +1 -1
- package/static/app/assets/{hard-drive-BQ8NZVkw.js → hard-drive-j1jJXYYf.js} +1 -1
- package/static/app/assets/{index-vtEfYvQY.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-BqZ5tsgL.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-D5zg_0fY.js → share-2-CVtZ_ewX.js} +1 -1
- package/static/app/assets/{shield-alert-B5pZzkUb.js → shield-alert-CBi2GNWM.js} +1 -1
- package/static/app/assets/{textarea-nEVIweKY.js → textarea-DNMpB5ih.js} +1 -1
- package/static/app/assets/{useFocusTrap-Cm99AHlz.js → useFocusTrap-C83t3GXF.js} +1 -1
- package/static/app/assets/useMutation-DtbJDoyz.js +1 -0
- package/static/app/assets/{useQuery-Dm__N6bL.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 -1377
- package/lattice_brain/portability.py +0 -1210
- package/latticeai/core/agent.py +0 -1412
- 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 -1422
- package/latticeai/services/brain_intelligence.py +0 -967
- package/latticeai/services/memory_service.py +0 -1177
- package/latticeai/services/model_runtime.py +0 -1281
- package/latticeai/setup/wizard.py +0 -1277
- package/static/app/assets/Act-D0HWqtn0.js +0 -1
- package/static/app/assets/AdminConsole-D-QDW-A4.js +0 -1
- package/static/app/assets/BrainHome-Btns-_TA.js +0 -2
- package/static/app/assets/BrainSignals-2dHQNkns.js +0 -1
- package/static/app/assets/Capture-CT8v1StE.js +0 -1
- package/static/app/assets/CommandPalette-DoLXC2KH.js +0 -1
- package/static/app/assets/Library-DDoxFE5c.js +0 -1
- package/static/app/assets/ProductFlow-DOYf7JIs.js +0 -1
- package/static/app/assets/ReviewCard-COQsqidK.js +0 -3
- package/static/app/assets/System-BRllvYXd.js +0 -1
- package/static/app/assets/arrow-left-DnyMzss-.js +0 -1
- package/static/app/assets/brain-uMb_5hnO.js +0 -1
- package/static/app/assets/index-0AvoEBzJ.js +0 -10
- package/static/app/assets/input-B_5ZJ9oy.js +0 -1
- package/static/app/assets/primitives-CVwew78r.js +0 -1
- package/static/app/assets/search-DkhnOKZt.js +0 -1
- package/static/app/assets/utils-DcDMoZIe.js +0 -4
- package/static/app/assets/workspace-LtRRSKTf.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
|
+
}
|