pygeai 0.2.7b51__py3-none-any.whl → 0.2.7b53__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/chat/clients.py +47 -1
- pygeai/chat/endpoints.py +1 -0
- pygeai/cli/commands/chat.py +95 -1
- pygeai/cli/commands/configuration.py +26 -1
- pygeai/cli/commands/lab/ai_lab.py +191 -0
- pygeai/core/common/config.py +11 -0
- pygeai/lab/agents/clients.py +80 -1
- pygeai/lab/agents/endpoints.py +2 -0
- pygeai/lab/models.py +4 -0
- pygeai/lab/tools/clients.py +42 -2
- pygeai/lab/tools/endpoints.py +1 -0
- pygeai/proxy/servers.py +1 -0
- pygeai/proxy/tool.py +6 -3
- pygeai/tests/proxy/__init__.py +1 -0
- pygeai/tests/proxy/test_clients.py +395 -0
- pygeai/tests/proxy/test_config.py +171 -0
- pygeai/tests/proxy/test_integration.py +304 -0
- pygeai/tests/proxy/test_managers.py +312 -0
- pygeai/tests/proxy/test_servers.py +387 -0
- pygeai/tests/proxy/test_tool.py +176 -0
- pygeai/tests/snippets/lab/tools/create_tool.py +1 -2
- {pygeai-0.2.7b51.dist-info → pygeai-0.2.7b53.dist-info}/METADATA +1 -1
- {pygeai-0.2.7b51.dist-info → pygeai-0.2.7b53.dist-info}/RECORD +27 -20
- {pygeai-0.2.7b51.dist-info → pygeai-0.2.7b53.dist-info}/WHEEL +0 -0
- {pygeai-0.2.7b51.dist-info → pygeai-0.2.7b53.dist-info}/entry_points.txt +0 -0
- {pygeai-0.2.7b51.dist-info → pygeai-0.2.7b53.dist-info}/licenses/LICENSE +0 -0
- {pygeai-0.2.7b51.dist-info → pygeai-0.2.7b53.dist-info}/top_level.txt +0 -0
pygeai/chat/clients.py
CHANGED
|
@@ -3,7 +3,7 @@ from json import JSONDecodeError
|
|
|
3
3
|
from typing import List, Dict, Optional, Union, Generator
|
|
4
4
|
|
|
5
5
|
from pygeai import logger
|
|
6
|
-
from pygeai.chat.endpoints import CHAT_V1
|
|
6
|
+
from pygeai.chat.endpoints import CHAT_V1, GENERATE_IMAGE_V1
|
|
7
7
|
from pygeai.core.base.clients import BaseClient
|
|
8
8
|
from pygeai.core.common.exceptions import InvalidAPIResponseException
|
|
9
9
|
|
|
@@ -193,3 +193,49 @@ class ChatClient(BaseClient):
|
|
|
193
193
|
continue
|
|
194
194
|
except Exception as e:
|
|
195
195
|
raise InvalidAPIResponseException(f"Unable to process streaming chat response: {e}")
|
|
196
|
+
|
|
197
|
+
def generate_image(
|
|
198
|
+
self,
|
|
199
|
+
model: str,
|
|
200
|
+
prompt: str,
|
|
201
|
+
n: int,
|
|
202
|
+
quality: str,
|
|
203
|
+
size: str,
|
|
204
|
+
aspect_ratio: Optional[str] = None
|
|
205
|
+
) -> dict:
|
|
206
|
+
"""
|
|
207
|
+
Generates an image based on the provided parameters.
|
|
208
|
+
|
|
209
|
+
:param model: str - The model specification for image generation, e.g., "openai/gpt-image-1". (Required)
|
|
210
|
+
:param prompt: str - Description of the desired image. (Required)
|
|
211
|
+
:param n: int - Number of images to generate (1-10, depending on the model). (Required)
|
|
212
|
+
:param quality: str - Rendering quality, e.g., "high". (Required)
|
|
213
|
+
:param size: str - Image dimensions, e.g., "1024x1024". (Required)
|
|
214
|
+
:param aspect_ratio: Optional[str] - Relationship between image’s width and height, e.g., "1:1", "9:16", "16:9", "3:4", "4:3". (Optional)
|
|
215
|
+
:return: dict - The API response containing the generated image data.
|
|
216
|
+
:raises InvalidAPIResponseException: If the API response cannot be processed.
|
|
217
|
+
"""
|
|
218
|
+
data = {
|
|
219
|
+
'model': model,
|
|
220
|
+
'prompt': prompt,
|
|
221
|
+
'n': n,
|
|
222
|
+
'quality': quality,
|
|
223
|
+
'size': size
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if aspect_ratio:
|
|
227
|
+
data['aspect_ratio'] = aspect_ratio
|
|
228
|
+
|
|
229
|
+
logger.debug(f"Generating image with data: {data}")
|
|
230
|
+
|
|
231
|
+
response = self.api_service.post(
|
|
232
|
+
endpoint=GENERATE_IMAGE_V1,
|
|
233
|
+
data=data
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
result = response.json()
|
|
238
|
+
logger.debug(f"Image generation result: {result}")
|
|
239
|
+
return result
|
|
240
|
+
except JSONDecodeError as e:
|
|
241
|
+
raise InvalidAPIResponseException(f"Unable to process image generation request: {response.text}")
|
pygeai/chat/endpoints.py
CHANGED
pygeai/cli/commands/chat.py
CHANGED
|
@@ -565,6 +565,92 @@ chat_with_agent_options = [
|
|
|
565
565
|
]
|
|
566
566
|
|
|
567
567
|
|
|
568
|
+
def get_generate_image(option_list: list):
|
|
569
|
+
model = None
|
|
570
|
+
prompt = None
|
|
571
|
+
n = None
|
|
572
|
+
quality = None
|
|
573
|
+
size = None
|
|
574
|
+
aspect_ratio = None
|
|
575
|
+
|
|
576
|
+
for option_flag, option_arg in option_list:
|
|
577
|
+
if option_flag.name == "model":
|
|
578
|
+
model = option_arg
|
|
579
|
+
if option_flag.name == "prompt":
|
|
580
|
+
prompt = option_arg
|
|
581
|
+
if option_flag.name == "n":
|
|
582
|
+
try:
|
|
583
|
+
n = int(option_arg)
|
|
584
|
+
if n < 1 or n > 10:
|
|
585
|
+
raise WrongArgumentError("n must be an integer between 1 and 10.")
|
|
586
|
+
except ValueError:
|
|
587
|
+
raise WrongArgumentError("n must be a valid integer.")
|
|
588
|
+
if option_flag.name == "quality":
|
|
589
|
+
quality = option_arg
|
|
590
|
+
if option_flag.name == "size":
|
|
591
|
+
size = option_arg
|
|
592
|
+
if option_flag.name == "aspect_ratio":
|
|
593
|
+
aspect_ratio = option_arg
|
|
594
|
+
|
|
595
|
+
if not (model and prompt and n is not None and quality and size):
|
|
596
|
+
raise MissingRequirementException("Cannot generate image without specifying model, prompt, n, quality, and size.")
|
|
597
|
+
|
|
598
|
+
client = ChatClient()
|
|
599
|
+
try:
|
|
600
|
+
result = client.generate_image(
|
|
601
|
+
model=model,
|
|
602
|
+
prompt=prompt,
|
|
603
|
+
n=n,
|
|
604
|
+
quality=quality,
|
|
605
|
+
size=size,
|
|
606
|
+
aspect_ratio=aspect_ratio
|
|
607
|
+
)
|
|
608
|
+
Console.write_stdout(f"Image generation result: \n{result}\n")
|
|
609
|
+
except Exception as e:
|
|
610
|
+
logger.error(f"Error generating image: {e}")
|
|
611
|
+
Console.write_stderr(f"Failed to generate image: {e}")
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
generate_image_options = [
|
|
615
|
+
Option(
|
|
616
|
+
"model",
|
|
617
|
+
["--model", "-m"],
|
|
618
|
+
"The model specification for image generation, e.g., 'openai/gpt-image-1'.",
|
|
619
|
+
True
|
|
620
|
+
),
|
|
621
|
+
Option(
|
|
622
|
+
"prompt",
|
|
623
|
+
["--prompt", "-p"],
|
|
624
|
+
"Description of the desired image.",
|
|
625
|
+
True
|
|
626
|
+
),
|
|
627
|
+
Option(
|
|
628
|
+
"n",
|
|
629
|
+
["--n"],
|
|
630
|
+
"Number of images to generate (1-10, depending on the model).",
|
|
631
|
+
True
|
|
632
|
+
),
|
|
633
|
+
Option(
|
|
634
|
+
"quality",
|
|
635
|
+
["--quality", "-q"],
|
|
636
|
+
"Rendering quality, e.g., 'high'.",
|
|
637
|
+
True
|
|
638
|
+
),
|
|
639
|
+
Option(
|
|
640
|
+
"size",
|
|
641
|
+
["--size", "-s"],
|
|
642
|
+
"Image dimensions, e.g., '1024x1024'.",
|
|
643
|
+
True
|
|
644
|
+
),
|
|
645
|
+
Option(
|
|
646
|
+
"aspect_ratio",
|
|
647
|
+
["--aspect-ratio", "-ar"],
|
|
648
|
+
"Relationship between image’s width and height, e.g., '1:1', '9:16', '16:9', '3:4', '4:3'.",
|
|
649
|
+
True
|
|
650
|
+
),
|
|
651
|
+
]
|
|
652
|
+
|
|
653
|
+
|
|
568
654
|
chat_commands = [
|
|
569
655
|
Command(
|
|
570
656
|
"help",
|
|
@@ -602,6 +688,14 @@ chat_commands = [
|
|
|
602
688
|
[],
|
|
603
689
|
chat_with_agent_options
|
|
604
690
|
),
|
|
605
|
-
|
|
691
|
+
Command(
|
|
692
|
+
"generate_image",
|
|
693
|
+
["generate-image", "gen-img"],
|
|
694
|
+
"Generate an image using the specified model and parameters",
|
|
695
|
+
get_generate_image,
|
|
696
|
+
ArgumentsEnum.REQUIRED,
|
|
697
|
+
[],
|
|
698
|
+
generate_image_options
|
|
699
|
+
),
|
|
606
700
|
|
|
607
701
|
]
|
|
@@ -23,7 +23,8 @@ def configure(option_list: list[str, str] = None):
|
|
|
23
23
|
configure_eval_url(eval_url, alias)
|
|
24
24
|
|
|
25
25
|
else:
|
|
26
|
-
list_alias =
|
|
26
|
+
list_alias = False
|
|
27
|
+
remove_alias = False
|
|
27
28
|
api_key = None
|
|
28
29
|
base_url = None
|
|
29
30
|
eval_url = None
|
|
@@ -40,9 +41,15 @@ def configure(option_list: list[str, str] = None):
|
|
|
40
41
|
base_url = option_arg
|
|
41
42
|
if option_flag.name == "eval_url":
|
|
42
43
|
eval_url = option_arg
|
|
44
|
+
if option_flag.name == "remove_alias":
|
|
45
|
+
alias = option_arg
|
|
46
|
+
remove_alias = True
|
|
47
|
+
|
|
43
48
|
|
|
44
49
|
if list_alias:
|
|
45
50
|
display_alias_list()
|
|
51
|
+
elif remove_alias:
|
|
52
|
+
remove_alias_from_config(alias)
|
|
46
53
|
else:
|
|
47
54
|
if api_key:
|
|
48
55
|
configure_api_key(api_key=api_key, alias=alias)
|
|
@@ -76,6 +83,17 @@ def display_alias_list():
|
|
|
76
83
|
Console.write_stdout(f"Alias: {alias} -> Base URL: {url}")
|
|
77
84
|
|
|
78
85
|
|
|
86
|
+
def remove_alias_from_config(alias: str):
|
|
87
|
+
delete_confirmed = str(input(f"-> Are you sure you want to delete {alias} from config file? (y/N) "))
|
|
88
|
+
if delete_confirmed.lower() in ["yes", "y"]:
|
|
89
|
+
settings = get_settings()
|
|
90
|
+
settings.remove_alias(alias)
|
|
91
|
+
Console.write_stdout(f"Alias {alias} removed from configuration file.")
|
|
92
|
+
else:
|
|
93
|
+
Console.write_stdout(f"Alias {alias} kept in configuration file.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
79
97
|
configuration_options = (
|
|
80
98
|
Option(
|
|
81
99
|
"api_key",
|
|
@@ -107,4 +125,11 @@ configuration_options = (
|
|
|
107
125
|
"List available alias",
|
|
108
126
|
False
|
|
109
127
|
),
|
|
128
|
+
Option(
|
|
129
|
+
"remove_alias",
|
|
130
|
+
["--remove-alias", "--ra"],
|
|
131
|
+
"Remove selected alias",
|
|
132
|
+
True
|
|
133
|
+
),
|
|
134
|
+
|
|
110
135
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
|
|
3
|
+
from pygeai import logger
|
|
3
4
|
from pygeai.cli.commands import Command, Option, ArgumentsEnum
|
|
4
5
|
from pygeai.cli.commands.builders import build_help_text
|
|
5
6
|
from pygeai.cli.commands.common import get_boolean_value
|
|
@@ -450,6 +451,111 @@ get_agent_options = [
|
|
|
450
451
|
]
|
|
451
452
|
|
|
452
453
|
|
|
454
|
+
def export_agent(option_list: list):
|
|
455
|
+
project_id = None
|
|
456
|
+
agent_id = None
|
|
457
|
+
file = None
|
|
458
|
+
|
|
459
|
+
for option_flag, option_arg in option_list:
|
|
460
|
+
if option_flag.name == "project_id":
|
|
461
|
+
project_id = option_arg
|
|
462
|
+
if option_flag.name == "agent_id":
|
|
463
|
+
agent_id = option_arg
|
|
464
|
+
if option_flag.name == "file":
|
|
465
|
+
file = option_arg
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
if not (project_id and agent_id):
|
|
469
|
+
raise MissingRequirementException("Project ID and Agent id must be specified.")
|
|
470
|
+
|
|
471
|
+
client = AgentClient()
|
|
472
|
+
result = client.export_agent(
|
|
473
|
+
project_id=project_id,
|
|
474
|
+
agent_id=agent_id,
|
|
475
|
+
)
|
|
476
|
+
if file:
|
|
477
|
+
try:
|
|
478
|
+
data = json.loads(result) if isinstance(result, str) else result
|
|
479
|
+
with open(file, "w") as f:
|
|
480
|
+
json.dump(data, f, indent=4)
|
|
481
|
+
Console.write_stdout(f"Result from API saved to {file}.")
|
|
482
|
+
except json.JSONDecodeError as e:
|
|
483
|
+
logger.error(f"Result from API endpoint is not in JSON format: {e}")
|
|
484
|
+
Console.write_stderr(f"Result from API endpoint is not in JSON format.")
|
|
485
|
+
else:
|
|
486
|
+
Console.write_stdout(f"Agent spec: \n{result}")
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
export_agent_options = [
|
|
491
|
+
Option(
|
|
492
|
+
"project_id",
|
|
493
|
+
["--project-id", "--pid"],
|
|
494
|
+
"ID of the project",
|
|
495
|
+
True
|
|
496
|
+
),
|
|
497
|
+
Option(
|
|
498
|
+
"agent_id",
|
|
499
|
+
["--agent-id", "--aid"],
|
|
500
|
+
"ID of the agent to retrieve",
|
|
501
|
+
True
|
|
502
|
+
),
|
|
503
|
+
Option(
|
|
504
|
+
"file",
|
|
505
|
+
["--file", "-f"],
|
|
506
|
+
"File path to save export specification for agent",
|
|
507
|
+
True
|
|
508
|
+
),
|
|
509
|
+
]
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def import_agent(option_list: list):
|
|
513
|
+
project_id = None
|
|
514
|
+
file = None
|
|
515
|
+
|
|
516
|
+
for option_flag, option_arg in option_list:
|
|
517
|
+
if option_flag.name == "project_id":
|
|
518
|
+
project_id = option_arg
|
|
519
|
+
if option_flag.name == "file":
|
|
520
|
+
file = option_arg
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
if not (project_id and file):
|
|
524
|
+
raise MissingRequirementException("Project ID and file path to spec must be specified.")
|
|
525
|
+
|
|
526
|
+
try:
|
|
527
|
+
with open(file, "r") as f:
|
|
528
|
+
agent_data = json.load(f)
|
|
529
|
+
except json.JSONDecodeError as e:
|
|
530
|
+
logger.error(f"File is not in JSON format: {e}")
|
|
531
|
+
Console.write_stderr(f"File is not in JSON format.")
|
|
532
|
+
|
|
533
|
+
client = AgentClient()
|
|
534
|
+
result = client.import_agent(
|
|
535
|
+
project_id=project_id,
|
|
536
|
+
data=agent_data
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
Console.write_stdout(f"Agent import details: {result}")
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
import_agent_options = [
|
|
544
|
+
Option(
|
|
545
|
+
"project_id",
|
|
546
|
+
["--project-id", "--pid"],
|
|
547
|
+
"ID of the project",
|
|
548
|
+
True
|
|
549
|
+
),
|
|
550
|
+
Option(
|
|
551
|
+
"file",
|
|
552
|
+
["--file", "-f"],
|
|
553
|
+
"File path to save export specification for agent",
|
|
554
|
+
True
|
|
555
|
+
),
|
|
556
|
+
]
|
|
557
|
+
|
|
558
|
+
|
|
453
559
|
def create_sharing_link(option_list: list):
|
|
454
560
|
project_id = None
|
|
455
561
|
agent_id = None
|
|
@@ -1188,6 +1294,64 @@ get_tool_options = [
|
|
|
1188
1294
|
]
|
|
1189
1295
|
|
|
1190
1296
|
|
|
1297
|
+
def export_tool(option_list: list):
|
|
1298
|
+
project_id = None
|
|
1299
|
+
tool_id = None
|
|
1300
|
+
file = None
|
|
1301
|
+
|
|
1302
|
+
for option_flag, option_arg in option_list:
|
|
1303
|
+
if option_flag.name == "project_id":
|
|
1304
|
+
project_id = option_arg
|
|
1305
|
+
if option_flag.name == "tool_id":
|
|
1306
|
+
tool_id = option_arg
|
|
1307
|
+
if option_flag.name == "file":
|
|
1308
|
+
file = option_arg
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
if not (project_id and tool_id):
|
|
1312
|
+
raise MissingRequirementException("Project ID and Tool id must be specified.")
|
|
1313
|
+
|
|
1314
|
+
client = ToolClient()
|
|
1315
|
+
result = client.export_tool(
|
|
1316
|
+
project_id=project_id,
|
|
1317
|
+
tool_id=tool_id,
|
|
1318
|
+
)
|
|
1319
|
+
Console.write_stdout(f"Tool spec: \n{result}")
|
|
1320
|
+
if file:
|
|
1321
|
+
try:
|
|
1322
|
+
data = json.loads(result) if isinstance(result, str) else result
|
|
1323
|
+
with open(file, "w") as f:
|
|
1324
|
+
json.dump(data, f, indent=4)
|
|
1325
|
+
Console.write_stdout(f"Result from API saved to {file}.")
|
|
1326
|
+
except json.JSONDecodeError as e:
|
|
1327
|
+
logger.error(f"Result from API endpoint is not in JSON format: {e}")
|
|
1328
|
+
Console.write_stderr(f"Result from API endpoint is not in JSON format.")
|
|
1329
|
+
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
export_tool_options = [
|
|
1333
|
+
Option(
|
|
1334
|
+
"project_id",
|
|
1335
|
+
["--project-id", "--pid"],
|
|
1336
|
+
"ID of the project",
|
|
1337
|
+
True
|
|
1338
|
+
),
|
|
1339
|
+
Option(
|
|
1340
|
+
"tool_id",
|
|
1341
|
+
["--tool-id", "--tid"],
|
|
1342
|
+
"ID of the tool to retrieve",
|
|
1343
|
+
True
|
|
1344
|
+
),
|
|
1345
|
+
Option(
|
|
1346
|
+
"file",
|
|
1347
|
+
["--file", "-f"],
|
|
1348
|
+
"File path to save export specification for tool",
|
|
1349
|
+
True
|
|
1350
|
+
),
|
|
1351
|
+
]
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
|
|
1191
1355
|
def delete_tool(option_list: list):
|
|
1192
1356
|
project_id = None
|
|
1193
1357
|
tool_id = None
|
|
@@ -3885,6 +4049,24 @@ ai_lab_commands = [
|
|
|
3885
4049
|
[],
|
|
3886
4050
|
get_agent_options
|
|
3887
4051
|
),
|
|
4052
|
+
Command(
|
|
4053
|
+
"export_agent",
|
|
4054
|
+
["export-agent", "ea"],
|
|
4055
|
+
"Export agent",
|
|
4056
|
+
export_agent,
|
|
4057
|
+
ArgumentsEnum.REQUIRED,
|
|
4058
|
+
[],
|
|
4059
|
+
export_agent_options
|
|
4060
|
+
),
|
|
4061
|
+
Command(
|
|
4062
|
+
"import_agent",
|
|
4063
|
+
["import-agent", "ia"],
|
|
4064
|
+
"Import agent",
|
|
4065
|
+
import_agent,
|
|
4066
|
+
ArgumentsEnum.REQUIRED,
|
|
4067
|
+
[],
|
|
4068
|
+
import_agent_options
|
|
4069
|
+
),
|
|
3888
4070
|
Command(
|
|
3889
4071
|
"create_sharing_link",
|
|
3890
4072
|
["create-sharing-link", "csl"],
|
|
@@ -3949,6 +4131,15 @@ ai_lab_commands = [
|
|
|
3949
4131
|
[],
|
|
3950
4132
|
get_tool_options
|
|
3951
4133
|
),
|
|
4134
|
+
#Command(
|
|
4135
|
+
# "export_tool",
|
|
4136
|
+
# ["export-tool", "et"],
|
|
4137
|
+
# "Export tool",
|
|
4138
|
+
# export_tool,
|
|
4139
|
+
# ArgumentsEnum.REQUIRED,
|
|
4140
|
+
# [],
|
|
4141
|
+
# export_tool_options
|
|
4142
|
+
#),
|
|
3952
4143
|
Command(
|
|
3953
4144
|
"delete_tool",
|
|
3954
4145
|
["delete-tool", "dt"],
|
pygeai/core/common/config.py
CHANGED
|
@@ -95,6 +95,17 @@ class SettingsManager:
|
|
|
95
95
|
for section in self.config.sections()
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
def remove_alias(self, alias: str):
|
|
99
|
+
"""Removes a specific alias and its settings from the credentials file."""
|
|
100
|
+
if alias in self.config:
|
|
101
|
+
self.config.remove_section(alias)
|
|
102
|
+
with self.GEAI_CREDS_FILE.open("w") as file:
|
|
103
|
+
self.config.write(file)
|
|
104
|
+
logger.info(f"Alias '{alias}' removed from the credentials file.")
|
|
105
|
+
else:
|
|
106
|
+
logger.warning(f"Alias '{alias}' not found in the credentials file.")
|
|
107
|
+
|
|
108
|
+
|
|
98
109
|
|
|
99
110
|
@lru_cache()
|
|
100
111
|
def get_settings():
|
pygeai/lab/agents/clients.py
CHANGED
|
@@ -5,7 +5,7 @@ from pygeai import logger
|
|
|
5
5
|
from pygeai.core.base.clients import BaseClient
|
|
6
6
|
from pygeai.core.common.exceptions import MissingRequirementException, InvalidAPIResponseException
|
|
7
7
|
from pygeai.lab.agents.endpoints import CREATE_AGENT_V2, LIST_AGENTS_V2, GET_AGENT_V2, CREATE_SHARING_LINK_V2, \
|
|
8
|
-
PUBLISH_AGENT_REVISION_V2, DELETE_AGENT_V2, UPDATE_AGENT_V2, UPSERT_AGENT_V2
|
|
8
|
+
PUBLISH_AGENT_REVISION_V2, DELETE_AGENT_V2, UPDATE_AGENT_V2, UPSERT_AGENT_V2, EXPORT_AGENT_V2, IMPORT_AGENT_V2
|
|
9
9
|
from pygeai.lab.constants import VALID_ACCESS_SCOPES
|
|
10
10
|
|
|
11
11
|
|
|
@@ -409,3 +409,82 @@ class AgentClient(BaseClient):
|
|
|
409
409
|
raise InvalidAPIResponseException(f"Unable to update agent {agent_id} in project {project_id}: {response.text}")
|
|
410
410
|
|
|
411
411
|
return result
|
|
412
|
+
|
|
413
|
+
def export_agent(
|
|
414
|
+
self,
|
|
415
|
+
project_id: str,
|
|
416
|
+
agent_id: str,
|
|
417
|
+
) -> dict:
|
|
418
|
+
"""
|
|
419
|
+
Retrieves details of a specific agent from the specified project.
|
|
420
|
+
|
|
421
|
+
:param project_id: str - Unique identifier of the project containing the agent.
|
|
422
|
+
:param agent_id: str - Unique identifier of the agent to retrieve.
|
|
423
|
+
:return: dict - JSON response containing the agent details.
|
|
424
|
+
:raises InvalidAPIResponseException: If the response cannot be parsed as JSON or an error occurs.
|
|
425
|
+
:raises MissingRequirementException: If project_id or agent_id is not provided.
|
|
426
|
+
"""
|
|
427
|
+
if not project_id:
|
|
428
|
+
raise MissingRequirementException("Cannot export agent without specifying project_id")
|
|
429
|
+
|
|
430
|
+
if not agent_id:
|
|
431
|
+
raise MissingRequirementException("agent_id must be specified in order to export the agent")
|
|
432
|
+
|
|
433
|
+
endpoint = EXPORT_AGENT_V2.format(agentId=agent_id)
|
|
434
|
+
headers = {
|
|
435
|
+
"Authorization": self.api_service.token,
|
|
436
|
+
"ProjectId": project_id
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
logger.debug(f"Exporting agent with ID {agent_id}")
|
|
440
|
+
|
|
441
|
+
response = self.api_service.get(
|
|
442
|
+
endpoint=endpoint,
|
|
443
|
+
headers=headers,
|
|
444
|
+
)
|
|
445
|
+
try:
|
|
446
|
+
result = response.json()
|
|
447
|
+
except JSONDecodeError as e:
|
|
448
|
+
logger.error(f"Unable to export agent {agent_id} for project {project_id}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
|
|
449
|
+
raise InvalidAPIResponseException(f"Unable to export agent {agent_id} for project {project_id}: {response.text}")
|
|
450
|
+
|
|
451
|
+
return result
|
|
452
|
+
|
|
453
|
+
def import_agent(
|
|
454
|
+
self,
|
|
455
|
+
project_id: str,
|
|
456
|
+
data: dict,
|
|
457
|
+
) -> dict:
|
|
458
|
+
"""
|
|
459
|
+
Retrieves details of a specific agent from the specified project.
|
|
460
|
+
|
|
461
|
+
:param project_id: str - Unique identifier of the project containing the agent.
|
|
462
|
+
:param data: dict - Agent specification to import
|
|
463
|
+
:return: dict - JSON response containing the agent details.
|
|
464
|
+
:raises InvalidAPIResponseException: If the response cannot be parsed as JSON or an error occurs.
|
|
465
|
+
:raises MissingRequirementException: If project_id or agent_id is not provided.
|
|
466
|
+
"""
|
|
467
|
+
if not project_id:
|
|
468
|
+
raise MissingRequirementException("Cannot import agent without specifying project_id")
|
|
469
|
+
|
|
470
|
+
if not data:
|
|
471
|
+
raise MissingRequirementException("data for spec must be specified in order to import the agent")
|
|
472
|
+
|
|
473
|
+
endpoint = IMPORT_AGENT_V2
|
|
474
|
+
headers = {
|
|
475
|
+
"Authorization": self.api_service.token,
|
|
476
|
+
"ProjectId": project_id
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
response = self.api_service.post(
|
|
480
|
+
endpoint=endpoint,
|
|
481
|
+
headers=headers,
|
|
482
|
+
data=data
|
|
483
|
+
)
|
|
484
|
+
try:
|
|
485
|
+
result = response.json()
|
|
486
|
+
except JSONDecodeError as e:
|
|
487
|
+
logger.error(f"Unable to import agent for project {project_id}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
|
|
488
|
+
raise InvalidAPIResponseException(f"Unable to import agent for project {project_id}: {response.text}")
|
|
489
|
+
|
|
490
|
+
return result
|
pygeai/lab/agents/endpoints.py
CHANGED
|
@@ -6,3 +6,5 @@ PUBLISH_AGENT_REVISION_V2 = "v2/agents/{agentId}/publish-revision" # POST -> Pu
|
|
|
6
6
|
DELETE_AGENT_V2 = "v2/agents/{agentId}" # DEL -> Delete agent
|
|
7
7
|
UPDATE_AGENT_V2 = "v2/agents/{agentId}" # PUT -> Update agent
|
|
8
8
|
UPSERT_AGENT_V2 = "v2/agents/{agentId}/upsert" # PUT -> Update or Insert agent
|
|
9
|
+
EXPORT_AGENT_V2 = "v2/agents/{agentId}/export" # GET -> Export agent
|
|
10
|
+
IMPORT_AGENT_V2 = "v2/agents/import" # POST -> Import agent
|
pygeai/lab/models.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from typing import Literal, Optional, List, Dict, Any, Union, Iterator
|
|
2
3
|
|
|
3
4
|
from pydantic import model_validator, Field, field_validator
|
|
@@ -708,6 +709,9 @@ class Tool(CustomBaseModel):
|
|
|
708
709
|
raise ValueError("name cannot be blank")
|
|
709
710
|
if ":" in value or "/" in value:
|
|
710
711
|
raise ValueError("name cannot contain ':' or '/'")
|
|
712
|
+
if not re.match(r'^[A-Za-z0-9_-]{1,64}$', value):
|
|
713
|
+
raise ValueError(
|
|
714
|
+
"name must contain only letters, numbers, underscores, or hyphens, and be 1-64 characters long")
|
|
711
715
|
return value
|
|
712
716
|
|
|
713
717
|
@field_validator("public_name")
|
pygeai/lab/tools/clients.py
CHANGED
|
@@ -3,10 +3,10 @@ from json import JSONDecodeError
|
|
|
3
3
|
|
|
4
4
|
from pygeai import logger
|
|
5
5
|
from pygeai.core.base.clients import BaseClient
|
|
6
|
-
from pygeai.core.common.exceptions import InvalidAPIResponseException
|
|
6
|
+
from pygeai.core.common.exceptions import InvalidAPIResponseException, MissingRequirementException
|
|
7
7
|
from pygeai.lab.constants import VALID_SCOPES, VALID_ACCESS_SCOPES, VALID_REPORT_EVENTS
|
|
8
8
|
from pygeai.lab.tools.endpoints import CREATE_TOOL_V2, LIST_TOOLS_V2, GET_TOOL_V2, UPDATE_TOOL_V2, UPSERT_TOOL_V2, \
|
|
9
|
-
PUBLISH_TOOL_REVISION_V2, GET_PARAMETER_V2, SET_PARAMETER_V2, DELETE_TOOL_V2
|
|
9
|
+
PUBLISH_TOOL_REVISION_V2, GET_PARAMETER_V2, SET_PARAMETER_V2, DELETE_TOOL_V2, EXPORT_TOOL_V2
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class ToolClient(BaseClient):
|
|
@@ -525,3 +525,43 @@ class ToolClient(BaseClient):
|
|
|
525
525
|
raise InvalidAPIResponseException(f"Unable to set parameters for tool {tool_id or tool_public_name} in project {project_id}: {response.text}")
|
|
526
526
|
|
|
527
527
|
return result
|
|
528
|
+
|
|
529
|
+
def export_tool(
|
|
530
|
+
self,
|
|
531
|
+
project_id: str,
|
|
532
|
+
tool_id: str,
|
|
533
|
+
) -> dict:
|
|
534
|
+
"""
|
|
535
|
+
Retrieves details of a specific tool from the specified project.
|
|
536
|
+
|
|
537
|
+
:param project_id: str - Unique identifier of the project containing the tool.
|
|
538
|
+
:param tool_id: str - Unique identifier of the tool to retrieve.
|
|
539
|
+
:return: dict - JSON response containing the tool details.
|
|
540
|
+
:raises InvalidAPIResponseException: If the response cannot be parsed as JSON or an error occurs.
|
|
541
|
+
:raises MissingRequirementException: If project_id or tool_id is not provided.
|
|
542
|
+
"""
|
|
543
|
+
if not project_id:
|
|
544
|
+
raise MissingRequirementException("Cannot retrieve tool without specifying project_id")
|
|
545
|
+
|
|
546
|
+
if not tool_id:
|
|
547
|
+
raise MissingRequirementException("tool_id must be specified in order to retrieve the tool")
|
|
548
|
+
|
|
549
|
+
endpoint = EXPORT_TOOL_V2.format(toolId=tool_id)
|
|
550
|
+
headers = {
|
|
551
|
+
"Authorization": self.api_service.token,
|
|
552
|
+
"ProjectId": project_id
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
logger.debug(f"Exporting tool with ID {tool_id}")
|
|
556
|
+
|
|
557
|
+
response = self.api_service.get(
|
|
558
|
+
endpoint=endpoint,
|
|
559
|
+
headers=headers,
|
|
560
|
+
)
|
|
561
|
+
try:
|
|
562
|
+
result = response.json()
|
|
563
|
+
except JSONDecodeError as e:
|
|
564
|
+
logger.error(f"Unable to export tool {tool_id} for project {project_id}: JSON parsing error (status {response.status_code}): {e}. Response: {response.text}")
|
|
565
|
+
raise InvalidAPIResponseException(f"Unable to export tool {tool_id} for project {project_id}: {response.text}")
|
|
566
|
+
|
|
567
|
+
return result
|
pygeai/lab/tools/endpoints.py
CHANGED
|
@@ -7,3 +7,4 @@ UPSERT_TOOL_V2 = "v2/tools/{toolId}/upsert" # PUT -> Update or insert tool data
|
|
|
7
7
|
PUBLISH_TOOL_REVISION_V2 = "v2/tools/{toolId}/publish-revision" # POST -> Publish Tool revision
|
|
8
8
|
GET_PARAMETER_V2 = "v2/tools/{toolPublicName}/config" # GET -> Get tool parameter
|
|
9
9
|
SET_PARAMETER_V2 = "v2/tools/{toolPublicName}/config" # POST -> Set tool parameter
|
|
10
|
+
EXPORT_TOOL_V2 = "v2/tools/{toolId}/export" # GET -> Export tool
|
pygeai/proxy/servers.py
CHANGED
|
@@ -193,6 +193,7 @@ class MCPServer(ToolServer):
|
|
|
193
193
|
"""
|
|
194
194
|
def __init__(self, sever_name: str, config: Dict[str, Any], settings: ProxySettingsManager):
|
|
195
195
|
super().__init__(sever_name, config, settings)
|
|
196
|
+
self.public_prefix = config.get("public_prefix")
|
|
196
197
|
self.stdio_context: Any | None = None
|
|
197
198
|
self.session: ClientSession | None = None
|
|
198
199
|
|
pygeai/proxy/tool.py
CHANGED
|
@@ -40,9 +40,12 @@ class ProxiedTool:
|
|
|
40
40
|
|
|
41
41
|
def get_public_name(self) -> str:
|
|
42
42
|
"""Get the public name of the tool."""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if not self.public_prefix:
|
|
44
|
+
return ""
|
|
45
|
+
if self.server_name in self.public_prefix:
|
|
46
|
+
return f"{self.public_prefix}.{self.name}"
|
|
47
|
+
else:
|
|
48
|
+
return f"{self.public_prefix}.{self.get_full_name()}"
|
|
46
49
|
|
|
47
50
|
def format_for_llm(self) -> str:
|
|
48
51
|
"""Format tool information for LLM.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|