aiverify-moonshot 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.
Files changed (163) hide show
  1. aiverify_moonshot-0.4.0.dist-info/METADATA +249 -0
  2. aiverify_moonshot-0.4.0.dist-info/RECORD +163 -0
  3. aiverify_moonshot-0.4.0.dist-info/WHEEL +4 -0
  4. aiverify_moonshot-0.4.0.dist-info/licenses/AUTHORS.md +5 -0
  5. aiverify_moonshot-0.4.0.dist-info/licenses/LICENSE.md +201 -0
  6. aiverify_moonshot-0.4.0.dist-info/licenses/NOTICES.md +3340 -0
  7. moonshot/__init__.py +0 -0
  8. moonshot/__main__.py +198 -0
  9. moonshot/api.py +155 -0
  10. moonshot/integrations/__init__.py +0 -0
  11. moonshot/integrations/cli/__init__.py +0 -0
  12. moonshot/integrations/cli/__main__.py +25 -0
  13. moonshot/integrations/cli/active_session_cfg.py +1 -0
  14. moonshot/integrations/cli/benchmark/__init__.py +0 -0
  15. moonshot/integrations/cli/benchmark/benchmark.py +186 -0
  16. moonshot/integrations/cli/benchmark/cookbook.py +545 -0
  17. moonshot/integrations/cli/benchmark/datasets.py +164 -0
  18. moonshot/integrations/cli/benchmark/metrics.py +141 -0
  19. moonshot/integrations/cli/benchmark/recipe.py +598 -0
  20. moonshot/integrations/cli/benchmark/result.py +216 -0
  21. moonshot/integrations/cli/benchmark/run.py +140 -0
  22. moonshot/integrations/cli/benchmark/runner.py +174 -0
  23. moonshot/integrations/cli/cli.py +64 -0
  24. moonshot/integrations/cli/common/__init__.py +0 -0
  25. moonshot/integrations/cli/common/common.py +72 -0
  26. moonshot/integrations/cli/common/connectors.py +325 -0
  27. moonshot/integrations/cli/common/display_helper.py +42 -0
  28. moonshot/integrations/cli/common/prompt_template.py +94 -0
  29. moonshot/integrations/cli/initialisation/__init__.py +0 -0
  30. moonshot/integrations/cli/initialisation/initialisation.py +14 -0
  31. moonshot/integrations/cli/redteam/__init__.py +0 -0
  32. moonshot/integrations/cli/redteam/attack_module.py +70 -0
  33. moonshot/integrations/cli/redteam/context_strategy.py +147 -0
  34. moonshot/integrations/cli/redteam/prompt_template.py +67 -0
  35. moonshot/integrations/cli/redteam/redteam.py +90 -0
  36. moonshot/integrations/cli/redteam/session.py +467 -0
  37. moonshot/integrations/web_api/.env.dev +7 -0
  38. moonshot/integrations/web_api/__init__.py +0 -0
  39. moonshot/integrations/web_api/__main__.py +56 -0
  40. moonshot/integrations/web_api/app.py +125 -0
  41. moonshot/integrations/web_api/container.py +146 -0
  42. moonshot/integrations/web_api/log/.gitkeep +0 -0
  43. moonshot/integrations/web_api/logging_conf.py +114 -0
  44. moonshot/integrations/web_api/routes/__init__.py +0 -0
  45. moonshot/integrations/web_api/routes/attack_modules.py +66 -0
  46. moonshot/integrations/web_api/routes/benchmark.py +116 -0
  47. moonshot/integrations/web_api/routes/benchmark_result.py +175 -0
  48. moonshot/integrations/web_api/routes/context_strategy.py +129 -0
  49. moonshot/integrations/web_api/routes/cookbook.py +225 -0
  50. moonshot/integrations/web_api/routes/dataset.py +120 -0
  51. moonshot/integrations/web_api/routes/endpoint.py +282 -0
  52. moonshot/integrations/web_api/routes/metric.py +78 -0
  53. moonshot/integrations/web_api/routes/prompt_template.py +128 -0
  54. moonshot/integrations/web_api/routes/recipe.py +219 -0
  55. moonshot/integrations/web_api/routes/redteam.py +609 -0
  56. moonshot/integrations/web_api/routes/runner.py +239 -0
  57. moonshot/integrations/web_api/schemas/__init__.py +0 -0
  58. moonshot/integrations/web_api/schemas/benchmark_runner_dto.py +13 -0
  59. moonshot/integrations/web_api/schemas/cookbook_create_dto.py +19 -0
  60. moonshot/integrations/web_api/schemas/cookbook_response_model.py +9 -0
  61. moonshot/integrations/web_api/schemas/dataset_response_dto.py +9 -0
  62. moonshot/integrations/web_api/schemas/endpoint_create_dto.py +21 -0
  63. moonshot/integrations/web_api/schemas/endpoint_response_model.py +11 -0
  64. moonshot/integrations/web_api/schemas/prompt_response_model.py +14 -0
  65. moonshot/integrations/web_api/schemas/prompt_template_response_model.py +10 -0
  66. moonshot/integrations/web_api/schemas/recipe_create_dto.py +32 -0
  67. moonshot/integrations/web_api/schemas/recipe_response_model.py +7 -0
  68. moonshot/integrations/web_api/schemas/session_create_dto.py +16 -0
  69. moonshot/integrations/web_api/schemas/session_prompt_dto.py +7 -0
  70. moonshot/integrations/web_api/schemas/session_response_model.py +38 -0
  71. moonshot/integrations/web_api/services/__init__.py +0 -0
  72. moonshot/integrations/web_api/services/attack_module_service.py +34 -0
  73. moonshot/integrations/web_api/services/auto_red_team_test_manager.py +86 -0
  74. moonshot/integrations/web_api/services/auto_red_team_test_state.py +57 -0
  75. moonshot/integrations/web_api/services/base_service.py +8 -0
  76. moonshot/integrations/web_api/services/benchmark_result_service.py +25 -0
  77. moonshot/integrations/web_api/services/benchmark_test_manager.py +106 -0
  78. moonshot/integrations/web_api/services/benchmark_test_state.py +56 -0
  79. moonshot/integrations/web_api/services/benchmarking_service.py +31 -0
  80. moonshot/integrations/web_api/services/context_strategy_service.py +22 -0
  81. moonshot/integrations/web_api/services/cookbook_service.py +194 -0
  82. moonshot/integrations/web_api/services/dataset_service.py +20 -0
  83. moonshot/integrations/web_api/services/endpoint_service.py +65 -0
  84. moonshot/integrations/web_api/services/metric_service.py +14 -0
  85. moonshot/integrations/web_api/services/prompt_template_service.py +39 -0
  86. moonshot/integrations/web_api/services/recipe_service.py +155 -0
  87. moonshot/integrations/web_api/services/runner_service.py +147 -0
  88. moonshot/integrations/web_api/services/session_service.py +350 -0
  89. moonshot/integrations/web_api/services/utils/exceptions_handler.py +41 -0
  90. moonshot/integrations/web_api/services/utils/results_formatter.py +47 -0
  91. moonshot/integrations/web_api/status_updater/interface/benchmark_progress_callback.py +14 -0
  92. moonshot/integrations/web_api/status_updater/interface/redteam_progress_callback.py +14 -0
  93. moonshot/integrations/web_api/status_updater/moonshot_ui_webhook.py +72 -0
  94. moonshot/integrations/web_api/types/types.py +99 -0
  95. moonshot/src/__init__.py +0 -0
  96. moonshot/src/api/__init__.py +0 -0
  97. moonshot/src/api/api_connector.py +58 -0
  98. moonshot/src/api/api_connector_endpoint.py +162 -0
  99. moonshot/src/api/api_context_strategy.py +57 -0
  100. moonshot/src/api/api_cookbook.py +160 -0
  101. moonshot/src/api/api_dataset.py +46 -0
  102. moonshot/src/api/api_environment_variables.py +17 -0
  103. moonshot/src/api/api_metrics.py +51 -0
  104. moonshot/src/api/api_prompt_template.py +43 -0
  105. moonshot/src/api/api_recipe.py +182 -0
  106. moonshot/src/api/api_red_teaming.py +59 -0
  107. moonshot/src/api/api_result.py +84 -0
  108. moonshot/src/api/api_run.py +74 -0
  109. moonshot/src/api/api_runner.py +132 -0
  110. moonshot/src/api/api_session.py +290 -0
  111. moonshot/src/configs/__init__.py +0 -0
  112. moonshot/src/configs/env_variables.py +187 -0
  113. moonshot/src/connectors/__init__.py +0 -0
  114. moonshot/src/connectors/connector.py +327 -0
  115. moonshot/src/connectors/connector_prompt_arguments.py +17 -0
  116. moonshot/src/connectors_endpoints/__init__.py +0 -0
  117. moonshot/src/connectors_endpoints/connector_endpoint.py +211 -0
  118. moonshot/src/connectors_endpoints/connector_endpoint_arguments.py +54 -0
  119. moonshot/src/cookbooks/__init__.py +0 -0
  120. moonshot/src/cookbooks/cookbook.py +225 -0
  121. moonshot/src/cookbooks/cookbook_arguments.py +34 -0
  122. moonshot/src/datasets/__init__.py +0 -0
  123. moonshot/src/datasets/dataset.py +255 -0
  124. moonshot/src/datasets/dataset_arguments.py +50 -0
  125. moonshot/src/metrics/__init__.py +0 -0
  126. moonshot/src/metrics/metric.py +192 -0
  127. moonshot/src/metrics/metric_interface.py +95 -0
  128. moonshot/src/prompt_templates/__init__.py +0 -0
  129. moonshot/src/prompt_templates/prompt_template.py +103 -0
  130. moonshot/src/recipes/__init__.py +0 -0
  131. moonshot/src/recipes/recipe.py +340 -0
  132. moonshot/src/recipes/recipe_arguments.py +111 -0
  133. moonshot/src/redteaming/__init__.py +0 -0
  134. moonshot/src/redteaming/attack/__init__.py +0 -0
  135. moonshot/src/redteaming/attack/attack_module.py +618 -0
  136. moonshot/src/redteaming/attack/attack_module_arguments.py +44 -0
  137. moonshot/src/redteaming/attack/context_strategy.py +131 -0
  138. moonshot/src/redteaming/context_strategy/__init__.py +0 -0
  139. moonshot/src/redteaming/context_strategy/context_strategy_interface.py +46 -0
  140. moonshot/src/redteaming/session/__init__.py +0 -0
  141. moonshot/src/redteaming/session/chat.py +209 -0
  142. moonshot/src/redteaming/session/red_teaming_progress.py +128 -0
  143. moonshot/src/redteaming/session/red_teaming_type.py +6 -0
  144. moonshot/src/redteaming/session/session.py +775 -0
  145. moonshot/src/results/__init__.py +0 -0
  146. moonshot/src/results/result.py +119 -0
  147. moonshot/src/results/result_arguments.py +44 -0
  148. moonshot/src/runners/__init__.py +0 -0
  149. moonshot/src/runners/runner.py +476 -0
  150. moonshot/src/runners/runner_arguments.py +46 -0
  151. moonshot/src/runners/runner_type.py +6 -0
  152. moonshot/src/runs/__init__.py +0 -0
  153. moonshot/src/runs/run.py +344 -0
  154. moonshot/src/runs/run_arguments.py +162 -0
  155. moonshot/src/runs/run_progress.py +145 -0
  156. moonshot/src/runs/run_status.py +10 -0
  157. moonshot/src/storage/__init__.py +0 -0
  158. moonshot/src/storage/db_interface.py +128 -0
  159. moonshot/src/storage/io_interface.py +31 -0
  160. moonshot/src/storage/storage.py +525 -0
  161. moonshot/src/utils/__init__.py +0 -0
  162. moonshot/src/utils/import_modules.py +96 -0
  163. moonshot/src/utils/timeit.py +25 -0
@@ -0,0 +1,340 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from pydantic import validate_call
6
+ from slugify import slugify
7
+
8
+ from moonshot.src.configs.env_variables import EnvVariables
9
+ from moonshot.src.datasets.dataset import Dataset
10
+ from moonshot.src.recipes.recipe_arguments import RecipeArguments
11
+ from moonshot.src.storage.storage import Storage
12
+
13
+
14
+ class Recipe:
15
+ def __init__(self, rec_args: RecipeArguments) -> None:
16
+ self.id = rec_args.id
17
+ self.name = rec_args.name
18
+ self.description = rec_args.description
19
+ self.tags = rec_args.tags
20
+ self.categories = rec_args.categories
21
+ self.datasets = rec_args.datasets
22
+ self.prompt_templates = rec_args.prompt_templates
23
+ self.metrics = rec_args.metrics
24
+ self.attack_modules = rec_args.attack_modules
25
+ self.grading_scale = rec_args.grading_scale
26
+ self.stats = rec_args.stats
27
+
28
+ @classmethod
29
+ def load(cls, rec_id: str) -> Recipe:
30
+ """
31
+ Loads a recipe from persistent storage.
32
+
33
+ This method constructs the file path for the recipe's JSON file using the provided recipe ID and the
34
+ predefined recipe directory. It reads the JSON file, deserializes the recipe data, and instantiates a Recipe
35
+ object with the loaded data.
36
+
37
+ Args:
38
+ rec_id (str): The unique identifier for the recipe to be loaded.
39
+
40
+ Returns:
41
+ Recipe: A Recipe object populated with the data from the recipe's JSON file.
42
+ """
43
+ return cls(Recipe.read(rec_id))
44
+
45
+ @staticmethod
46
+ def create(rec_args: RecipeArguments) -> str:
47
+ """
48
+ Creates a new recipe and saves its details in a JSON file.
49
+
50
+ This method uses the `rec_args` parameter to generate a unique recipe ID by slugifying the recipe name.
51
+ It then builds a dictionary with the recipe's details and writes this information to a JSON file.
52
+ The JSON file is named after the recipe ID and is stored in the directory specified by
53
+ `EnvironmentVars.RECIPES`.
54
+ If any error occurs during the process, an exception is thrown and the error message is printed.
55
+
56
+ Args:
57
+ rec_args (RecipeArguments): An object that holds the necessary details to create a new recipe.
58
+
59
+ Returns:
60
+ str: The unique ID of the newly created recipe.
61
+
62
+ Raises:
63
+ Exception: If an error occurs during the file writing process or any other operation within the method.
64
+ """
65
+ try:
66
+ rec_id = slugify(rec_args.name, lowercase=True)
67
+ # check if the recipe exists
68
+ if Storage.is_object_exists(EnvVariables.RECIPES.name, rec_id, "json"):
69
+ raise RuntimeError(f"Recipe with ID '{rec_id}' already exists.")
70
+
71
+ rec_info = {
72
+ "id": rec_id,
73
+ "name": rec_args.name,
74
+ "description": rec_args.description,
75
+ "tags": rec_args.tags,
76
+ "categories": rec_args.categories,
77
+ "datasets": rec_args.datasets,
78
+ "prompt_templates": rec_args.prompt_templates,
79
+ "metrics": rec_args.metrics,
80
+ "attack_modules": rec_args.attack_modules,
81
+ "grading_scale": rec_args.grading_scale,
82
+ }
83
+
84
+ Recipe.check_file_exists(
85
+ EnvVariables.PROMPT_TEMPLATES.name,
86
+ rec_args.prompt_templates,
87
+ "Prompt Template",
88
+ "json",
89
+ )
90
+ Recipe.check_file_exists(
91
+ EnvVariables.DATASETS.name, rec_args.datasets, "Dataset", "json"
92
+ )
93
+ Recipe.check_file_exists(
94
+ EnvVariables.METRICS.name, rec_args.metrics, "Metric", "py"
95
+ )
96
+ Recipe.check_file_exists(
97
+ EnvVariables.ATTACK_MODULES.name,
98
+ rec_args.attack_modules,
99
+ "Attack Module",
100
+ "py",
101
+ )
102
+
103
+ # Write as json output
104
+ Storage.create_object(EnvVariables.RECIPES.name, rec_id, rec_info, "json")
105
+ return rec_id
106
+
107
+ except Exception as e:
108
+ print(f"Failed to create recipe: {str(e)}")
109
+ raise e
110
+
111
+ @staticmethod
112
+ @validate_call
113
+ def read(rec_id: str) -> RecipeArguments:
114
+ """
115
+ Retrieves the details of a specific recipe.
116
+
117
+ This static method takes a recipe ID as input, locates the corresponding JSON file within the directory
118
+ specified by `EnvironmentVars.RECIPES`, and constructs a RecipeArguments object that contains the details
119
+ of the recipe.
120
+
121
+ Args:
122
+ rec_id (str): The unique identifier for the recipe to be retrieved.
123
+
124
+ Returns:
125
+ RecipeArguments: A populated object with the recipe's details.
126
+
127
+ Raises:
128
+ Exception: If there is an issue reading the file or during any other part of the process.
129
+ """
130
+ try:
131
+ if rec_id:
132
+ return RecipeArguments(**Recipe._read_recipe(rec_id, {}))
133
+ else:
134
+ raise RuntimeError("Recipe ID is empty")
135
+
136
+ except Exception as e:
137
+ print(f"Failed to read recipe: {str(e)}")
138
+ raise e
139
+
140
+ @staticmethod
141
+ def _get_datasets_prompt_counts() -> dict:
142
+ """
143
+ Generates a mapping of dataset IDs to their number of prompts.
144
+
145
+ This method reads the cache information from the storage, which contains the number of prompts for each dataset.
146
+ It then creates a dictionary mapping each dataset ID to the corresponding number of prompts.
147
+
148
+ Returns:
149
+ dict: A dictionary where keys are dataset IDs and values are the number of prompts for that dataset.
150
+ """
151
+ # Calculate statistics for the recipe and update the results dictionary with them
152
+ _, dataset_results = Dataset.get_available_items()
153
+ # Create a mapping of dataset IDs to their number of prompts
154
+ return {
155
+ dataset.id: dataset.num_of_dataset_prompts for dataset in dataset_results
156
+ }
157
+
158
+ @staticmethod
159
+ def _read_recipe(rec_id: str, dataset_prompts_count: dict) -> dict:
160
+ """
161
+ Reads the recipe JSON file based on the provided recipe ID and dataset prompts count
162
+ and returns the recipe information as a dictionary.
163
+
164
+ Args:
165
+ rec_id (str): The unique identifier for the recipe.
166
+ dataset_prompts_count (dict): A dictionary mapping dataset IDs to their number of prompts.
167
+
168
+ Returns:
169
+ dict: A dictionary containing the recipe information.
170
+
171
+ Raises:
172
+ RuntimeError: If the recipe file cannot be read or does not exist.
173
+ """
174
+ obj_results = Storage.read_object(EnvVariables.RECIPES.name, rec_id, "json")
175
+ if not obj_results:
176
+ raise RuntimeError(f"Unable to get results for {rec_id}.")
177
+
178
+ # Calculate statistics for the recipe and update the results dictionary with them
179
+ stats = {
180
+ "num_of_tags": len(obj_results["tags"]),
181
+ "num_of_datasets": len(obj_results["datasets"]),
182
+ "num_of_prompt_templates": len(obj_results["prompt_templates"]),
183
+ "num_of_metrics": len(obj_results["metrics"]),
184
+ "num_of_attack_modules": len(obj_results["attack_modules"]),
185
+ "num_of_datasets_prompts": {},
186
+ }
187
+
188
+ if dataset_prompts_count:
189
+ stats["num_of_datasets_prompts"] = {
190
+ dataset_name: dataset_prompts_count.get(dataset_name, 0)
191
+ for dataset_name in obj_results["datasets"]
192
+ }
193
+ else:
194
+ _, datasets_metadata = Dataset.get_available_items(obj_results["datasets"])
195
+ stats["num_of_datasets_prompts"] = {
196
+ dataset.id: dataset.num_of_dataset_prompts
197
+ for dataset in datasets_metadata
198
+ }
199
+
200
+ obj_results["stats"] = stats
201
+ return obj_results
202
+
203
+ @staticmethod
204
+ def update(rec_args: RecipeArguments) -> bool:
205
+ """
206
+ Updates the recipe information based on the provided RecipeArguments.
207
+
208
+ This method takes RecipeArguments, converts it to a dictionary, and writes the updated
209
+ recipe information to the storage. If the operation is successful, it returns True.
210
+ If an exception occurs, it prints an error message and re-raises the exception.
211
+
212
+ Args:
213
+ rec_args (RecipeArguments): The recipe arguments containing updated values.
214
+
215
+ Returns:
216
+ bool: True if the recipe was successfully updated.
217
+
218
+ Raises:
219
+ Exception: If an error occurs during the update process.
220
+ """
221
+ try:
222
+ # Convert the recipe arguments to a dictionary
223
+ rec_info = rec_args.to_dict()
224
+
225
+ Recipe.check_file_exists(
226
+ EnvVariables.PROMPT_TEMPLATES.name,
227
+ rec_args.prompt_templates,
228
+ "Prompt Template",
229
+ "json",
230
+ )
231
+ Recipe.check_file_exists(
232
+ EnvVariables.DATASETS.name, rec_args.datasets, "Dataset", "json"
233
+ )
234
+ Recipe.check_file_exists(
235
+ EnvVariables.METRICS.name, rec_args.metrics, "Metric", "py"
236
+ )
237
+ Recipe.check_file_exists(
238
+ EnvVariables.ATTACK_MODULES.name,
239
+ rec_args.attack_modules,
240
+ "Attack Module",
241
+ "py",
242
+ )
243
+
244
+ # Write the updated recipe information to the file
245
+ Storage.create_object(
246
+ EnvVariables.RECIPES.name, rec_args.id, rec_info, "json"
247
+ )
248
+ return True
249
+
250
+ except Exception as e:
251
+ print(f"Failed to update recipe: {str(e)}")
252
+ raise e
253
+
254
+ @staticmethod
255
+ @validate_call
256
+ def delete(rec_id: str) -> bool:
257
+ """
258
+ Deletes a recipe identified by its unique ID.
259
+
260
+ This method attempts to delete the recipe with the given ID from the storage.
261
+ If the deletion is successful, it returns True. If an exception occurs during the deletion
262
+ process, it prints an error message and re-raises the exception.
263
+
264
+ Args:
265
+ rec_id (str): The unique identifier of the recipe to be deleted.
266
+
267
+ Returns:
268
+ bool: True if the recipe was successfully deleted.
269
+
270
+ Raises:
271
+ Exception: If an error occurs during the deletion process.
272
+ """
273
+ try:
274
+ Storage.delete_object(EnvVariables.RECIPES.name, rec_id, "json")
275
+ return True
276
+
277
+ except Exception as e:
278
+ print(f"Failed to delete recipe: {str(e)}")
279
+ raise e
280
+
281
+ @staticmethod
282
+ def get_available_items() -> tuple[list[str], list[RecipeArguments]]:
283
+ """
284
+ Retrieves a list of available recipe IDs and their corresponding recipe information.
285
+
286
+ This method queries the storage for all available recipes and filters out any system files or directories.
287
+ It then creates a list of RecipeArguments objects with detailed information about each recipe and a list of
288
+ their IDs. Finally, it returns a tuple containing the list of recipe IDs and the list of RecipeArguments
289
+ objects.
290
+
291
+ Returns:
292
+ tuple[list[str], list[RecipeArguments]]: A tuple containing a list of recipe IDs and a list of
293
+ RecipeArguments objects for each available recipe.
294
+
295
+ Raises:
296
+ Exception: If there's an error during the retrieval process.
297
+ """
298
+ try:
299
+ retn_recs = []
300
+ retn_recs_ids = []
301
+
302
+ datasets_prompt_counts = Recipe._get_datasets_prompt_counts()
303
+ recs = Storage.get_objects(EnvVariables.RECIPES.name, "json")
304
+ for rec in recs:
305
+ if "__" in rec:
306
+ continue
307
+
308
+ rec_info = RecipeArguments(
309
+ **Recipe._read_recipe(Path(rec).stem, datasets_prompt_counts)
310
+ )
311
+ retn_recs.append(rec_info)
312
+ retn_recs_ids.append(rec_info.id)
313
+
314
+ return retn_recs_ids, retn_recs
315
+
316
+ except Exception as e:
317
+ print(f"Failed to get available recipes: {str(e)}")
318
+ raise e
319
+
320
+ @staticmethod
321
+ def check_file_exists(
322
+ env_var_name: str, file_list: list, file_type: str, extension: str
323
+ ) -> None:
324
+ """
325
+ Checks if a specified file exists in the storage.
326
+
327
+ Args:
328
+ env_var_name (str): The environment variable name where the file is stored.
329
+ file_name (str): The name of the file to check.
330
+ file_type (str): The type of the file.
331
+ extension (str): The extension of the file.
332
+
333
+ Raises:
334
+ RuntimeError: If the file does not exist in the storage.
335
+ """
336
+ for file_name in file_list:
337
+ if file_name and not Storage.is_object_exists(
338
+ env_var_name, file_name, extension
339
+ ):
340
+ raise RuntimeError(f"[Recipe] {file_type} {file_name} does not exist.")
@@ -0,0 +1,111 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class RecipeArguments(BaseModel):
7
+ # id (str): The ID of the recipe.
8
+ id: str
9
+
10
+ name: str = Field(min_length=1) # name (str): The name for the endpoint.
11
+
12
+ # description (str): The description of the recipe.
13
+ description: str
14
+
15
+ # tags (list): The list of tags in the recipe.
16
+ tags: list[str]
17
+
18
+ # categories (list): The list of categories in the recipe.
19
+ categories: list[str]
20
+
21
+ # datasets (list): The list of datasets used in the recipe.
22
+ datasets: list[str] = Field(min_length=1)
23
+
24
+ # prompt_templates (list): The list of prompt templates in the recipe.
25
+ prompt_templates: list[str]
26
+
27
+ # metrics (list): The list of metrics in the recipe.
28
+ metrics: list[str] = Field(min_length=1)
29
+
30
+ # attack_modules (list): The list of attack modules in the recipe.
31
+ attack_modules: list[str]
32
+
33
+ # grading_scale (dict): A dictionary where keys are grading categories and values are lists of grading scale.
34
+ grading_scale: dict[str, list[int]]
35
+
36
+ # stats (dict): A dictionary containing statistics about the recipe.
37
+ stats: dict = {}
38
+
39
+ def __init__(self, **data: Any):
40
+ super().__init__(**data)
41
+ self.validate_grading_scale()
42
+
43
+ def get_start_of_grading_scale(self, item: tuple[str, list[int]]) -> int:
44
+ """
45
+ Retrieve the starting value of a grading scale for a given grade.
46
+
47
+ Args:
48
+ item (tuple[str, list[int]]): A tuple containing the grade as a string and the associated grading scale
49
+ as a list of ints.
50
+
51
+ Returns:
52
+ int: The first value in the grading scale list, representing the start of the grading range.
53
+ """
54
+ _, grading_scale = item
55
+ return grading_scale[0]
56
+
57
+ def validate_grading_scale(self) -> None:
58
+ """
59
+ Validate the grading scale to ensure that it covers a continuous range from 0 to 100.
60
+
61
+ This method checks that each grading range starts where the previous one ended and that
62
+ the final grading range ends at 100. If any grading range does not start as expected or
63
+ if the end of the last grading range is not 100, it raises a ValueError.
64
+
65
+ Raises:
66
+ ValueError: If any grading range does not start as expected or if the grading ranges
67
+ do not cover the full range from 0 to 100.
68
+ """
69
+ if not self.grading_scale: # Check if grading_scale is empty
70
+ return # If empty, it's considered valid and the method returns early
71
+
72
+ expected_start = 0
73
+ for grade, grading_range in sorted(
74
+ self.grading_scale.items(), key=self.get_start_of_grading_scale
75
+ ):
76
+ start, end = grading_range
77
+ if start != expected_start:
78
+ raise ValueError(
79
+ f"Invalid grading range for '{grade}'. Expected start: {expected_start}, got {start}."
80
+ )
81
+ if end < start:
82
+ raise ValueError(
83
+ f"Invalid grading range for '{grade}'. The end value {end} is less than "
84
+ f"the start value {start}."
85
+ )
86
+ expected_start = end + 1
87
+
88
+ if expected_start - 1 != 100:
89
+ raise ValueError("Grading ranges do not cover 0 to 100.")
90
+
91
+ def to_dict(self) -> dict:
92
+ """
93
+ Convert the RecipeArguments object to a dictionary.
94
+
95
+ Returns:
96
+ dict: A dictionary representation of the RecipeArguments object.
97
+ The keys are the attribute names and the values are the attribute values.
98
+ """
99
+ return {
100
+ "id": self.id,
101
+ "name": self.name,
102
+ "description": self.description,
103
+ "tags": self.tags,
104
+ "categories": self.categories,
105
+ "datasets": self.datasets,
106
+ "prompt_templates": self.prompt_templates,
107
+ "metrics": self.metrics,
108
+ "attack_modules": self.attack_modules,
109
+ "grading_scale": self.grading_scale,
110
+ "stats": self.stats,
111
+ }
File without changes
File without changes