pygeai 0.4.0b3__py3-none-any.whl → 0.5.0__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.
- pygeai/__init__.py +1 -1
- pygeai/assistant/rag/models.py +1 -1
- pygeai/chat/ui.py +0 -1
- pygeai/cli/__init__.py +1 -1
- pygeai/cli/commands/chat.py +54 -56
- pygeai/cli/commands/lab/ai_lab.py +129 -466
- pygeai/cli/commands/lab/options.py +8 -0
- pygeai/cli/commands/lab/utils.py +13 -0
- pygeai/cli/geai.py +5 -2
- pygeai/cli/texts/help.py +12 -0
- pygeai/core/base/session.py +1 -1
- pygeai/core/common/config.py +0 -2
- pygeai/core/common/exceptions.py +6 -0
- pygeai/lab/agents/clients.py +30 -61
- pygeai/lab/clients.py +20 -0
- pygeai/lab/managers.py +6 -58
- pygeai/lab/models.py +1 -1
- pygeai/lab/processes/clients.py +81 -129
- pygeai/lab/processes/mappers.py +2 -2
- pygeai/lab/strategies/clients.py +11 -17
- pygeai/lab/tools/clients.py +59 -59
- pygeai/lab/tools/mappers.py +5 -5
- pygeai/tests/cli/docker/__init__.py +0 -0
- pygeai/tests/integration/assistants/__init__.py +0 -0
- pygeai/tests/integration/assistants/rag/__init__.py +0 -0
- pygeai/tests/integration/assistants/rag/test_create_rag.py +91 -0
- pygeai/tests/integration/chat/__init__.py +0 -0
- pygeai/tests/integration/chat/test_generate_image.py +158 -0
- pygeai/tests/integration/lab/agents/test_create_agent.py +21 -19
- pygeai/tests/integration/lab/agents/test_create_sharing_link.py +4 -1
- pygeai/tests/integration/lab/agents/test_publish_agent_revision.py +0 -1
- pygeai/tests/integration/lab/agents/test_update_agent.py +19 -31
- pygeai/tests/integration/lab/processes/__init__.py +0 -0
- pygeai/tests/integration/lab/processes/test_create_process.py +345 -0
- pygeai/tests/integration/lab/processes/test_get_process.py +201 -0
- pygeai/tests/integration/lab/processes/test_update_process.py +289 -0
- pygeai/tests/integration/lab/reasoning_strategies/__init__.py +0 -0
- pygeai/tests/integration/lab/reasoning_strategies/test_get_reasoning_strategy.py +70 -0
- pygeai/tests/integration/lab/reasoning_strategies/test_list_reasoning_strategies.py +93 -0
- pygeai/tests/integration/lab/reasoning_strategies/test_update_reasoning_strategy.py +149 -0
- pygeai/tests/integration/lab/tools/test_create_tool.py +14 -20
- pygeai/tests/integration/lab/tools/test_delete_tool.py +3 -3
- pygeai/tests/integration/lab/tools/test_get_parameter.py +98 -0
- pygeai/tests/integration/lab/tools/test_get_tool.py +3 -3
- pygeai/tests/integration/lab/tools/test_list_tools.py +106 -0
- pygeai/tests/integration/lab/tools/test_publish_tool_revision.py +119 -0
- pygeai/tests/integration/lab/tools/test_set_parameter.py +114 -0
- pygeai/tests/integration/lab/tools/test_update_tool.py +267 -0
- pygeai/tests/snippets/lab/agentic_flow_example_4.py +23 -23
- pygeai/tests/snippets/lab/agents/get_sharing_link.py +1 -2
- pygeai/tests/snippets/lab/samples/summarize_files.py +3 -3
- pygeai/tests/snippets/lab/tools/create_tool.py +1 -1
- pygeai/tests/snippets/lab/use_cases/file_summarizer_example.py +3 -3
- pygeai/tests/snippets/lab/use_cases/file_summarizer_example_2.py +11 -11
- pygeai/tests/snippets/lab/use_cases/update_web_reader.py +1 -2
- {pygeai-0.4.0b3.dist-info → pygeai-0.5.0.dist-info}/METADATA +47 -19
- {pygeai-0.4.0b3.dist-info → pygeai-0.5.0.dist-info}/RECORD +61 -39
- {pygeai-0.4.0b3.dist-info → pygeai-0.5.0.dist-info}/WHEEL +0 -0
- {pygeai-0.4.0b3.dist-info → pygeai-0.5.0.dist-info}/entry_points.txt +0 -0
- {pygeai-0.4.0b3.dist-info → pygeai-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {pygeai-0.4.0b3.dist-info → pygeai-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
from pygeai.core.common.exceptions import APIResponseError, APIError
|
|
6
|
+
from pygeai.lab.managers import AILabManager
|
|
7
|
+
from pygeai.lab.models import (
|
|
8
|
+
AgenticProcess, KnowledgeBase, AgenticActivity, ArtifactSignal, Task,
|
|
9
|
+
UserSignal, Event, SequenceFlow, Variable
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestAILabUpdateProcessIntegration(TestCase):
|
|
14
|
+
def setUp(self):
|
|
15
|
+
"""
|
|
16
|
+
Set up the test environment.
|
|
17
|
+
"""
|
|
18
|
+
self.ai_lab_manager = AILabManager(alias="beta")
|
|
19
|
+
self.process_to_update = self.__load_process()
|
|
20
|
+
self.created_process: AgenticProcess = None
|
|
21
|
+
self.updated_process: AgenticProcess = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def tearDown(self):
|
|
25
|
+
"""
|
|
26
|
+
Clean up after each test if necessary.
|
|
27
|
+
This can be used to delete the created process
|
|
28
|
+
"""
|
|
29
|
+
if isinstance(self.created_process, AgenticProcess):
|
|
30
|
+
self.ai_lab_manager.delete_process(self.created_process.id)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def __load_process(self):
|
|
34
|
+
"""
|
|
35
|
+
Helper to load a complete process configuration for testing.
|
|
36
|
+
"""
|
|
37
|
+
self.random_str = str(uuid.uuid4())
|
|
38
|
+
return AgenticProcess(
|
|
39
|
+
id="3c071d2f-a044-4513-b2c0-6d27d9420693",
|
|
40
|
+
key=f"sdk_project_updated_process_{self.random_str[:8]}",
|
|
41
|
+
name=f"Updated Agentic Process {self.random_str[:8]}",
|
|
42
|
+
description=f"Process updated for SDK testing purposes {self.random_str[:8]}",
|
|
43
|
+
kb=KnowledgeBase(name="basic-sample", artifact_type_name=["sample-artifact"]),
|
|
44
|
+
agentic_activities=[
|
|
45
|
+
AgenticActivity(key="activityOne", name="First Step Updated", task_name="basic-task", agent_name="GoogleSummarizer2", agent_revision_id=0)
|
|
46
|
+
],
|
|
47
|
+
artifact_signals=[
|
|
48
|
+
ArtifactSignal(key="artifact.upload.1", name="artifact.upload.updated", handling_type="C", artifact_type_name=["sample-artifact"])
|
|
49
|
+
],
|
|
50
|
+
user_signals=[
|
|
51
|
+
UserSignal(key="signal_done", name="process-completed-updated")
|
|
52
|
+
],
|
|
53
|
+
start_event=Event(key="artifact.upload.1", name="artifact.upload.updated"),
|
|
54
|
+
end_event=Event(key="end", name="Done Updated"),
|
|
55
|
+
sequence_flows=[
|
|
56
|
+
SequenceFlow(key="step1", source_key="artifact.upload.1", target_key="activityOne"),
|
|
57
|
+
SequenceFlow(key="step2", source_key="activityOne", target_key="signal_done"),
|
|
58
|
+
SequenceFlow(key="stepEnd", source_key="signal_done", target_key="end")
|
|
59
|
+
]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def __update_process(self, process: AgenticProcess = None, automatic_publish: bool = False, upsert: bool = False):
|
|
64
|
+
"""
|
|
65
|
+
Helper method to update a process.
|
|
66
|
+
"""
|
|
67
|
+
return self.ai_lab_manager.update_process(
|
|
68
|
+
process=self.process_to_update if process is None else process,
|
|
69
|
+
automatic_publish=automatic_publish,
|
|
70
|
+
upsert=upsert
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_update_process_all_fields_success(self):
|
|
75
|
+
"""
|
|
76
|
+
Test updating a process with all available fields populated.
|
|
77
|
+
"""
|
|
78
|
+
self.updated_process = self.__update_process()
|
|
79
|
+
|
|
80
|
+
# Assertions
|
|
81
|
+
self.assertEqual(self.updated_process.name, self.process_to_update.name)
|
|
82
|
+
self.assertEqual(self.updated_process.description, self.process_to_update.description)
|
|
83
|
+
|
|
84
|
+
# Assert knowledge base
|
|
85
|
+
self.assertIsNotNone(self.updated_process.kb)
|
|
86
|
+
self.assertEqual(self.updated_process.kb.name, self.process_to_update.kb.name)
|
|
87
|
+
|
|
88
|
+
# Assert agentic activities
|
|
89
|
+
self.assertIsNotNone(self.updated_process.agentic_activities)
|
|
90
|
+
updated_activity = self.updated_process.agentic_activities[0]
|
|
91
|
+
original_activity = self.process_to_update.agentic_activities[0]
|
|
92
|
+
self.assertEqual(updated_activity.name, original_activity.name)
|
|
93
|
+
|
|
94
|
+
# Assert artifact signals
|
|
95
|
+
updated_signal = self.updated_process.artifact_signals[0]
|
|
96
|
+
original_signal = self.process_to_update.artifact_signals[0]
|
|
97
|
+
self.assertTrue(isinstance(updated_signal, ArtifactSignal))
|
|
98
|
+
self.assertEqual(updated_signal.key.lower(), original_signal.key.lower())
|
|
99
|
+
self.assertEqual(updated_signal.name, original_signal.name)
|
|
100
|
+
self.assertEqual(updated_signal.handling_type, original_signal.handling_type)
|
|
101
|
+
|
|
102
|
+
# Assert that the process is in draft state after update
|
|
103
|
+
self.assertTrue(self.updated_process.is_draft, "Expected process to be in draft state after update")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_update_process_minimum_fields_success(self):
|
|
107
|
+
"""
|
|
108
|
+
Test updating a process with minimum required fields (only name).
|
|
109
|
+
"""
|
|
110
|
+
# Update with minimal data
|
|
111
|
+
minimal_update = AgenticProcess(
|
|
112
|
+
id=self.process_to_update.id,
|
|
113
|
+
name=f"Minimal Updated Process {str(uuid.uuid4())[:8]}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
self.updated_process = self.__update_process(process=minimal_update)
|
|
117
|
+
self.assertEqual(self.updated_process.name, minimal_update.name)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_update_process_invalid_name(self):
|
|
121
|
+
"""
|
|
122
|
+
Test updating a process with invalid characters in name.
|
|
123
|
+
"""
|
|
124
|
+
test_params = [True, False]
|
|
125
|
+
|
|
126
|
+
for auto_publish in test_params:
|
|
127
|
+
with self.subTest(input=auto_publish):
|
|
128
|
+
# Test invalid characters
|
|
129
|
+
invalid_names = [
|
|
130
|
+
f"{self.process_to_update.name}:invalid",
|
|
131
|
+
f"{self.process_to_update.name}/invalid"
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
for invalid_name in invalid_names:
|
|
135
|
+
with self.assertRaises(APIError) as exception:
|
|
136
|
+
self.process_to_update.name = invalid_name
|
|
137
|
+
self.__update_process(automatic_publish=auto_publish)
|
|
138
|
+
|
|
139
|
+
self.assertIn(
|
|
140
|
+
f"Invalid character in name ({':' if ":" in invalid_name else "/"} is not allowed).",
|
|
141
|
+
str(exception.exception),
|
|
142
|
+
f"Expected an error about invalid character in process name with autopublish {'enabled' if auto_publish else 'disabled'}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_update_process_duplicated_name(self):
|
|
147
|
+
"""
|
|
148
|
+
Test updating a process with a name that already exists.
|
|
149
|
+
"""
|
|
150
|
+
test_params = [True, False]
|
|
151
|
+
|
|
152
|
+
for auto_publish in test_params:
|
|
153
|
+
with self.subTest(input=auto_publish):
|
|
154
|
+
self.process_to_update.name = "Test Process For Sdk Project"
|
|
155
|
+
|
|
156
|
+
with self.assertRaises(APIError) as exception:
|
|
157
|
+
self.__update_process(automatic_publish=auto_publish)
|
|
158
|
+
|
|
159
|
+
self.assertIn(
|
|
160
|
+
"A process with this name already exists",
|
|
161
|
+
str(exception.exception),
|
|
162
|
+
f"Expected an error about duplicated process name with autopublish {'enabled' if auto_publish else 'disabled'}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_update_process_no_name(self):
|
|
167
|
+
"""
|
|
168
|
+
Test updating a process without providing a name.
|
|
169
|
+
"""
|
|
170
|
+
test_params = [True, False]
|
|
171
|
+
|
|
172
|
+
for auto_publish in test_params:
|
|
173
|
+
with self.subTest(input=auto_publish):
|
|
174
|
+
self.process_to_update.name = ""
|
|
175
|
+
|
|
176
|
+
with self.assertRaises(APIError) as exception:
|
|
177
|
+
self.__update_process(automatic_publish=auto_publish)
|
|
178
|
+
|
|
179
|
+
self.assertIn(
|
|
180
|
+
"Process name cannot be empty",
|
|
181
|
+
str(exception.exception),
|
|
182
|
+
f"Expected an error when process name is empty with autopublish {'enabled' if auto_publish else 'disabled'}"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_update_process_invalid_id(self):
|
|
187
|
+
"""
|
|
188
|
+
Test updating a process with an invalid ID.
|
|
189
|
+
"""
|
|
190
|
+
test_params = [True, False]
|
|
191
|
+
|
|
192
|
+
for auto_publish in test_params:
|
|
193
|
+
with self.subTest(input=auto_publish):
|
|
194
|
+
invalid_id = "0026e53d-ea78-4cac-af9f-12650invalid"
|
|
195
|
+
self.process_to_update.id = invalid_id
|
|
196
|
+
|
|
197
|
+
with self.assertRaises(APIError) as exception:
|
|
198
|
+
self.__update_process(automatic_publish=auto_publish)
|
|
199
|
+
|
|
200
|
+
self.assertIn(
|
|
201
|
+
f"Process-Definition not found [IdOrName= {invalid_id}]",
|
|
202
|
+
str(exception.exception),
|
|
203
|
+
f"Expected an error when process id is invalid and autopublish is {'enabled' if auto_publish else 'disabled'}"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_update_process_no_id_invalid_name(self):
|
|
208
|
+
"""
|
|
209
|
+
Test updating a process without ID.
|
|
210
|
+
"""
|
|
211
|
+
test_params = [True, False]
|
|
212
|
+
|
|
213
|
+
for auto_publish in test_params:
|
|
214
|
+
with self.subTest(input=auto_publish):
|
|
215
|
+
self.process_to_update.id = ""
|
|
216
|
+
|
|
217
|
+
with self.assertRaises(APIError) as exception:
|
|
218
|
+
self.__update_process(automatic_publish=auto_publish)
|
|
219
|
+
|
|
220
|
+
self.assertIn(
|
|
221
|
+
f"Process-Definition not found [IdOrName= {self.process_to_update.name}]",
|
|
222
|
+
str(exception.exception),
|
|
223
|
+
f"Expected an error when process id is invalid and autopublish is {'enabled' if auto_publish else 'disabled'}"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_update_process_knowledge_base(self):
|
|
228
|
+
"""
|
|
229
|
+
Test updating a process with different knowledge base configurations.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
# Update with different KB
|
|
233
|
+
self.process_to_update.kb = KnowledgeBase(name="updated-kb", artifact_type_name=["updated-artifact"])
|
|
234
|
+
|
|
235
|
+
self.updated_process = self.__update_process()
|
|
236
|
+
self.assertEqual(self.updated_process.kb.name, "updated-kb")
|
|
237
|
+
self.assertEqual(self.updated_process.kb.artifact_type_name, ["updated-artifact"])
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def test_update_process_automatic_publish(self):
|
|
241
|
+
"""
|
|
242
|
+
Test updating a process with automatic publish enabled.
|
|
243
|
+
"""
|
|
244
|
+
# Create a task for publishing
|
|
245
|
+
unique_key = str(uuid.uuid4())
|
|
246
|
+
task = Task(name=unique_key, description="Basic task for process", title_template="Basic Task")
|
|
247
|
+
self.ai_lab_manager.create_task(task=task, automatic_publish=True)
|
|
248
|
+
|
|
249
|
+
# Update process with task reference
|
|
250
|
+
self.process_to_update.agentic_activities[0].task_name = unique_key
|
|
251
|
+
|
|
252
|
+
self.updated_process = self.__update_process(automatic_publish=True)
|
|
253
|
+
self.assertFalse(self.updated_process.is_draft, "Expected process to be published after update with automatic_publish=True")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_update_process_upsert_existing(self):
|
|
257
|
+
"""
|
|
258
|
+
Test updating a process with upsert=True when process exists.
|
|
259
|
+
"""
|
|
260
|
+
# Update existing process with upsert
|
|
261
|
+
self.updated_process = self.__update_process(upsert=True)
|
|
262
|
+
self.assertEqual(self.updated_process.name, self.process_to_update.name)
|
|
263
|
+
self.assertEqual(self.updated_process.id, self.process_to_update.id)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_update_process_upsert_new(self):
|
|
267
|
+
"""
|
|
268
|
+
Test updating a process with upsert=True when process doesn't exist (should create new).
|
|
269
|
+
"""
|
|
270
|
+
new_id = str(uuid.uuid4())
|
|
271
|
+
self.process_to_update.id = new_id
|
|
272
|
+
|
|
273
|
+
self.created_process = self.__update_process(upsert=True)
|
|
274
|
+
self.assertEqual(self.created_process.name, self.process_to_update.name)
|
|
275
|
+
self.assertIsNotNone(self.created_process.id)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_update_process_by_name_instead_of_id(self):
|
|
279
|
+
"""
|
|
280
|
+
Test updating a process by name instead of ID.
|
|
281
|
+
"""
|
|
282
|
+
self.process = self.ai_lab_manager.get_process(self.process_to_update.id)
|
|
283
|
+
self.process_to_update.id = None
|
|
284
|
+
self.process_to_update.name = self.process.name
|
|
285
|
+
self.process_to_update.description = "Updated via name instead of ID"
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
self.updated_process = self.__update_process()
|
|
289
|
+
self.assertEqual(self.updated_process.description, "Updated via name instead of ID")
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
from pygeai.lab.managers import AILabManager
|
|
3
|
+
from pygeai.lab.models import ReasoningStrategy
|
|
4
|
+
from pygeai.core.common.exceptions import APIError
|
|
5
|
+
|
|
6
|
+
ai_lab_manager: AILabManager
|
|
7
|
+
|
|
8
|
+
class TestAILabGetReasoningStrategyIntegration(TestCase):
|
|
9
|
+
|
|
10
|
+
def setUp(self):
|
|
11
|
+
self.ai_lab_manager = AILabManager(alias="beta")
|
|
12
|
+
self.strategy_id = "0a3b039e-25bd-4cf9-ad67-7af2b654874b"
|
|
13
|
+
self.strategy_name = "Chain of Thought"
|
|
14
|
+
|
|
15
|
+
def __get_reasoning_strategy(self, strategy_id = None, strategy_name = None):
|
|
16
|
+
return self.ai_lab_manager.get_reasoning_strategy(
|
|
17
|
+
reasoning_strategy_id= strategy_id if strategy_id is not None else self.strategy_id,
|
|
18
|
+
reasoning_strategy_name=strategy_name if strategy_name is not None else self.strategy_name
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def test_get_reasoning_strategy_by_id(self):
|
|
22
|
+
strategy = self.__get_reasoning_strategy(strategy_name="")
|
|
23
|
+
|
|
24
|
+
self.assertIsInstance(strategy, ReasoningStrategy, "Expected a reasoning strategy")
|
|
25
|
+
self.assertEqual(strategy.id, self.strategy_id, "Expected reasoning strategy ID to match")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_get_reasoning_strategy_by_name(self):
|
|
29
|
+
strategy = self.__get_reasoning_strategy(strategy_id="")
|
|
30
|
+
|
|
31
|
+
self.assertIsInstance(strategy, ReasoningStrategy, "Expected a reasoning strategy")
|
|
32
|
+
self.assertEqual(
|
|
33
|
+
strategy.name,
|
|
34
|
+
self.strategy_name,
|
|
35
|
+
"Expected reasoning strategy name to match"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_get_reasoning_strategy_no_id_or_name(self):
|
|
40
|
+
with self.assertRaises(Exception) as context:
|
|
41
|
+
self.__get_reasoning_strategy(strategy_id="", strategy_name="")
|
|
42
|
+
|
|
43
|
+
self.assertIn(
|
|
44
|
+
"Either reasoning_strategy_id or reasoning_strategy_name must be provided.",
|
|
45
|
+
str(context.exception),
|
|
46
|
+
"Expected error message for missing id and name"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_get_reasoning_strategy_invalid_id(self):
|
|
51
|
+
invalid_id = "invalid-id-1234"
|
|
52
|
+
with self.assertRaises(APIError) as context:
|
|
53
|
+
self.__get_reasoning_strategy(strategy_id=invalid_id)
|
|
54
|
+
self.assertIn(
|
|
55
|
+
f"Reasoning-Strategy not found (idOrName={invalid_id}",
|
|
56
|
+
str(context.exception),
|
|
57
|
+
"Expected an error for invalid reasoning strategy id"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_get_reasoning_strategy_invalid_name(self):
|
|
62
|
+
invalid_name = "Nonexistent Strategy"
|
|
63
|
+
with self.assertRaises(APIError) as context:
|
|
64
|
+
self.__get_reasoning_strategy(strategy_id="", strategy_name=invalid_name)
|
|
65
|
+
|
|
66
|
+
self.assertIn(
|
|
67
|
+
f"Reasoning-Strategy not found (idOrName={invalid_name})",
|
|
68
|
+
str(context.exception),
|
|
69
|
+
"Expected an error for invalid reasoning strategy name"
|
|
70
|
+
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
from pygeai.lab.managers import AILabManager
|
|
3
|
+
from pygeai.lab.models import FilterSettings, ReasoningStrategyList
|
|
4
|
+
import copy
|
|
5
|
+
|
|
6
|
+
ai_lab_manager: AILabManager
|
|
7
|
+
|
|
8
|
+
class TestAILabListReasoningStrategiesIntegration(TestCase):
|
|
9
|
+
|
|
10
|
+
def setUp(self):
|
|
11
|
+
self.ai_lab_manager = AILabManager(alias="beta")
|
|
12
|
+
self.filter_settings = FilterSettings(
|
|
13
|
+
name="",
|
|
14
|
+
start=0,
|
|
15
|
+
count=100,
|
|
16
|
+
allow_external=True,
|
|
17
|
+
access_scope="public"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def __list_reasoning_strategies(self, filter_settings: FilterSettings = None):
|
|
22
|
+
filter_settings = filter_settings if filter_settings is not None else self.filter_settings
|
|
23
|
+
return self.ai_lab_manager.list_reasoning_strategies(filter_settings=filter_settings)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_public_list_reasoning_strategies(self):
|
|
27
|
+
result = self.__list_reasoning_strategies()
|
|
28
|
+
|
|
29
|
+
self.assertIsInstance(result, ReasoningStrategyList, "Expected a list of reasoning strategies")
|
|
30
|
+
for strategy in result.strategies:
|
|
31
|
+
self.assertTrue(
|
|
32
|
+
strategy.access_scope == "public",
|
|
33
|
+
"Expected all reasoning strategies to be public"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_private_list_reasoning_strategies(self):
|
|
38
|
+
filter_settings = copy.deepcopy(self.filter_settings)
|
|
39
|
+
filter_settings.access_scope = "private"
|
|
40
|
+
result = self.__list_reasoning_strategies(filter_settings=filter_settings)
|
|
41
|
+
|
|
42
|
+
self.assertIsInstance(result, ReasoningStrategyList, "Expected a list of reasoning strategies")
|
|
43
|
+
for strategy in result.strategies:
|
|
44
|
+
self.assertTrue(
|
|
45
|
+
strategy.access_scope == "private",
|
|
46
|
+
"Expected all reasoning strategies to be private"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_list_reasoning_strategies_small_count(self):
|
|
51
|
+
filter_settings = copy.deepcopy(self.filter_settings)
|
|
52
|
+
filter_settings.count = 2
|
|
53
|
+
result = self.__list_reasoning_strategies(filter_settings=filter_settings)
|
|
54
|
+
self.assertEqual(
|
|
55
|
+
len(result), 2,
|
|
56
|
+
"Expected list of reasoning strategies returned to be 2"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_list_reasoning_strategies_big_count(self):
|
|
61
|
+
filter_settings = copy.deepcopy(self.filter_settings)
|
|
62
|
+
filter_settings.count = 500
|
|
63
|
+
result = self.__list_reasoning_strategies(filter_settings=filter_settings)
|
|
64
|
+
self.assertLessEqual(
|
|
65
|
+
len(result), 500,
|
|
66
|
+
"Expected list of reasoning strategies returned to be 500 or less"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_list_reasoning_strategies_name_filter(self):
|
|
71
|
+
filter_settings = copy.deepcopy(self.filter_settings)
|
|
72
|
+
filter_settings.name = "Chain of Thought"
|
|
73
|
+
result = self.__list_reasoning_strategies(filter_settings=filter_settings)
|
|
74
|
+
|
|
75
|
+
for strategy in result.strategies:
|
|
76
|
+
self.assertIn(
|
|
77
|
+
"Chain of Thought",
|
|
78
|
+
strategy.name,
|
|
79
|
+
"Expected reasoning strategy name to contain filter value"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_list_reasoning_strategies_invalid_scope(self):
|
|
84
|
+
filter_settings = copy.deepcopy(self.filter_settings)
|
|
85
|
+
filter_settings.access_scope = "project"
|
|
86
|
+
|
|
87
|
+
with self.assertRaises(ValueError) as exception:
|
|
88
|
+
self.__list_reasoning_strategies(filter_settings=filter_settings)
|
|
89
|
+
self.assertIn(
|
|
90
|
+
"Access scope must be either 'public' or 'private'.",
|
|
91
|
+
str(exception.exception),
|
|
92
|
+
"The expected error about invalid scope was not returned"
|
|
93
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
import unittest
|
|
3
|
+
import uuid
|
|
4
|
+
from pygeai.lab.managers import AILabManager
|
|
5
|
+
from pygeai.lab.models import LocalizedDescription, ReasoningStrategy
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
from pygeai.core.common.exceptions import APIError, APIResponseError
|
|
8
|
+
|
|
9
|
+
class TestAILabCreateReasoningStrategyIntegration(TestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
"""
|
|
12
|
+
Set up the test environment.
|
|
13
|
+
"""
|
|
14
|
+
self.ai_lab_manager = AILabManager(alias="beta")
|
|
15
|
+
self.strategy_to_update = self.__load_strategy()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def __load_strategy(self):
|
|
19
|
+
self.random_str = str(uuid.uuid4())
|
|
20
|
+
return ReasoningStrategy(
|
|
21
|
+
id="323f5f94-5a3d-4717-89f3-3554a33c093f",
|
|
22
|
+
name=f"UpdatedStrategy_{self.random_str}",
|
|
23
|
+
system_prompt=f"Let's think step by step. {self.random_str}",
|
|
24
|
+
access_scope="private",
|
|
25
|
+
type="addendum",
|
|
26
|
+
localized_descriptions=[
|
|
27
|
+
LocalizedDescription(language="spanish", description=f"RSName spanish description {self.random_str}"),
|
|
28
|
+
LocalizedDescription(language="english", description=f"RSName english description {self.random_str}"),
|
|
29
|
+
LocalizedDescription(language="japanese", description=f"RSName japanese description {self.random_str}")
|
|
30
|
+
]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __update_strategy(self, strategy=None, upsert = False):
|
|
35
|
+
"""
|
|
36
|
+
Helper to create a reasoning strategy using ai_lab_manager.
|
|
37
|
+
"""
|
|
38
|
+
return self.ai_lab_manager.update_reasoning_strategy(
|
|
39
|
+
strategy=self.strategy_to_update if strategy is None else strategy,
|
|
40
|
+
upsert=upsert
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_update_strategy_full_data(self):
|
|
45
|
+
self.updated_strategy = self.__update_strategy()
|
|
46
|
+
|
|
47
|
+
self.assertEqual(self.updated_strategy.name, self.strategy_to_update.name)
|
|
48
|
+
self.assertEqual(self.updated_strategy.system_prompt, self.strategy_to_update.system_prompt)
|
|
49
|
+
|
|
50
|
+
for locale in self.updated_strategy.localized_descriptions:
|
|
51
|
+
self.assertIn(
|
|
52
|
+
self.random_str,
|
|
53
|
+
locale.description,
|
|
54
|
+
"Expected the localized description to be updated correctly"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_update_strategy_no_name(self):
|
|
59
|
+
self.strategy_to_update.name = None
|
|
60
|
+
|
|
61
|
+
with self.assertRaises(APIError) as exception:
|
|
62
|
+
self.__update_strategy()
|
|
63
|
+
self.assertIn(
|
|
64
|
+
"ReasoningStrategy name cannot be empty",
|
|
65
|
+
str(exception.exception),
|
|
66
|
+
"The expected error about empty name was not returned"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_update_strategy_no_system_prompt(self):
|
|
71
|
+
self.strategy_to_update.system_prompt = None
|
|
72
|
+
|
|
73
|
+
with self.assertRaises(APIError) as exception:
|
|
74
|
+
self.__update_strategy()
|
|
75
|
+
self.assertIn(
|
|
76
|
+
"ReasoningStrategy template or systemPrompt are required.",
|
|
77
|
+
str(exception.exception),
|
|
78
|
+
"The expected error about empty system_prompt was not returned"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_update_strategy_no_public_name(self):
|
|
83
|
+
self.strategy_to_update.access_scope = "public"
|
|
84
|
+
with self.assertRaises(APIError) as exception:
|
|
85
|
+
self.__update_strategy()
|
|
86
|
+
self.assertIn(
|
|
87
|
+
"ReasoningStrategy publicName is required for public strategies",
|
|
88
|
+
str(exception.exception),
|
|
89
|
+
"The expected error about missing public name was not returned"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_update_strategy_no_access_scope(self):
|
|
94
|
+
self.strategy_to_update.access_scope = ""
|
|
95
|
+
with self.assertRaises(ValueError) as exception:
|
|
96
|
+
self.__update_strategy()
|
|
97
|
+
self.assertIn(
|
|
98
|
+
"Access scope must be either 'public' or 'private'",
|
|
99
|
+
str(exception.exception),
|
|
100
|
+
"The expected error about missing access scope was not returned"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_update_strategy_no_type(self):
|
|
105
|
+
self.strategy_to_update.type = ""
|
|
106
|
+
with self.assertRaises(ValueError) as exception:
|
|
107
|
+
self.__update_strategy()
|
|
108
|
+
self.assertIn(
|
|
109
|
+
"Type must be 'addendum'",
|
|
110
|
+
str(exception.exception),
|
|
111
|
+
"The expected error about missing type was not returned"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_update_strategy_invalid_type(self):
|
|
116
|
+
self.strategy_to_update.type = "strategy_type"
|
|
117
|
+
|
|
118
|
+
with self.assertRaises(ValueError) as exception:
|
|
119
|
+
self.__update_strategy()
|
|
120
|
+
self.assertIn(
|
|
121
|
+
"Type must be 'addendum'",
|
|
122
|
+
str(exception.exception),
|
|
123
|
+
"The expected error about missing type was not returned"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_update_strategy_no_localized_descriptions(self):
|
|
128
|
+
self.strategy_to_update.localized_descriptions = []
|
|
129
|
+
updated_strategy = self.__update_strategy()
|
|
130
|
+
self.assertIsNone(
|
|
131
|
+
updated_strategy.localized_descriptions,
|
|
132
|
+
"Expected no localized descriptions after update"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@unittest.skip("Skipping upsert test to avoid creating new strategies during routine testing before having a cleanup mechanism.")
|
|
136
|
+
def test_update_strategy_upsert(self):
|
|
137
|
+
new_id = str(uuid.uuid4())
|
|
138
|
+
new_strategy = ReasoningStrategy(
|
|
139
|
+
id=new_id,
|
|
140
|
+
name=f"UpsertStrategy_{self.random_str}",
|
|
141
|
+
system_prompt=f"Upsert system prompt {self.random_str}",
|
|
142
|
+
access_scope="private",
|
|
143
|
+
type="addendum",
|
|
144
|
+
localized_descriptions=[
|
|
145
|
+
LocalizedDescription(language="english", description=f"Upsert description {self.random_str}")
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
upserted_strategy = self.__update_strategy(strategy=new_strategy, upsert=True)
|
|
149
|
+
self.assertEqual(upserted_strategy.id, new_id, "Expected the reasoning strategy to be created via upsert")
|