amd-gaia 0.15.2__py3-none-any.whl → 0.15.3__py3-none-any.whl
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.
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/METADATA +2 -1
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/RECORD +17 -15
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/WHEEL +1 -1
- gaia/agents/base/agent.py +272 -23
- gaia/agents/base/console.py +208 -9
- gaia/agents/tools/__init__.py +11 -0
- gaia/agents/tools/file_tools.py +715 -0
- gaia/cli.py +242 -2
- gaia/installer/init_command.py +433 -103
- gaia/installer/lemonade_installer.py +62 -3
- gaia/llm/lemonade_client.py +143 -0
- gaia/llm/lemonade_manager.py +55 -11
- gaia/llm/providers/lemonade.py +9 -0
- gaia/version.py +2 -2
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/entry_points.txt +0 -0
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/licenses/LICENSE.md +0 -0
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/top_level.txt +0 -0
gaia/installer/init_command.py
CHANGED
|
@@ -14,13 +14,11 @@ Main entry point for `gaia init` command that:
|
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
16
|
import os
|
|
17
|
+
import subprocess
|
|
17
18
|
import sys
|
|
18
|
-
import time
|
|
19
19
|
from dataclasses import dataclass
|
|
20
20
|
from typing import Callable, Optional
|
|
21
21
|
|
|
22
|
-
import requests
|
|
23
|
-
|
|
24
22
|
# Rich imports for better CLI formatting
|
|
25
23
|
try:
|
|
26
24
|
from rich.console import Console
|
|
@@ -44,30 +42,52 @@ INIT_PROFILES = {
|
|
|
44
42
|
"agent": "minimal",
|
|
45
43
|
"models": ["Qwen3-4B-Instruct-2507-GGUF"], # Override default minimal model
|
|
46
44
|
"approx_size": "~2.5 GB",
|
|
45
|
+
"min_lemonade_version": "9.0.4",
|
|
46
|
+
"min_context_size": 4096,
|
|
47
|
+
},
|
|
48
|
+
"sd": {
|
|
49
|
+
"description": "Image generation with multi-modal AI (LLM + SD + VLM)",
|
|
50
|
+
"agent": "sd",
|
|
51
|
+
"models": [
|
|
52
|
+
"SDXL-Turbo", # Image generation (6.5GB)
|
|
53
|
+
"Qwen3-8B-GGUF", # Agentic reasoning + prompt enhancement (5.0GB)
|
|
54
|
+
"Qwen3-VL-4B-Instruct-GGUF", # Vision analysis + stories (3.2GB)
|
|
55
|
+
],
|
|
56
|
+
"approx_size": "~15 GB",
|
|
57
|
+
"min_lemonade_version": "9.2.0", # SDXL-Turbo requires v9.2.0+
|
|
58
|
+
"min_context_size": 16384, # SD agent needs 16K for multi-step planning
|
|
47
59
|
},
|
|
48
60
|
"chat": {
|
|
49
61
|
"description": "Interactive chat with RAG and vision support",
|
|
50
62
|
"agent": "chat",
|
|
51
63
|
"models": None, # Use agent profile defaults
|
|
52
64
|
"approx_size": "~25 GB",
|
|
65
|
+
"min_lemonade_version": "9.0.4",
|
|
66
|
+
"min_context_size": 32768,
|
|
53
67
|
},
|
|
54
68
|
"code": {
|
|
55
69
|
"description": "Autonomous coding assistant",
|
|
56
70
|
"agent": "code",
|
|
57
71
|
"models": None,
|
|
58
72
|
"approx_size": "~18 GB",
|
|
73
|
+
"min_lemonade_version": "9.0.4",
|
|
74
|
+
"min_context_size": 32768,
|
|
59
75
|
},
|
|
60
76
|
"rag": {
|
|
61
77
|
"description": "Document Q&A with retrieval",
|
|
62
78
|
"agent": "rag",
|
|
63
79
|
"models": None,
|
|
64
80
|
"approx_size": "~25 GB",
|
|
81
|
+
"min_lemonade_version": "9.0.4",
|
|
82
|
+
"min_context_size": 32768,
|
|
65
83
|
},
|
|
66
84
|
"all": {
|
|
67
85
|
"description": "All models for all agents",
|
|
68
86
|
"agent": "all",
|
|
69
87
|
"models": None,
|
|
70
88
|
"approx_size": "~26 GB",
|
|
89
|
+
"min_lemonade_version": "9.2.0", # Includes SD, so needs v9.2.0+
|
|
90
|
+
"min_context_size": 32768, # Max requirement across all agents
|
|
71
91
|
},
|
|
72
92
|
}
|
|
73
93
|
|
|
@@ -97,6 +117,7 @@ class InitCommand:
|
|
|
97
117
|
self,
|
|
98
118
|
profile: str = "chat",
|
|
99
119
|
skip_models: bool = False,
|
|
120
|
+
skip_lemonade: bool = False,
|
|
100
121
|
force_reinstall: bool = False,
|
|
101
122
|
force_models: bool = False,
|
|
102
123
|
yes: bool = False,
|
|
@@ -110,6 +131,7 @@ class InitCommand:
|
|
|
110
131
|
Args:
|
|
111
132
|
profile: Profile to initialize (minimal, chat, code, rag, all)
|
|
112
133
|
skip_models: Skip model downloads
|
|
134
|
+
skip_lemonade: Skip Lemonade installation check (for CI)
|
|
113
135
|
force_reinstall: Force reinstall even if compatible version exists
|
|
114
136
|
force_models: Force re-download models even if already available
|
|
115
137
|
yes: Skip confirmation prompts
|
|
@@ -119,6 +141,7 @@ class InitCommand:
|
|
|
119
141
|
"""
|
|
120
142
|
self.profile = profile.lower()
|
|
121
143
|
self.skip_models = skip_models
|
|
144
|
+
self.skip_lemonade = skip_lemonade
|
|
122
145
|
self.force_reinstall = force_reinstall
|
|
123
146
|
self.force_models = force_models
|
|
124
147
|
self.yes = yes
|
|
@@ -134,11 +157,12 @@ class InitCommand:
|
|
|
134
157
|
# Initialize Rich console if available (before installer for console pass-through)
|
|
135
158
|
self.console = Console() if RICH_AVAILABLE else None
|
|
136
159
|
|
|
137
|
-
# Initialize AgentConsole for
|
|
160
|
+
# Initialize AgentConsole for formatted output
|
|
138
161
|
self.agent_console = AgentConsole()
|
|
139
162
|
|
|
140
|
-
# Use minimal installer for minimal profile
|
|
141
|
-
|
|
163
|
+
# Use minimal installer for minimal profile OR when using --yes (silent mode)
|
|
164
|
+
# Minimal installer is faster and more reliable for CI
|
|
165
|
+
use_minimal = self.profile == "minimal" or yes
|
|
142
166
|
|
|
143
167
|
self.installer = LemonadeInstaller(
|
|
144
168
|
target_version=LEMONADE_VERSION,
|
|
@@ -147,6 +171,10 @@ class InitCommand:
|
|
|
147
171
|
console=self.console,
|
|
148
172
|
)
|
|
149
173
|
|
|
174
|
+
# Context verification state (set during model loading)
|
|
175
|
+
self._ctx_verified = None
|
|
176
|
+
self._ctx_warning = None
|
|
177
|
+
|
|
150
178
|
def _print(self, message: str, end: str = "\n"):
|
|
151
179
|
"""Print message to stdout."""
|
|
152
180
|
if RICH_AVAILABLE and self.console:
|
|
@@ -310,12 +338,24 @@ class InitCommand:
|
|
|
310
338
|
total_steps = 4 if not self.skip_models else 3
|
|
311
339
|
|
|
312
340
|
try:
|
|
313
|
-
# Step 1: Check/Install Lemonade (skip for remote servers)
|
|
341
|
+
# Step 1: Check/Install Lemonade (skip for remote servers or CI)
|
|
314
342
|
if self.remote:
|
|
315
343
|
self._print_step(
|
|
316
344
|
1, total_steps, "Skipping local Lemonade check (remote mode)..."
|
|
317
345
|
)
|
|
318
346
|
self._print_success("Using remote Lemonade Server")
|
|
347
|
+
elif self.skip_lemonade:
|
|
348
|
+
self._print_step(
|
|
349
|
+
1, total_steps, "Skipping Lemonade installation check..."
|
|
350
|
+
)
|
|
351
|
+
# Still show version info for transparency
|
|
352
|
+
info = self.installer.check_installation()
|
|
353
|
+
if info.installed and info.version:
|
|
354
|
+
self._print_success(
|
|
355
|
+
f"Using pre-installed Lemonade Server v{info.version}"
|
|
356
|
+
)
|
|
357
|
+
else:
|
|
358
|
+
self._print_success("Using pre-installed Lemonade Server")
|
|
319
359
|
else:
|
|
320
360
|
self._print_step(
|
|
321
361
|
1, total_steps, "Checking Lemonade Server installation..."
|
|
@@ -499,10 +539,44 @@ class InitCommand:
|
|
|
499
539
|
self._print(" This may cause compatibility issues.")
|
|
500
540
|
self._print("")
|
|
501
541
|
|
|
542
|
+
# Check if upgrade is required based on profile's minimum version
|
|
543
|
+
profile_config = INIT_PROFILES[self.profile]
|
|
544
|
+
min_version_required = profile_config.get("min_lemonade_version", "9.0.0")
|
|
545
|
+
from packaging import version as pkg_version
|
|
546
|
+
|
|
547
|
+
needs_upgrade = pkg_version.parse(current_ver) < pkg_version.parse(
|
|
548
|
+
min_version_required
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# In CI mode (--yes), auto-upgrade if needed for this profile
|
|
552
|
+
if self.yes and not self.force_reinstall:
|
|
553
|
+
if needs_upgrade:
|
|
554
|
+
self._print("")
|
|
555
|
+
if RICH_AVAILABLE and self.console:
|
|
556
|
+
self.console.print(
|
|
557
|
+
f" [yellow]⚠️ Profile '{self.profile}' requires Lemonade v{min_version_required}+[/yellow]"
|
|
558
|
+
)
|
|
559
|
+
self.console.print(
|
|
560
|
+
f" [bold cyan]Upgrading:[/bold cyan] v{current_ver} → v{target_ver}"
|
|
561
|
+
)
|
|
562
|
+
else:
|
|
563
|
+
self._print_warning(
|
|
564
|
+
f"Profile '{self.profile}' requires Lemonade v{min_version_required}+"
|
|
565
|
+
)
|
|
566
|
+
self._print(
|
|
567
|
+
f" Upgrading from v{current_ver} to v{target_ver}..."
|
|
568
|
+
)
|
|
569
|
+
return self._upgrade_lemonade(current_ver)
|
|
570
|
+
else:
|
|
571
|
+
self._print_success(
|
|
572
|
+
f"Version v{current_ver} is sufficient for profile '{self.profile}'"
|
|
573
|
+
)
|
|
574
|
+
return True
|
|
575
|
+
|
|
502
576
|
# Prompt user to upgrade
|
|
503
577
|
if not self._prompt_yes_no(
|
|
504
578
|
f"Upgrade to v{target_ver}? (will uninstall current version)",
|
|
505
|
-
default=
|
|
579
|
+
default=False, # Default to no for safety
|
|
506
580
|
):
|
|
507
581
|
self._print_warning("Continuing with current version")
|
|
508
582
|
return True
|
|
@@ -565,14 +639,15 @@ class InitCommand:
|
|
|
565
639
|
self._print("")
|
|
566
640
|
self._print_success("Download complete")
|
|
567
641
|
|
|
568
|
-
# Install (
|
|
642
|
+
# Install (silent in CI with --yes, interactive otherwise for desktop icon)
|
|
569
643
|
self.console.print(" [bold]Installing...[/bold]")
|
|
570
|
-
self.
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
644
|
+
if not self.yes:
|
|
645
|
+
self.console.print()
|
|
646
|
+
self.console.print(
|
|
647
|
+
" [yellow]⚠️ The installer window will appear - please complete the installation[/yellow]"
|
|
648
|
+
)
|
|
649
|
+
self.console.print()
|
|
650
|
+
result = self.installer.install(installer_path, silent=self.yes)
|
|
576
651
|
|
|
577
652
|
if result.success:
|
|
578
653
|
self._print_success(f"Installed Lemonade v{result.version}")
|
|
@@ -722,18 +797,81 @@ class InitCommand:
|
|
|
722
797
|
)
|
|
723
798
|
return False
|
|
724
799
|
|
|
725
|
-
# Server not running -
|
|
726
|
-
self._print_error("Lemonade Server is not running")
|
|
727
|
-
|
|
728
|
-
# In non-interactive mode (-y), fail immediately
|
|
800
|
+
# Server not running - start it automatically in CI mode, or prompt user
|
|
729
801
|
if self.yes:
|
|
802
|
+
# In CI mode, just inform and auto-start (not an error)
|
|
803
|
+
self._print(" Lemonade Server is not running")
|
|
730
804
|
self.console.print()
|
|
731
805
|
self.console.print(
|
|
732
|
-
" [dim]
|
|
806
|
+
" [dim]Auto-starting Lemonade Server (CI mode)...[/dim]"
|
|
733
807
|
)
|
|
734
|
-
return False
|
|
735
808
|
|
|
736
|
-
|
|
809
|
+
try:
|
|
810
|
+
# Find lemonade-server executable
|
|
811
|
+
import shutil
|
|
812
|
+
|
|
813
|
+
# Check env var first (set by install-lemonade action in CI)
|
|
814
|
+
lemonade_path = os.environ.get("LEMONADE_SERVER_PATH")
|
|
815
|
+
if not lemonade_path:
|
|
816
|
+
# Fall back to PATH search
|
|
817
|
+
lemonade_path = shutil.which("lemonade-server")
|
|
818
|
+
|
|
819
|
+
if not lemonade_path:
|
|
820
|
+
raise FileNotFoundError("lemonade-server not found in PATH")
|
|
821
|
+
|
|
822
|
+
# Start server in background
|
|
823
|
+
if sys.platform == "win32":
|
|
824
|
+
# Windows: use subprocess.Popen with no window
|
|
825
|
+
subprocess.Popen(
|
|
826
|
+
[lemonade_path, "serve", "--no-tray"],
|
|
827
|
+
stdout=subprocess.DEVNULL,
|
|
828
|
+
stderr=subprocess.DEVNULL,
|
|
829
|
+
creationflags=(
|
|
830
|
+
subprocess.CREATE_NO_WINDOW
|
|
831
|
+
if hasattr(subprocess, "CREATE_NO_WINDOW")
|
|
832
|
+
else 0
|
|
833
|
+
),
|
|
834
|
+
)
|
|
835
|
+
else:
|
|
836
|
+
# Linux/Mac: background process
|
|
837
|
+
subprocess.Popen(
|
|
838
|
+
[lemonade_path, "serve"],
|
|
839
|
+
stdout=subprocess.DEVNULL,
|
|
840
|
+
stderr=subprocess.DEVNULL,
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
# Wait for server to start
|
|
844
|
+
import time
|
|
845
|
+
|
|
846
|
+
max_wait = 30
|
|
847
|
+
waited = 0
|
|
848
|
+
while waited < max_wait:
|
|
849
|
+
time.sleep(2)
|
|
850
|
+
waited += 2
|
|
851
|
+
try:
|
|
852
|
+
health = client.health_check()
|
|
853
|
+
if (
|
|
854
|
+
health
|
|
855
|
+
and isinstance(health, dict)
|
|
856
|
+
and health.get("status") == "ok"
|
|
857
|
+
):
|
|
858
|
+
self._print_success(
|
|
859
|
+
f"Server started and ready (waited {waited}s)"
|
|
860
|
+
)
|
|
861
|
+
return True
|
|
862
|
+
except Exception:
|
|
863
|
+
pass
|
|
864
|
+
|
|
865
|
+
self._print_error(f"Server failed to start after {max_wait}s")
|
|
866
|
+
return False
|
|
867
|
+
|
|
868
|
+
except Exception as e:
|
|
869
|
+
self._print_error(f"Failed to start server: {e}")
|
|
870
|
+
return False
|
|
871
|
+
else:
|
|
872
|
+
# Interactive mode - prompt user to start manually
|
|
873
|
+
self._print_error("Lemonade Server is not running")
|
|
874
|
+
self.console.print()
|
|
737
875
|
self.console.print(" [bold]Please start Lemonade Server:[/bold]")
|
|
738
876
|
if sys.platform == "win32":
|
|
739
877
|
self.console.print(
|
|
@@ -841,11 +979,13 @@ class InitCommand:
|
|
|
841
979
|
# Use agent profile defaults
|
|
842
980
|
model_ids = client.get_required_models(agent)
|
|
843
981
|
|
|
844
|
-
#
|
|
845
|
-
|
|
982
|
+
# Include default CPU model for profiles that need gaia llm
|
|
983
|
+
# SD profile has its own LLM (Qwen3-8B) and doesn't need the 0.5B model
|
|
984
|
+
if self.profile != "sd":
|
|
985
|
+
from gaia.llm.lemonade_client import DEFAULT_MODEL_NAME
|
|
846
986
|
|
|
847
|
-
|
|
848
|
-
|
|
987
|
+
if DEFAULT_MODEL_NAME not in model_ids:
|
|
988
|
+
model_ids = list(model_ids) + [DEFAULT_MODEL_NAME]
|
|
849
989
|
|
|
850
990
|
if not model_ids:
|
|
851
991
|
self._print_success("No models required for this profile")
|
|
@@ -886,63 +1026,96 @@ class InitCommand:
|
|
|
886
1026
|
except Exception as e:
|
|
887
1027
|
self._print_error(f"Failed to delete {model_id}: {e}")
|
|
888
1028
|
|
|
889
|
-
#
|
|
1029
|
+
# Find lemonade-server executable
|
|
1030
|
+
lemonade_path = self._find_lemonade_server()
|
|
1031
|
+
if not lemonade_path:
|
|
1032
|
+
self._print_error("Could not find lemonade-server executable")
|
|
1033
|
+
self._print(
|
|
1034
|
+
" Please ensure Lemonade Server is installed and in your PATH"
|
|
1035
|
+
)
|
|
1036
|
+
return False
|
|
1037
|
+
|
|
1038
|
+
# Download each model using CLI
|
|
890
1039
|
success = True
|
|
891
1040
|
for model_id in model_ids:
|
|
892
1041
|
self._print("")
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1042
|
+
self.agent_console.print(
|
|
1043
|
+
f" [bold cyan]Downloading:[/bold cyan] {model_id}"
|
|
1044
|
+
)
|
|
896
1045
|
|
|
897
1046
|
try:
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
event_count += 1
|
|
904
|
-
event_type = event.get("event")
|
|
905
|
-
|
|
906
|
-
if event_type == "progress":
|
|
907
|
-
# Skip first 2 spurious events from Lemonade
|
|
908
|
-
if event_count <= 2:
|
|
909
|
-
continue
|
|
910
|
-
|
|
911
|
-
# Calculate download speed
|
|
912
|
-
current_bytes = event.get("bytes_downloaded", 0)
|
|
913
|
-
current_time = time.time()
|
|
914
|
-
time_delta = current_time - last_time
|
|
915
|
-
|
|
916
|
-
speed_mbps = 0.0
|
|
917
|
-
if time_delta > 0.1 and current_bytes > last_bytes:
|
|
918
|
-
bytes_delta = current_bytes - last_bytes
|
|
919
|
-
speed_mbps = (bytes_delta / time_delta) / (1024 * 1024)
|
|
920
|
-
last_bytes = current_bytes
|
|
921
|
-
last_time = current_time
|
|
922
|
-
|
|
923
|
-
self.agent_console.print_download_progress(
|
|
924
|
-
percent=event.get("percent", 0),
|
|
925
|
-
bytes_downloaded=current_bytes,
|
|
926
|
-
bytes_total=event.get("bytes_total", 0),
|
|
927
|
-
speed_mbps=speed_mbps,
|
|
928
|
-
)
|
|
1047
|
+
# Use lemonade-server CLI pull command with visible progress
|
|
1048
|
+
result = subprocess.run(
|
|
1049
|
+
[lemonade_path, "pull", model_id],
|
|
1050
|
+
check=False,
|
|
1051
|
+
)
|
|
929
1052
|
|
|
930
|
-
|
|
931
|
-
|
|
1053
|
+
self._print("")
|
|
1054
|
+
if result.returncode == 0:
|
|
1055
|
+
# Verify the model was actually downloaded successfully
|
|
1056
|
+
# Check if model is now available (not just exit code)
|
|
1057
|
+
if client.check_model_available(model_id):
|
|
1058
|
+
self._print_success(f"Downloaded {model_id}")
|
|
1059
|
+
else:
|
|
1060
|
+
# Pull succeeded but model not available - likely validation error
|
|
1061
|
+
self._print_error(
|
|
1062
|
+
f"Download validation failed for {model_id}"
|
|
1063
|
+
)
|
|
1064
|
+
self.agent_console.print(
|
|
1065
|
+
" [yellow]Corrupted download detected. Deleting and retrying...[/yellow]"
|
|
1066
|
+
)
|
|
932
1067
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1068
|
+
# Delete the corrupted model
|
|
1069
|
+
delete_result = subprocess.run(
|
|
1070
|
+
[lemonade_path, "delete", model_id],
|
|
1071
|
+
check=False,
|
|
1072
|
+
capture_output=True,
|
|
936
1073
|
)
|
|
937
|
-
success = False
|
|
938
|
-
break
|
|
939
1074
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1075
|
+
if delete_result.returncode == 0:
|
|
1076
|
+
self.agent_console.print(
|
|
1077
|
+
f" [dim]Deleted corrupted {model_id}[/dim]"
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
# Retry download
|
|
1081
|
+
self.agent_console.print(
|
|
1082
|
+
f" [bold cyan]Retrying download:[/bold cyan] {model_id}"
|
|
1083
|
+
)
|
|
1084
|
+
retry_result = subprocess.run(
|
|
1085
|
+
[lemonade_path, "pull", model_id],
|
|
1086
|
+
check=False,
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
self._print("")
|
|
1090
|
+
if (
|
|
1091
|
+
retry_result.returncode == 0
|
|
1092
|
+
and client.check_model_available(model_id)
|
|
1093
|
+
):
|
|
1094
|
+
self._print_success(
|
|
1095
|
+
f"Downloaded {model_id} (retry successful)"
|
|
1096
|
+
)
|
|
1097
|
+
else:
|
|
1098
|
+
self._print_error(f"Retry failed for {model_id}")
|
|
1099
|
+
success = False
|
|
1100
|
+
else:
|
|
1101
|
+
self._print_error(
|
|
1102
|
+
f"Failed to delete corrupted model {model_id}"
|
|
1103
|
+
)
|
|
1104
|
+
success = False
|
|
1105
|
+
else:
|
|
1106
|
+
self._print_error(
|
|
1107
|
+
f"Failed to download {model_id} (exit code: {result.returncode})"
|
|
1108
|
+
)
|
|
1109
|
+
success = False
|
|
1110
|
+
|
|
1111
|
+
except FileNotFoundError:
|
|
1112
|
+
self._print("")
|
|
1113
|
+
self._print_error(f"lemonade-server not found at: {lemonade_path}")
|
|
943
1114
|
success = False
|
|
1115
|
+
break
|
|
944
1116
|
except Exception as e:
|
|
945
|
-
self.
|
|
1117
|
+
self._print("")
|
|
1118
|
+
self._print_error(f"Error downloading {model_id}: {e}")
|
|
946
1119
|
success = False
|
|
947
1120
|
|
|
948
1121
|
return success
|
|
@@ -963,13 +1136,92 @@ class InitCommand:
|
|
|
963
1136
|
Tuple of (success: bool, error_message: str or None)
|
|
964
1137
|
"""
|
|
965
1138
|
try:
|
|
966
|
-
#
|
|
967
|
-
|
|
1139
|
+
# Check if profile requires specific context size for this model
|
|
1140
|
+
profile_config = INIT_PROFILES.get(self.profile, {})
|
|
1141
|
+
min_ctx = profile_config.get("min_context_size")
|
|
1142
|
+
|
|
1143
|
+
# Load the model (with context size if required)
|
|
1144
|
+
is_llm = not (
|
|
1145
|
+
"embed" in model_id.lower()
|
|
1146
|
+
or any(sd in model_id.upper() for sd in ["SDXL", "SD-", "SD1", "SD2"])
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
if is_llm and min_ctx:
|
|
1150
|
+
# Force unload if already loaded to ensure recipe_options are saved
|
|
1151
|
+
if client.check_model_loaded(model_id):
|
|
1152
|
+
client.unload_model()
|
|
1153
|
+
|
|
1154
|
+
# Load with explicit context size and save it
|
|
1155
|
+
client.load_model(
|
|
1156
|
+
model_id,
|
|
1157
|
+
auto_download=False,
|
|
1158
|
+
prompt=False,
|
|
1159
|
+
ctx_size=min_ctx,
|
|
1160
|
+
save_options=True,
|
|
1161
|
+
)
|
|
1162
|
+
|
|
1163
|
+
# Verify context size was set correctly by reading it back
|
|
1164
|
+
try:
|
|
1165
|
+
# Get full model list with recipe_options
|
|
1166
|
+
models_list = client.list_models()
|
|
1167
|
+
model_info = next(
|
|
1168
|
+
(
|
|
1169
|
+
m
|
|
1170
|
+
for m in models_list.get("data", [])
|
|
1171
|
+
if m.get("id") == model_id
|
|
1172
|
+
),
|
|
1173
|
+
None,
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
if not model_info:
|
|
1177
|
+
return (False, "Model info not found")
|
|
1178
|
+
|
|
1179
|
+
actual_ctx = model_info.get("recipe_options", {}).get("ctx_size")
|
|
968
1180
|
|
|
969
|
-
|
|
1181
|
+
if actual_ctx and actual_ctx >= min_ctx:
|
|
1182
|
+
# Success - context verified
|
|
1183
|
+
# Store for success message, and flag if larger than expected
|
|
1184
|
+
self._ctx_verified = actual_ctx
|
|
1185
|
+
if actual_ctx > min_ctx:
|
|
1186
|
+
self._ctx_warning = (
|
|
1187
|
+
f"(configured: {actual_ctx}, required: {min_ctx})"
|
|
1188
|
+
)
|
|
1189
|
+
elif actual_ctx:
|
|
1190
|
+
# Context was set but is too small
|
|
1191
|
+
return (False, f"Context {actual_ctx} < {min_ctx} required")
|
|
1192
|
+
else:
|
|
1193
|
+
# Context not in recipe_options - should not happen after forced unload/reload
|
|
1194
|
+
# Mark as unverified but don't fail the test
|
|
1195
|
+
self._ctx_verified = None # Explicitly mark as unverified
|
|
1196
|
+
except Exception as e:
|
|
1197
|
+
return (False, f"Context check failed: {str(e)[:50]}")
|
|
1198
|
+
else:
|
|
1199
|
+
# Load without context size (SD models, embedding models, or no requirement)
|
|
1200
|
+
client.load_model(model_id, auto_download=False, prompt=False)
|
|
1201
|
+
|
|
1202
|
+
# Check model type
|
|
970
1203
|
is_embedding_model = "embed" in model_id.lower()
|
|
1204
|
+
is_sd_model = any(
|
|
1205
|
+
sd in model_id.upper() for sd in ["SDXL", "SD-", "SD1", "SD2"]
|
|
1206
|
+
)
|
|
971
1207
|
|
|
972
|
-
if
|
|
1208
|
+
if is_sd_model:
|
|
1209
|
+
# Test SD model with image generation
|
|
1210
|
+
response = client.generate_image(
|
|
1211
|
+
prompt="test",
|
|
1212
|
+
model=model_id,
|
|
1213
|
+
steps=1, # Minimal steps for quick test
|
|
1214
|
+
size="512x512",
|
|
1215
|
+
)
|
|
1216
|
+
# Check if we got a valid image in b64_json format
|
|
1217
|
+
if (
|
|
1218
|
+
response
|
|
1219
|
+
and response.get("data")
|
|
1220
|
+
and response["data"][0].get("b64_json")
|
|
1221
|
+
):
|
|
1222
|
+
return (True, None)
|
|
1223
|
+
return (False, "No image generated")
|
|
1224
|
+
elif is_embedding_model:
|
|
973
1225
|
# Test embedding model with a simple text
|
|
974
1226
|
response = client.embeddings(
|
|
975
1227
|
input_texts=["test"],
|
|
@@ -1031,6 +1283,28 @@ class InitCommand:
|
|
|
1031
1283
|
self._print_error("Server not responding")
|
|
1032
1284
|
return False
|
|
1033
1285
|
|
|
1286
|
+
# Ensure proper context size for this profile
|
|
1287
|
+
profile_config = INIT_PROFILES[self.profile]
|
|
1288
|
+
min_ctx = profile_config.get("min_context_size")
|
|
1289
|
+
if min_ctx:
|
|
1290
|
+
from gaia.llm.lemonade_manager import LemonadeManager
|
|
1291
|
+
|
|
1292
|
+
self.console.print()
|
|
1293
|
+
self.console.print(
|
|
1294
|
+
f" [dim]Ensuring {min_ctx} token context for {self.profile} profile...[/dim]"
|
|
1295
|
+
)
|
|
1296
|
+
success = LemonadeManager.ensure_ready(
|
|
1297
|
+
min_context_size=min_ctx, quiet=True
|
|
1298
|
+
)
|
|
1299
|
+
if success:
|
|
1300
|
+
self._print_success(f"Context size verified: {min_ctx} tokens")
|
|
1301
|
+
else:
|
|
1302
|
+
self._print_error(f"Failed to configure {min_ctx} token context")
|
|
1303
|
+
self._print_error(
|
|
1304
|
+
f"Try: lemonade-server serve --ctx-size {min_ctx}"
|
|
1305
|
+
)
|
|
1306
|
+
return False
|
|
1307
|
+
|
|
1034
1308
|
# Get models to verify
|
|
1035
1309
|
profile_config = INIT_PROFILES[self.profile]
|
|
1036
1310
|
if profile_config["models"]:
|
|
@@ -1038,11 +1312,13 @@ class InitCommand:
|
|
|
1038
1312
|
else:
|
|
1039
1313
|
model_ids = client.get_required_models(profile_config["agent"])
|
|
1040
1314
|
|
|
1041
|
-
#
|
|
1042
|
-
|
|
1315
|
+
# Include default CPU model for profiles that need gaia llm
|
|
1316
|
+
# SD profile has its own LLM and doesn't need the 0.5B model
|
|
1317
|
+
if self.profile != "sd":
|
|
1318
|
+
from gaia.llm.lemonade_client import DEFAULT_MODEL_NAME
|
|
1043
1319
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1320
|
+
if DEFAULT_MODEL_NAME not in model_ids:
|
|
1321
|
+
model_ids = list(model_ids) + [DEFAULT_MODEL_NAME]
|
|
1046
1322
|
|
|
1047
1323
|
if not model_ids or self.skip_models:
|
|
1048
1324
|
return True
|
|
@@ -1064,7 +1340,6 @@ class InitCommand:
|
|
|
1064
1340
|
# Test each model with a small inference request
|
|
1065
1341
|
self.console.print()
|
|
1066
1342
|
self.console.print(" [bold]Testing models with inference:[/bold]")
|
|
1067
|
-
self.console.print(" [yellow]⚠️ Press Ctrl+C to skip.[/yellow]")
|
|
1068
1343
|
|
|
1069
1344
|
models_passed = 0
|
|
1070
1345
|
models_failed = []
|
|
@@ -1091,8 +1366,25 @@ class InitCommand:
|
|
|
1091
1366
|
# Clear the line and show result
|
|
1092
1367
|
print("\r" + " " * 80 + "\r", end="")
|
|
1093
1368
|
if success:
|
|
1369
|
+
# Check if context was verified
|
|
1370
|
+
ctx_msg = ""
|
|
1371
|
+
if hasattr(self, "_ctx_verified"):
|
|
1372
|
+
if self._ctx_verified:
|
|
1373
|
+
# Context successfully verified
|
|
1374
|
+
ctx_msg = f" [dim](ctx: {self._ctx_verified})[/dim]"
|
|
1375
|
+
|
|
1376
|
+
# Warn if context is larger than required
|
|
1377
|
+
if hasattr(self, "_ctx_warning"):
|
|
1378
|
+
ctx_msg = f" [yellow]{self._ctx_warning}[/yellow]"
|
|
1379
|
+
delattr(self, "_ctx_warning")
|
|
1380
|
+
elif self._ctx_verified is None:
|
|
1381
|
+
# Context could not be verified
|
|
1382
|
+
ctx_msg = " [yellow]⚠️ Context unverified![/yellow]"
|
|
1383
|
+
|
|
1384
|
+
delattr(self, "_ctx_verified") # Reset for next model
|
|
1385
|
+
|
|
1094
1386
|
self.console.print(
|
|
1095
|
-
f" [green]✓[/green] [cyan]{model_id}[/cyan] [dim]- OK[/dim]"
|
|
1387
|
+
f" [green]✓[/green] [cyan]{model_id}[/cyan] [dim]- OK[/dim]{ctx_msg}"
|
|
1096
1388
|
)
|
|
1097
1389
|
models_passed += 1
|
|
1098
1390
|
else:
|
|
@@ -1189,24 +1481,43 @@ class InitCommand:
|
|
|
1189
1481
|
)
|
|
1190
1482
|
self.console.print()
|
|
1191
1483
|
self.console.print(" [bold]Quick start commands:[/bold]")
|
|
1192
|
-
self.console.print(
|
|
1193
|
-
" [cyan]gaia chat[/cyan] Start interactive chat"
|
|
1194
|
-
)
|
|
1195
|
-
self.console.print(
|
|
1196
|
-
" [cyan]gaia llm 'Hello'[/cyan] Quick LLM query"
|
|
1197
|
-
)
|
|
1198
|
-
self.console.print(
|
|
1199
|
-
" [cyan]gaia talk[/cyan] Voice interaction"
|
|
1200
|
-
)
|
|
1201
|
-
self.console.print()
|
|
1202
1484
|
|
|
1203
|
-
|
|
1204
|
-
if
|
|
1485
|
+
# Profile-specific quick start commands
|
|
1486
|
+
if self.profile == "sd":
|
|
1205
1487
|
self.console.print(
|
|
1206
|
-
|
|
1488
|
+
' [cyan]gaia sd "create a cute robot kitten and tell me a story"[/cyan]'
|
|
1489
|
+
)
|
|
1490
|
+
self.console.print(' [cyan]gaia sd "sunset over mountains"[/cyan]')
|
|
1491
|
+
self.console.print(
|
|
1492
|
+
" [cyan]gaia sd -i[/cyan] Interactive mode"
|
|
1493
|
+
)
|
|
1494
|
+
elif self.profile == "chat":
|
|
1495
|
+
self.console.print(
|
|
1496
|
+
" [cyan]gaia chat[/cyan] Start interactive chat with RAG"
|
|
1497
|
+
)
|
|
1498
|
+
self.console.print(
|
|
1499
|
+
" [cyan]gaia chat init[/cyan] Setup document folder"
|
|
1500
|
+
)
|
|
1501
|
+
elif self.profile == "minimal":
|
|
1502
|
+
self.console.print(
|
|
1503
|
+
" [cyan]gaia llm 'Hello'[/cyan] Quick LLM query"
|
|
1504
|
+
)
|
|
1505
|
+
self.console.print(
|
|
1506
|
+
" [dim]Note: Minimal profile installed. For full features, run:[/dim]"
|
|
1207
1507
|
)
|
|
1208
1508
|
self.console.print(" [cyan]gaia init --profile chat[/cyan]")
|
|
1209
|
-
|
|
1509
|
+
else:
|
|
1510
|
+
# Default commands for other profiles
|
|
1511
|
+
self.console.print(
|
|
1512
|
+
" [cyan]gaia chat[/cyan] Start interactive chat"
|
|
1513
|
+
)
|
|
1514
|
+
self.console.print(
|
|
1515
|
+
" [cyan]gaia llm 'Hello'[/cyan] Quick LLM query"
|
|
1516
|
+
)
|
|
1517
|
+
self.console.print(
|
|
1518
|
+
" [cyan]gaia talk[/cyan] Voice interaction"
|
|
1519
|
+
)
|
|
1520
|
+
self.console.print()
|
|
1210
1521
|
else:
|
|
1211
1522
|
self._print("")
|
|
1212
1523
|
self._print("=" * 60)
|
|
@@ -1214,23 +1525,40 @@ class InitCommand:
|
|
|
1214
1525
|
self._print("=" * 60)
|
|
1215
1526
|
self._print("")
|
|
1216
1527
|
self._print(" Quick start commands:")
|
|
1217
|
-
self._print(" gaia chat # Start interactive chat")
|
|
1218
|
-
self._print(" gaia llm 'Hello' # Quick LLM query")
|
|
1219
|
-
self._print(" gaia talk # Voice interaction")
|
|
1220
|
-
self._print("")
|
|
1221
1528
|
|
|
1222
|
-
|
|
1223
|
-
if
|
|
1529
|
+
# Profile-specific quick start commands
|
|
1530
|
+
if self.profile == "sd":
|
|
1531
|
+
self._print(
|
|
1532
|
+
' gaia sd "create a cute robot kitten and tell me a story"'
|
|
1533
|
+
)
|
|
1534
|
+
self._print(' gaia sd "sunset over mountains"')
|
|
1535
|
+
self._print(
|
|
1536
|
+
" gaia sd -i # Interactive mode"
|
|
1537
|
+
)
|
|
1538
|
+
elif self.profile == "chat":
|
|
1539
|
+
self._print(
|
|
1540
|
+
" gaia chat # Start interactive chat with RAG"
|
|
1541
|
+
)
|
|
1542
|
+
self._print(" gaia chat init # Setup document folder")
|
|
1543
|
+
elif self.profile == "minimal":
|
|
1544
|
+
self._print(" gaia llm 'Hello' # Quick LLM query")
|
|
1545
|
+
self._print("")
|
|
1224
1546
|
self._print(
|
|
1225
1547
|
" Note: Minimal profile installed. For full features, run:"
|
|
1226
1548
|
)
|
|
1227
1549
|
self._print(" gaia init --profile chat")
|
|
1228
|
-
|
|
1550
|
+
else:
|
|
1551
|
+
# Default commands for other profiles
|
|
1552
|
+
self._print(" gaia chat # Start interactive chat")
|
|
1553
|
+
self._print(" gaia llm 'Hello' # Quick LLM query")
|
|
1554
|
+
self._print(" gaia talk # Voice interaction")
|
|
1555
|
+
self._print("")
|
|
1229
1556
|
|
|
1230
1557
|
|
|
1231
1558
|
def run_init(
|
|
1232
1559
|
profile: str = "chat",
|
|
1233
1560
|
skip_models: bool = False,
|
|
1561
|
+
skip_lemonade: bool = False,
|
|
1234
1562
|
force_reinstall: bool = False,
|
|
1235
1563
|
force_models: bool = False,
|
|
1236
1564
|
yes: bool = False,
|
|
@@ -1243,6 +1571,7 @@ def run_init(
|
|
|
1243
1571
|
Args:
|
|
1244
1572
|
profile: Profile to initialize (minimal, chat, code, rag, all)
|
|
1245
1573
|
skip_models: Skip model downloads
|
|
1574
|
+
skip_lemonade: Skip Lemonade installation check (for CI)
|
|
1246
1575
|
force_reinstall: Force reinstall even if compatible version exists
|
|
1247
1576
|
force_models: Force re-download models (deletes then re-downloads)
|
|
1248
1577
|
yes: Skip confirmation prompts
|
|
@@ -1256,6 +1585,7 @@ def run_init(
|
|
|
1256
1585
|
cmd = InitCommand(
|
|
1257
1586
|
profile=profile,
|
|
1258
1587
|
skip_models=skip_models,
|
|
1588
|
+
skip_lemonade=skip_lemonade,
|
|
1259
1589
|
force_reinstall=force_reinstall,
|
|
1260
1590
|
force_models=force_models,
|
|
1261
1591
|
yes=yes,
|