amd-gaia 0.15.0__py3-none-any.whl → 0.15.2__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.0.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +222 -223
- amd_gaia-0.15.2.dist-info/RECORD +182 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2132 -2177
- gaia/agents/base/api_agent.py +119 -120
- gaia/agents/base/console.py +1967 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +88 -83
- gaia/agents/blender/__init__.py +7 -0
- gaia/agents/blender/agent.py +553 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +809 -835
- gaia/agents/chat/app.py +1065 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1744 -1729
- gaia/agents/chat/tools/shell_tools.py +437 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2034 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +643 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1504 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1972 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +184 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +428 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5659 -5632
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/installer/__init__.py +23 -0
- gaia/installer/init_command.py +1275 -0
- gaia/installer/lemonade_installer.py +619 -0
- gaia/llm/__init__.py +10 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3421 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +118 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +183 -163
- gaia/talk/app.py +287 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.15.0.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -723
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
gaia/llm/lemonade_manager.py
CHANGED
|
@@ -1,294 +1,294 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
"""
|
|
4
|
-
Lazy Lemonade Server Manager for GAIA.
|
|
5
|
-
|
|
6
|
-
Provides singleton initialization shared by CLI and SDK flows.
|
|
7
|
-
Operates at the LLM level (not agent level) for flexibility with community agents.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
import sys
|
|
12
|
-
import threading
|
|
13
|
-
from enum import Enum
|
|
14
|
-
from typing import Optional
|
|
15
|
-
|
|
16
|
-
from gaia.llm.lemonade_client import LemonadeClient
|
|
17
|
-
from gaia.logger import get_logger
|
|
18
|
-
|
|
19
|
-
# Default context size for GAIA agents (supports most complex tasks)
|
|
20
|
-
DEFAULT_CONTEXT_SIZE = 32768
|
|
21
|
-
DEFAULT_LEMONADE_URL = "http://localhost:8000"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class MessageType(Enum):
|
|
25
|
-
"""Message type for context size notifications."""
|
|
26
|
-
|
|
27
|
-
ERROR = "error"
|
|
28
|
-
WARNING = "warning"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class LemonadeManager:
|
|
32
|
-
"""Singleton manager for lazy Lemonade server initialization.
|
|
33
|
-
|
|
34
|
-
Operates at the LLM level, not tied to specific agent implementations.
|
|
35
|
-
This allows community agents to use GAIA without being hardcoded into profiles.
|
|
36
|
-
|
|
37
|
-
Example:
|
|
38
|
-
# Basic usage - just ensure Lemonade is running (default: 32768 context)
|
|
39
|
-
if LemonadeManager.ensure_ready():
|
|
40
|
-
print("Lemonade is ready")
|
|
41
|
-
|
|
42
|
-
# With smaller context size for simple tasks
|
|
43
|
-
LemonadeManager.ensure_ready(min_context_size=4096)
|
|
44
|
-
|
|
45
|
-
# CLI usage (verbose)
|
|
46
|
-
LemonadeManager.ensure_ready(quiet=False)
|
|
47
|
-
|
|
48
|
-
# Get base URL after initialization if needed
|
|
49
|
-
base_url = LemonadeManager.get_base_url()
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
_initialized = False
|
|
53
|
-
_base_url: Optional[str] = None
|
|
54
|
-
_context_size: int = 0
|
|
55
|
-
_lock = threading.Lock()
|
|
56
|
-
_log = get_logger(__name__)
|
|
57
|
-
|
|
58
|
-
@classmethod
|
|
59
|
-
def is_lemonade_installed(cls) -> bool:
|
|
60
|
-
"""Check if Lemonade server is installed."""
|
|
61
|
-
client = LemonadeClient(verbose=False)
|
|
62
|
-
return client.get_lemonade_version() is not None
|
|
63
|
-
|
|
64
|
-
@classmethod
|
|
65
|
-
def print_server_error(cls, min_context_size: int = DEFAULT_CONTEXT_SIZE):
|
|
66
|
-
"""Print informative error when Lemonade server is not running.
|
|
67
|
-
|
|
68
|
-
Shared by CLI and SDK for consistent error messages.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
min_context_size: Context size to recommend in error message.
|
|
72
|
-
"""
|
|
73
|
-
print(
|
|
74
|
-
"❌ Error: Lemonade server is not running or not accessible.",
|
|
75
|
-
file=sys.stderr,
|
|
76
|
-
)
|
|
77
|
-
print("", file=sys.stderr)
|
|
78
|
-
|
|
79
|
-
if not cls.is_lemonade_installed():
|
|
80
|
-
print(
|
|
81
|
-
"📥 Lemonade server is not installed on your system.", file=sys.stderr
|
|
82
|
-
)
|
|
83
|
-
print("", file=sys.stderr)
|
|
84
|
-
print("To install Lemonade server:", file=sys.stderr)
|
|
85
|
-
print(" 1. Visit: https://lemonade-server.ai", file=sys.stderr)
|
|
86
|
-
print(" 2. Download the installer for your platform", file=sys.stderr)
|
|
87
|
-
print(" 3. Run the installer and follow prompts", file=sys.stderr)
|
|
88
|
-
print("", file=sys.stderr)
|
|
89
|
-
print("After installation, try your command again.", file=sys.stderr)
|
|
90
|
-
else:
|
|
91
|
-
print("Lemonade server is installed but not running.", file=sys.stderr)
|
|
92
|
-
print("", file=sys.stderr)
|
|
93
|
-
print(
|
|
94
|
-
"GAIA will automatically start Lemonade Server if installed.",
|
|
95
|
-
file=sys.stderr,
|
|
96
|
-
)
|
|
97
|
-
print("If auto-start fails, you can start it manually by:", file=sys.stderr)
|
|
98
|
-
print(" • Double-clicking the desktop shortcut, or", file=sys.stderr)
|
|
99
|
-
if min_context_size >= 32768:
|
|
100
|
-
print(
|
|
101
|
-
f" • Running: lemonade-server serve --ctx-size {min_context_size}",
|
|
102
|
-
file=sys.stderr,
|
|
103
|
-
)
|
|
104
|
-
else:
|
|
105
|
-
print(" • Running: lemonade-server serve", file=sys.stderr)
|
|
106
|
-
print("", file=sys.stderr)
|
|
107
|
-
if min_context_size >= 32768:
|
|
108
|
-
print(
|
|
109
|
-
f"Note: GAIA requires larger context size ({min_context_size} tokens)",
|
|
110
|
-
file=sys.stderr,
|
|
111
|
-
)
|
|
112
|
-
print("", file=sys.stderr)
|
|
113
|
-
base_url = os.getenv("LEMONADE_BASE_URL", f"{DEFAULT_LEMONADE_URL}/api/v1")
|
|
114
|
-
print(
|
|
115
|
-
f"The server should be accessible at {base_url}/health",
|
|
116
|
-
file=sys.stderr,
|
|
117
|
-
)
|
|
118
|
-
print("Then try your command again.", file=sys.stderr)
|
|
119
|
-
|
|
120
|
-
@classmethod
|
|
121
|
-
def print_context_message(
|
|
122
|
-
cls,
|
|
123
|
-
current_size: int,
|
|
124
|
-
required_size: int,
|
|
125
|
-
message_type: MessageType = MessageType.ERROR,
|
|
126
|
-
):
|
|
127
|
-
"""Print message when context size is insufficient.
|
|
128
|
-
|
|
129
|
-
Shared by CLI and SDK for consistent messages.
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
current_size: Current server context size in tokens.
|
|
133
|
-
required_size: Required context size in tokens.
|
|
134
|
-
message_type: MessageType.WARNING for warning, MessageType.ERROR for error.
|
|
135
|
-
"""
|
|
136
|
-
if message_type == MessageType.WARNING:
|
|
137
|
-
symbol = "⚠️ "
|
|
138
|
-
label = "Context size below recommended"
|
|
139
|
-
else:
|
|
140
|
-
symbol = "❌"
|
|
141
|
-
label = "Insufficient context size"
|
|
142
|
-
|
|
143
|
-
print("", file=sys.stderr)
|
|
144
|
-
print(f"{symbol} {label}.", file=sys.stderr)
|
|
145
|
-
print(
|
|
146
|
-
f" Current: {current_size} tokens, Required: {required_size} tokens",
|
|
147
|
-
file=sys.stderr,
|
|
148
|
-
)
|
|
149
|
-
print("", file=sys.stderr)
|
|
150
|
-
print(" To fix this issue:", file=sys.stderr)
|
|
151
|
-
print(" 1. Stop the Lemonade server (if running)", file=sys.stderr)
|
|
152
|
-
print(
|
|
153
|
-
f" 2. Restart with: lemonade-server serve --ctx-size {required_size}",
|
|
154
|
-
file=sys.stderr,
|
|
155
|
-
)
|
|
156
|
-
print("", file=sys.stderr)
|
|
157
|
-
|
|
158
|
-
@classmethod
|
|
159
|
-
def ensure_ready(
|
|
160
|
-
cls,
|
|
161
|
-
min_context_size: int = DEFAULT_CONTEXT_SIZE,
|
|
162
|
-
quiet: bool = True,
|
|
163
|
-
base_url: Optional[str] = None,
|
|
164
|
-
host: Optional[str] = None,
|
|
165
|
-
port: Optional[int] = None,
|
|
166
|
-
) -> bool:
|
|
167
|
-
"""Ensure Lemonade server is running with sufficient context size.
|
|
168
|
-
|
|
169
|
-
This is the main entry point for both CLI and SDK flows.
|
|
170
|
-
Safe to call multiple times - validates context size on each call.
|
|
171
|
-
|
|
172
|
-
Args:
|
|
173
|
-
min_context_size: Minimum context size required (default: 32768).
|
|
174
|
-
quiet: Suppress output (default: True for SDK, set False for CLI)
|
|
175
|
-
base_url: Full base URL (e.g., "http://localhost:8000/api/v1").
|
|
176
|
-
If provided, host and port are parsed from it.
|
|
177
|
-
host: Override host (default: from LEMONADE_BASE_URL env or localhost)
|
|
178
|
-
port: Override port (default: from LEMONADE_BASE_URL env or 8000)
|
|
179
|
-
|
|
180
|
-
Returns:
|
|
181
|
-
True if Lemonade server is ready, False otherwise.
|
|
182
|
-
Use get_base_url() to retrieve the server URL after initialization.
|
|
183
|
-
|
|
184
|
-
Note:
|
|
185
|
-
The Lemonade server must be running before calling this method.
|
|
186
|
-
Start it with: lemonade-server serve --ctx-size 32768
|
|
187
|
-
"""
|
|
188
|
-
# Parse host and port from base_url if provided
|
|
189
|
-
if base_url and (host is None or port is None):
|
|
190
|
-
from urllib.parse import urlparse
|
|
191
|
-
|
|
192
|
-
parsed = urlparse(base_url)
|
|
193
|
-
if host is None:
|
|
194
|
-
host = parsed.hostname
|
|
195
|
-
if port is None:
|
|
196
|
-
port = parsed.port
|
|
197
|
-
with cls._lock:
|
|
198
|
-
# If already initialized, just verify context size
|
|
199
|
-
if cls._initialized:
|
|
200
|
-
if cls._context_size >= min_context_size:
|
|
201
|
-
cls._log.debug(
|
|
202
|
-
"Lemonade already initialized with sufficient context"
|
|
203
|
-
)
|
|
204
|
-
return True
|
|
205
|
-
else:
|
|
206
|
-
# Context size insufficient - warn and continue
|
|
207
|
-
cls._log.warning(
|
|
208
|
-
f"Lemonade running with {cls._context_size} tokens, "
|
|
209
|
-
f"but {min_context_size} requested. "
|
|
210
|
-
f"Restart with: lemonade-server serve --ctx-size {min_context_size}"
|
|
211
|
-
)
|
|
212
|
-
if not quiet:
|
|
213
|
-
cls.print_context_message(
|
|
214
|
-
cls._context_size, min_context_size, MessageType.WARNING
|
|
215
|
-
)
|
|
216
|
-
return True
|
|
217
|
-
|
|
218
|
-
cls._log.debug(f"Initializing Lemonade (min context: {min_context_size})")
|
|
219
|
-
|
|
220
|
-
try:
|
|
221
|
-
client = LemonadeClient(
|
|
222
|
-
host=host,
|
|
223
|
-
port=port,
|
|
224
|
-
keep_alive=True,
|
|
225
|
-
verbose=not quiet,
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
# Just check server status - no agent profile required
|
|
229
|
-
status = client.get_status()
|
|
230
|
-
|
|
231
|
-
if not status.running:
|
|
232
|
-
cls._log.warning("Lemonade server is not running")
|
|
233
|
-
if not quiet:
|
|
234
|
-
cls.print_server_error(min_context_size)
|
|
235
|
-
return False
|
|
236
|
-
|
|
237
|
-
# Cache server state for subsequent calls.
|
|
238
|
-
# We set initialized=True even if context check fails below,
|
|
239
|
-
# so future calls can use cached state without reconnecting.
|
|
240
|
-
cls._initialized = True
|
|
241
|
-
cls._base_url = client.base_url
|
|
242
|
-
cls._context_size = status.context_size or 0
|
|
243
|
-
|
|
244
|
-
cls._log.debug(
|
|
245
|
-
f"Lemonade ready at {cls._base_url} "
|
|
246
|
-
f"(context: {cls._context_size} tokens)"
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
# Verify context size - warn if insufficient
|
|
250
|
-
if cls._context_size < min_context_size:
|
|
251
|
-
cls._log.warning(
|
|
252
|
-
f"Context size {cls._context_size} is less than "
|
|
253
|
-
f"requested {min_context_size}. Some features may not work correctly."
|
|
254
|
-
)
|
|
255
|
-
if not quiet:
|
|
256
|
-
cls.print_context_message(
|
|
257
|
-
cls._context_size, min_context_size, MessageType.WARNING
|
|
258
|
-
)
|
|
259
|
-
return True
|
|
260
|
-
|
|
261
|
-
return True
|
|
262
|
-
|
|
263
|
-
except Exception as e:
|
|
264
|
-
cls._log.warning(f"Failed to initialize Lemonade: {e}")
|
|
265
|
-
if not quiet:
|
|
266
|
-
cls.print_server_error(min_context_size)
|
|
267
|
-
return False
|
|
268
|
-
|
|
269
|
-
@classmethod
|
|
270
|
-
def is_initialized(cls) -> bool:
|
|
271
|
-
"""Check if Lemonade has been initialized."""
|
|
272
|
-
return cls._initialized
|
|
273
|
-
|
|
274
|
-
@classmethod
|
|
275
|
-
def get_base_url(cls) -> Optional[str]:
|
|
276
|
-
"""Get the base URL if initialized."""
|
|
277
|
-
return cls._base_url
|
|
278
|
-
|
|
279
|
-
@classmethod
|
|
280
|
-
def get_context_size(cls) -> int:
|
|
281
|
-
"""Get the current context size."""
|
|
282
|
-
return cls._context_size
|
|
283
|
-
|
|
284
|
-
@classmethod
|
|
285
|
-
def reset(cls):
|
|
286
|
-
"""Reset initialization state.
|
|
287
|
-
|
|
288
|
-
Primarily used for testing to allow re-initialization.
|
|
289
|
-
"""
|
|
290
|
-
with cls._lock:
|
|
291
|
-
cls._initialized = False
|
|
292
|
-
cls._base_url = None
|
|
293
|
-
cls._context_size = 0
|
|
294
|
-
cls._log.debug("LemonadeManager state reset")
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
Lazy Lemonade Server Manager for GAIA.
|
|
5
|
+
|
|
6
|
+
Provides singleton initialization shared by CLI and SDK flows.
|
|
7
|
+
Operates at the LLM level (not agent level) for flexibility with community agents.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import threading
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from gaia.llm.lemonade_client import LemonadeClient
|
|
17
|
+
from gaia.logger import get_logger
|
|
18
|
+
|
|
19
|
+
# Default context size for GAIA agents (supports most complex tasks)
|
|
20
|
+
DEFAULT_CONTEXT_SIZE = 32768
|
|
21
|
+
DEFAULT_LEMONADE_URL = "http://localhost:8000"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MessageType(Enum):
|
|
25
|
+
"""Message type for context size notifications."""
|
|
26
|
+
|
|
27
|
+
ERROR = "error"
|
|
28
|
+
WARNING = "warning"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LemonadeManager:
|
|
32
|
+
"""Singleton manager for lazy Lemonade server initialization.
|
|
33
|
+
|
|
34
|
+
Operates at the LLM level, not tied to specific agent implementations.
|
|
35
|
+
This allows community agents to use GAIA without being hardcoded into profiles.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
# Basic usage - just ensure Lemonade is running (default: 32768 context)
|
|
39
|
+
if LemonadeManager.ensure_ready():
|
|
40
|
+
print("Lemonade is ready")
|
|
41
|
+
|
|
42
|
+
# With smaller context size for simple tasks
|
|
43
|
+
LemonadeManager.ensure_ready(min_context_size=4096)
|
|
44
|
+
|
|
45
|
+
# CLI usage (verbose)
|
|
46
|
+
LemonadeManager.ensure_ready(quiet=False)
|
|
47
|
+
|
|
48
|
+
# Get base URL after initialization if needed
|
|
49
|
+
base_url = LemonadeManager.get_base_url()
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
_initialized = False
|
|
53
|
+
_base_url: Optional[str] = None
|
|
54
|
+
_context_size: int = 0
|
|
55
|
+
_lock = threading.Lock()
|
|
56
|
+
_log = get_logger(__name__)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def is_lemonade_installed(cls) -> bool:
|
|
60
|
+
"""Check if Lemonade server is installed."""
|
|
61
|
+
client = LemonadeClient(verbose=False)
|
|
62
|
+
return client.get_lemonade_version() is not None
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def print_server_error(cls, min_context_size: int = DEFAULT_CONTEXT_SIZE):
|
|
66
|
+
"""Print informative error when Lemonade server is not running.
|
|
67
|
+
|
|
68
|
+
Shared by CLI and SDK for consistent error messages.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
min_context_size: Context size to recommend in error message.
|
|
72
|
+
"""
|
|
73
|
+
print(
|
|
74
|
+
"❌ Error: Lemonade server is not running or not accessible.",
|
|
75
|
+
file=sys.stderr,
|
|
76
|
+
)
|
|
77
|
+
print("", file=sys.stderr)
|
|
78
|
+
|
|
79
|
+
if not cls.is_lemonade_installed():
|
|
80
|
+
print(
|
|
81
|
+
"📥 Lemonade server is not installed on your system.", file=sys.stderr
|
|
82
|
+
)
|
|
83
|
+
print("", file=sys.stderr)
|
|
84
|
+
print("To install Lemonade server:", file=sys.stderr)
|
|
85
|
+
print(" 1. Visit: https://lemonade-server.ai", file=sys.stderr)
|
|
86
|
+
print(" 2. Download the installer for your platform", file=sys.stderr)
|
|
87
|
+
print(" 3. Run the installer and follow prompts", file=sys.stderr)
|
|
88
|
+
print("", file=sys.stderr)
|
|
89
|
+
print("After installation, try your command again.", file=sys.stderr)
|
|
90
|
+
else:
|
|
91
|
+
print("Lemonade server is installed but not running.", file=sys.stderr)
|
|
92
|
+
print("", file=sys.stderr)
|
|
93
|
+
print(
|
|
94
|
+
"GAIA will automatically start Lemonade Server if installed.",
|
|
95
|
+
file=sys.stderr,
|
|
96
|
+
)
|
|
97
|
+
print("If auto-start fails, you can start it manually by:", file=sys.stderr)
|
|
98
|
+
print(" • Double-clicking the desktop shortcut, or", file=sys.stderr)
|
|
99
|
+
if min_context_size >= 32768:
|
|
100
|
+
print(
|
|
101
|
+
f" • Running: lemonade-server serve --ctx-size {min_context_size}",
|
|
102
|
+
file=sys.stderr,
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
print(" • Running: lemonade-server serve", file=sys.stderr)
|
|
106
|
+
print("", file=sys.stderr)
|
|
107
|
+
if min_context_size >= 32768:
|
|
108
|
+
print(
|
|
109
|
+
f"Note: GAIA requires larger context size ({min_context_size} tokens)",
|
|
110
|
+
file=sys.stderr,
|
|
111
|
+
)
|
|
112
|
+
print("", file=sys.stderr)
|
|
113
|
+
base_url = os.getenv("LEMONADE_BASE_URL", f"{DEFAULT_LEMONADE_URL}/api/v1")
|
|
114
|
+
print(
|
|
115
|
+
f"The server should be accessible at {base_url}/health",
|
|
116
|
+
file=sys.stderr,
|
|
117
|
+
)
|
|
118
|
+
print("Then try your command again.", file=sys.stderr)
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def print_context_message(
|
|
122
|
+
cls,
|
|
123
|
+
current_size: int,
|
|
124
|
+
required_size: int,
|
|
125
|
+
message_type: MessageType = MessageType.ERROR,
|
|
126
|
+
):
|
|
127
|
+
"""Print message when context size is insufficient.
|
|
128
|
+
|
|
129
|
+
Shared by CLI and SDK for consistent messages.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
current_size: Current server context size in tokens.
|
|
133
|
+
required_size: Required context size in tokens.
|
|
134
|
+
message_type: MessageType.WARNING for warning, MessageType.ERROR for error.
|
|
135
|
+
"""
|
|
136
|
+
if message_type == MessageType.WARNING:
|
|
137
|
+
symbol = "⚠️ "
|
|
138
|
+
label = "Context size below recommended"
|
|
139
|
+
else:
|
|
140
|
+
symbol = "❌"
|
|
141
|
+
label = "Insufficient context size"
|
|
142
|
+
|
|
143
|
+
print("", file=sys.stderr)
|
|
144
|
+
print(f"{symbol} {label}.", file=sys.stderr)
|
|
145
|
+
print(
|
|
146
|
+
f" Current: {current_size} tokens, Required: {required_size} tokens",
|
|
147
|
+
file=sys.stderr,
|
|
148
|
+
)
|
|
149
|
+
print("", file=sys.stderr)
|
|
150
|
+
print(" To fix this issue:", file=sys.stderr)
|
|
151
|
+
print(" 1. Stop the Lemonade server (if running)", file=sys.stderr)
|
|
152
|
+
print(
|
|
153
|
+
f" 2. Restart with: lemonade-server serve --ctx-size {required_size}",
|
|
154
|
+
file=sys.stderr,
|
|
155
|
+
)
|
|
156
|
+
print("", file=sys.stderr)
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def ensure_ready(
|
|
160
|
+
cls,
|
|
161
|
+
min_context_size: int = DEFAULT_CONTEXT_SIZE,
|
|
162
|
+
quiet: bool = True,
|
|
163
|
+
base_url: Optional[str] = None,
|
|
164
|
+
host: Optional[str] = None,
|
|
165
|
+
port: Optional[int] = None,
|
|
166
|
+
) -> bool:
|
|
167
|
+
"""Ensure Lemonade server is running with sufficient context size.
|
|
168
|
+
|
|
169
|
+
This is the main entry point for both CLI and SDK flows.
|
|
170
|
+
Safe to call multiple times - validates context size on each call.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
min_context_size: Minimum context size required (default: 32768).
|
|
174
|
+
quiet: Suppress output (default: True for SDK, set False for CLI)
|
|
175
|
+
base_url: Full base URL (e.g., "http://localhost:8000/api/v1").
|
|
176
|
+
If provided, host and port are parsed from it.
|
|
177
|
+
host: Override host (default: from LEMONADE_BASE_URL env or localhost)
|
|
178
|
+
port: Override port (default: from LEMONADE_BASE_URL env or 8000)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
True if Lemonade server is ready, False otherwise.
|
|
182
|
+
Use get_base_url() to retrieve the server URL after initialization.
|
|
183
|
+
|
|
184
|
+
Note:
|
|
185
|
+
The Lemonade server must be running before calling this method.
|
|
186
|
+
Start it with: lemonade-server serve --ctx-size 32768
|
|
187
|
+
"""
|
|
188
|
+
# Parse host and port from base_url if provided
|
|
189
|
+
if base_url and (host is None or port is None):
|
|
190
|
+
from urllib.parse import urlparse
|
|
191
|
+
|
|
192
|
+
parsed = urlparse(base_url)
|
|
193
|
+
if host is None:
|
|
194
|
+
host = parsed.hostname
|
|
195
|
+
if port is None:
|
|
196
|
+
port = parsed.port
|
|
197
|
+
with cls._lock:
|
|
198
|
+
# If already initialized, just verify context size
|
|
199
|
+
if cls._initialized:
|
|
200
|
+
if cls._context_size >= min_context_size:
|
|
201
|
+
cls._log.debug(
|
|
202
|
+
"Lemonade already initialized with sufficient context"
|
|
203
|
+
)
|
|
204
|
+
return True
|
|
205
|
+
else:
|
|
206
|
+
# Context size insufficient - warn and continue
|
|
207
|
+
cls._log.warning(
|
|
208
|
+
f"Lemonade running with {cls._context_size} tokens, "
|
|
209
|
+
f"but {min_context_size} requested. "
|
|
210
|
+
f"Restart with: lemonade-server serve --ctx-size {min_context_size}"
|
|
211
|
+
)
|
|
212
|
+
if not quiet:
|
|
213
|
+
cls.print_context_message(
|
|
214
|
+
cls._context_size, min_context_size, MessageType.WARNING
|
|
215
|
+
)
|
|
216
|
+
return True
|
|
217
|
+
|
|
218
|
+
cls._log.debug(f"Initializing Lemonade (min context: {min_context_size})")
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
client = LemonadeClient(
|
|
222
|
+
host=host,
|
|
223
|
+
port=port,
|
|
224
|
+
keep_alive=True,
|
|
225
|
+
verbose=not quiet,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Just check server status - no agent profile required
|
|
229
|
+
status = client.get_status()
|
|
230
|
+
|
|
231
|
+
if not status.running:
|
|
232
|
+
cls._log.warning("Lemonade server is not running")
|
|
233
|
+
if not quiet:
|
|
234
|
+
cls.print_server_error(min_context_size)
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
# Cache server state for subsequent calls.
|
|
238
|
+
# We set initialized=True even if context check fails below,
|
|
239
|
+
# so future calls can use cached state without reconnecting.
|
|
240
|
+
cls._initialized = True
|
|
241
|
+
cls._base_url = client.base_url
|
|
242
|
+
cls._context_size = status.context_size or 0
|
|
243
|
+
|
|
244
|
+
cls._log.debug(
|
|
245
|
+
f"Lemonade ready at {cls._base_url} "
|
|
246
|
+
f"(context: {cls._context_size} tokens)"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Verify context size - warn if insufficient
|
|
250
|
+
if cls._context_size < min_context_size:
|
|
251
|
+
cls._log.warning(
|
|
252
|
+
f"Context size {cls._context_size} is less than "
|
|
253
|
+
f"requested {min_context_size}. Some features may not work correctly."
|
|
254
|
+
)
|
|
255
|
+
if not quiet:
|
|
256
|
+
cls.print_context_message(
|
|
257
|
+
cls._context_size, min_context_size, MessageType.WARNING
|
|
258
|
+
)
|
|
259
|
+
return True
|
|
260
|
+
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
except Exception as e:
|
|
264
|
+
cls._log.warning(f"Failed to initialize Lemonade: {e}")
|
|
265
|
+
if not quiet:
|
|
266
|
+
cls.print_server_error(min_context_size)
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def is_initialized(cls) -> bool:
|
|
271
|
+
"""Check if Lemonade has been initialized."""
|
|
272
|
+
return cls._initialized
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
def get_base_url(cls) -> Optional[str]:
|
|
276
|
+
"""Get the base URL if initialized."""
|
|
277
|
+
return cls._base_url
|
|
278
|
+
|
|
279
|
+
@classmethod
|
|
280
|
+
def get_context_size(cls) -> int:
|
|
281
|
+
"""Get the current context size."""
|
|
282
|
+
return cls._context_size
|
|
283
|
+
|
|
284
|
+
@classmethod
|
|
285
|
+
def reset(cls):
|
|
286
|
+
"""Reset initialization state.
|
|
287
|
+
|
|
288
|
+
Primarily used for testing to allow re-initialization.
|
|
289
|
+
"""
|
|
290
|
+
with cls._lock:
|
|
291
|
+
cls._initialized = False
|
|
292
|
+
cls._base_url = None
|
|
293
|
+
cls._context_size = 0
|
|
294
|
+
cls._log.debug("LemonadeManager state reset")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""LLM provider implementations."""
|
|
4
|
+
|
|
5
|
+
from .claude import ClaudeProvider
|
|
6
|
+
from .lemonade import LemonadeProvider
|
|
7
|
+
from .openai_provider import OpenAIProvider
|
|
8
|
+
|
|
9
|
+
__all__ = ["ClaudeProvider", "LemonadeProvider", "OpenAIProvider"]
|