ltcai 4.3.3 → 4.5.1

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.
Files changed (138) hide show
  1. package/README.md +53 -20
  2. package/docs/CHANGELOG.md +122 -0
  3. package/docs/V4_4_0_EXTRACTION_REPORT.md +239 -0
  4. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +49 -0
  5. package/docs/V4_5_0_GRAPH_UX_REPORT.md +34 -0
  6. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +40 -0
  7. package/docs/V4_5_0_ONBOARDING_REPORT.md +31 -0
  8. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +49 -0
  9. package/docs/V4_5_0_VALIDATION_REPORT.md +60 -0
  10. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +33 -0
  11. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +37 -0
  12. package/docs/V4_5_1_NAVIGATION_REPORT.md +37 -0
  13. package/docs/V4_5_1_ONBOARDING_REPORT.md +29 -0
  14. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +61 -0
  15. package/docs/V4_5_1_RC_ARTIFACTS.md +44 -0
  16. package/docs/V4_5_1_UX_REPORT.md +45 -0
  17. package/docs/V4_5_1_VALIDATION_REPORT.md +54 -0
  18. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +30 -0
  19. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +16 -16
  20. package/docs/architecture.md +8 -4
  21. package/frontend/src/App.tsx +152 -91
  22. package/frontend/src/api/client.ts +83 -1
  23. package/frontend/src/components/FirstRunGuide.tsx +99 -0
  24. package/frontend/src/components/primitives.tsx +131 -25
  25. package/frontend/src/components/ui/badge.tsx +2 -2
  26. package/frontend/src/components/ui/button.tsx +7 -7
  27. package/frontend/src/components/ui/card.tsx +5 -5
  28. package/frontend/src/components/ui/input.tsx +1 -1
  29. package/frontend/src/components/ui/textarea.tsx +1 -1
  30. package/frontend/src/pages/Act.tsx +58 -28
  31. package/frontend/src/pages/Ask.tsx +51 -19
  32. package/frontend/src/pages/Brain.tsx +60 -42
  33. package/frontend/src/pages/Capture.tsx +24 -24
  34. package/frontend/src/pages/Library.tsx +222 -32
  35. package/frontend/src/pages/System.tsx +56 -34
  36. package/frontend/src/routes.ts +15 -13
  37. package/frontend/src/store/appStore.ts +8 -1
  38. package/frontend/src/styles.css +666 -36
  39. package/lattice_brain/__init__.py +38 -23
  40. package/lattice_brain/_kg_common.py +11 -1
  41. package/lattice_brain/context.py +212 -2
  42. package/lattice_brain/conversations.py +234 -1
  43. package/lattice_brain/discovery.py +11 -1
  44. package/lattice_brain/documents.py +11 -1
  45. package/lattice_brain/graph/__init__.py +28 -0
  46. package/lattice_brain/graph/_kg_common.py +1123 -0
  47. package/lattice_brain/graph/curator.py +473 -0
  48. package/lattice_brain/graph/discovery.py +1455 -0
  49. package/lattice_brain/graph/documents.py +218 -0
  50. package/lattice_brain/graph/identity.py +175 -0
  51. package/lattice_brain/graph/ingest.py +644 -0
  52. package/lattice_brain/graph/network.py +205 -0
  53. package/lattice_brain/graph/projection.py +571 -0
  54. package/lattice_brain/graph/provenance.py +401 -0
  55. package/lattice_brain/graph/retrieval.py +1341 -0
  56. package/lattice_brain/graph/schema.py +640 -0
  57. package/lattice_brain/graph/store.py +237 -0
  58. package/lattice_brain/graph/write_master.py +225 -0
  59. package/lattice_brain/identity.py +11 -13
  60. package/lattice_brain/ingest.py +11 -1
  61. package/lattice_brain/ingestion.py +318 -0
  62. package/lattice_brain/memory.py +100 -1
  63. package/lattice_brain/network.py +11 -1
  64. package/lattice_brain/portability.py +431 -0
  65. package/lattice_brain/projection.py +11 -1
  66. package/lattice_brain/provenance.py +11 -1
  67. package/lattice_brain/retrieval.py +11 -1
  68. package/lattice_brain/runtime/__init__.py +32 -0
  69. package/lattice_brain/runtime/agent_runtime.py +569 -0
  70. package/lattice_brain/runtime/hooks.py +754 -0
  71. package/lattice_brain/runtime/multi_agent.py +795 -0
  72. package/lattice_brain/schema.py +11 -1
  73. package/lattice_brain/store.py +10 -2
  74. package/lattice_brain/workflow.py +461 -0
  75. package/lattice_brain/write_master.py +11 -1
  76. package/latticeai/__init__.py +1 -1
  77. package/latticeai/api/agents.py +2 -2
  78. package/latticeai/api/browser.py +1 -1
  79. package/latticeai/api/chat.py +1 -1
  80. package/latticeai/api/computer_use.py +1 -1
  81. package/latticeai/api/hooks.py +2 -2
  82. package/latticeai/api/mcp.py +1 -1
  83. package/latticeai/api/models.py +107 -18
  84. package/latticeai/api/tools.py +1 -1
  85. package/latticeai/api/workflow_designer.py +2 -2
  86. package/latticeai/app_factory.py +4 -4
  87. package/latticeai/brain/__init__.py +24 -6
  88. package/latticeai/brain/_kg_common.py +11 -1117
  89. package/latticeai/brain/context.py +12 -208
  90. package/latticeai/brain/conversations.py +12 -231
  91. package/latticeai/brain/discovery.py +13 -1451
  92. package/latticeai/brain/documents.py +13 -214
  93. package/latticeai/brain/identity.py +11 -169
  94. package/latticeai/brain/ingest.py +13 -640
  95. package/latticeai/brain/memory.py +12 -97
  96. package/latticeai/brain/network.py +12 -200
  97. package/latticeai/brain/projection.py +13 -567
  98. package/latticeai/brain/provenance.py +13 -397
  99. package/latticeai/brain/retrieval.py +13 -1337
  100. package/latticeai/brain/schema.py +12 -635
  101. package/latticeai/brain/store.py +13 -233
  102. package/latticeai/brain/write_master.py +13 -221
  103. package/latticeai/core/agent.py +1 -1
  104. package/latticeai/core/agent_registry.py +2 -2
  105. package/latticeai/core/builtin_hooks.py +2 -2
  106. package/latticeai/core/graph_curator.py +6 -468
  107. package/latticeai/core/hooks.py +6 -749
  108. package/latticeai/core/marketplace.py +1 -1
  109. package/latticeai/core/model_compat.py +250 -0
  110. package/latticeai/core/multi_agent.py +6 -790
  111. package/latticeai/core/workflow_engine.py +6 -456
  112. package/latticeai/core/workspace_os.py +1 -1
  113. package/latticeai/models/router.py +136 -32
  114. package/latticeai/services/agent_runtime.py +6 -564
  115. package/latticeai/services/ingestion.py +6 -313
  116. package/latticeai/services/kg_portability.py +6 -426
  117. package/latticeai/services/model_catalog.py +2 -2
  118. package/latticeai/services/model_recommendation.py +8 -1
  119. package/latticeai/services/model_runtime.py +18 -3
  120. package/latticeai/services/platform_runtime.py +3 -3
  121. package/latticeai/services/run_executor.py +1 -1
  122. package/latticeai/services/upload_service.py +1 -1
  123. package/p_reinforce.py +1 -1
  124. package/package.json +1 -1
  125. package/scripts/build_frontend_assets.mjs +12 -1
  126. package/scripts/bump_version.py +1 -1
  127. package/scripts/wheel_smoke.py +7 -0
  128. package/src-tauri/Cargo.lock +1 -1
  129. package/src-tauri/Cargo.toml +1 -1
  130. package/src-tauri/tauri.conf.json +1 -1
  131. package/static/app/asset-manifest.json +5 -5
  132. package/static/app/assets/index-3G8qcrIS.js +336 -0
  133. package/static/app/assets/index-3G8qcrIS.js.map +1 -0
  134. package/static/app/assets/index-C0wYZp7k.css +2 -0
  135. package/static/app/index.html +2 -2
  136. package/static/app/assets/index-CHHal8Zl.css +0 -2
  137. package/static/app/assets/index-pdzil9ac.js +0 -333
  138. package/static/app/assets/index-pdzil9ac.js.map +0 -1
@@ -135,6 +135,8 @@ def create_models_router(
135
135
  loaded_ids: Optional[List[str]] = None,
136
136
  current_id: Optional[str] = None,
137
137
  ) -> List[Dict[str, object]]:
138
+ from latticeai.core.model_compat import model_runtime_compatibility
139
+
138
140
  engine_lookup = {str(engine.get("id") or ""): engine for engine in engines or []}
139
141
  model_lookup: Dict[str, Dict[str, object]] = {}
140
142
  for engine in engines or []:
@@ -147,30 +149,93 @@ def create_models_router(
147
149
  for item in items:
148
150
  short_id = str(item["id"]).lower()
149
151
  aliases = MODEL_ENGINE_ALIASES.get(short_id) or {}
150
- options: List[Dict[str, str]] = []
152
+ options: List[Dict[str, object]] = []
151
153
  for engine_name in ("local_mlx", "ollama", "lmstudio", "llamacpp", "vllm"):
152
154
  real = aliases.get(engine_name)
153
155
  if not real:
154
156
  continue
157
+ load_id = real if engine_name == "local_mlx" else f"{engine_name}:{real}"
158
+ engine_info = engine_lookup.get(engine_name) or {}
159
+ model_info = model_lookup.get(load_id) or model_lookup.get(real) or {}
160
+ option_loaded = load_id in loaded or real in loaded or current_id in {load_id, real}
161
+ option_runtime = model_runtime_compatibility(load_id, engine=engine_name)
162
+ option_supported = option_runtime.get("supported") is not False
163
+ option_pulled = bool(model_info.get("pulled"))
164
+ option_download_required = bool(item.get("pullable", True) and not option_pulled and not option_loaded)
155
165
  options.append({
156
166
  "engine": engine_name,
157
167
  "model_id": real,
158
- "load_id": real if engine_name == "local_mlx" else f"{engine_name}:{real}",
168
+ "load_id": load_id,
169
+ "installed": bool(engine_info.get("installed")),
170
+ "pulled": option_pulled,
171
+ "loaded": option_loaded,
172
+ "download_required": option_download_required,
173
+ "runtime_compatibility": option_runtime,
174
+ "runtime_supported": option_supported,
175
+ "runtime_label": str(option_runtime.get("preferred_runtime") or engine_info.get("name") or engine_name),
159
176
  })
160
177
  if not options:
161
- options.append({"engine": "local_mlx", "model_id": item["id"], "load_id": item["id"]})
162
- recommended_engine = options[0]["engine"]
163
- load_id = options[0]["load_id"]
164
- engine_info = engine_lookup.get(recommended_engine) or {}
165
- model_info = model_lookup.get(load_id) or model_lookup.get(str(item["id"])) or {}
166
- pulled = bool(model_info.get("pulled"))
167
- is_loaded = load_id in loaded or str(item["id"]) in loaded or current_id in {load_id, str(item["id"])}
168
- engine_installed = bool(engine_info.get("installed"))
178
+ raw_id = str(item["id"])
179
+ engine_info = engine_lookup.get("local_mlx") or {}
180
+ model_info = model_lookup.get(raw_id) or {}
181
+ option_loaded = raw_id in loaded or current_id == raw_id
182
+ option_pulled = bool(model_info.get("pulled"))
183
+ runtime_compatibility = model_runtime_compatibility(str(item["id"]), engine="local_mlx")
184
+ options.append({
185
+ "engine": "local_mlx",
186
+ "model_id": item["id"],
187
+ "load_id": item["id"],
188
+ "installed": bool(engine_info.get("installed")),
189
+ "pulled": option_pulled,
190
+ "loaded": option_loaded,
191
+ "download_required": bool(item.get("pullable", True) and not option_pulled and not option_loaded),
192
+ "runtime_compatibility": runtime_compatibility,
193
+ "runtime_supported": runtime_compatibility.get("supported") is not False,
194
+ "runtime_label": str(runtime_compatibility.get("preferred_runtime") or "MLX"),
195
+ })
196
+
197
+ def option_rank(option: Dict[str, object]) -> tuple[int, int, int, int]:
198
+ runtime_supported = bool(option.get("runtime_supported"))
199
+ installed = bool(option.get("installed"))
200
+ loaded_option = bool(option.get("loaded"))
201
+ ready_without_download = installed and not bool(option.get("download_required"))
202
+ return (
203
+ 0 if runtime_supported else 1,
204
+ 0 if loaded_option or ready_without_download else 1,
205
+ 0 if installed else 1,
206
+ ["local_mlx", "ollama", "lmstudio", "llamacpp", "vllm"].index(str(option.get("engine") or "vllm")),
207
+ )
208
+
209
+ primary_option = options[0]
210
+ primary_compatibility = dict(primary_option.get("runtime_compatibility") or {})
211
+ hard_primary_statuses = {
212
+ "runtime_update_needed",
213
+ "unsupported_format",
214
+ "repair_model",
215
+ "incomplete_download",
216
+ }
217
+ selected_option = (
218
+ primary_option
219
+ if primary_compatibility.get("supported") is False
220
+ and primary_compatibility.get("status") in hard_primary_statuses
221
+ else min(options, key=option_rank)
222
+ )
223
+ recommended_engine = str(selected_option["engine"])
224
+ load_id = str(selected_option["load_id"])
225
+ model_info = model_lookup.get(load_id) or model_lookup.get(str(selected_option.get("model_id") or "")) or {}
226
+ pulled = bool(selected_option.get("pulled") or model_info.get("pulled"))
227
+ is_loaded = bool(selected_option.get("loaded"))
228
+ engine_installed = bool(selected_option.get("installed"))
169
229
  pullable = bool(item.get("pullable", True))
170
- download_required = bool(pullable and not pulled and not is_loaded)
230
+ runtime_compatibility = dict(selected_option.get("runtime_compatibility") or {})
231
+ runtime_supported = runtime_compatibility.get("supported") is not False
232
+ download_required = bool(selected_option.get("download_required") and pullable and not is_loaded)
171
233
  if is_loaded:
172
234
  load_status = "loaded"
173
235
  unavailable_reason = None
236
+ elif not runtime_supported:
237
+ load_status = str(runtime_compatibility.get("status") or "unsupported")
238
+ unavailable_reason = str(runtime_compatibility.get("user_message") or "This model is not supported by the installed runtime.")
174
239
  elif not engine_installed:
175
240
  load_status = "unavailable"
176
241
  unavailable_reason = f"{engine_info.get('name') or recommended_engine} runtime is not installed."
@@ -196,9 +261,13 @@ def create_models_router(
196
261
  "source_display_order": item.get("source_display_order"),
197
262
  "pulled": pulled,
198
263
  "download_required": download_required,
199
- "load_available": is_loaded or (engine_installed and not download_required),
264
+ "load_available": is_loaded or (runtime_supported and engine_installed and not download_required),
200
265
  "load_status": load_status,
201
266
  "unavailable_reason": unavailable_reason,
267
+ "runtime_compatibility": runtime_compatibility,
268
+ "recovery_guidance": runtime_compatibility.get("recovery_guidance") or [],
269
+ "alternative_recommendations": runtime_compatibility.get("alternatives") or [],
270
+ "runtime_label": selected_option.get("runtime_label"),
202
271
  }
203
272
  base["engine_options"] = options
204
273
  base["recommended_engine"] = recommended_engine
@@ -272,10 +341,20 @@ def create_models_router(
272
341
  @router.post("/engines/prepare-model")
273
342
  async def engines_prepare_model(req: PrepareModelRequest, request: Request):
274
343
  require_user(request)
275
- return await prepare_and_load_model(
276
- req.model, request, engine=req.engine, user_email=req.user_email,
277
- allow_download=req.allow_download,
278
- )
344
+ try:
345
+ return await prepare_and_load_model(
346
+ req.model, request, engine=req.engine, user_email=req.user_email,
347
+ allow_download=req.allow_download,
348
+ )
349
+ except HTTPException:
350
+ raise
351
+ except Exception as exc:
352
+ from latticeai.core.model_compat import friendly_model_runtime_error
353
+
354
+ raise HTTPException(
355
+ status_code=500,
356
+ detail=friendly_model_runtime_error(exc, model_id=req.model, engine=req.engine),
357
+ )
279
358
 
280
359
  @router.post("/engines/prepare-model/stream")
281
360
  async def engines_prepare_model_stream(req: PrepareModelRequest, request: Request):
@@ -295,9 +374,11 @@ def create_models_router(
295
374
  })
296
375
  except Exception as exc:
297
376
  logging.exception("model prepare stream failed")
377
+ from latticeai.core.model_compat import friendly_model_runtime_error
378
+
298
379
  yield sse_event("error", {
299
380
  "status_code": 500,
300
- "detail": str(exc)[-1000:] or "모델 준비에 실패했습니다.",
381
+ "detail": friendly_model_runtime_error(exc, model_id=req.model, engine=req.engine),
301
382
  })
302
383
 
303
384
  return StreamingResponse(
@@ -356,6 +437,8 @@ def create_models_router(
356
437
  @router.post("/models/load")
357
438
  async def load_model(req: LoadModelRequest, request: Request):
358
439
  try:
440
+ from latticeai.core.model_compat import friendly_model_runtime_error, model_runtime_compatibility
441
+
359
442
  model_id = req.model_id
360
443
  requested_engine = req.engine or (model_id.split(":", 1)[0] if ":" in model_id else "local_mlx")
361
444
  if IS_PUBLIC_MODE and not ALLOW_LOCAL_MODELS and requested_engine in {"local_mlx", "mlx"}:
@@ -363,6 +446,9 @@ def create_models_router(
363
446
  status_code=400,
364
447
  detail="Public mode blocks local MLX model loading. Use openai:, openrouter:, groq:, together:, or set LATTICEAI_ALLOW_LOCAL_MODELS=true.",
365
448
  )
449
+ compatibility = model_runtime_compatibility(model_id, engine=requested_engine)
450
+ if compatibility.get("supported") is False:
451
+ raise HTTPException(status_code=400, detail=compatibility)
366
452
  return await prepare_and_load_model(
367
453
  model_id, request, engine=req.engine, user_email=req.user_email,
368
454
  adapter_path=req.adapter_path, draft_model_id=req.draft_model_id,
@@ -371,7 +457,10 @@ def create_models_router(
371
457
  except HTTPException:
372
458
  raise
373
459
  except Exception as e:
374
- raise HTTPException(status_code=500, detail=str(e))
460
+ raise HTTPException(
461
+ status_code=500,
462
+ detail=friendly_model_runtime_error(e, model_id=req.model_id, engine=req.engine),
463
+ )
375
464
 
376
465
  @router.post("/models/switch/{model_id:path}")
377
466
  async def switch_model(model_id: str, request: Request):
@@ -15,7 +15,7 @@ from pydantic import BaseModel
15
15
 
16
16
  from latticeai.api.computer_use import create_computer_use_router
17
17
  from latticeai.api.local_files import create_local_files_router
18
- from latticeai.core.hooks import dispatch_tool
18
+ from lattice_brain.runtime.hooks import dispatch_tool
19
19
  from latticeai.api.mcp import create_mcp_router
20
20
  from latticeai.api.permissions import create_permissions_router
21
21
  from latticeai.services.upload_service import process_uploaded_document
@@ -1,7 +1,7 @@
1
1
  """Workflow Designer API router (v2).
2
2
 
3
3
  Create / edit / validate / execute / inspect / export / import workflows plus
4
- run history, layered on :mod:`latticeai.core.workflow_engine` and the existing
4
+ run history, layered on :mod:`lattice_brain.workflow` and the existing
5
5
  ``WorkspaceOSStore`` workflow persistence (so pre-2.0 workflow history is
6
6
  preserved). Paths are namespaced under ``/workflows`` to avoid colliding with
7
7
  ``/workspace/workflows``.
@@ -67,7 +67,7 @@ def create_workflow_designer_router(
67
67
  run_executor: Any = None,
68
68
  trigger_service: Any = None,
69
69
  ) -> APIRouter:
70
- from latticeai.core.workflow_engine import (
70
+ from lattice_brain.workflow import (
71
71
  WorkflowEngine,
72
72
  validate_definition,
73
73
  export_workflow,
@@ -110,7 +110,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
110
110
  from latticeai.services.chat_service import ChatService
111
111
  from latticeai.services.search_service import SearchService
112
112
  from latticeai.core.embedding_providers import resolve_embedder, resolve_embedding_profile
113
- from latticeai.services.agent_runtime import AgentRuntime
113
+ from lattice_brain.runtime.agent_runtime import AgentRuntime
114
114
  from latticeai.services.model_runtime import (
115
115
  CLOUD_VERIFY_TTL_SECONDS,
116
116
  ENGINE_MODEL_CATALOG,
@@ -152,7 +152,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
152
152
  from latticeai.api.garden import create_garden_router
153
153
  from latticeai.api.setup import create_setup_router
154
154
  from latticeai.api.hooks import create_hooks_router
155
- from latticeai.core.hooks import HooksRegistry
155
+ from lattice_brain.runtime.hooks import HooksRegistry
156
156
  from latticeai.core.builtin_hooks import register_builtin_hook_runners
157
157
  from latticeai.core.product_hardening import build_product_hardening_status
158
158
  from latticeai.api.agent_registry import create_agent_registry_router
@@ -161,7 +161,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
161
161
  from latticeai.api.browser import create_browser_router
162
162
  from latticeai.api.portability import create_portability_router
163
163
  from latticeai.services.memory_service import MemoryService
164
- from latticeai.services.ingestion import IngestionItem, IngestionPipeline
164
+ from lattice_brain.ingestion import IngestionItem, IngestionPipeline
165
165
  from lattice_brain import BrainCore, ConversationStore
166
166
  from lattice_brain.storage import storage_from_env
167
167
  from lattice_brain.context import ContextAssembler
@@ -169,7 +169,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
169
169
  from lattice_brain.identity import DeviceIdentity
170
170
  from lattice_brain.network import BrainNetwork
171
171
  from latticeai.api.network import create_network_router
172
- from latticeai.services.kg_portability import KGPortabilityService
172
+ from lattice_brain.portability import KGPortabilityService
173
173
  # The aliased names below look unused but are part of the legacy
174
174
  # ``server_app`` attribute surface: every local is exported via
175
175
  # ``dict(locals())`` and reached through ``server_app.__getattr__``
@@ -1,10 +1,28 @@
1
- """Compatibility namespace for the standalone :mod:`lattice_brain` package."""
1
+ """Deprecated compatibility namespace for the standalone :mod:`lattice_brain` package.
2
2
 
3
- from lattice_brain.core import BrainCore, BrainCoreConfig
4
- from latticeai.brain.context import AssembledContext, ContextAssembler, ContextSection
5
- from latticeai.brain.conversations import ConversationStore
6
- from latticeai.brain.memory import BrainMemory
7
- from latticeai.brain.store import KnowledgeGraphStore
3
+ The Brain Core implementation physically lives in ``lattice_brain`` as of
4
+ v4.4.0. This package only re-exports the public surface for older imports;
5
+ new code must import ``lattice_brain`` directly.
6
+ """
7
+
8
+ import warnings
9
+
10
+ from lattice_brain import (
11
+ AssembledContext,
12
+ BrainCore,
13
+ BrainCoreConfig,
14
+ BrainMemory,
15
+ ContextAssembler,
16
+ ContextSection,
17
+ ConversationStore,
18
+ KnowledgeGraphStore,
19
+ )
20
+
21
+ warnings.warn(
22
+ "latticeai.brain is deprecated; import lattice_brain instead",
23
+ DeprecationWarning,
24
+ stacklevel=2,
25
+ )
8
26
 
9
27
  __all__ = [
10
28
  "AssembledContext",