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
|
@@ -386,13 +386,46 @@ class LemonadeInstaller:
|
|
|
386
386
|
if silent:
|
|
387
387
|
cmd.extend(["/qn", "/norestart"])
|
|
388
388
|
|
|
389
|
+
log_dir = Path.home() / ".cache" / "gaia" / "installer"
|
|
390
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
391
|
+
msi_log = log_dir / "msi_install.log"
|
|
392
|
+
cmd.extend(["/l*v", str(msi_log)]) # Verbose logging to file
|
|
393
|
+
|
|
389
394
|
log.debug(f"Running: {' '.join(cmd)}")
|
|
390
395
|
|
|
396
|
+
# Check for other msiexec processes before starting (helps diagnose hangs)
|
|
397
|
+
try:
|
|
398
|
+
check_result = subprocess.run(
|
|
399
|
+
["tasklist", "/FI", "IMAGENAME eq msiexec.exe", "/NH"],
|
|
400
|
+
capture_output=True,
|
|
401
|
+
text=True,
|
|
402
|
+
timeout=5,
|
|
403
|
+
check=False,
|
|
404
|
+
)
|
|
405
|
+
if (
|
|
406
|
+
check_result.returncode == 0
|
|
407
|
+
and "msiexec.exe" in check_result.stdout
|
|
408
|
+
):
|
|
409
|
+
msi_count = check_result.stdout.count("msiexec.exe")
|
|
410
|
+
log.warning(
|
|
411
|
+
f"Found {msi_count} existing msiexec process(es) - installation may be blocked"
|
|
412
|
+
)
|
|
413
|
+
except Exception:
|
|
414
|
+
pass # Skip check if tasklist fails
|
|
415
|
+
|
|
416
|
+
if silent:
|
|
417
|
+
self._print_status(
|
|
418
|
+
"Running silent MSI installer (should complete in ~10 seconds)..."
|
|
419
|
+
)
|
|
420
|
+
else:
|
|
421
|
+
self._print_status("Running MSI installer...")
|
|
422
|
+
|
|
423
|
+
# MSI should install in 10-15 seconds, timeout after 60 seconds (indicates stuck process)
|
|
391
424
|
result = subprocess.run(
|
|
392
425
|
cmd,
|
|
393
426
|
capture_output=True,
|
|
394
427
|
text=True,
|
|
395
|
-
timeout=
|
|
428
|
+
timeout=60, # 60 second timeout (should complete in ~10s)
|
|
396
429
|
check=False,
|
|
397
430
|
)
|
|
398
431
|
|
|
@@ -418,7 +451,32 @@ class LemonadeInstaller:
|
|
|
418
451
|
)
|
|
419
452
|
|
|
420
453
|
except subprocess.TimeoutExpired:
|
|
421
|
-
|
|
454
|
+
# Print MSI log to help diagnose the hang
|
|
455
|
+
error_msg = "Installation timed out (expected ~10s, hung for 60s)"
|
|
456
|
+
try:
|
|
457
|
+
if msi_log.exists():
|
|
458
|
+
self._print_status(f"MSI log file: {msi_log}")
|
|
459
|
+
log_content = msi_log.read_text(encoding="utf-16", errors="ignore")
|
|
460
|
+
# Print last 100 lines of log
|
|
461
|
+
log_lines = log_content.split("\n")
|
|
462
|
+
relevant_lines = log_lines[-100:]
|
|
463
|
+
log.error("=== MSI Install Log (last 100 lines) ===")
|
|
464
|
+
for line in relevant_lines:
|
|
465
|
+
log.error(line)
|
|
466
|
+
log.error("=== End MSI Install Log ===")
|
|
467
|
+
|
|
468
|
+
# Also print to console
|
|
469
|
+
if self.console:
|
|
470
|
+
self.console.print(
|
|
471
|
+
"\n [red]MSI Install Log (last 50 lines):[/red]"
|
|
472
|
+
)
|
|
473
|
+
for line in relevant_lines[-50:]:
|
|
474
|
+
if line.strip():
|
|
475
|
+
self.console.print(f" [dim]{line}[/dim]")
|
|
476
|
+
except Exception as e:
|
|
477
|
+
log.debug(f"Could not read MSI log: {e}")
|
|
478
|
+
|
|
479
|
+
return InstallResult(success=False, error=error_msg)
|
|
422
480
|
except FileNotFoundError:
|
|
423
481
|
return InstallResult(success=False, error="msiexec not found")
|
|
424
482
|
except Exception as e:
|
|
@@ -533,9 +591,10 @@ class LemonadeInstaller:
|
|
|
533
591
|
installed_version = info.version.lstrip("v")
|
|
534
592
|
|
|
535
593
|
# Create installer for the installed version (not target version)
|
|
594
|
+
# Use minimal=True (lemonade-server-minimal.msi exists for all versions)
|
|
536
595
|
# Pass console to child installer for consistent output
|
|
537
596
|
uninstall_installer = LemonadeInstaller(
|
|
538
|
-
target_version=installed_version, console=self.console
|
|
597
|
+
target_version=installed_version, minimal=True, console=self.console
|
|
539
598
|
)
|
|
540
599
|
|
|
541
600
|
# Download the MSI matching the installed version
|
gaia/llm/lemonade_client.py
CHANGED
|
@@ -1601,6 +1601,139 @@ class LemonadeClient:
|
|
|
1601
1601
|
self.log.error(f"Error generating embeddings: {str(e)}")
|
|
1602
1602
|
raise LemonadeClientError(f"Error generating embeddings: {str(e)}")
|
|
1603
1603
|
|
|
1604
|
+
# =========================================================================
|
|
1605
|
+
# Image Generation (Stable Diffusion)
|
|
1606
|
+
# =========================================================================
|
|
1607
|
+
|
|
1608
|
+
# Supported SD configurations
|
|
1609
|
+
SD_MODELS = ["SD-1.5", "SD-Turbo", "SDXL-Base-1.0", "SDXL-Turbo"]
|
|
1610
|
+
SD_SIZES = ["512x512", "768x768", "1024x1024"]
|
|
1611
|
+
|
|
1612
|
+
# Model-specific defaults
|
|
1613
|
+
SD_MODEL_DEFAULTS = {
|
|
1614
|
+
"SD-1.5": {"steps": 20, "cfg_scale": 7.5, "size": "512x512"},
|
|
1615
|
+
"SD-Turbo": {"steps": 4, "cfg_scale": 1.0, "size": "512x512"},
|
|
1616
|
+
"SDXL-Base-1.0": {"steps": 20, "cfg_scale": 7.5, "size": "1024x1024"},
|
|
1617
|
+
"SDXL-Turbo": {"steps": 4, "cfg_scale": 1.0, "size": "512x512"},
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
def generate_image(
|
|
1621
|
+
self,
|
|
1622
|
+
prompt: str,
|
|
1623
|
+
model: str = "SDXL-Turbo",
|
|
1624
|
+
size: Optional[str] = None,
|
|
1625
|
+
steps: Optional[int] = None,
|
|
1626
|
+
cfg_scale: Optional[float] = None,
|
|
1627
|
+
seed: Optional[int] = None,
|
|
1628
|
+
timeout: int = 300,
|
|
1629
|
+
) -> Dict[str, Any]:
|
|
1630
|
+
"""
|
|
1631
|
+
Generate an image from a text prompt using Stable Diffusion.
|
|
1632
|
+
|
|
1633
|
+
Args:
|
|
1634
|
+
prompt: Text description of the image to generate
|
|
1635
|
+
model: SD model - SD-1.5, SD-Turbo, SDXL-Base-1.0 (photorealistic), SDXL-Turbo
|
|
1636
|
+
size: Image dimensions (auto-selected if None, or 512x512, 768x768, 1024x1024)
|
|
1637
|
+
steps: Inference steps (auto-selected if None: Turbo=4, Base=20)
|
|
1638
|
+
cfg_scale: CFG scale (auto-selected if None: Turbo=1.0, Base=7.5)
|
|
1639
|
+
seed: Random seed for reproducibility (optional)
|
|
1640
|
+
timeout: Request timeout in seconds (default: 300 for slower Base models)
|
|
1641
|
+
|
|
1642
|
+
Returns:
|
|
1643
|
+
Dict with 'data' containing list of generated images in b64_json format
|
|
1644
|
+
|
|
1645
|
+
Raises:
|
|
1646
|
+
LemonadeClientError: If generation fails or invalid parameters
|
|
1647
|
+
|
|
1648
|
+
Example:
|
|
1649
|
+
# Photorealistic with SDXL-Base-1.0 (auto-settings)
|
|
1650
|
+
result = client.generate_image(
|
|
1651
|
+
prompt="a sunset over mountains, golden hour, photorealistic",
|
|
1652
|
+
model="SDXL-Base-1.0"
|
|
1653
|
+
)
|
|
1654
|
+
|
|
1655
|
+
# Fast stylized with SDXL-Turbo
|
|
1656
|
+
result = client.generate_image(
|
|
1657
|
+
prompt="cyberpunk city",
|
|
1658
|
+
model="SDXL-Turbo"
|
|
1659
|
+
)
|
|
1660
|
+
"""
|
|
1661
|
+
# Validate model
|
|
1662
|
+
if model not in self.SD_MODELS:
|
|
1663
|
+
raise LemonadeClientError(
|
|
1664
|
+
f"Invalid model '{model}'. Choose from: {self.SD_MODELS}"
|
|
1665
|
+
)
|
|
1666
|
+
|
|
1667
|
+
# Apply model-specific defaults
|
|
1668
|
+
defaults = self.SD_MODEL_DEFAULTS.get(model, {})
|
|
1669
|
+
size = size or defaults.get("size", "512x512")
|
|
1670
|
+
steps = steps if steps is not None else defaults.get("steps", 20)
|
|
1671
|
+
cfg_scale = (
|
|
1672
|
+
cfg_scale if cfg_scale is not None else defaults.get("cfg_scale", 7.5)
|
|
1673
|
+
)
|
|
1674
|
+
|
|
1675
|
+
# Validate size
|
|
1676
|
+
if size not in self.SD_SIZES:
|
|
1677
|
+
raise LemonadeClientError(
|
|
1678
|
+
f"Invalid size '{size}'. Choose from: {self.SD_SIZES}"
|
|
1679
|
+
)
|
|
1680
|
+
|
|
1681
|
+
try:
|
|
1682
|
+
# Generate random seed if not provided for varied results
|
|
1683
|
+
import random
|
|
1684
|
+
|
|
1685
|
+
if seed is None:
|
|
1686
|
+
seed = random.randint(0, 2**32 - 1)
|
|
1687
|
+
|
|
1688
|
+
payload = {
|
|
1689
|
+
"prompt": prompt,
|
|
1690
|
+
"model": model,
|
|
1691
|
+
"size": size,
|
|
1692
|
+
"n": 1,
|
|
1693
|
+
"response_format": "b64_json",
|
|
1694
|
+
"cfg_scale": cfg_scale,
|
|
1695
|
+
"steps": steps,
|
|
1696
|
+
"seed": seed,
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
self.log.info(
|
|
1700
|
+
f"Generating image: model={model}, size={size}, steps={steps}, cfg={cfg_scale}"
|
|
1701
|
+
)
|
|
1702
|
+
url = f"{self.base_url}/images/generations"
|
|
1703
|
+
response = self._send_request("POST", url, data=payload, timeout=timeout)
|
|
1704
|
+
|
|
1705
|
+
return response
|
|
1706
|
+
|
|
1707
|
+
except LemonadeClientError:
|
|
1708
|
+
raise
|
|
1709
|
+
except Exception as e:
|
|
1710
|
+
self.log.error(f"Error generating image: {str(e)}")
|
|
1711
|
+
raise LemonadeClientError(f"Error generating image: {str(e)}")
|
|
1712
|
+
|
|
1713
|
+
def list_sd_models(self) -> List[Dict[str, Any]]:
|
|
1714
|
+
"""
|
|
1715
|
+
List available Stable Diffusion models from the server.
|
|
1716
|
+
|
|
1717
|
+
Returns:
|
|
1718
|
+
List of SD model info dicts with id, labels, and image_defaults
|
|
1719
|
+
|
|
1720
|
+
Example:
|
|
1721
|
+
sd_models = client.list_sd_models()
|
|
1722
|
+
for m in sd_models:
|
|
1723
|
+
print(f"{m['id']}: {m.get('image_defaults', {})}")
|
|
1724
|
+
"""
|
|
1725
|
+
try:
|
|
1726
|
+
models = self.list_models()
|
|
1727
|
+
sd_models = [
|
|
1728
|
+
m
|
|
1729
|
+
for m in models.get("data", [])
|
|
1730
|
+
if m.get("id") in self.SD_MODELS or "image" in m.get("labels", [])
|
|
1731
|
+
]
|
|
1732
|
+
return sd_models
|
|
1733
|
+
except Exception as e:
|
|
1734
|
+
self.log.error(f"Error listing SD models: {str(e)}")
|
|
1735
|
+
raise LemonadeClientError(f"Error listing SD models: {str(e)}")
|
|
1736
|
+
|
|
1604
1737
|
def list_models(self, show_all: bool = False) -> Dict[str, Any]:
|
|
1605
1738
|
"""
|
|
1606
1739
|
List available models from the server.
|
|
@@ -2184,6 +2317,8 @@ class LemonadeClient:
|
|
|
2184
2317
|
auto_download: bool = False,
|
|
2185
2318
|
_download_timeout: int = 7200, # Reserved for future use
|
|
2186
2319
|
llamacpp_args: Optional[str] = None,
|
|
2320
|
+
ctx_size: Optional[int] = None,
|
|
2321
|
+
save_options: bool = False,
|
|
2187
2322
|
prompt: bool = True,
|
|
2188
2323
|
) -> Dict[str, Any]:
|
|
2189
2324
|
"""
|
|
@@ -2203,6 +2338,10 @@ class LemonadeClient:
|
|
|
2203
2338
|
Large models can be 100GB+ and take hours to download
|
|
2204
2339
|
llamacpp_args: Optional llama.cpp arguments (e.g., "--ubatch-size 2048").
|
|
2205
2340
|
Used to configure model loading parameters like batch sizes.
|
|
2341
|
+
ctx_size: Context size for the model in tokens (e.g., 8192, 32768).
|
|
2342
|
+
Overrides the default value for this model.
|
|
2343
|
+
save_options: If True, persists ctx_size and llamacpp_args to config file.
|
|
2344
|
+
Model will use these settings on future loads.
|
|
2206
2345
|
prompt: If True, prompt user before downloading (default: True).
|
|
2207
2346
|
Set to False to download automatically without user confirmation.
|
|
2208
2347
|
|
|
@@ -2219,6 +2358,10 @@ class LemonadeClient:
|
|
|
2219
2358
|
request_data = {"model_name": model_name}
|
|
2220
2359
|
if llamacpp_args:
|
|
2221
2360
|
request_data["llamacpp_args"] = llamacpp_args
|
|
2361
|
+
if ctx_size is not None:
|
|
2362
|
+
request_data["ctx_size"] = ctx_size
|
|
2363
|
+
if save_options:
|
|
2364
|
+
request_data["save_options"] = save_options
|
|
2222
2365
|
url = f"{self.base_url}/load"
|
|
2223
2366
|
|
|
2224
2367
|
try:
|
gaia/llm/lemonade_manager.py
CHANGED
|
@@ -203,16 +203,45 @@ class LemonadeManager:
|
|
|
203
203
|
)
|
|
204
204
|
return True
|
|
205
205
|
else:
|
|
206
|
-
# Context size
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
cls._context_size, min_context_size, MessageType.WARNING
|
|
206
|
+
# Context size may be cached from before models were loaded
|
|
207
|
+
# Re-check current status to see if models are loaded now
|
|
208
|
+
try:
|
|
209
|
+
client = LemonadeClient(
|
|
210
|
+
host=host,
|
|
211
|
+
port=port,
|
|
212
|
+
keep_alive=True,
|
|
213
|
+
verbose=not quiet,
|
|
215
214
|
)
|
|
215
|
+
status = client.get_status()
|
|
216
|
+
# Update cached context size
|
|
217
|
+
cls._context_size = status.context_size or 0
|
|
218
|
+
|
|
219
|
+
# Only warn if LLM models are loaded AND context is insufficient
|
|
220
|
+
# SD models don't have context size, only LLM models do
|
|
221
|
+
llm_models_loaded = any(
|
|
222
|
+
"image" not in model.get("labels", [])
|
|
223
|
+
for model in status.loaded_models
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Only warn if context_size is non-zero (0 means no model loaded or still loading)
|
|
227
|
+
if (
|
|
228
|
+
cls._context_size > 0
|
|
229
|
+
and cls._context_size < min_context_size
|
|
230
|
+
and llm_models_loaded
|
|
231
|
+
):
|
|
232
|
+
cls._log.warning(
|
|
233
|
+
f"Lemonade running with {cls._context_size} tokens, "
|
|
234
|
+
f"but {min_context_size} requested. "
|
|
235
|
+
f"Restart with: lemonade-server serve --ctx-size {min_context_size}"
|
|
236
|
+
)
|
|
237
|
+
if not quiet:
|
|
238
|
+
cls.print_context_message(
|
|
239
|
+
cls._context_size,
|
|
240
|
+
min_context_size,
|
|
241
|
+
MessageType.WARNING,
|
|
242
|
+
)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
cls._log.debug(f"Failed to re-check status: {e}")
|
|
216
245
|
return True
|
|
217
246
|
|
|
218
247
|
cls._log.debug(f"Initializing Lemonade (min context: {min_context_size})")
|
|
@@ -246,8 +275,23 @@ class LemonadeManager:
|
|
|
246
275
|
f"(context: {cls._context_size} tokens)"
|
|
247
276
|
)
|
|
248
277
|
|
|
249
|
-
# Verify context size - warn if insufficient
|
|
250
|
-
|
|
278
|
+
# Verify context size - only warn if insufficient AND LLM models are loaded
|
|
279
|
+
# SD models don't have context size, only LLM models do
|
|
280
|
+
# Check if any loaded models are LLMs (not SD models with "image" label)
|
|
281
|
+
llm_models_loaded = any(
|
|
282
|
+
"image" not in model.get("labels", [])
|
|
283
|
+
for model in status.loaded_models
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Only warn if:
|
|
287
|
+
# 1. Context size is non-zero (0 means no model loaded or model still loading)
|
|
288
|
+
# 2. Context size is less than required
|
|
289
|
+
# 3. LLM models are loaded (SD models don't have context size)
|
|
290
|
+
if (
|
|
291
|
+
cls._context_size > 0
|
|
292
|
+
and cls._context_size < min_context_size
|
|
293
|
+
and llm_models_loaded
|
|
294
|
+
):
|
|
251
295
|
cls._log.warning(
|
|
252
296
|
f"Context size {cls._context_size} is less than "
|
|
253
297
|
f"requested {min_context_size}. Some features may not work correctly."
|
gaia/llm/providers/lemonade.py
CHANGED
|
@@ -79,6 +79,15 @@ class LemonadeProvider(LLMClient):
|
|
|
79
79
|
)
|
|
80
80
|
if stream:
|
|
81
81
|
return self._handle_stream(response)
|
|
82
|
+
|
|
83
|
+
# Handle error responses gracefully
|
|
84
|
+
if not isinstance(response, dict) or "choices" not in response:
|
|
85
|
+
error_msg = f"Unexpected response format from Lemonade Server: {response}"
|
|
86
|
+
raise ValueError(error_msg)
|
|
87
|
+
|
|
88
|
+
if not response["choices"] or len(response["choices"]) == 0:
|
|
89
|
+
raise ValueError("Empty choices in response from Lemonade Server")
|
|
90
|
+
|
|
82
91
|
return response["choices"][0]["message"]["content"]
|
|
83
92
|
|
|
84
93
|
def embed(self, texts: list[str], **kwargs) -> list[list[float]]:
|
gaia/version.py
CHANGED
|
@@ -6,10 +6,10 @@ import os
|
|
|
6
6
|
import subprocess
|
|
7
7
|
from importlib.metadata import version as get_package_version_metadata
|
|
8
8
|
|
|
9
|
-
__version__ = "0.15.
|
|
9
|
+
__version__ = "0.15.3"
|
|
10
10
|
|
|
11
11
|
# Lemonade version used across CI and installer
|
|
12
|
-
LEMONADE_VERSION = "9.
|
|
12
|
+
LEMONADE_VERSION = "9.2.0"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def get_package_version() -> str:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|