amd-gaia 0.14.3__py3-none-any.whl → 0.15.1__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.14.3.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
- amd_gaia-0.15.1.dist-info/RECORD +178 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.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 +2177 -2177
- gaia/agents/base/api_agent.py +120 -120
- gaia/agents/base/console.py +1841 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +83 -83
- gaia/agents/blender/agent.py +556 -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 +835 -835
- gaia/agents/chat/app.py +1058 -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 +1729 -1729
- gaia/agents/chat/tools/shell_tools.py +436 -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 +2036 -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 +642 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1506 -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 +1974 -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 +173 -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 +430 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5481 -5621
- 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/llm/__init__.py +9 -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 +3236 -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 +120 -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 +163 -163
- gaia/talk/app.py +289 -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.14.3.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -729
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/top_level.txt +0 -0
gaia/testing/fixtures.py
CHANGED
|
@@ -1,333 +1,333 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
|
|
4
|
-
"""Test fixtures and factories for GAIA agent testing."""
|
|
5
|
-
|
|
6
|
-
import tempfile
|
|
7
|
-
from contextlib import contextmanager
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import List, Optional, Type, TypeVar
|
|
10
|
-
|
|
11
|
-
from gaia.testing.mocks import MockLLMProvider, MockVLMClient
|
|
12
|
-
|
|
13
|
-
# Type variable for agent classes
|
|
14
|
-
AgentT = TypeVar("AgentT")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@contextmanager
|
|
18
|
-
def temp_directory():
|
|
19
|
-
"""
|
|
20
|
-
Create a temporary directory for testing with automatic cleanup.
|
|
21
|
-
|
|
22
|
-
Yields a Path object to the temporary directory. The directory
|
|
23
|
-
and all its contents are automatically deleted when the context exits.
|
|
24
|
-
|
|
25
|
-
Example:
|
|
26
|
-
from gaia.testing import temp_directory
|
|
27
|
-
|
|
28
|
-
def test_file_processing():
|
|
29
|
-
with temp_directory() as tmp_dir:
|
|
30
|
-
# Create test files
|
|
31
|
-
test_file = tmp_dir / "test.txt"
|
|
32
|
-
test_file.write_text("Hello World")
|
|
33
|
-
|
|
34
|
-
# Test your agent
|
|
35
|
-
agent = MyAgent(data_dir=str(tmp_dir))
|
|
36
|
-
result = agent.process_file(str(test_file))
|
|
37
|
-
|
|
38
|
-
assert result["status"] == "success"
|
|
39
|
-
|
|
40
|
-
# Directory automatically deleted after context exits
|
|
41
|
-
|
|
42
|
-
Yields:
|
|
43
|
-
Path: Path to the temporary directory
|
|
44
|
-
"""
|
|
45
|
-
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
46
|
-
yield Path(tmp_dir)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@contextmanager
|
|
50
|
-
def temp_file(
|
|
51
|
-
content: str = "",
|
|
52
|
-
suffix: str = ".txt",
|
|
53
|
-
prefix: str = "test_",
|
|
54
|
-
):
|
|
55
|
-
"""
|
|
56
|
-
Create a temporary file for testing with automatic cleanup.
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
content: Initial content to write to the file
|
|
60
|
-
suffix: File extension (default: .txt)
|
|
61
|
-
prefix: Filename prefix (default: test_)
|
|
62
|
-
|
|
63
|
-
Yields:
|
|
64
|
-
Path: Path to the temporary file
|
|
65
|
-
|
|
66
|
-
Example:
|
|
67
|
-
from gaia.testing import temp_file
|
|
68
|
-
|
|
69
|
-
def test_file_reading():
|
|
70
|
-
with temp_file(content="test data", suffix=".json") as tmp_path:
|
|
71
|
-
result = my_agent.read_file(str(tmp_path))
|
|
72
|
-
assert "test data" in result
|
|
73
|
-
"""
|
|
74
|
-
with tempfile.NamedTemporaryFile(
|
|
75
|
-
mode="w",
|
|
76
|
-
suffix=suffix,
|
|
77
|
-
prefix=prefix,
|
|
78
|
-
delete=False,
|
|
79
|
-
) as f:
|
|
80
|
-
f.write(content)
|
|
81
|
-
tmp_path = Path(f.name)
|
|
82
|
-
|
|
83
|
-
try:
|
|
84
|
-
yield tmp_path
|
|
85
|
-
finally:
|
|
86
|
-
tmp_path.unlink(missing_ok=True)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def create_test_agent(
|
|
90
|
-
agent_class: Type[AgentT],
|
|
91
|
-
mock_responses: Optional[List[str]] = None,
|
|
92
|
-
mock_vlm_text: Optional[str] = None,
|
|
93
|
-
inject_mocks: bool = True,
|
|
94
|
-
**agent_kwargs,
|
|
95
|
-
) -> AgentT:
|
|
96
|
-
"""
|
|
97
|
-
Create an agent instance configured for testing.
|
|
98
|
-
|
|
99
|
-
Creates an agent with silent mode enabled and optionally injects
|
|
100
|
-
mock LLM and VLM providers for testing without real API calls.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
agent_class: The Agent class to instantiate
|
|
104
|
-
mock_responses: List of LLM responses to return in sequence
|
|
105
|
-
mock_vlm_text: Text for mock VLM to return
|
|
106
|
-
inject_mocks: Whether to inject mock providers (default: True)
|
|
107
|
-
**agent_kwargs: Additional arguments passed to agent constructor
|
|
108
|
-
|
|
109
|
-
Returns:
|
|
110
|
-
Configured agent instance
|
|
111
|
-
|
|
112
|
-
Example:
|
|
113
|
-
from gaia.testing import create_test_agent
|
|
114
|
-
from my_agent import CustomerAgent
|
|
115
|
-
|
|
116
|
-
def test_customer_search():
|
|
117
|
-
agent = create_test_agent(
|
|
118
|
-
CustomerAgent,
|
|
119
|
-
mock_responses=[
|
|
120
|
-
'{"tool": "search", "args": {"name": "John"}}',
|
|
121
|
-
"Found 1 customer named John."
|
|
122
|
-
],
|
|
123
|
-
db_url="sqlite:///:memory:",
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
result = agent.process_query("Find customer John")
|
|
127
|
-
assert "John" in result["answer"]
|
|
128
|
-
|
|
129
|
-
Note:
|
|
130
|
-
The agent is created with `skip_lemonade=True` and `silent_mode=True`
|
|
131
|
-
to avoid requiring a running Lemonade server during tests.
|
|
132
|
-
"""
|
|
133
|
-
# Ensure testing-friendly defaults
|
|
134
|
-
agent_kwargs.setdefault("skip_lemonade", True)
|
|
135
|
-
agent_kwargs.setdefault("silent_mode", True)
|
|
136
|
-
|
|
137
|
-
# Create the agent
|
|
138
|
-
agent = agent_class(**agent_kwargs)
|
|
139
|
-
|
|
140
|
-
# Inject mocks if requested
|
|
141
|
-
if inject_mocks:
|
|
142
|
-
# Create and attach mock LLM
|
|
143
|
-
mock_llm = MockLLMProvider(responses=mock_responses)
|
|
144
|
-
agent._mock_llm = mock_llm # pylint: disable=protected-access
|
|
145
|
-
|
|
146
|
-
# Inject into chat attribute if it exists
|
|
147
|
-
if hasattr(agent, "chat"):
|
|
148
|
-
agent.chat = mock_llm
|
|
149
|
-
|
|
150
|
-
# Create and attach mock VLM if text provided
|
|
151
|
-
if mock_vlm_text is not None:
|
|
152
|
-
mock_vlm = MockVLMClient(extracted_text=mock_vlm_text)
|
|
153
|
-
agent._mock_vlm = mock_vlm # pylint: disable=protected-access
|
|
154
|
-
|
|
155
|
-
if hasattr(agent, "vlm"):
|
|
156
|
-
agent.vlm = mock_vlm
|
|
157
|
-
|
|
158
|
-
return agent
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
class AgentTestContext:
|
|
162
|
-
"""
|
|
163
|
-
Context manager for comprehensive agent testing.
|
|
164
|
-
|
|
165
|
-
Provides a clean testing environment with temporary directories,
|
|
166
|
-
mock providers, and automatic cleanup.
|
|
167
|
-
|
|
168
|
-
Example:
|
|
169
|
-
from gaia.testing import AgentTestContext
|
|
170
|
-
from my_agent import IntakeAgent
|
|
171
|
-
|
|
172
|
-
def test_intake_processing():
|
|
173
|
-
with AgentTestContext(IntakeAgent) as ctx:
|
|
174
|
-
# Create test file
|
|
175
|
-
test_file = ctx.create_file("form.txt", "Patient: John Doe")
|
|
176
|
-
|
|
177
|
-
# Process with agent
|
|
178
|
-
result = ctx.agent.process_query(f"Process {test_file}")
|
|
179
|
-
|
|
180
|
-
# Verify
|
|
181
|
-
assert ctx.mock_llm.was_called
|
|
182
|
-
assert "John" in result["answer"]
|
|
183
|
-
"""
|
|
184
|
-
|
|
185
|
-
def __init__(
|
|
186
|
-
self,
|
|
187
|
-
agent_class: Type[AgentT],
|
|
188
|
-
mock_responses: Optional[List[str]] = None,
|
|
189
|
-
mock_vlm_text: Optional[str] = None,
|
|
190
|
-
**agent_kwargs,
|
|
191
|
-
):
|
|
192
|
-
"""
|
|
193
|
-
Initialize test context.
|
|
194
|
-
|
|
195
|
-
Args:
|
|
196
|
-
agent_class: Agent class to instantiate
|
|
197
|
-
mock_responses: LLM responses to use
|
|
198
|
-
mock_vlm_text: VLM extraction text to use
|
|
199
|
-
**agent_kwargs: Additional agent arguments
|
|
200
|
-
"""
|
|
201
|
-
self.agent_class = agent_class
|
|
202
|
-
self.mock_responses = mock_responses
|
|
203
|
-
self.mock_vlm_text = mock_vlm_text
|
|
204
|
-
self.agent_kwargs = agent_kwargs
|
|
205
|
-
|
|
206
|
-
self.agent: Optional[AgentT] = None
|
|
207
|
-
self.mock_llm: Optional[MockLLMProvider] = None
|
|
208
|
-
self.mock_vlm: Optional[MockVLMClient] = None
|
|
209
|
-
self._temp_dir: Optional[Path] = None
|
|
210
|
-
self._temp_dir_context = None
|
|
211
|
-
|
|
212
|
-
def __enter__(self) -> "AgentTestContext":
|
|
213
|
-
"""Enter context and set up test environment."""
|
|
214
|
-
# Create temporary directory
|
|
215
|
-
self._temp_dir_context = tempfile.TemporaryDirectory()
|
|
216
|
-
self._temp_dir = Path(self._temp_dir_context.name)
|
|
217
|
-
|
|
218
|
-
# Create mock providers
|
|
219
|
-
self.mock_llm = MockLLMProvider(responses=self.mock_responses)
|
|
220
|
-
if self.mock_vlm_text is not None:
|
|
221
|
-
self.mock_vlm = MockVLMClient(extracted_text=self.mock_vlm_text)
|
|
222
|
-
|
|
223
|
-
# Create agent with mocks
|
|
224
|
-
self.agent_kwargs.setdefault("skip_lemonade", True)
|
|
225
|
-
self.agent_kwargs.setdefault("silent_mode", True)
|
|
226
|
-
|
|
227
|
-
self.agent = self.agent_class(**self.agent_kwargs)
|
|
228
|
-
|
|
229
|
-
# Inject mocks
|
|
230
|
-
self.agent._mock_llm = self.mock_llm # pylint: disable=protected-access
|
|
231
|
-
if hasattr(self.agent, "chat"):
|
|
232
|
-
self.agent.chat = self.mock_llm
|
|
233
|
-
|
|
234
|
-
if self.mock_vlm is not None:
|
|
235
|
-
self.agent._mock_vlm = self.mock_vlm # pylint: disable=protected-access
|
|
236
|
-
if hasattr(self.agent, "vlm"):
|
|
237
|
-
self.agent.vlm = self.mock_vlm
|
|
238
|
-
|
|
239
|
-
return self
|
|
240
|
-
|
|
241
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
242
|
-
"""Exit context and clean up."""
|
|
243
|
-
# Clean up agent if it has cleanup method
|
|
244
|
-
if self.agent is not None:
|
|
245
|
-
if hasattr(self.agent, "close"):
|
|
246
|
-
try:
|
|
247
|
-
self.agent.close()
|
|
248
|
-
except Exception:
|
|
249
|
-
pass
|
|
250
|
-
if hasattr(self.agent, "close_db"):
|
|
251
|
-
try:
|
|
252
|
-
self.agent.close_db()
|
|
253
|
-
except Exception:
|
|
254
|
-
pass
|
|
255
|
-
if hasattr(self.agent, "stop_all_watchers"):
|
|
256
|
-
try:
|
|
257
|
-
self.agent.stop_all_watchers()
|
|
258
|
-
except Exception:
|
|
259
|
-
pass
|
|
260
|
-
|
|
261
|
-
# Clean up temp directory
|
|
262
|
-
if self._temp_dir_context is not None:
|
|
263
|
-
self._temp_dir_context.cleanup()
|
|
264
|
-
|
|
265
|
-
return False # Don't suppress exceptions
|
|
266
|
-
|
|
267
|
-
@property
|
|
268
|
-
def temp_dir(self) -> Path:
|
|
269
|
-
"""Get the temporary directory path."""
|
|
270
|
-
if self._temp_dir is None:
|
|
271
|
-
raise RuntimeError("Context not entered. Use 'with' statement.")
|
|
272
|
-
return self._temp_dir
|
|
273
|
-
|
|
274
|
-
def create_file(
|
|
275
|
-
self,
|
|
276
|
-
name: str,
|
|
277
|
-
content: str = "",
|
|
278
|
-
subdir: Optional[str] = None,
|
|
279
|
-
) -> Path:
|
|
280
|
-
"""
|
|
281
|
-
Create a file in the temporary directory.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
name: Filename
|
|
285
|
-
content: File content
|
|
286
|
-
subdir: Optional subdirectory to create file in
|
|
287
|
-
|
|
288
|
-
Returns:
|
|
289
|
-
Path to created file
|
|
290
|
-
"""
|
|
291
|
-
if subdir:
|
|
292
|
-
file_dir = self.temp_dir / subdir
|
|
293
|
-
file_dir.mkdir(parents=True, exist_ok=True)
|
|
294
|
-
else:
|
|
295
|
-
file_dir = self.temp_dir
|
|
296
|
-
|
|
297
|
-
file_path = file_dir / name
|
|
298
|
-
file_path.write_text(content)
|
|
299
|
-
return file_path
|
|
300
|
-
|
|
301
|
-
def create_directory(self, name: str) -> Path:
|
|
302
|
-
"""
|
|
303
|
-
Create a subdirectory in the temporary directory.
|
|
304
|
-
|
|
305
|
-
Args:
|
|
306
|
-
name: Directory name
|
|
307
|
-
|
|
308
|
-
Returns:
|
|
309
|
-
Path to created directory
|
|
310
|
-
"""
|
|
311
|
-
dir_path = self.temp_dir / name
|
|
312
|
-
dir_path.mkdir(parents=True, exist_ok=True)
|
|
313
|
-
return dir_path
|
|
314
|
-
|
|
315
|
-
def set_llm_responses(self, responses: List[str]) -> None:
|
|
316
|
-
"""
|
|
317
|
-
Set new LLM responses.
|
|
318
|
-
|
|
319
|
-
Args:
|
|
320
|
-
responses: New list of responses
|
|
321
|
-
"""
|
|
322
|
-
if self.mock_llm:
|
|
323
|
-
self.mock_llm.set_responses(responses)
|
|
324
|
-
|
|
325
|
-
def set_vlm_text(self, text: str) -> None:
|
|
326
|
-
"""
|
|
327
|
-
Set new VLM extraction text.
|
|
328
|
-
|
|
329
|
-
Args:
|
|
330
|
-
text: New extraction text
|
|
331
|
-
"""
|
|
332
|
-
if self.mock_vlm:
|
|
333
|
-
self.mock_vlm.set_extracted_text(text)
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""Test fixtures and factories for GAIA agent testing."""
|
|
5
|
+
|
|
6
|
+
import tempfile
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Optional, Type, TypeVar
|
|
10
|
+
|
|
11
|
+
from gaia.testing.mocks import MockLLMProvider, MockVLMClient
|
|
12
|
+
|
|
13
|
+
# Type variable for agent classes
|
|
14
|
+
AgentT = TypeVar("AgentT")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@contextmanager
|
|
18
|
+
def temp_directory():
|
|
19
|
+
"""
|
|
20
|
+
Create a temporary directory for testing with automatic cleanup.
|
|
21
|
+
|
|
22
|
+
Yields a Path object to the temporary directory. The directory
|
|
23
|
+
and all its contents are automatically deleted when the context exits.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
from gaia.testing import temp_directory
|
|
27
|
+
|
|
28
|
+
def test_file_processing():
|
|
29
|
+
with temp_directory() as tmp_dir:
|
|
30
|
+
# Create test files
|
|
31
|
+
test_file = tmp_dir / "test.txt"
|
|
32
|
+
test_file.write_text("Hello World")
|
|
33
|
+
|
|
34
|
+
# Test your agent
|
|
35
|
+
agent = MyAgent(data_dir=str(tmp_dir))
|
|
36
|
+
result = agent.process_file(str(test_file))
|
|
37
|
+
|
|
38
|
+
assert result["status"] == "success"
|
|
39
|
+
|
|
40
|
+
# Directory automatically deleted after context exits
|
|
41
|
+
|
|
42
|
+
Yields:
|
|
43
|
+
Path: Path to the temporary directory
|
|
44
|
+
"""
|
|
45
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
46
|
+
yield Path(tmp_dir)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@contextmanager
|
|
50
|
+
def temp_file(
|
|
51
|
+
content: str = "",
|
|
52
|
+
suffix: str = ".txt",
|
|
53
|
+
prefix: str = "test_",
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Create a temporary file for testing with automatic cleanup.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
content: Initial content to write to the file
|
|
60
|
+
suffix: File extension (default: .txt)
|
|
61
|
+
prefix: Filename prefix (default: test_)
|
|
62
|
+
|
|
63
|
+
Yields:
|
|
64
|
+
Path: Path to the temporary file
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
from gaia.testing import temp_file
|
|
68
|
+
|
|
69
|
+
def test_file_reading():
|
|
70
|
+
with temp_file(content="test data", suffix=".json") as tmp_path:
|
|
71
|
+
result = my_agent.read_file(str(tmp_path))
|
|
72
|
+
assert "test data" in result
|
|
73
|
+
"""
|
|
74
|
+
with tempfile.NamedTemporaryFile(
|
|
75
|
+
mode="w",
|
|
76
|
+
suffix=suffix,
|
|
77
|
+
prefix=prefix,
|
|
78
|
+
delete=False,
|
|
79
|
+
) as f:
|
|
80
|
+
f.write(content)
|
|
81
|
+
tmp_path = Path(f.name)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
yield tmp_path
|
|
85
|
+
finally:
|
|
86
|
+
tmp_path.unlink(missing_ok=True)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def create_test_agent(
|
|
90
|
+
agent_class: Type[AgentT],
|
|
91
|
+
mock_responses: Optional[List[str]] = None,
|
|
92
|
+
mock_vlm_text: Optional[str] = None,
|
|
93
|
+
inject_mocks: bool = True,
|
|
94
|
+
**agent_kwargs,
|
|
95
|
+
) -> AgentT:
|
|
96
|
+
"""
|
|
97
|
+
Create an agent instance configured for testing.
|
|
98
|
+
|
|
99
|
+
Creates an agent with silent mode enabled and optionally injects
|
|
100
|
+
mock LLM and VLM providers for testing without real API calls.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
agent_class: The Agent class to instantiate
|
|
104
|
+
mock_responses: List of LLM responses to return in sequence
|
|
105
|
+
mock_vlm_text: Text for mock VLM to return
|
|
106
|
+
inject_mocks: Whether to inject mock providers (default: True)
|
|
107
|
+
**agent_kwargs: Additional arguments passed to agent constructor
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Configured agent instance
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
from gaia.testing import create_test_agent
|
|
114
|
+
from my_agent import CustomerAgent
|
|
115
|
+
|
|
116
|
+
def test_customer_search():
|
|
117
|
+
agent = create_test_agent(
|
|
118
|
+
CustomerAgent,
|
|
119
|
+
mock_responses=[
|
|
120
|
+
'{"tool": "search", "args": {"name": "John"}}',
|
|
121
|
+
"Found 1 customer named John."
|
|
122
|
+
],
|
|
123
|
+
db_url="sqlite:///:memory:",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
result = agent.process_query("Find customer John")
|
|
127
|
+
assert "John" in result["answer"]
|
|
128
|
+
|
|
129
|
+
Note:
|
|
130
|
+
The agent is created with `skip_lemonade=True` and `silent_mode=True`
|
|
131
|
+
to avoid requiring a running Lemonade server during tests.
|
|
132
|
+
"""
|
|
133
|
+
# Ensure testing-friendly defaults
|
|
134
|
+
agent_kwargs.setdefault("skip_lemonade", True)
|
|
135
|
+
agent_kwargs.setdefault("silent_mode", True)
|
|
136
|
+
|
|
137
|
+
# Create the agent
|
|
138
|
+
agent = agent_class(**agent_kwargs)
|
|
139
|
+
|
|
140
|
+
# Inject mocks if requested
|
|
141
|
+
if inject_mocks:
|
|
142
|
+
# Create and attach mock LLM
|
|
143
|
+
mock_llm = MockLLMProvider(responses=mock_responses)
|
|
144
|
+
agent._mock_llm = mock_llm # pylint: disable=protected-access
|
|
145
|
+
|
|
146
|
+
# Inject into chat attribute if it exists
|
|
147
|
+
if hasattr(agent, "chat"):
|
|
148
|
+
agent.chat = mock_llm
|
|
149
|
+
|
|
150
|
+
# Create and attach mock VLM if text provided
|
|
151
|
+
if mock_vlm_text is not None:
|
|
152
|
+
mock_vlm = MockVLMClient(extracted_text=mock_vlm_text)
|
|
153
|
+
agent._mock_vlm = mock_vlm # pylint: disable=protected-access
|
|
154
|
+
|
|
155
|
+
if hasattr(agent, "vlm"):
|
|
156
|
+
agent.vlm = mock_vlm
|
|
157
|
+
|
|
158
|
+
return agent
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class AgentTestContext:
|
|
162
|
+
"""
|
|
163
|
+
Context manager for comprehensive agent testing.
|
|
164
|
+
|
|
165
|
+
Provides a clean testing environment with temporary directories,
|
|
166
|
+
mock providers, and automatic cleanup.
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
from gaia.testing import AgentTestContext
|
|
170
|
+
from my_agent import IntakeAgent
|
|
171
|
+
|
|
172
|
+
def test_intake_processing():
|
|
173
|
+
with AgentTestContext(IntakeAgent) as ctx:
|
|
174
|
+
# Create test file
|
|
175
|
+
test_file = ctx.create_file("form.txt", "Patient: John Doe")
|
|
176
|
+
|
|
177
|
+
# Process with agent
|
|
178
|
+
result = ctx.agent.process_query(f"Process {test_file}")
|
|
179
|
+
|
|
180
|
+
# Verify
|
|
181
|
+
assert ctx.mock_llm.was_called
|
|
182
|
+
assert "John" in result["answer"]
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def __init__(
|
|
186
|
+
self,
|
|
187
|
+
agent_class: Type[AgentT],
|
|
188
|
+
mock_responses: Optional[List[str]] = None,
|
|
189
|
+
mock_vlm_text: Optional[str] = None,
|
|
190
|
+
**agent_kwargs,
|
|
191
|
+
):
|
|
192
|
+
"""
|
|
193
|
+
Initialize test context.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
agent_class: Agent class to instantiate
|
|
197
|
+
mock_responses: LLM responses to use
|
|
198
|
+
mock_vlm_text: VLM extraction text to use
|
|
199
|
+
**agent_kwargs: Additional agent arguments
|
|
200
|
+
"""
|
|
201
|
+
self.agent_class = agent_class
|
|
202
|
+
self.mock_responses = mock_responses
|
|
203
|
+
self.mock_vlm_text = mock_vlm_text
|
|
204
|
+
self.agent_kwargs = agent_kwargs
|
|
205
|
+
|
|
206
|
+
self.agent: Optional[AgentT] = None
|
|
207
|
+
self.mock_llm: Optional[MockLLMProvider] = None
|
|
208
|
+
self.mock_vlm: Optional[MockVLMClient] = None
|
|
209
|
+
self._temp_dir: Optional[Path] = None
|
|
210
|
+
self._temp_dir_context = None
|
|
211
|
+
|
|
212
|
+
def __enter__(self) -> "AgentTestContext":
|
|
213
|
+
"""Enter context and set up test environment."""
|
|
214
|
+
# Create temporary directory
|
|
215
|
+
self._temp_dir_context = tempfile.TemporaryDirectory()
|
|
216
|
+
self._temp_dir = Path(self._temp_dir_context.name)
|
|
217
|
+
|
|
218
|
+
# Create mock providers
|
|
219
|
+
self.mock_llm = MockLLMProvider(responses=self.mock_responses)
|
|
220
|
+
if self.mock_vlm_text is not None:
|
|
221
|
+
self.mock_vlm = MockVLMClient(extracted_text=self.mock_vlm_text)
|
|
222
|
+
|
|
223
|
+
# Create agent with mocks
|
|
224
|
+
self.agent_kwargs.setdefault("skip_lemonade", True)
|
|
225
|
+
self.agent_kwargs.setdefault("silent_mode", True)
|
|
226
|
+
|
|
227
|
+
self.agent = self.agent_class(**self.agent_kwargs)
|
|
228
|
+
|
|
229
|
+
# Inject mocks
|
|
230
|
+
self.agent._mock_llm = self.mock_llm # pylint: disable=protected-access
|
|
231
|
+
if hasattr(self.agent, "chat"):
|
|
232
|
+
self.agent.chat = self.mock_llm
|
|
233
|
+
|
|
234
|
+
if self.mock_vlm is not None:
|
|
235
|
+
self.agent._mock_vlm = self.mock_vlm # pylint: disable=protected-access
|
|
236
|
+
if hasattr(self.agent, "vlm"):
|
|
237
|
+
self.agent.vlm = self.mock_vlm
|
|
238
|
+
|
|
239
|
+
return self
|
|
240
|
+
|
|
241
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
242
|
+
"""Exit context and clean up."""
|
|
243
|
+
# Clean up agent if it has cleanup method
|
|
244
|
+
if self.agent is not None:
|
|
245
|
+
if hasattr(self.agent, "close"):
|
|
246
|
+
try:
|
|
247
|
+
self.agent.close()
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
if hasattr(self.agent, "close_db"):
|
|
251
|
+
try:
|
|
252
|
+
self.agent.close_db()
|
|
253
|
+
except Exception:
|
|
254
|
+
pass
|
|
255
|
+
if hasattr(self.agent, "stop_all_watchers"):
|
|
256
|
+
try:
|
|
257
|
+
self.agent.stop_all_watchers()
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
|
|
261
|
+
# Clean up temp directory
|
|
262
|
+
if self._temp_dir_context is not None:
|
|
263
|
+
self._temp_dir_context.cleanup()
|
|
264
|
+
|
|
265
|
+
return False # Don't suppress exceptions
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def temp_dir(self) -> Path:
|
|
269
|
+
"""Get the temporary directory path."""
|
|
270
|
+
if self._temp_dir is None:
|
|
271
|
+
raise RuntimeError("Context not entered. Use 'with' statement.")
|
|
272
|
+
return self._temp_dir
|
|
273
|
+
|
|
274
|
+
def create_file(
|
|
275
|
+
self,
|
|
276
|
+
name: str,
|
|
277
|
+
content: str = "",
|
|
278
|
+
subdir: Optional[str] = None,
|
|
279
|
+
) -> Path:
|
|
280
|
+
"""
|
|
281
|
+
Create a file in the temporary directory.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
name: Filename
|
|
285
|
+
content: File content
|
|
286
|
+
subdir: Optional subdirectory to create file in
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Path to created file
|
|
290
|
+
"""
|
|
291
|
+
if subdir:
|
|
292
|
+
file_dir = self.temp_dir / subdir
|
|
293
|
+
file_dir.mkdir(parents=True, exist_ok=True)
|
|
294
|
+
else:
|
|
295
|
+
file_dir = self.temp_dir
|
|
296
|
+
|
|
297
|
+
file_path = file_dir / name
|
|
298
|
+
file_path.write_text(content)
|
|
299
|
+
return file_path
|
|
300
|
+
|
|
301
|
+
def create_directory(self, name: str) -> Path:
|
|
302
|
+
"""
|
|
303
|
+
Create a subdirectory in the temporary directory.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
name: Directory name
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Path to created directory
|
|
310
|
+
"""
|
|
311
|
+
dir_path = self.temp_dir / name
|
|
312
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
313
|
+
return dir_path
|
|
314
|
+
|
|
315
|
+
def set_llm_responses(self, responses: List[str]) -> None:
|
|
316
|
+
"""
|
|
317
|
+
Set new LLM responses.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
responses: New list of responses
|
|
321
|
+
"""
|
|
322
|
+
if self.mock_llm:
|
|
323
|
+
self.mock_llm.set_responses(responses)
|
|
324
|
+
|
|
325
|
+
def set_vlm_text(self, text: str) -> None:
|
|
326
|
+
"""
|
|
327
|
+
Set new VLM extraction text.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
text: New extraction text
|
|
331
|
+
"""
|
|
332
|
+
if self.mock_vlm:
|
|
333
|
+
self.mock_vlm.set_extracted_text(text)
|