sapiopycommons 2025.10.2a773__py3-none-any.whl → 2025.10.9a777__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.
Potentially problematic release.
This version of sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/ai/{tool_service_base.py → agent_service_base.py} +161 -127
- sapiopycommons/ai/external_credentials.py +37 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.py +41 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.pyi +35 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/plan/script/script_pb2.py +17 -15
- sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi +10 -4
- sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py +29 -27
- sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi +14 -6
- sapiopycommons/ai/request_validation.py +40 -21
- sapiopycommons/ai/server.py +11 -11
- sapiopycommons/ai/test_client.py +26 -23
- sapiopycommons/files/temp_files.py +1 -1
- {sapiopycommons-2025.10.2a773.dist-info → sapiopycommons-2025.10.9a777.dist-info}/METADATA +1 -1
- {sapiopycommons-2025.10.2a773.dist-info → sapiopycommons-2025.10.9a777.dist-info}/RECORD +17 -13
- {sapiopycommons-2025.10.2a773.dist-info → sapiopycommons-2025.10.9a777.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.10.2a773.dist-info → sapiopycommons-2025.10.9a777.dist-info}/licenses/LICENSE +0 -0
|
@@ -17,6 +17,8 @@ from grpc import ServicerContext
|
|
|
17
17
|
from sapiopylib.rest.User import SapioUser, ensure_logger_initialized
|
|
18
18
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition
|
|
19
19
|
|
|
20
|
+
from sapiopycommons.ai.external_credentials import ExternalCredentials
|
|
21
|
+
from sapiopycommons.ai.protoapi.externalcredentials.external_credentials_pb2 import ExternalCredentialsPbo
|
|
20
22
|
from sapiopycommons.ai.protoapi.fielddefinitions.fields_pb2 import FieldValueMapPbo, FieldValuePbo
|
|
21
23
|
from sapiopycommons.ai.protoapi.fielddefinitions.velox_field_def_pb2 import VeloxFieldDefPbo, FieldTypePbo, \
|
|
22
24
|
SelectionPropertiesPbo, IntegerPropertiesPbo, DoublePropertiesPbo, BooleanPropertiesPbo, StringPropertiesPbo, \
|
|
@@ -38,22 +40,22 @@ from sapiopycommons.general.aliases import FieldMap, FieldValue
|
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
# FR-47422: Created classes.
|
|
41
|
-
class
|
|
43
|
+
class SapioAgentResult(ABC):
|
|
42
44
|
"""
|
|
43
|
-
A class representing a result from a Sapio
|
|
45
|
+
A class representing a result from a Sapio agent. Instantiate one of the subclasses to create a result object.
|
|
44
46
|
"""
|
|
45
47
|
|
|
46
48
|
@abstractmethod
|
|
47
49
|
def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
|
|
48
50
|
"""
|
|
49
|
-
Convert this
|
|
51
|
+
Convert this SapioAgentResult object to a StepOutputBatchPbo or list of FieldValueMapPbo proto objects.
|
|
50
52
|
"""
|
|
51
53
|
pass
|
|
52
54
|
|
|
53
55
|
|
|
54
|
-
class BinaryResult(
|
|
56
|
+
class BinaryResult(SapioAgentResult):
|
|
55
57
|
"""
|
|
56
|
-
A class representing binary results from a Sapio
|
|
58
|
+
A class representing binary results from a Sapio agent.
|
|
57
59
|
"""
|
|
58
60
|
binary_data: list[bytes]
|
|
59
61
|
content_type: str
|
|
@@ -66,7 +68,7 @@ class BinaryResult(SapioToolResult):
|
|
|
66
68
|
:param binary_data: The binary data as a list of bytes.
|
|
67
69
|
:param content_type: The content type of the data.
|
|
68
70
|
:param file_extensions: A list of file extensions that this binary data can be saved as.
|
|
69
|
-
:param name: An optional identifying name for this result that will be accessible to the next
|
|
71
|
+
:param name: An optional identifying name for this result that will be accessible to the next agent.
|
|
70
72
|
"""
|
|
71
73
|
self.binary_data = binary_data
|
|
72
74
|
self.content_type = content_type
|
|
@@ -83,9 +85,9 @@ class BinaryResult(SapioToolResult):
|
|
|
83
85
|
)
|
|
84
86
|
|
|
85
87
|
|
|
86
|
-
class CsvResult(
|
|
88
|
+
class CsvResult(SapioAgentResult):
|
|
87
89
|
"""
|
|
88
|
-
A class representing CSV results from a Sapio
|
|
90
|
+
A class representing CSV results from a Sapio agent.
|
|
89
91
|
"""
|
|
90
92
|
csv_data: list[dict[str, Any]]
|
|
91
93
|
content_type: str
|
|
@@ -98,7 +100,7 @@ class CsvResult(SapioToolResult):
|
|
|
98
100
|
:param csv_data: The list of CSV data results, provided as a list of dictionaries of column name to value.
|
|
99
101
|
:param content_type: The content type of the data.
|
|
100
102
|
:param file_extensions: A list of file extensions that this binary data can be saved as.
|
|
101
|
-
:param name: An optional identifying name for this result that will be accessible to the next
|
|
103
|
+
:param name: An optional identifying name for this result that will be accessible to the next agent.
|
|
102
104
|
"""
|
|
103
105
|
self.csv_data = csv_data
|
|
104
106
|
self.content_type = content_type
|
|
@@ -118,16 +120,16 @@ class CsvResult(SapioToolResult):
|
|
|
118
120
|
)
|
|
119
121
|
|
|
120
122
|
|
|
121
|
-
class FieldMapResult(
|
|
123
|
+
class FieldMapResult(SapioAgentResult):
|
|
122
124
|
"""
|
|
123
|
-
A class representing field map results from a Sapio
|
|
125
|
+
A class representing field map results from a Sapio agent.
|
|
124
126
|
"""
|
|
125
127
|
field_maps: list[FieldMap]
|
|
126
128
|
|
|
127
129
|
def __init__(self, field_maps: list[FieldMap]):
|
|
128
130
|
"""
|
|
129
131
|
:param field_maps: A list of field maps, where each map is a dictionary of field names to values. Each entry
|
|
130
|
-
will create a new data record in the system, so long as the
|
|
132
|
+
will create a new data record in the system, so long as the agent definition specifies an output data type
|
|
131
133
|
name.
|
|
132
134
|
"""
|
|
133
135
|
self.field_maps = field_maps
|
|
@@ -151,9 +153,9 @@ class FieldMapResult(SapioToolResult):
|
|
|
151
153
|
return new_records
|
|
152
154
|
|
|
153
155
|
|
|
154
|
-
class JsonResult(
|
|
156
|
+
class JsonResult(SapioAgentResult):
|
|
155
157
|
"""
|
|
156
|
-
A class representing JSON results from a Sapio
|
|
158
|
+
A class representing JSON results from a Sapio agent.
|
|
157
159
|
"""
|
|
158
160
|
json_data: list[dict[str, Any]]
|
|
159
161
|
content_type: str
|
|
@@ -167,7 +169,7 @@ class JsonResult(SapioToolResult):
|
|
|
167
169
|
These entries must be able to be serialized to JSON using json.dumps().
|
|
168
170
|
:param content_type: The content type of the data.
|
|
169
171
|
:param file_extensions: A list of file extensions that this binary data can be saved as.
|
|
170
|
-
:param name: An optional identifying name for this result that will be accessible to the next
|
|
172
|
+
:param name: An optional identifying name for this result that will be accessible to the next agent.
|
|
171
173
|
"""
|
|
172
174
|
# Verify that the given json_data is actually a list of dictionaries.
|
|
173
175
|
if not isinstance(json_data, list) or not all(isinstance(x, dict) for x in json_data):
|
|
@@ -187,9 +189,9 @@ class JsonResult(SapioToolResult):
|
|
|
187
189
|
)
|
|
188
190
|
|
|
189
191
|
|
|
190
|
-
class TextResult(
|
|
192
|
+
class TextResult(SapioAgentResult):
|
|
191
193
|
"""
|
|
192
|
-
A class representing text results from a Sapio
|
|
194
|
+
A class representing text results from a Sapio agent.
|
|
193
195
|
"""
|
|
194
196
|
text_data: list[str]
|
|
195
197
|
content_type: str
|
|
@@ -202,7 +204,7 @@ class TextResult(SapioToolResult):
|
|
|
202
204
|
:param text_data: The text data as a list of strings.
|
|
203
205
|
:param content_type: The content type of the data.
|
|
204
206
|
:param file_extensions: A list of file extensions that this binary data can be saved as.
|
|
205
|
-
:param name: An optional identifying name for this result that will be accessible to the next
|
|
207
|
+
:param name: An optional identifying name for this result that will be accessible to the next agent.
|
|
206
208
|
"""
|
|
207
209
|
self.text_data = text_data
|
|
208
210
|
self.content_type = content_type
|
|
@@ -219,24 +221,24 @@ class TextResult(SapioToolResult):
|
|
|
219
221
|
)
|
|
220
222
|
|
|
221
223
|
|
|
222
|
-
class
|
|
224
|
+
class AgentServiceBase(ToolServiceServicer, ABC):
|
|
223
225
|
"""
|
|
224
|
-
A base class for implementing
|
|
225
|
-
their
|
|
226
|
+
A base class for implementing an agent service. Subclasses should implement the register_agents method to register
|
|
227
|
+
their agents with the service.
|
|
226
228
|
"""
|
|
227
229
|
debug_mode: bool = False
|
|
228
230
|
|
|
229
231
|
def GetToolDetails(self, request: ToolDetailsRequestPbo, context: ServicerContext) -> ToolDetailsResponsePbo:
|
|
230
232
|
try:
|
|
231
|
-
# Get the
|
|
233
|
+
# Get the agent details from the registered agents.
|
|
232
234
|
details: list[ToolDetailsPbo] = []
|
|
233
|
-
for
|
|
234
|
-
details.append(
|
|
235
|
+
for agent in self.register_agents():
|
|
236
|
+
details.append(agent().to_pbo())
|
|
235
237
|
if not details:
|
|
236
|
-
raise Exception("No
|
|
237
|
-
return ToolDetailsResponsePbo(tool_framework_version=self.
|
|
238
|
+
raise Exception("No agents registered with this service.")
|
|
239
|
+
return ToolDetailsResponsePbo(tool_framework_version=self.server_version(), tool_details=details)
|
|
238
240
|
except Exception as e:
|
|
239
|
-
# Woe to you if you somehow cause an exception to be raised when just initializing your
|
|
241
|
+
# Woe to you if you somehow cause an exception to be raised when just initializing your agents.
|
|
240
242
|
# There's no way to log this.
|
|
241
243
|
print(f"CRITICAL ERROR: {e}")
|
|
242
244
|
print(traceback.format_exc())
|
|
@@ -246,7 +248,7 @@ class ToolServiceBase(ToolServiceServicer, ABC):
|
|
|
246
248
|
try:
|
|
247
249
|
# Convert the SapioConnectionInfo proto object to a SapioUser object.
|
|
248
250
|
user = self._create_user(request.sapio_user)
|
|
249
|
-
# Get the
|
|
251
|
+
# Get the agent results from the registered agent matching the request.
|
|
250
252
|
success, msg, results, logs = self.run(user, request, context)
|
|
251
253
|
# Convert the results to protobuf objects.
|
|
252
254
|
output_data: list[StepOutputBatchPbo] = []
|
|
@@ -262,7 +264,7 @@ class ToolServiceBase(ToolServiceServicer, ABC):
|
|
|
262
264
|
return ProcessStepResponsePbo(status=status, status_message=msg, output=output_data, log=logs,
|
|
263
265
|
new_records=new_records)
|
|
264
266
|
except Exception as e:
|
|
265
|
-
# This try/except should never be needed, as the
|
|
267
|
+
# This try/except should never be needed, as the agent should handle its own exceptions, but better safe
|
|
266
268
|
# than sorry.
|
|
267
269
|
print(f"CRITICAL ERROR: {e}")
|
|
268
270
|
print(traceback.format_exc())
|
|
@@ -294,88 +296,88 @@ class ToolServiceBase(ToolServiceServicer, ABC):
|
|
|
294
296
|
return user
|
|
295
297
|
|
|
296
298
|
@staticmethod
|
|
297
|
-
def
|
|
299
|
+
def server_version() -> int:
|
|
298
300
|
"""
|
|
299
|
-
:return: The version of this set of
|
|
301
|
+
:return: The version of this set of .
|
|
300
302
|
"""
|
|
301
303
|
return 1
|
|
302
304
|
|
|
303
305
|
@abstractmethod
|
|
304
|
-
def
|
|
306
|
+
def register_agents(self) -> list[type[AgentBase]]:
|
|
305
307
|
"""
|
|
306
|
-
Register
|
|
308
|
+
Register agent types with this service. Provided agents should implement the AgentBase class.
|
|
307
309
|
|
|
308
|
-
:return: A list of
|
|
310
|
+
:return: A list of agents to register to this service.
|
|
309
311
|
"""
|
|
310
312
|
pass
|
|
311
313
|
|
|
312
314
|
def run(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext) \
|
|
313
|
-
-> tuple[bool, str, list[
|
|
315
|
+
-> tuple[bool, str, list[SapioAgentResult], list[str]]:
|
|
314
316
|
"""
|
|
315
|
-
Execute
|
|
317
|
+
Execute an agent from this service.
|
|
316
318
|
|
|
317
319
|
:param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
|
|
318
320
|
system.
|
|
319
321
|
:param request: The request object containing the input data.
|
|
320
322
|
:param context: The gRPC context.
|
|
321
|
-
:return: Whether or not the
|
|
322
|
-
generated by the
|
|
323
|
-
"""
|
|
324
|
-
# Locate the
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if
|
|
328
|
-
# If the
|
|
329
|
-
# the
|
|
330
|
-
|
|
331
|
-
msg: str = (f"
|
|
332
|
-
f"
|
|
323
|
+
:return: Whether or not the agent succeeded, the status message, the results of the agent, and any logs
|
|
324
|
+
generated by the agent.
|
|
325
|
+
"""
|
|
326
|
+
# Locate the agent named in the request.
|
|
327
|
+
find_agent: str = request.tool_name
|
|
328
|
+
registered_agents: dict[str, type[AgentBase]] = {t.name(): t for t in self.register_agents()}
|
|
329
|
+
if find_agent not in registered_agents:
|
|
330
|
+
# If the agent is not found, list all of the registered agents for this service so that the LLM can correct
|
|
331
|
+
# the agent it is requesting.
|
|
332
|
+
all_agent_names: str = "\n".join(registered_agents.keys())
|
|
333
|
+
msg: str = (f"Agent \"{find_agent}\" not found in the registered agents for this service. The registered "
|
|
334
|
+
f"agents for this service are: \n{all_agent_names}")
|
|
333
335
|
return False, msg, [], []
|
|
334
336
|
|
|
335
|
-
# Instantiate the
|
|
336
|
-
|
|
337
|
+
# Instantiate the agent class.
|
|
338
|
+
agent: AgentBase = registered_agents[find_agent]()
|
|
337
339
|
try:
|
|
338
|
-
# Setup the
|
|
339
|
-
|
|
340
|
-
# Validate that the provided inputs match the
|
|
340
|
+
# Setup the agent with details from the request.
|
|
341
|
+
agent.setup(user, request, context, self.debug_mode)
|
|
342
|
+
# Validate that the provided inputs match the agent's expected inputs.
|
|
341
343
|
msg: str = ""
|
|
342
|
-
if len(request.input) != len(
|
|
343
|
-
msg = f"Expected {len(
|
|
344
|
+
if len(request.input) != len(agent.input_configs):
|
|
345
|
+
msg = f"Expected {len(agent.input_configs)} inputs for this agent, but got {len(request.input)} instead."
|
|
344
346
|
else:
|
|
345
|
-
errors: list[str] =
|
|
347
|
+
errors: list[str] = agent.validate_input()
|
|
346
348
|
if errors:
|
|
347
349
|
msg = "\n".join(errors)
|
|
348
350
|
# If there is no error message, then the inputs are valid.
|
|
349
351
|
success: bool = not bool(msg)
|
|
350
352
|
# If this is a dry run, then provide the fixed dry run output.
|
|
351
|
-
# Otherwise, if the inputs were successfully validated, then the
|
|
352
|
-
results: list[
|
|
353
|
+
# Otherwise, if the inputs were successfully validated, then the agent is executed normally.
|
|
354
|
+
results: list[SapioAgentResult] = []
|
|
353
355
|
if request.dry_run:
|
|
354
|
-
results =
|
|
356
|
+
results = agent.dry_run_output()
|
|
355
357
|
elif success:
|
|
356
|
-
results =
|
|
357
|
-
# Update the status message to reflect the successful execution of the
|
|
358
|
-
msg = f"{
|
|
359
|
-
return success, msg, results,
|
|
358
|
+
results = agent.run(user)
|
|
359
|
+
# Update the status message to reflect the successful execution of the agent.
|
|
360
|
+
msg = f"{agent.name()} successfully completed."
|
|
361
|
+
return success, msg, results, agent.logs
|
|
360
362
|
except Exception as e:
|
|
361
|
-
|
|
362
|
-
return False, str(e), [],
|
|
363
|
+
agent.log_exception("Exception occurred during agent execution.", e)
|
|
364
|
+
return False, str(e), [], agent.logs
|
|
363
365
|
finally:
|
|
364
|
-
# Clean up any temporary files created by the
|
|
366
|
+
# Clean up any temporary files created by the agent. If in debug mode, then log the files instead
|
|
365
367
|
# so that they can be manually inspected.
|
|
366
368
|
if self.debug_mode:
|
|
367
|
-
print("Temporary files/directories created during
|
|
368
|
-
for directory in
|
|
369
|
+
print("Temporary files/directories created during agent execution:")
|
|
370
|
+
for directory in agent.temp_data.directories:
|
|
369
371
|
print(f"\tDirectory: {directory}")
|
|
370
|
-
for file in
|
|
372
|
+
for file in agent.temp_data.files:
|
|
371
373
|
print(f"\tFile: {file}")
|
|
372
374
|
else:
|
|
373
|
-
|
|
375
|
+
agent.temp_data.cleanup()
|
|
374
376
|
|
|
375
377
|
|
|
376
|
-
class
|
|
378
|
+
class AgentBase(ABC):
|
|
377
379
|
"""
|
|
378
|
-
A base class for implementing
|
|
380
|
+
A base class for implementing an agent.
|
|
379
381
|
"""
|
|
380
382
|
input_configs: list[ToolInputDetailsPbo]
|
|
381
383
|
input_container_types: list[ContainerType]
|
|
@@ -398,9 +400,9 @@ class ToolBase(ABC):
|
|
|
398
400
|
@abstractmethod
|
|
399
401
|
def identifier(cls):
|
|
400
402
|
"""
|
|
401
|
-
:return: The unique identifier of the
|
|
402
|
-
updated if
|
|
403
|
-
otherwise a duplicate
|
|
403
|
+
:return: The unique identifier of the agent. This is used by the system to determine which agent should be
|
|
404
|
+
updated if an agent is re-imported. This should not be changed after the first time that an agent is
|
|
405
|
+
imported, otherwise a duplicate agent will be created.
|
|
404
406
|
"""
|
|
405
407
|
pass
|
|
406
408
|
|
|
@@ -408,7 +410,7 @@ class ToolBase(ABC):
|
|
|
408
410
|
@abstractmethod
|
|
409
411
|
def name() -> str:
|
|
410
412
|
"""
|
|
411
|
-
:return: The display name of the
|
|
413
|
+
:return: The display name of the agent. This should be unique across all agents in the service.
|
|
412
414
|
"""
|
|
413
415
|
pass
|
|
414
416
|
|
|
@@ -416,7 +418,7 @@ class ToolBase(ABC):
|
|
|
416
418
|
@abstractmethod
|
|
417
419
|
def category() -> str:
|
|
418
420
|
"""
|
|
419
|
-
:return: The category of the
|
|
421
|
+
:return: The category of the agent. This is used to group similar agents together in the plan manager.
|
|
420
422
|
"""
|
|
421
423
|
pass
|
|
422
424
|
|
|
@@ -424,7 +426,7 @@ class ToolBase(ABC):
|
|
|
424
426
|
@abstractmethod
|
|
425
427
|
def description() -> str:
|
|
426
428
|
"""
|
|
427
|
-
:return: A description of the
|
|
429
|
+
:return: A description of the agent.
|
|
428
430
|
"""
|
|
429
431
|
pass
|
|
430
432
|
|
|
@@ -432,14 +434,14 @@ class ToolBase(ABC):
|
|
|
432
434
|
@abstractmethod
|
|
433
435
|
def citations() -> dict[str, str]:
|
|
434
436
|
"""
|
|
435
|
-
:return: Any citations or references for this
|
|
437
|
+
:return: Any citations or references for this agent, as a dictionary of citation name to URL.
|
|
436
438
|
"""
|
|
437
439
|
pass
|
|
438
440
|
|
|
439
441
|
@staticmethod
|
|
440
442
|
def data_type_name() -> str | None:
|
|
441
443
|
"""
|
|
442
|
-
:return: The name of the output data type of this
|
|
444
|
+
:return: The name of the output data type of this agent, if applicable. When this agent returns
|
|
443
445
|
FieldMapResult objects in its run method, this name will be used to set the data type of the output data.
|
|
444
446
|
"""
|
|
445
447
|
return None
|
|
@@ -447,8 +449,8 @@ class ToolBase(ABC):
|
|
|
447
449
|
@staticmethod
|
|
448
450
|
def license_flag() -> str | None:
|
|
449
451
|
"""
|
|
450
|
-
:return: The license flag for this
|
|
451
|
-
If None, the
|
|
452
|
+
:return: The license flag for this agent. The system must have this license in order to use this agent.
|
|
453
|
+
If None, the agent is not license locked.
|
|
452
454
|
"""
|
|
453
455
|
return None
|
|
454
456
|
|
|
@@ -460,19 +462,19 @@ class ToolBase(ABC):
|
|
|
460
462
|
self.config_fields = []
|
|
461
463
|
self.temp_data = TempFileHandler()
|
|
462
464
|
self.logs = []
|
|
463
|
-
self.logger = logging.getLogger(f"
|
|
465
|
+
self.logger = logging.getLogger(f"AgentBase.{self.name()}")
|
|
464
466
|
ensure_logger_initialized(self.logger)
|
|
465
467
|
|
|
466
468
|
def setup(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext, debug_mode: bool) -> None:
|
|
467
469
|
"""
|
|
468
|
-
Setup the
|
|
470
|
+
Setup the agent with the user, request, and context. This method can be overridden by subclasses to perform
|
|
469
471
|
additional setup.
|
|
470
472
|
|
|
471
473
|
:param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
|
|
472
474
|
system.
|
|
473
475
|
:param request: The request object containing the input data.
|
|
474
476
|
:param context: The gRPC context.
|
|
475
|
-
:param debug_mode: If true, the
|
|
477
|
+
:param debug_mode: If true, the agent should run in debug mode, providing additional logging and not cleaning
|
|
476
478
|
up temporary files.
|
|
477
479
|
"""
|
|
478
480
|
self.user = user
|
|
@@ -486,7 +488,7 @@ class ToolBase(ABC):
|
|
|
486
488
|
input_count: tuple[int, int] | None = None, is_paged: bool = False,
|
|
487
489
|
page_size: tuple[int, int] | None = None, max_request_bytes: int | None = None) -> None:
|
|
488
490
|
"""
|
|
489
|
-
Add an input configuration to the
|
|
491
|
+
Add an input configuration to the agent. This determines how many inputs this agent will accept in the plan
|
|
490
492
|
manager, as well as what those inputs are. The IO number of the input will be set to the current number of
|
|
491
493
|
inputs. That is, the first time this is called, the IO number will be 0, the second time it is called, the IO
|
|
492
494
|
number will be 1, and so on.
|
|
@@ -499,11 +501,11 @@ class ToolBase(ABC):
|
|
|
499
501
|
JSON output may look. This does not need to be an entirely valid example, and should often be truncated for
|
|
500
502
|
brevity. This must be provided for any container type other than BINARY.
|
|
501
503
|
:param validation: An optional validation string for the input.
|
|
502
|
-
:param input_count: A tuple of the minimum and maximum number of inputs allowed for this
|
|
504
|
+
:param input_count: A tuple of the minimum and maximum number of inputs allowed for this agent.
|
|
503
505
|
:param is_paged: If true, this input will be paged. If false, this input will not be paged.
|
|
504
|
-
:param page_size: A tuple of the minimum and maximum page size for this
|
|
506
|
+
:param page_size: A tuple of the minimum and maximum page size for this agent. The input must be paged in order
|
|
505
507
|
for this to have an effect.
|
|
506
|
-
:param max_request_bytes: The maximum request size in bytes for this
|
|
508
|
+
:param max_request_bytes: The maximum request size in bytes for this agent.
|
|
507
509
|
"""
|
|
508
510
|
if container_type != ContainerType.BINARY and structure_example is None:
|
|
509
511
|
raise ValueError("structure_example must be provided for inputs with a container_type other than BINARY.")
|
|
@@ -537,7 +539,7 @@ class ToolBase(ABC):
|
|
|
537
539
|
def add_output(self, container_type: ContainerType, content_type: str, display_name: str, description: str,
|
|
538
540
|
testing_example: str | bytes, structure_example: str | bytes | None = None) -> None:
|
|
539
541
|
"""
|
|
540
|
-
Add an output configuration to the
|
|
542
|
+
Add an output configuration to the agent. This determines how many inputs this agent will accept in the plan
|
|
541
543
|
manager, as well as what those inputs are. The IO number of the output will be set to the current number of
|
|
542
544
|
outputs. That is, the first time this is called, the IO number will be 0, the second time it is called, the IO
|
|
543
545
|
number will be 1, and so on.
|
|
@@ -546,8 +548,8 @@ class ToolBase(ABC):
|
|
|
546
548
|
:param content_type: The content type of the output.
|
|
547
549
|
:param display_name: The display name of the output.
|
|
548
550
|
:param description: The description of the output.
|
|
549
|
-
:param testing_example: An example of the input to be used when testing this
|
|
550
|
-
an entirely valid example of what an output of this
|
|
551
|
+
:param testing_example: An example of the input to be used when testing this agent in the system. This must be
|
|
552
|
+
an entirely valid example of what an output of this agent could look like so that it can be properly used
|
|
551
553
|
to run tests with. The provided example may be a string, such as for representing JSON or CSV outputs,
|
|
552
554
|
or bytes, such as for representing binary outputs like images or files.
|
|
553
555
|
:param structure_example: An optional example of the structure of the input, such as how the structure of a
|
|
@@ -583,7 +585,7 @@ class ToolBase(ABC):
|
|
|
583
585
|
|
|
584
586
|
def add_config_field(self, field: VeloxFieldDefPbo) -> None:
|
|
585
587
|
"""
|
|
586
|
-
Add a configuration field to the
|
|
588
|
+
Add a configuration field to the agent. This field will be used to configure the agent in the plan manager.
|
|
587
589
|
|
|
588
590
|
:param field: The configuration field details.
|
|
589
591
|
"""
|
|
@@ -591,7 +593,7 @@ class ToolBase(ABC):
|
|
|
591
593
|
|
|
592
594
|
def add_config_field_def(self, field: AbstractVeloxFieldDefinition) -> None:
|
|
593
595
|
"""
|
|
594
|
-
Add a configuration field to the
|
|
596
|
+
Add a configuration field to the agent. This field will be used to configure the agent in the plan manager.
|
|
595
597
|
|
|
596
598
|
:param field: The configuration field details.
|
|
597
599
|
"""
|
|
@@ -600,7 +602,7 @@ class ToolBase(ABC):
|
|
|
600
602
|
def add_boolean_config_field(self, field_name: str, display_name: str, description: str,
|
|
601
603
|
default_value: bool | None = None, optional: bool = False) -> None:
|
|
602
604
|
"""
|
|
603
|
-
Add a boolean configuration field to the
|
|
605
|
+
Add a boolean configuration field to the agent. This field will be used to configure the agent in the plan
|
|
604
606
|
manager.
|
|
605
607
|
|
|
606
608
|
:param field_name: The name of the field.
|
|
@@ -625,7 +627,7 @@ class ToolBase(ABC):
|
|
|
625
627
|
default_value: float | None = None, min_value: float = -10.**120,
|
|
626
628
|
max_value: float = 10.**120, precision: int = 2, optional: bool = False) -> None:
|
|
627
629
|
"""
|
|
628
|
-
Add a double configuration field to the
|
|
630
|
+
Add a double configuration field to the agent. This field will be used to configure the agent in the plan
|
|
629
631
|
manager.
|
|
630
632
|
|
|
631
633
|
:param field_name: The name of the field.
|
|
@@ -656,7 +658,7 @@ class ToolBase(ABC):
|
|
|
656
658
|
default_value: int | None = None, min_value: int = -2**31, max_value: int = 2**31-1,
|
|
657
659
|
optional: bool = False) -> None:
|
|
658
660
|
"""
|
|
659
|
-
Add an integer configuration field to the
|
|
661
|
+
Add an integer configuration field to the agent. This field will be used to configure the agent in the plan
|
|
660
662
|
manager.
|
|
661
663
|
|
|
662
664
|
:param field_name: The name of the field.
|
|
@@ -685,7 +687,7 @@ class ToolBase(ABC):
|
|
|
685
687
|
default_value: str | None = None, max_length: int = 1000, optional: bool = False,
|
|
686
688
|
validation_regex: str | None = None, error_msg: str | None = None) -> None:
|
|
687
689
|
"""
|
|
688
|
-
Add a string configuration field to the
|
|
690
|
+
Add a string configuration field to the agent. This field will be used to configure the agent in the plan
|
|
689
691
|
manager.
|
|
690
692
|
|
|
691
693
|
:param field_name: The name of the field.
|
|
@@ -716,7 +718,7 @@ class ToolBase(ABC):
|
|
|
716
718
|
direct_edit: bool = False, optional: bool = False,
|
|
717
719
|
validation_regex: str | None = None, error_msg: str | None = None) -> None:
|
|
718
720
|
"""
|
|
719
|
-
Add a list configuration field to the
|
|
721
|
+
Add a list configuration field to the agent. This field will be used to configure the agent in the plan
|
|
720
722
|
manager.
|
|
721
723
|
|
|
722
724
|
:param field_name: The name of the field.
|
|
@@ -750,7 +752,7 @@ class ToolBase(ABC):
|
|
|
750
752
|
direct_edit: bool = False, optional: bool = False,
|
|
751
753
|
validation_regex: str | None = None, error_msg: str | None = None) -> None:
|
|
752
754
|
"""
|
|
753
|
-
Add a multi-select list configuration field to the
|
|
755
|
+
Add a multi-select list configuration field to the agent. This field will be used to configure the agent in the
|
|
754
756
|
plan manager.
|
|
755
757
|
|
|
756
758
|
:param field_name: The name of the field.
|
|
@@ -782,7 +784,7 @@ class ToolBase(ABC):
|
|
|
782
784
|
|
|
783
785
|
def to_pbo(self) -> ToolDetailsPbo:
|
|
784
786
|
"""
|
|
785
|
-
:return: The ToolDetailsPbo proto object representing this
|
|
787
|
+
:return: The ToolDetailsPbo proto object representing this agent.
|
|
786
788
|
"""
|
|
787
789
|
return ToolDetailsPbo(
|
|
788
790
|
import_id=self.identifier(),
|
|
@@ -800,7 +802,7 @@ class ToolBase(ABC):
|
|
|
800
802
|
@abstractmethod
|
|
801
803
|
def validate_input(self) -> list[str] | None:
|
|
802
804
|
"""
|
|
803
|
-
Validate the request given to this
|
|
805
|
+
Validate the request given to this agent. If the request is validly formatted, this method should return None.
|
|
804
806
|
If the request is not valid, this method should return an error message indicating what is wrong with the
|
|
805
807
|
request.
|
|
806
808
|
|
|
@@ -816,18 +818,18 @@ class ToolBase(ABC):
|
|
|
816
818
|
"""
|
|
817
819
|
pass
|
|
818
820
|
|
|
819
|
-
def dry_run_output(self) -> list[
|
|
821
|
+
def dry_run_output(self) -> list[SapioAgentResult]:
|
|
820
822
|
"""
|
|
821
|
-
Provide fixed results for a dry run of this
|
|
822
|
-
request. It should only return example outputs that can be used to test the next
|
|
823
|
+
Provide fixed results for a dry run of this agent. This method should not perform any actual processing of the
|
|
824
|
+
request. It should only return example outputs that can be used to test the next agent in the plan.
|
|
823
825
|
|
|
824
826
|
The default implementation of this method looks at the testing_example field of each output configuration
|
|
825
|
-
and returns a
|
|
827
|
+
and returns a SapioAgentResult object based on the content type of the output.
|
|
826
828
|
|
|
827
|
-
:return: A list of
|
|
828
|
-
corresponds to a separate output from the
|
|
829
|
+
:return: A list of SapioAgentResult objects containing example outputs for this agent. Each result in the list
|
|
830
|
+
corresponds to a separate output from the agent.
|
|
829
831
|
"""
|
|
830
|
-
results: list[
|
|
832
|
+
results: list[SapioAgentResult] = []
|
|
831
833
|
for output, container_type in zip(self.output_configs, self.output_container_types):
|
|
832
834
|
config: ToolIoConfigBasePbo = output.base_config
|
|
833
835
|
example: ExampleContainerPbo = config.testing_example
|
|
@@ -860,9 +862,9 @@ class ToolBase(ABC):
|
|
|
860
862
|
return results
|
|
861
863
|
|
|
862
864
|
@abstractmethod
|
|
863
|
-
def run(self, user: SapioUser) -> list[
|
|
865
|
+
def run(self, user: SapioUser) -> list[SapioAgentResult]:
|
|
864
866
|
"""
|
|
865
|
-
Execute this
|
|
867
|
+
Execute this agent.
|
|
866
868
|
|
|
867
869
|
The request inputs can be accessed using the self.get_input_*() methods.
|
|
868
870
|
The request settings can be accessed using the self.get_config_fields() method.
|
|
@@ -870,12 +872,44 @@ class ToolBase(ABC):
|
|
|
870
872
|
|
|
871
873
|
:param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
|
|
872
874
|
system.
|
|
873
|
-
:return: A list of
|
|
874
|
-
a separate output from the
|
|
875
|
-
appearing as records related to the plan step during the run.
|
|
875
|
+
:return: A list of SapioAgentResult objects containing the response data. Each result in the list corresponds to
|
|
876
|
+
a separate output from the agent. Field map results do not appear as agent output in the plan manager,
|
|
877
|
+
instead appearing as records related to the plan step during the run.
|
|
876
878
|
"""
|
|
877
879
|
pass
|
|
878
880
|
|
|
881
|
+
def get_credentials(self, category: str = None, host: str = None) -> ExternalCredentials:
|
|
882
|
+
"""
|
|
883
|
+
Get credentials for the given category and host.
|
|
884
|
+
|
|
885
|
+
:param category: The category of the credentials to retrieve.
|
|
886
|
+
:param host: The host for which to retrieve the credentials.
|
|
887
|
+
:return: An ExternalCredentials object containing the credentials for the given category and host.
|
|
888
|
+
"""
|
|
889
|
+
# Remove leading/trailing whitespace
|
|
890
|
+
category = category.strip() if category else None
|
|
891
|
+
host = host.strip() if host else None
|
|
892
|
+
|
|
893
|
+
matching_creds: list[ExternalCredentialsPbo] = []
|
|
894
|
+
for cred in self.request.external_credential:
|
|
895
|
+
# Do case insensitive comparison
|
|
896
|
+
if category and cred.category.lower() != category.lower():
|
|
897
|
+
continue
|
|
898
|
+
if host:
|
|
899
|
+
# Parse the URL to get the host and compare
|
|
900
|
+
from urllib.parse import urlparse
|
|
901
|
+
parsed_url = urlparse(cred.url)
|
|
902
|
+
if parsed_url.hostname is None or parsed_url.hostname.lower() != host.lower():
|
|
903
|
+
continue
|
|
904
|
+
|
|
905
|
+
matching_creds.append(cred)
|
|
906
|
+
if len(matching_creds) == 0:
|
|
907
|
+
raise ValueError(f"No credentials found for category '{category}' and host '{host}'.")
|
|
908
|
+
if len(matching_creds) > 1:
|
|
909
|
+
raise ValueError(f"Multiple credentials found for category '{category}' and host '{host}'.")
|
|
910
|
+
|
|
911
|
+
return ExternalCredentials(matching_creds[0])
|
|
912
|
+
|
|
879
913
|
def call_subprocess(self,
|
|
880
914
|
args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]],
|
|
881
915
|
cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = None,
|
|
@@ -901,7 +935,7 @@ class ToolBase(ABC):
|
|
|
901
935
|
|
|
902
936
|
def log_info(self, message: str) -> None:
|
|
903
937
|
"""
|
|
904
|
-
Log an info message for this
|
|
938
|
+
Log an info message for this agent. If verbose logging is enabled, this message will be included in the logs
|
|
905
939
|
returned to the caller. Empty/None inputs will not be logged.
|
|
906
940
|
|
|
907
941
|
:param message: The message to log.
|
|
@@ -914,7 +948,7 @@ class ToolBase(ABC):
|
|
|
914
948
|
|
|
915
949
|
def log_warning(self, message: str) -> None:
|
|
916
950
|
"""
|
|
917
|
-
Log a warning message for this
|
|
951
|
+
Log a warning message for this agent. This message will be included in the logs returned to the caller.
|
|
918
952
|
Empty/None inputs will not be logged.
|
|
919
953
|
|
|
920
954
|
:param message: The message to log.
|
|
@@ -926,7 +960,7 @@ class ToolBase(ABC):
|
|
|
926
960
|
|
|
927
961
|
def log_error(self, message: str) -> None:
|
|
928
962
|
"""
|
|
929
|
-
Log an error message for this
|
|
963
|
+
Log an error message for this agent. This message will be included in the logs returned to the caller.
|
|
930
964
|
Empty/None inputs will not be logged.
|
|
931
965
|
|
|
932
966
|
:param message: The message to log.
|
|
@@ -938,7 +972,7 @@ class ToolBase(ABC):
|
|
|
938
972
|
|
|
939
973
|
def log_exception(self, message: str, e: Exception) -> None:
|
|
940
974
|
"""
|
|
941
|
-
Log an exception for this
|
|
975
|
+
Log an exception for this agent. This message will be included in the logs returned to the caller.
|
|
942
976
|
Empty/None inputs will not be logged.
|
|
943
977
|
|
|
944
978
|
:param message: The message to log.
|
|
@@ -953,7 +987,7 @@ class ToolBase(ABC):
|
|
|
953
987
|
"""
|
|
954
988
|
Check if the input at the given index is marked as partial.
|
|
955
989
|
|
|
956
|
-
:param index: The index of the input to check. Defaults to 0. Used for
|
|
990
|
+
:param index: The index of the input to check. Defaults to 0. Used for agents that accept multiple inputs.
|
|
957
991
|
:return: True if the input is marked as partial, False otherwise.
|
|
958
992
|
"""
|
|
959
993
|
return self.request.input[index].is_partial
|
|
@@ -962,7 +996,7 @@ class ToolBase(ABC):
|
|
|
962
996
|
"""
|
|
963
997
|
Get the name of the input from the request object.
|
|
964
998
|
|
|
965
|
-
:param index: The index of the input to parse. Defaults to 0. Used for
|
|
999
|
+
:param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
|
|
966
1000
|
:return: The name of the input from the request object, or None if no name is set.
|
|
967
1001
|
"""
|
|
968
1002
|
return self.request.input[index].item_container.container_name
|
|
@@ -971,7 +1005,7 @@ class ToolBase(ABC):
|
|
|
971
1005
|
"""
|
|
972
1006
|
Get the content type of the input from the request object.
|
|
973
1007
|
|
|
974
|
-
:param index: The index of the input to parse. Defaults to 0. Used for
|
|
1008
|
+
:param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
|
|
975
1009
|
:return: The content type of the input from the request object.
|
|
976
1010
|
"""
|
|
977
1011
|
return self.request.input[index].item_container.content_type
|
|
@@ -980,7 +1014,7 @@ class ToolBase(ABC):
|
|
|
980
1014
|
"""
|
|
981
1015
|
Get the binary data from the request object.
|
|
982
1016
|
|
|
983
|
-
:param index: The index of the input to parse. Defaults to 0. Used for
|
|
1017
|
+
:param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
|
|
984
1018
|
:return: The binary data from the request object.
|
|
985
1019
|
"""
|
|
986
1020
|
container: StepItemContainerPbo = self.request.input[index].item_container
|
|
@@ -992,7 +1026,7 @@ class ToolBase(ABC):
|
|
|
992
1026
|
"""
|
|
993
1027
|
Parse the CSV data from the request object.
|
|
994
1028
|
|
|
995
|
-
:param index: The index of the input to parse. Defaults to 0. Used for
|
|
1029
|
+
:param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
|
|
996
1030
|
:return: A tuple containing the header row and the data rows. The header row is a list of strings representing
|
|
997
1031
|
the column names, and the data rows are a list of dictionaries where each dictionary represents a row in the
|
|
998
1032
|
CSV with the column names as keys and the corresponding values as strings.
|
|
@@ -1013,7 +1047,7 @@ class ToolBase(ABC):
|
|
|
1013
1047
|
"""
|
|
1014
1048
|
Parse the JSON data from the request object.
|
|
1015
1049
|
|
|
1016
|
-
:param index: The index of the input to parse. Defaults to 0. Used for
|
|
1050
|
+
:param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
|
|
1017
1051
|
:return: A list of parsed JSON objects, which are represented as dictionaries.
|
|
1018
1052
|
"""
|
|
1019
1053
|
container: StepItemContainerPbo = self.request.input[index].item_container
|
|
@@ -1021,7 +1055,7 @@ class ToolBase(ABC):
|
|
|
1021
1055
|
raise Exception(f"Input {index} does not contain a JSON container.")
|
|
1022
1056
|
input_json: list[Any] = [json.loads(x) for x in container.json_container.items]
|
|
1023
1057
|
# Verify that the given JSON actually is a list of dictionaries. If they aren't then the previous step provided
|
|
1024
|
-
# bad input.
|
|
1058
|
+
# bad input. Agents are enforced to result in a list of dictionaries when returning JSON data, so this is likely
|
|
1025
1059
|
# an error caused by a script or static input step.
|
|
1026
1060
|
for i, entry in enumerate(input_json):
|
|
1027
1061
|
if not isinstance(entry, dict):
|
|
@@ -1033,7 +1067,7 @@ class ToolBase(ABC):
|
|
|
1033
1067
|
"""
|
|
1034
1068
|
Parse the text data from the request object.
|
|
1035
1069
|
|
|
1036
|
-
:param index: The index of the input to parse. Defaults to 0. Used for
|
|
1070
|
+
:param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
|
|
1037
1071
|
:return: A list of text data as strings.
|
|
1038
1072
|
"""
|
|
1039
1073
|
container: StepItemContainerPbo = self.request.input[index].item_container
|
|
@@ -1043,7 +1077,7 @@ class ToolBase(ABC):
|
|
|
1043
1077
|
|
|
1044
1078
|
def get_config_defs(self) -> dict[str, VeloxFieldDefPbo]:
|
|
1045
1079
|
"""
|
|
1046
|
-
Get the config field definitions for this
|
|
1080
|
+
Get the config field definitions for this agent.
|
|
1047
1081
|
|
|
1048
1082
|
:return: A dictionary of field definitions, where the keys are the field names and the values are the
|
|
1049
1083
|
VeloxFieldDefPbo objects representing the field definitions.
|