oracle-ads 2.12.9__py3-none-any.whl → 2.12.10__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.
Files changed (74) hide show
  1. ads/aqua/__init__.py +4 -3
  2. ads/aqua/app.py +28 -16
  3. ads/aqua/client/__init__.py +3 -0
  4. ads/aqua/client/client.py +799 -0
  5. ads/aqua/common/enums.py +3 -0
  6. ads/aqua/common/utils.py +62 -2
  7. ads/aqua/data.py +2 -19
  8. ads/aqua/evaluation/evaluation.py +20 -12
  9. ads/aqua/extension/aqua_ws_msg_handler.py +14 -7
  10. ads/aqua/extension/base_handler.py +12 -9
  11. ads/aqua/extension/finetune_handler.py +8 -14
  12. ads/aqua/extension/model_handler.py +24 -2
  13. ads/aqua/finetuning/constants.py +5 -2
  14. ads/aqua/finetuning/entities.py +67 -17
  15. ads/aqua/finetuning/finetuning.py +69 -54
  16. ads/aqua/model/entities.py +3 -1
  17. ads/aqua/model/model.py +196 -98
  18. ads/aqua/modeldeployment/deployment.py +22 -10
  19. ads/cli.py +16 -8
  20. ads/common/auth.py +9 -9
  21. ads/llm/autogen/__init__.py +2 -0
  22. ads/llm/autogen/constants.py +15 -0
  23. ads/llm/autogen/reports/__init__.py +2 -0
  24. ads/llm/autogen/reports/base.py +67 -0
  25. ads/llm/autogen/reports/data.py +103 -0
  26. ads/llm/autogen/reports/session.py +526 -0
  27. ads/llm/autogen/reports/templates/chat_box.html +13 -0
  28. ads/llm/autogen/reports/templates/chat_box_lt.html +5 -0
  29. ads/llm/autogen/reports/templates/chat_box_rt.html +6 -0
  30. ads/llm/autogen/reports/utils.py +56 -0
  31. ads/llm/autogen/v02/__init__.py +4 -0
  32. ads/llm/autogen/{client_v02.py → v02/client.py} +23 -10
  33. ads/llm/autogen/v02/log_handlers/__init__.py +2 -0
  34. ads/llm/autogen/v02/log_handlers/oci_file_handler.py +83 -0
  35. ads/llm/autogen/v02/loggers/__init__.py +6 -0
  36. ads/llm/autogen/v02/loggers/metric_logger.py +320 -0
  37. ads/llm/autogen/v02/loggers/session_logger.py +580 -0
  38. ads/llm/autogen/v02/loggers/utils.py +86 -0
  39. ads/llm/autogen/v02/runtime_logging.py +163 -0
  40. ads/llm/langchain/plugins/chat_models/oci_data_science.py +12 -11
  41. ads/model/__init__.py +11 -13
  42. ads/model/artifact.py +47 -8
  43. ads/model/extractor/embedding_onnx_extractor.py +80 -0
  44. ads/model/framework/embedding_onnx_model.py +438 -0
  45. ads/model/generic_model.py +26 -24
  46. ads/model/model_metadata.py +8 -7
  47. ads/opctl/config/merger.py +13 -14
  48. ads/opctl/operator/common/operator_config.py +4 -4
  49. ads/opctl/operator/lowcode/common/transformations.py +50 -8
  50. ads/opctl/operator/lowcode/common/utils.py +22 -6
  51. ads/opctl/operator/lowcode/forecast/__main__.py +10 -0
  52. ads/opctl/operator/lowcode/forecast/const.py +2 -0
  53. ads/opctl/operator/lowcode/forecast/model/arima.py +19 -13
  54. ads/opctl/operator/lowcode/forecast/model/automlx.py +129 -36
  55. ads/opctl/operator/lowcode/forecast/model/autots.py +1 -0
  56. ads/opctl/operator/lowcode/forecast/model/base_model.py +61 -14
  57. ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py +1 -1
  58. ads/opctl/operator/lowcode/forecast/model/neuralprophet.py +10 -3
  59. ads/opctl/operator/lowcode/forecast/model/prophet.py +25 -18
  60. ads/opctl/operator/lowcode/forecast/operator_config.py +31 -0
  61. ads/opctl/operator/lowcode/forecast/schema.yaml +76 -0
  62. ads/opctl/operator/lowcode/forecast/utils.py +4 -3
  63. ads/opctl/operator/lowcode/forecast/whatifserve/__init__.py +7 -0
  64. ads/opctl/operator/lowcode/forecast/whatifserve/deployment_manager.py +233 -0
  65. ads/opctl/operator/lowcode/forecast/whatifserve/score.py +238 -0
  66. ads/telemetry/base.py +18 -11
  67. ads/telemetry/client.py +33 -13
  68. ads/templates/schemas/openapi.json +1740 -0
  69. ads/templates/score_embedding_onnx.jinja2 +202 -0
  70. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/METADATA +9 -8
  71. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/RECORD +74 -48
  72. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/LICENSE.txt +0 -0
  73. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/WHEEL +0 -0
  74. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env python
2
- # Copyright (c) 2024 Oracle and/or its affiliates.
2
+ # Copyright (c) 2024, 2025 Oracle and/or its affiliates.
3
3
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
4
4
 
5
- import logging
6
5
  import shlex
7
6
  from typing import Dict, List, Optional, Union
8
7
 
@@ -271,7 +270,7 @@ class AquaDeploymentApp(AquaApp):
271
270
  f"field. Either re-register the model with custom container URI, or set container_image_uri "
272
271
  f"parameter when creating this deployment."
273
272
  ) from err
274
- logging.info(
273
+ logger.info(
275
274
  f"Aqua Image used for deploying {aqua_model.id} : {container_image_uri}"
276
275
  )
277
276
 
@@ -282,14 +281,14 @@ class AquaDeploymentApp(AquaApp):
282
281
  default_cmd_var = shlex.split(cmd_var_string)
283
282
  if default_cmd_var:
284
283
  cmd_var = validate_cmd_var(default_cmd_var, cmd_var)
285
- logging.info(f"CMD used for deploying {aqua_model.id} :{cmd_var}")
284
+ logger.info(f"CMD used for deploying {aqua_model.id} :{cmd_var}")
286
285
  except ValueError:
287
- logging.debug(
286
+ logger.debug(
288
287
  f"CMD will be ignored for this deployment as {AQUA_DEPLOYMENT_CONTAINER_CMD_VAR_METADATA_NAME} "
289
288
  f"key is not available in the custom metadata field for this model."
290
289
  )
291
290
  except Exception as e:
292
- logging.error(
291
+ logger.error(
293
292
  f"There was an issue processing CMD arguments. Error: {str(e)}"
294
293
  )
295
294
 
@@ -385,7 +384,7 @@ class AquaDeploymentApp(AquaApp):
385
384
  if key not in env_var:
386
385
  env_var.update(env)
387
386
 
388
- logging.info(f"Env vars used for deploying {aqua_model.id} :{env_var}")
387
+ logger.info(f"Env vars used for deploying {aqua_model.id} :{env_var}")
389
388
 
390
389
  # Start model deployment
391
390
  # configure model deployment infrastructure
@@ -440,10 +439,14 @@ class AquaDeploymentApp(AquaApp):
440
439
  .with_runtime(container_runtime)
441
440
  ).deploy(wait_for_completion=False)
442
441
 
442
+ deployment_id = deployment.dsc_model_deployment.id
443
+ logger.info(
444
+ f"Aqua model deployment {deployment_id} created for model {aqua_model.id}."
445
+ )
443
446
  model_type = (
444
447
  AQUA_MODEL_TYPE_CUSTOM if is_fine_tuned_model else AQUA_MODEL_TYPE_SERVICE
445
448
  )
446
- deployment_id = deployment.dsc_model_deployment.id
449
+
447
450
  # we arbitrarily choose last 8 characters of OCID to identify MD in telemetry
448
451
  telemetry_kwargs = {"ocid": get_ocid_substring(deployment_id, key_len=8)}
449
452
 
@@ -539,6 +542,9 @@ class AquaDeploymentApp(AquaApp):
539
542
  value=state,
540
543
  )
541
544
 
545
+ logger.info(
546
+ f"Fetched {len(results)} model deployments from compartment_id={compartment_id}."
547
+ )
542
548
  # tracks number of times deployment listing was called
543
549
  self.telemetry.record_event_async(category="aqua/deployment", action="list")
544
550
 
@@ -546,18 +552,21 @@ class AquaDeploymentApp(AquaApp):
546
552
 
547
553
  @telemetry(entry_point="plugin=deployment&action=delete", name="aqua")
548
554
  def delete(self, model_deployment_id: str):
555
+ logger.info(f"Deleting model deployment {model_deployment_id}.")
549
556
  return self.ds_client.delete_model_deployment(
550
557
  model_deployment_id=model_deployment_id
551
558
  ).data
552
559
 
553
560
  @telemetry(entry_point="plugin=deployment&action=deactivate", name="aqua")
554
561
  def deactivate(self, model_deployment_id: str):
562
+ logger.info(f"Deactivating model deployment {model_deployment_id}.")
555
563
  return self.ds_client.deactivate_model_deployment(
556
564
  model_deployment_id=model_deployment_id
557
565
  ).data
558
566
 
559
567
  @telemetry(entry_point="plugin=deployment&action=activate", name="aqua")
560
568
  def activate(self, model_deployment_id: str):
569
+ logger.info(f"Activating model deployment {model_deployment_id}.")
561
570
  return self.ds_client.activate_model_deployment(
562
571
  model_deployment_id=model_deployment_id
563
572
  ).data
@@ -579,6 +588,8 @@ class AquaDeploymentApp(AquaApp):
579
588
  AquaDeploymentDetail:
580
589
  The instance of the Aqua model deployment details.
581
590
  """
591
+ logger.info(f"Fetching model deployment details for {model_deployment_id}.")
592
+
582
593
  model_deployment = self.ds_client.get_model_deployment(
583
594
  model_deployment_id=model_deployment_id, **kwargs
584
595
  ).data
@@ -594,7 +605,8 @@ class AquaDeploymentApp(AquaApp):
594
605
 
595
606
  if not oci_aqua:
596
607
  raise AquaRuntimeError(
597
- f"Target deployment {model_deployment_id} is not Aqua deployment."
608
+ f"Target deployment {model_deployment_id} is not Aqua deployment as it does not contain "
609
+ f"{Tags.AQUA_TAG} tag."
598
610
  )
599
611
 
600
612
  log_id = ""
@@ -652,7 +664,7 @@ class AquaDeploymentApp(AquaApp):
652
664
  config = self.get_config(model_id, AQUA_MODEL_DEPLOYMENT_CONFIG)
653
665
  if not config:
654
666
  logger.debug(
655
- f"Deployment config for custom model: {model_id} is not available."
667
+ f"Deployment config for custom model: {model_id} is not available. Use defaults."
656
668
  )
657
669
  return config
658
670
 
ads/cli.py CHANGED
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env python
2
- # -*- coding: utf-8 -*--
3
-
4
- # Copyright (c) 2021, 2024 Oracle and/or its affiliates.
2
+ # Copyright (c) 2021, 2025 Oracle and/or its affiliates.
5
3
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
6
4
 
5
+ import json
6
+ import logging
7
7
  import sys
8
8
  import traceback
9
- from dataclasses import is_dataclass
9
+ import uuid
10
10
 
11
11
  import fire
12
+ from pydantic import BaseModel
12
13
 
13
14
  from ads.common import logger
14
15
 
@@ -27,7 +28,7 @@ except Exception as ex:
27
28
  )
28
29
  logger.debug(ex)
29
30
  logger.debug(traceback.format_exc())
30
- exit()
31
+ sys.exit()
31
32
 
32
33
  # https://packaging.python.org/en/latest/guides/single-sourcing-package-version/#single-sourcing-the-package-version
33
34
  if sys.version_info >= (3, 8):
@@ -84,7 +85,13 @@ def serialize(data):
84
85
  The string representation of each dataclass object.
85
86
  """
86
87
  if isinstance(data, list):
87
- [print(str(item)) for item in data]
88
+ for item in data:
89
+ if isinstance(item, BaseModel):
90
+ print(json.dumps(item.dict(), indent=4))
91
+ else:
92
+ print(str(item))
93
+ elif isinstance(data, BaseModel):
94
+ print(json.dumps(data.dict(), indent=4))
88
95
  else:
89
96
  print(str(data))
90
97
 
@@ -122,8 +129,9 @@ def exit_program(ex: Exception, logger: "logging.Logger") -> None:
122
129
  ... exit_program(e, logger)
123
130
  """
124
131
 
125
- logger.debug(traceback.format_exc())
126
- logger.error(str(ex))
132
+ request_id = str(uuid.uuid4())
133
+ logger.debug(f"Error Request ID: {request_id}\nError: {traceback.format_exc()}")
134
+ logger.error(f"Error Request ID: {request_id}\n" f"Error: {str(ex)}")
127
135
 
128
136
  exit_code = getattr(ex, "exit_code", 1)
129
137
  logger.error(f"Exit code: {exit_code}")
ads/common/auth.py CHANGED
@@ -424,7 +424,7 @@ def create_signer(
424
424
  "signer": signer,
425
425
  "client_kwargs": client_kwargs,
426
426
  }
427
- logger.info(f"Using authentication signer type {type(signer)}.")
427
+ logger.debug(f"Using authentication signer type {type(signer)}.")
428
428
  return signer_dict
429
429
  else:
430
430
  signer_args = dict(
@@ -492,7 +492,7 @@ def default_signer(client_kwargs: Optional[Dict] = None) -> Dict:
492
492
  **(client_kwargs or {}),
493
493
  },
494
494
  }
495
- logger.info(f"Using authentication signer type {type(signer)}.")
495
+ logger.debug(f"Using authentication signer type {type(signer)}.")
496
496
  return signer_dict
497
497
  else:
498
498
  signer_args = dict(
@@ -621,7 +621,7 @@ class APIKey(AuthSignerGenerator):
621
621
  )
622
622
 
623
623
  oci.config.validate_config(configuration)
624
- logger.info(f"Using 'api_key' authentication.")
624
+ logger.debug(f"Using 'api_key' authentication.")
625
625
  return {
626
626
  "config": configuration,
627
627
  "signer": oci.signer.Signer(
@@ -684,7 +684,7 @@ class ResourcePrincipal(AuthSignerGenerator):
684
684
  "signer": oci.auth.signers.get_resource_principals_signer(),
685
685
  "client_kwargs": self.client_kwargs,
686
686
  }
687
- logger.info(f"Using 'resource_principal' authentication.")
687
+ logger.debug(f"Using 'resource_principal' authentication.")
688
688
  return signer_dict
689
689
 
690
690
  @staticmethod
@@ -747,7 +747,7 @@ class InstancePrincipal(AuthSignerGenerator):
747
747
  ),
748
748
  "client_kwargs": self.client_kwargs,
749
749
  }
750
- logger.info(f"Using 'instance_principal' authentication.")
750
+ logger.debug(f"Using 'instance_principal' authentication.")
751
751
  return signer_dict
752
752
 
753
753
 
@@ -814,7 +814,7 @@ class SecurityToken(AuthSignerGenerator):
814
814
  oci.config.from_file(self.oci_config_location, self.oci_key_profile)
815
815
  )
816
816
 
817
- logger.info(f"Using 'security_token' authentication.")
817
+ logger.debug(f"Using 'security_token' authentication.")
818
818
 
819
819
  for parameter in self.SECURITY_TOKEN_REQUIRED:
820
820
  if parameter not in configuration:
@@ -883,7 +883,7 @@ class SecurityToken(AuthSignerGenerator):
883
883
  )
884
884
 
885
885
  date_time = datetime.fromtimestamp(time_expired).strftime("%Y-%m-%d %H:%M:%S")
886
- logger.info(f"Session is valid until {date_time}.")
886
+ logger.debug(f"Session is valid until {date_time}.")
887
887
 
888
888
  def _read_security_token_file(self, security_token_file: str) -> str:
889
889
  """Reads security token from file.
@@ -1020,10 +1020,10 @@ class OCIAuthContext:
1020
1020
  """
1021
1021
  if self.profile:
1022
1022
  ads.set_auth(auth=AuthType.API_KEY, profile=self.profile)
1023
- logger.info(f"OCI profile set to {self.profile}")
1023
+ logger.debug(f"OCI profile set to {self.profile}")
1024
1024
  else:
1025
1025
  ads.set_auth(auth=AuthType.RESOURCE_PRINCIPAL)
1026
- logger.info(f"OCI auth set to resource principal")
1026
+ logger.debug(f"OCI auth set to resource principal")
1027
1027
  return self
1028
1028
 
1029
1029
  def __exit__(self, exc_type, exc_val, exc_tb):
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
@@ -0,0 +1,15 @@
1
+ # Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+
4
+
5
+ class Events:
6
+ KEY = "event_name"
7
+
8
+ EXCEPTION = "exception"
9
+ LLM_CALL = "llm_call"
10
+ TOOL_CALL = "tool_call"
11
+ NEW_AGENT = "new_agent"
12
+ NEW_CLIENT = "new_client"
13
+ RECEIVED_MESSAGE = "received_message"
14
+ SESSION_START = "logging_session_start"
15
+ SESSION_STOP = "logging_session_stop"
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
@@ -0,0 +1,67 @@
1
+ # Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3
+ import json
4
+ import logging
5
+ import os
6
+
7
+ from jinja2 import Environment, FileSystemLoader
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class BaseReport:
13
+ """Base class containing utilities for generating reports."""
14
+
15
+ @staticmethod
16
+ def format_json_string(s) -> str:
17
+ """Formats the JSON string in markdown."""
18
+ return f"```json\n{json.dumps(json.loads(s), indent=2)}\n```"
19
+
20
+ @staticmethod
21
+ def _parse_date_time(datetime_string: str):
22
+ """Parses a datetime string in the logs into date and time.
23
+ Keeps only the seconds in the time.
24
+ """
25
+ date_str, time_str = datetime_string.split(" ", 1)
26
+ time_str = time_str.split(".", 1)[0]
27
+ return date_str, time_str
28
+
29
+ @staticmethod
30
+ def _preview_message(message: str, max_length=30) -> str:
31
+ """Shows the beginning part of a string message."""
32
+ # Return the entire string if it is less than the max_length
33
+ if len(message) <= max_length:
34
+ return message
35
+ # Go backward until we find the first whitespace
36
+ idx = 30
37
+ while not message[idx].isspace() and idx > 0:
38
+ idx -= 1
39
+ # If we found a whitespace
40
+ if idx > 0:
41
+ return message[:idx] + "..."
42
+ # If we didn't find a whitespace
43
+ return message[:30] + "..."
44
+
45
+ @classmethod
46
+ def _render_template(cls, template_path, **kwargs) -> str:
47
+ """Render Jinja template with kwargs."""
48
+ template_dir = os.path.join(os.path.dirname(__file__), "templates")
49
+ environment = Environment(
50
+ loader=FileSystemLoader(template_dir), autoescape=True
51
+ )
52
+ template = environment.get_template(template_path)
53
+ try:
54
+ html = template.render(**kwargs)
55
+ except Exception:
56
+ logger.error(
57
+ "Unable to render template %s with data:\n%s",
58
+ template_path,
59
+ str(kwargs),
60
+ )
61
+ return cls._render_template(
62
+ template_path=template_path,
63
+ sender=kwargs.get("sender", "N/A"),
64
+ content="TEMPLATE RENDER ERROR",
65
+ timestamp=kwargs.get("timestamp", ""),
66
+ )
67
+ return html
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python
2
+ # Copyright (c) 2024 Oracle and/or its affiliates.
3
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
4
+ """Contains the data structure for logging and reporting."""
5
+ import copy
6
+ import json
7
+ from dataclasses import asdict, dataclass, field
8
+ from typing import Optional, Union
9
+
10
+ from ads.llm.autogen.constants import Events
11
+
12
+
13
+ @dataclass
14
+ class LogData:
15
+ """Base class for the data field of LogRecord."""
16
+
17
+ def to_dict(self):
18
+ """Convert the log data to dictionary."""
19
+ return asdict(self)
20
+
21
+
22
+ @dataclass
23
+ class LogRecord:
24
+ """Represents a log record.
25
+
26
+ The `data` field is for pre-defined structured data, which should be an instance of LogData.
27
+ The `kwargs` field is for freeform key value pairs.
28
+ """
29
+
30
+ session_id: str
31
+ thread_id: int
32
+ timestamp: str
33
+ event_name: str
34
+ source_id: Optional[int] = None
35
+ source_name: Optional[str] = None
36
+ # Structured data for specific type of logs
37
+ data: Optional[LogData] = None
38
+ # Freeform data
39
+ kwargs: dict = field(default_factory=dict)
40
+
41
+ def to_dict(self):
42
+ """Convert the log record to dictionary."""
43
+ return asdict(self)
44
+
45
+ def to_string(self):
46
+ """Serialize the log record to JSON string."""
47
+ return json.dumps(self.to_dict(), default=str)
48
+
49
+ @classmethod
50
+ def from_dict(cls, data: dict) -> "LogRecord":
51
+ """Initializes a LogRecord object from dictionary."""
52
+ event_mapping = {
53
+ Events.NEW_AGENT: AgentData,
54
+ Events.TOOL_CALL: ToolCallData,
55
+ Events.LLM_CALL: LLMCompletionData,
56
+ }
57
+ if Events.KEY not in data:
58
+ raise KeyError("event_name not found in data.")
59
+
60
+ data = copy.deepcopy(data)
61
+
62
+ event_name = data["event_name"]
63
+ if event_name in event_mapping and data.get("data"):
64
+ data["data"] = event_mapping[event_name](**data.pop("data"))
65
+
66
+ return cls(**data)
67
+
68
+
69
+ @dataclass
70
+ class AgentData(LogData):
71
+ """Represents agent log Data."""
72
+
73
+ agent_name: str
74
+ agent_class: str
75
+ agent_module: Optional[str] = None
76
+ is_manager: Optional[bool] = None
77
+
78
+
79
+ @dataclass
80
+ class LLMCompletionData(LogData):
81
+ """Represents LLM completion log data."""
82
+
83
+ invocation_id: str
84
+ request: dict
85
+ response: dict
86
+ start_time: str
87
+ end_time: str
88
+ cost: Optional[float] = None
89
+ is_cached: Optional[bool] = None
90
+
91
+
92
+ @dataclass
93
+ class ToolCallData(LogData):
94
+ """Represents tool call log data."""
95
+
96
+ tool_name: str
97
+ start_time: str
98
+ end_time: str
99
+ agent_name: str
100
+ agent_class: str
101
+ agent_module: Optional[str] = None
102
+ input_args: dict = field(default_factory=dict)
103
+ returns: Optional[Union[str, list, dict, tuple]] = None