pygeai 0.3.2__py3-none-any.whl → 0.4.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/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/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/processes/clients.py +81 -129
- pygeai/lab/strategies/clients.py +11 -17
- pygeai/lab/tools/clients.py +59 -59
- 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 +72 -0
- pygeai/tests/integration/chat/__init__.py +0 -0
- pygeai/tests/integration/chat/test_generate_image.py +162 -0
- pygeai/tests/integration/lab/agents/test_create_agent.py +9 -13
- pygeai/tests/integration/lab/agents/test_publish_agent_revision.py +0 -1
- pygeai/tests/integration/lab/agents/test_update_agent.py +6 -15
- pygeai/tests/integration/lab/tools/__init__.py +0 -0
- pygeai/tests/integration/lab/tools/test_create_tool.py +292 -0
- pygeai/tests/integration/lab/tools/test_delete_tool.py +87 -0
- pygeai/tests/integration/lab/tools/test_get_parameter.py +98 -0
- pygeai/tests/integration/lab/tools/test_get_tool.py +91 -0
- 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 +268 -0
- pygeai/tests/snippets/lab/agents/create_agent_edge_case.py +48 -0
- pygeai/tests/snippets/lab/agents/create_agent_without_instructions.py +48 -0
- pygeai/tests/snippets/lab/agents/get_sharing_link.py +1 -2
- pygeai/tests/snippets/lab/tools/create_tool.py +1 -1
- pygeai/tests/snippets/lab/tools/create_tool_edge_case.py +50 -0
- {pygeai-0.3.2.dist-info → pygeai-0.4.0.dist-info}/METADATA +1 -1
- {pygeai-0.3.2.dist-info → pygeai-0.4.0.dist-info}/RECORD +45 -25
- {pygeai-0.3.2.dist-info → pygeai-0.4.0.dist-info}/WHEEL +0 -0
- {pygeai-0.3.2.dist-info → pygeai-0.4.0.dist-info}/entry_points.txt +0 -0
- {pygeai-0.3.2.dist-info → pygeai-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {pygeai-0.3.2.dist-info → pygeai-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
from pygeai.chat.clients import ChatClient
|
|
3
|
+
|
|
4
|
+
chat_client: ChatClient
|
|
5
|
+
|
|
6
|
+
class TestChatGenerateImageIntegration(TestCase):
|
|
7
|
+
|
|
8
|
+
def setUp(self):
|
|
9
|
+
self.chat_client = ChatClient(alias="beta")
|
|
10
|
+
self.new_image = self.__load_image()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __load_image(self):
|
|
14
|
+
return {
|
|
15
|
+
"model": "openai/gpt-image-1",
|
|
16
|
+
"prompt": "generate an image of a futuristic city skyline at sunset",
|
|
17
|
+
"n": 1,
|
|
18
|
+
"quality": "high",
|
|
19
|
+
"size": "1024x1536"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def __generate_image(self, image = None):
|
|
24
|
+
image = image if image is not None else self.new_image
|
|
25
|
+
return self.chat_client.generate_image(
|
|
26
|
+
model=image["model"],
|
|
27
|
+
prompt=image["prompt"],
|
|
28
|
+
n=image["n"],
|
|
29
|
+
quality=image["quality"],
|
|
30
|
+
size=image["size"],
|
|
31
|
+
aspect_ratio= image["aspect_ratio"] if "aspect_ratio" in image else None
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_generate_image(self):
|
|
36
|
+
created_image = self.__generate_image()
|
|
37
|
+
self.assertEqual(len(created_image["data"]), 1, "Expected an image to be generated")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_generate_image_invalid_model(self):
|
|
41
|
+
self.new_image["model"] = "openai/gpt-image-10",
|
|
42
|
+
created_image = self.__generate_image()
|
|
43
|
+
|
|
44
|
+
self.assertEqual(
|
|
45
|
+
created_image["error"]["code"], 400,
|
|
46
|
+
"Expected a 400 code for invalid model"
|
|
47
|
+
)
|
|
48
|
+
self.assertEqual(
|
|
49
|
+
created_image["error"]["message"],
|
|
50
|
+
'Provider \'["openai\' does not exists.',
|
|
51
|
+
"Expected an error message when model does not exists"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_generate_image_no_model(self):
|
|
56
|
+
self.new_image["model"] = ""
|
|
57
|
+
created_image = self.__generate_image()
|
|
58
|
+
|
|
59
|
+
self.assertEqual(
|
|
60
|
+
created_image["error"]["code"], 400,
|
|
61
|
+
"Expected a 400 code for no model"
|
|
62
|
+
)
|
|
63
|
+
self.assertEqual(
|
|
64
|
+
created_image["error"]["message"],
|
|
65
|
+
"Invalid 'model' name. Must follow pattern {provider}/{modelName}",
|
|
66
|
+
"Expected an error message when no model is provided"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_generate_image_no_prompt(self):
|
|
71
|
+
self.new_image["prompt"] = ""
|
|
72
|
+
created_image = self.__generate_image()
|
|
73
|
+
|
|
74
|
+
self.assertEqual(
|
|
75
|
+
created_image["error"]["type"],
|
|
76
|
+
"invalid_request_error",
|
|
77
|
+
"Expected a 400 code for no model"
|
|
78
|
+
)
|
|
79
|
+
self.assertEqual(
|
|
80
|
+
created_image["error"]["param"], "prompt",
|
|
81
|
+
"Expected an error message when no model is provided"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_generate_image_specific_n(self):
|
|
86
|
+
self.new_image["n"] = 2
|
|
87
|
+
|
|
88
|
+
created_image = self.__generate_image()
|
|
89
|
+
self.assertEqual(len(created_image["data"]), 2, "Expected two images to be generated")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_generate_image_no_n(self):
|
|
93
|
+
self.new_image["n"] = None # default is 1
|
|
94
|
+
|
|
95
|
+
created_image = self.__generate_image()
|
|
96
|
+
self.assertEqual(len(created_image["data"]), 1, "Expected an image to be generated")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_generate_image_no_supported_n(self):
|
|
100
|
+
self.new_image["model"] = "openai/dall-e-3"
|
|
101
|
+
self.new_image["n"] = 5
|
|
102
|
+
|
|
103
|
+
created_image = self.__generate_image()
|
|
104
|
+
self.assertIn(
|
|
105
|
+
"Invalid 'n': integer above maximum value",
|
|
106
|
+
created_image["error"]["message"],
|
|
107
|
+
"Expected an error message when n is not supported by the model"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_generate_image_no_quality(self):
|
|
112
|
+
self.new_image["quality"] = ""
|
|
113
|
+
|
|
114
|
+
created_image = self.__generate_image()
|
|
115
|
+
self.assertIn(
|
|
116
|
+
"Invalid value: ''. Supported values are: 'low', 'medium', 'high', and 'auto'",
|
|
117
|
+
created_image["error"]["message"],
|
|
118
|
+
"Expected an error message when quality is not provided"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_generate_image_no_supported_quality(self):
|
|
123
|
+
self.new_image["model"] = "openai/dall-e-3"
|
|
124
|
+
|
|
125
|
+
created_image = self.__generate_image()
|
|
126
|
+
self.assertIn(
|
|
127
|
+
"Invalid value: 'high'. Supported values are: 'standard' and 'hd'",
|
|
128
|
+
created_image["error"]["message"],
|
|
129
|
+
"Expected an error message when quality is not supported by the model"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_generate_image_no_size(self):
|
|
134
|
+
self.new_image["size"] = ""
|
|
135
|
+
|
|
136
|
+
created_image = self.__generate_image()
|
|
137
|
+
self.assertIn(
|
|
138
|
+
"Invalid value: ''. Supported values are: '1024x1024', '1024x1536', '1536x1024', and 'auto'",
|
|
139
|
+
created_image["error"]["message"],
|
|
140
|
+
"Expected an error message when no size is provided"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_generate_image_no_supported_size(self):
|
|
145
|
+
self.new_image["size"] = 1024
|
|
146
|
+
self.new_image["quality"] = None
|
|
147
|
+
|
|
148
|
+
created_image = self.__generate_image()
|
|
149
|
+
self.assertIn(
|
|
150
|
+
"Invalid type for 'size': expected one of '1024x1024', '1024x1536', '1536x1024', or 'auto', but got an integer instead",
|
|
151
|
+
created_image["error"]["message"],
|
|
152
|
+
"Expected an error message when no size is provided"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_generate_image_with_aspect_ratio(self):
|
|
157
|
+
self.new_image["model"] = "vertex_ai/imagen-3.0-generate-001"
|
|
158
|
+
self.new_image["aspect_ratio"] = "4:3"
|
|
159
|
+
self.new_image["quality"] = None
|
|
160
|
+
|
|
161
|
+
created_image = self.__generate_image()
|
|
162
|
+
self.assertEqual(len(created_image["data"]), 1, "Expected an image to be generated")
|
|
@@ -33,7 +33,6 @@ class TestAILabCreateAgentIntegration(TestCase):
|
|
|
33
33
|
access_scope="public",
|
|
34
34
|
public_name=f"public_{random_str}",
|
|
35
35
|
job_description="Translator",
|
|
36
|
-
avatar_image="https://www.shareicon.net/data/128x128/2016/11/09/851442_logo_512x512.png",
|
|
37
36
|
description="Agent that translates from any language to english.",
|
|
38
37
|
agent_data=AgentData(
|
|
39
38
|
prompt=Prompt(
|
|
@@ -287,21 +286,18 @@ class TestAILabCreateAgentIntegration(TestCase):
|
|
|
287
286
|
)
|
|
288
287
|
|
|
289
288
|
|
|
290
|
-
@unittest.skip("Agent is getting created regardless of the prompt instructions being empty")
|
|
291
289
|
def test_create_agent_no_prompt_instructions(self):
|
|
292
290
|
self.new_agent.agent_data.prompt.instructions = ""
|
|
293
|
-
|
|
294
|
-
self.__create_agent()
|
|
295
|
-
self.assertIn(
|
|
296
|
-
"instructions",
|
|
297
|
-
str(exception.exception),
|
|
298
|
-
"Expected a validation error about allowed values for instructions"
|
|
299
|
-
)
|
|
291
|
+
self.created_agent = self.__create_agent()
|
|
300
292
|
|
|
301
|
-
self.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
293
|
+
self.assertTrue(
|
|
294
|
+
isinstance(self.created_agent, Agent),
|
|
295
|
+
"Expected a created agent"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
self.assertIsNone(
|
|
299
|
+
self.created_agent.agent_data.prompt.instructions,
|
|
300
|
+
"Expected the created agent to not have prompt instructions"
|
|
305
301
|
)
|
|
306
302
|
|
|
307
303
|
|
|
@@ -28,7 +28,6 @@ class TestAILabPublishAgentRevisionIntegration(TestCase):
|
|
|
28
28
|
access_scope="public",
|
|
29
29
|
public_name=f"public_{random_str}",
|
|
30
30
|
job_description=f"SummarizerAgent{random_str}",
|
|
31
|
-
avatar_image="https://www.shareicon.net/data/128x128/2016/11/09/851442_logo_512x512.png",
|
|
32
31
|
description=f"Agent that summarized documents. {random_str}",
|
|
33
32
|
agent_data=AgentData(
|
|
34
33
|
prompt=Prompt(
|
|
@@ -25,7 +25,6 @@ class TestAILabUpdateAgentIntegration(TestCase):
|
|
|
25
25
|
access_scope="public",
|
|
26
26
|
public_name=f"public_{random_str}",
|
|
27
27
|
job_description=f"SummarizerAgent{random_str}",
|
|
28
|
-
avatar_image="https://www.shareicon.net/data/128x128/2016/11/09/851442_logo_512x512.png",
|
|
29
28
|
description=f"Agent that summarized documents. {random_str}",
|
|
30
29
|
agent_data=AgentData(
|
|
31
30
|
prompt=Prompt(
|
|
@@ -207,28 +206,20 @@ class TestAILabUpdateAgentIntegration(TestCase):
|
|
|
207
206
|
f"Expected a validation error about allowed values for instructions when autopublish is {'enabled' if auto_publish else 'disabled'}"
|
|
208
207
|
)
|
|
209
208
|
|
|
210
|
-
|
|
209
|
+
# TODO: Change validation when API behavior is fixed
|
|
211
210
|
def test_update_agent_no_model(self):
|
|
212
211
|
test_params = [ True, False ]
|
|
213
212
|
self.agent_to_update.agent_data.models[0].name = ""
|
|
214
213
|
|
|
215
214
|
for auto_publish in test_params:
|
|
216
215
|
with self.subTest(input=auto_publish):
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
|
|
217
|
+
# If the agent is not published, the API returns a warning message for invalid model name. However, the sdk mapping is not returning it.
|
|
218
|
+
if auto_publish == False:
|
|
219
|
+
updated_agent = self.__update_agent(automatic_publish=auto_publish)
|
|
220
220
|
error_msg = str(exception.exception)
|
|
221
221
|
|
|
222
|
-
self.
|
|
223
|
-
"name",
|
|
224
|
-
error_msg,
|
|
225
|
-
"Expected a validation error about empty model name"
|
|
226
|
-
)
|
|
227
|
-
self.assertIn(
|
|
228
|
-
"Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]",
|
|
229
|
-
error_msg,
|
|
230
|
-
"Expected a validation error about empty model name"
|
|
231
|
-
)
|
|
222
|
+
self.assertTrue(self.isInstance(updated_agent), Agent)
|
|
232
223
|
else:
|
|
233
224
|
with self.assertRaises(APIError) as exception:
|
|
234
225
|
self.__update_agent(automatic_publish=auto_publish)
|
|
File without changes
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
import uuid
|
|
3
|
+
from pygeai.lab.managers import AILabManager
|
|
4
|
+
from pygeai.lab.models import Tool, ToolParameter
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
from pygeai.core.common.exceptions import APIError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestAILabCreateToolIntegration(TestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
"""
|
|
12
|
+
Set up the test environment.
|
|
13
|
+
"""
|
|
14
|
+
self.ai_lab_manager = AILabManager(alias="beta")
|
|
15
|
+
self.new_tool = self.__load_tool()
|
|
16
|
+
self.created_tool: Tool = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def tearDown(self):
|
|
20
|
+
"""
|
|
21
|
+
Clean up after each test if necessary.
|
|
22
|
+
This can be used to delete the created tool
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(self.created_tool, Tool):
|
|
25
|
+
|
|
26
|
+
self.ai_lab_manager.delete_tool(self.created_tool.id)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def __load_tool(self):
|
|
30
|
+
return Tool(
|
|
31
|
+
name=str(uuid.uuid4()),
|
|
32
|
+
description="Tool created for sdk testing purposes",
|
|
33
|
+
scope="builtin",
|
|
34
|
+
openApi="https://raw.usercontent.com//openapi.json",
|
|
35
|
+
openApiJson={"openapi": "3.0.0","info": {"title": "Simple API overview","version": "2.0.0"}},
|
|
36
|
+
accessScope="private",
|
|
37
|
+
reportEvents="None",
|
|
38
|
+
parameters=[{"key": "param", "description": "param description", "type":"app", "value":"param value", "data_type": "String", "isRequired": False}],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def __create_tool(self, tool=None, automatic_publish=False):
|
|
43
|
+
"""
|
|
44
|
+
Helper to create a tool using ai_lab_manager.
|
|
45
|
+
"""
|
|
46
|
+
return self.ai_lab_manager.create_tool(
|
|
47
|
+
tool=self.new_tool if tool is None else tool,
|
|
48
|
+
automatic_publish=automatic_publish
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_create_tool_full_data(self):
|
|
53
|
+
self.created_tool = self.__create_tool()
|
|
54
|
+
created_tool = self.created_tool
|
|
55
|
+
tool = self.new_tool
|
|
56
|
+
self.assertTrue(isinstance(created_tool, Tool), "Expected a created tool")
|
|
57
|
+
|
|
58
|
+
# Assert the main fields of the created tool
|
|
59
|
+
self.assertIsNotNone(created_tool.id)
|
|
60
|
+
self.assertEqual(created_tool.name, tool.name)
|
|
61
|
+
self.assertEqual(created_tool.description, tool.description)
|
|
62
|
+
self.assertEqual(created_tool.scope, tool.scope)
|
|
63
|
+
self.assertEqual(created_tool.access_scope, tool.access_scope)
|
|
64
|
+
self.assertEqual(created_tool.open_api, tool.open_api)
|
|
65
|
+
self.assertEqual(created_tool.status, "active")
|
|
66
|
+
|
|
67
|
+
# Assert agentData fields
|
|
68
|
+
tool_param = created_tool.parameters[0]
|
|
69
|
+
self.assertTrue(isinstance(tool_param, ToolParameter), "Expected parameters to be of type ToolParameter")
|
|
70
|
+
self.assertEqual(tool_param.key, tool.parameters[0].key)
|
|
71
|
+
self.assertEqual(tool_param.data_type, tool.parameters[0].data_type)
|
|
72
|
+
self.assertEqual(tool_param.description, tool.parameters[0].description)
|
|
73
|
+
self.assertEqual(tool_param.is_required, tool.parameters[0].is_required)
|
|
74
|
+
self.assertEqual(tool_param.type, tool.parameters[0].type)
|
|
75
|
+
self.assertEqual(tool_param.value, tool.parameters[0].value)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_create_tool_minimum_required_data(self):
|
|
79
|
+
self.new_tool = Tool(
|
|
80
|
+
name=str(uuid.uuid4()),
|
|
81
|
+
description="Tool created for sdk testing purposes",
|
|
82
|
+
scope="builtin"
|
|
83
|
+
)
|
|
84
|
+
self.created_tool = self.__create_tool()
|
|
85
|
+
tool = self.new_tool
|
|
86
|
+
|
|
87
|
+
self.assertIsNotNone(self.created_tool.id)
|
|
88
|
+
self.assertEqual(self.created_tool.name, tool.name)
|
|
89
|
+
self.assertEqual(self.created_tool.description, tool.description)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_create_tool_without_required_data(self):
|
|
93
|
+
test_params = [ True, False ]
|
|
94
|
+
|
|
95
|
+
for auto_publish in test_params:
|
|
96
|
+
|
|
97
|
+
with self.subTest(input=auto_publish):
|
|
98
|
+
with self.assertRaises(ValidationError) as context:
|
|
99
|
+
self.new_tool = Tool(
|
|
100
|
+
name=str(uuid.uuid4())
|
|
101
|
+
)
|
|
102
|
+
self.__create_tool(automatic_publish=auto_publish)
|
|
103
|
+
|
|
104
|
+
self.assertIn("description", str(context.exception))
|
|
105
|
+
self.assertIn("Field required", str(context.exception))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_create_tool_no_name(self):
|
|
109
|
+
test_params = [ True, False ]
|
|
110
|
+
|
|
111
|
+
for auto_publish in test_params:
|
|
112
|
+
with self.subTest(input=auto_publish):
|
|
113
|
+
self.new_tool.name = ""
|
|
114
|
+
with self.assertRaises(APIError) as exception:
|
|
115
|
+
self.__create_tool(automatic_publish=auto_publish)
|
|
116
|
+
|
|
117
|
+
self.assertIn(
|
|
118
|
+
"Tool name cannot be empty.",
|
|
119
|
+
str(exception.exception),
|
|
120
|
+
f"Expected an error about the missing tool name with autopublish {'enabled' if auto_publish else 'disabled'}"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_create_tool_duplicated_name(self):
|
|
125
|
+
test_params = [ True, False ]
|
|
126
|
+
|
|
127
|
+
for auto_publish in test_params:
|
|
128
|
+
|
|
129
|
+
with self.subTest(input=auto_publish):
|
|
130
|
+
self.new_tool.name = "sdk_project_gemini_tool"
|
|
131
|
+
with self.assertRaises(APIError) as exception:
|
|
132
|
+
self.__create_tool(automatic_publish=auto_publish)
|
|
133
|
+
self.assertIn(
|
|
134
|
+
"Tool already exists [name=sdk_project_gemini_tool]..",
|
|
135
|
+
str(exception.exception),
|
|
136
|
+
f"Expected an error about duplicated tool name with autopublish {'enabled' if auto_publish else 'disabled'}"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_create_tool_invalid_name(self):
|
|
141
|
+
test_params = [ True, False ]
|
|
142
|
+
|
|
143
|
+
for auto_publish in test_params:
|
|
144
|
+
with self.subTest(input=auto_publish):
|
|
145
|
+
new_tool = self.__load_tool()
|
|
146
|
+
new_tool2 = self.__load_tool()
|
|
147
|
+
|
|
148
|
+
with self.assertRaises(APIError) as exception:
|
|
149
|
+
new_tool.name = f"{new_tool.name}:invalid"
|
|
150
|
+
self.__create_tool(tool=new_tool, automatic_publish=auto_publish)
|
|
151
|
+
self.assertIn(
|
|
152
|
+
"Invalid character in name (: is not allowed).",
|
|
153
|
+
str(exception.exception),
|
|
154
|
+
f"Expected an error about invalid character (:) in tool name with autopublish {'enabled' if auto_publish else 'disabled'}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
with self.assertRaises(APIError) as exception:
|
|
158
|
+
new_tool2.name = f"{new_tool2.name}/invalid"
|
|
159
|
+
self.__create_tool(tool=new_tool2, automatic_publish=auto_publish)
|
|
160
|
+
self.assertIn(
|
|
161
|
+
"Invalid character in name (/ is not allowed).",
|
|
162
|
+
str(exception.exception),
|
|
163
|
+
f"Expected an error about invalid character (/) in tool name with autopublish {'enabled' if auto_publish else 'disabled'}"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_create_tool_invalid_access_scope(self):
|
|
168
|
+
self.new_tool.access_scope = "project"
|
|
169
|
+
with self.assertRaises(ValueError) as exc:
|
|
170
|
+
self.__create_tool()
|
|
171
|
+
self.assertEqual(
|
|
172
|
+
str(exc.exception),
|
|
173
|
+
"Access scope must be one of public, private.",
|
|
174
|
+
"Expected a ValueError exception for invalid access scope"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_create_tool_default_scope(self):
|
|
179
|
+
self.new_tool.access_scope = None
|
|
180
|
+
self.created_tool = self.__create_tool()
|
|
181
|
+
|
|
182
|
+
self.assertEqual(self.created_tool.access_scope, "private", "Expected the default access scope to be 'private' when not specified")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_create_tool_no_public_name(self):
|
|
186
|
+
test_params = [ True, False ]
|
|
187
|
+
|
|
188
|
+
for auto_publish in test_params:
|
|
189
|
+
|
|
190
|
+
with self.subTest(input=auto_publish):
|
|
191
|
+
self.new_tool.access_scope = "public"
|
|
192
|
+
self.new_tool.public_name = None
|
|
193
|
+
with self.assertRaises(APIError) as exception:
|
|
194
|
+
self.__create_tool(automatic_publish=auto_publish)
|
|
195
|
+
self.assertIn(
|
|
196
|
+
"Tool publicName is required for tools with accessScope=public.",
|
|
197
|
+
str(exception.exception),
|
|
198
|
+
f"Expected an error about missing publicName for public access scope with autopublish {'enabled' if auto_publish else 'disabled'}"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_create_tool_invalid_public_name(self):
|
|
203
|
+
test_params = [ True, False ]
|
|
204
|
+
|
|
205
|
+
for auto_publish in test_params:
|
|
206
|
+
with self.subTest(input=auto_publish):
|
|
207
|
+
self.new_tool.access_scope = "public"
|
|
208
|
+
self.new_tool.public_name = "com.sdk.testing#" # Add invalid character to public name
|
|
209
|
+
with self.assertRaises(APIError) as exception:
|
|
210
|
+
self.__create_tool(automatic_publish=auto_publish)
|
|
211
|
+
|
|
212
|
+
self.assertIn(
|
|
213
|
+
"Invalid public name, it can only contain lowercase letters, numbers, periods (.), dashes (-), and underscores (_). Please remove any other characters.",
|
|
214
|
+
str(exception.exception),
|
|
215
|
+
f"The expected error about invalid publicName was not returned when autopublish is {'enabled' if auto_publish else 'disabled'}"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_create_tool_duplicated_public_name(self):
|
|
220
|
+
test_params = [ True, False ]
|
|
221
|
+
|
|
222
|
+
# Set the scope as public and assign a public name
|
|
223
|
+
self.new_tool.access_scope = "public"
|
|
224
|
+
self.new_tool.public_name=f"public_{self.new_tool.name}"
|
|
225
|
+
self.created_tool = self.__create_tool()
|
|
226
|
+
|
|
227
|
+
for auto_publish in test_params:
|
|
228
|
+
with self.subTest(input=auto_publish):
|
|
229
|
+
|
|
230
|
+
# Create a new with the same public name of created_tool
|
|
231
|
+
duplicated_pn_tool = self.__load_tool()
|
|
232
|
+
duplicated_pn_tool.access_scope = "public"
|
|
233
|
+
duplicated_pn_tool.public_name = self.created_tool.public_name
|
|
234
|
+
|
|
235
|
+
with self.assertRaises(APIError) as exception:
|
|
236
|
+
self.__create_tool(tool=duplicated_pn_tool, automatic_publish=auto_publish)
|
|
237
|
+
self.assertIn(
|
|
238
|
+
f"Tool already exists [publicName={self.created_tool.public_name}].",
|
|
239
|
+
str(exception.exception),
|
|
240
|
+
f"Expected an error about the duplicated public name when autopublish is {'enabled' if auto_publish else 'disabled'}"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_create_tool_api_scope_with_no_open_api(self):
|
|
245
|
+
self.new_tool.scope = "api"
|
|
246
|
+
with self.assertRaises(ValueError) as exception:
|
|
247
|
+
self.new_tool.openApi = ""
|
|
248
|
+
self.assertIn(
|
|
249
|
+
'"Tool" object has no field "openApi"',
|
|
250
|
+
str(exception.exception),
|
|
251
|
+
"Expected a validation error when openApi is not provided for api scope"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_create_tool_api_scope_with_no_open_api_json(self):
|
|
256
|
+
self.new_tool.scope = "api"
|
|
257
|
+
with self.assertRaises(ValueError) as exception:
|
|
258
|
+
self.new_tool.openApiJson = ""
|
|
259
|
+
|
|
260
|
+
self.assertIn(
|
|
261
|
+
'"Tool" object has no field "openApiJson"',
|
|
262
|
+
str(exception.exception),
|
|
263
|
+
"Expected a validation error when openApiJson is not provided for api scope"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_create_tool_invalid_scope(self):
|
|
268
|
+
test_params = [ True, False ]
|
|
269
|
+
|
|
270
|
+
for auto_publish in test_params:
|
|
271
|
+
with self.subTest(input=auto_publish):
|
|
272
|
+
|
|
273
|
+
with self.assertRaises(ValueError) as exception:
|
|
274
|
+
self.new_tool.scope = "source"
|
|
275
|
+
self.__create_tool()
|
|
276
|
+
self.assertIn(
|
|
277
|
+
'Scope must be one of builtin, external, api, proxied',
|
|
278
|
+
str(exception.exception),
|
|
279
|
+
"Expected a validation error about allowed values for instructions"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def test_create_tool_autopublish(self):
|
|
284
|
+
self.created_tool = self.__create_tool(automatic_publish=True)
|
|
285
|
+
self.assertFalse(self.created_tool.is_draft, "Expected the tool to be published automatically")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_create_tool_autopublish_private_scope(self):
|
|
289
|
+
self.new_tool.access_scope = "private"
|
|
290
|
+
|
|
291
|
+
self.created_tool = self.__create_tool(automatic_publish=True)
|
|
292
|
+
self.assertFalse(self.created_tool.is_draft, "Expected the tool to be published automatically even with private scope")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
import uuid
|
|
3
|
+
from pygeai.lab.managers import AILabManager
|
|
4
|
+
from pygeai.lab.models import Tool, AgentData, Prompt, LlmConfig, Model
|
|
5
|
+
from pygeai.core.common.exceptions import MissingRequirementException, InvalidAPIResponseException
|
|
6
|
+
|
|
7
|
+
ai_lab_manager: AILabManager
|
|
8
|
+
|
|
9
|
+
class TestAILabDeleteToolIntegration(TestCase):
|
|
10
|
+
|
|
11
|
+
def setUp(self):
|
|
12
|
+
self.ai_lab_manager = AILabManager(alias="beta")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __create_tool(self):
|
|
16
|
+
"""
|
|
17
|
+
Helper to create a tool
|
|
18
|
+
"""
|
|
19
|
+
tool = Tool(
|
|
20
|
+
name=str(uuid.uuid4()),
|
|
21
|
+
description="Agent that translates from any language to english.",
|
|
22
|
+
scope="builtin"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
return self.ai_lab_manager.create_tool(
|
|
27
|
+
tool=tool
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def __delete_tool(self, tool_id: str = None, tool_name: str = None):
|
|
31
|
+
return self.ai_lab_manager.delete_tool(tool_id=tool_id, tool_name=tool_name)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_delete_tool_by_id(self):
|
|
35
|
+
created_tool = self.__create_tool()
|
|
36
|
+
deleted_tool = self.__delete_tool(tool_id=created_tool.id)
|
|
37
|
+
|
|
38
|
+
self.assertEqual(
|
|
39
|
+
deleted_tool.content,
|
|
40
|
+
"Tool deleted successfully",
|
|
41
|
+
"Expected confirmation message after deletion"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_delete_tool_by_name(self):
|
|
46
|
+
created_tool = self.__create_tool()
|
|
47
|
+
deleted_tool = self.__delete_tool(tool_name=created_tool.name)
|
|
48
|
+
|
|
49
|
+
self.assertEqual(
|
|
50
|
+
deleted_tool.content,
|
|
51
|
+
"Tool deleted successfully",
|
|
52
|
+
"Expected confirmation message after deletion"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_delete_tool_no_id_nor_name(self):
|
|
57
|
+
with self.assertRaises(MissingRequirementException) as exception:
|
|
58
|
+
self.__delete_tool()
|
|
59
|
+
self.assertIn(
|
|
60
|
+
"Either tool_id or tool_name must be provided",
|
|
61
|
+
str(exception.exception),
|
|
62
|
+
"Expected error message when neither tool_id nor tool_name is provided"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_delete_tool_invalid_id_valid_name(self):
|
|
67
|
+
invalid_id = "0026e53d-ea78-4cac-af9f-12650invalid"
|
|
68
|
+
created_tool = self.__create_tool()
|
|
69
|
+
with self.assertRaises(InvalidAPIResponseException) as exception:
|
|
70
|
+
self.__delete_tool(tool_name=created_tool.name, tool_id=invalid_id)
|
|
71
|
+
|
|
72
|
+
self.assertIn(
|
|
73
|
+
f"Tool not found [IdOrName= {invalid_id}].",
|
|
74
|
+
str(exception.exception),
|
|
75
|
+
"Expected error message for valid tool name and invalid tool id"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_delete_tool_invalid_name_valid_id(self):
|
|
80
|
+
created_tool = self.__create_tool()
|
|
81
|
+
deleted_tool = self.__delete_tool(tool_id=created_tool.id, tool_name="toolName")
|
|
82
|
+
|
|
83
|
+
self.assertEqual(
|
|
84
|
+
deleted_tool.content,
|
|
85
|
+
"Tool deleted successfully",
|
|
86
|
+
"Expected confirmation message after deletion"
|
|
87
|
+
)
|