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.
- aiverify_moonshot-0.4.0.dist-info/METADATA +249 -0
- aiverify_moonshot-0.4.0.dist-info/RECORD +163 -0
- aiverify_moonshot-0.4.0.dist-info/WHEEL +4 -0
- aiverify_moonshot-0.4.0.dist-info/licenses/AUTHORS.md +5 -0
- aiverify_moonshot-0.4.0.dist-info/licenses/LICENSE.md +201 -0
- aiverify_moonshot-0.4.0.dist-info/licenses/NOTICES.md +3340 -0
- moonshot/__init__.py +0 -0
- moonshot/__main__.py +198 -0
- moonshot/api.py +155 -0
- moonshot/integrations/__init__.py +0 -0
- moonshot/integrations/cli/__init__.py +0 -0
- moonshot/integrations/cli/__main__.py +25 -0
- moonshot/integrations/cli/active_session_cfg.py +1 -0
- moonshot/integrations/cli/benchmark/__init__.py +0 -0
- moonshot/integrations/cli/benchmark/benchmark.py +186 -0
- moonshot/integrations/cli/benchmark/cookbook.py +545 -0
- moonshot/integrations/cli/benchmark/datasets.py +164 -0
- moonshot/integrations/cli/benchmark/metrics.py +141 -0
- moonshot/integrations/cli/benchmark/recipe.py +598 -0
- moonshot/integrations/cli/benchmark/result.py +216 -0
- moonshot/integrations/cli/benchmark/run.py +140 -0
- moonshot/integrations/cli/benchmark/runner.py +174 -0
- moonshot/integrations/cli/cli.py +64 -0
- moonshot/integrations/cli/common/__init__.py +0 -0
- moonshot/integrations/cli/common/common.py +72 -0
- moonshot/integrations/cli/common/connectors.py +325 -0
- moonshot/integrations/cli/common/display_helper.py +42 -0
- moonshot/integrations/cli/common/prompt_template.py +94 -0
- moonshot/integrations/cli/initialisation/__init__.py +0 -0
- moonshot/integrations/cli/initialisation/initialisation.py +14 -0
- moonshot/integrations/cli/redteam/__init__.py +0 -0
- moonshot/integrations/cli/redteam/attack_module.py +70 -0
- moonshot/integrations/cli/redteam/context_strategy.py +147 -0
- moonshot/integrations/cli/redteam/prompt_template.py +67 -0
- moonshot/integrations/cli/redteam/redteam.py +90 -0
- moonshot/integrations/cli/redteam/session.py +467 -0
- moonshot/integrations/web_api/.env.dev +7 -0
- moonshot/integrations/web_api/__init__.py +0 -0
- moonshot/integrations/web_api/__main__.py +56 -0
- moonshot/integrations/web_api/app.py +125 -0
- moonshot/integrations/web_api/container.py +146 -0
- moonshot/integrations/web_api/log/.gitkeep +0 -0
- moonshot/integrations/web_api/logging_conf.py +114 -0
- moonshot/integrations/web_api/routes/__init__.py +0 -0
- moonshot/integrations/web_api/routes/attack_modules.py +66 -0
- moonshot/integrations/web_api/routes/benchmark.py +116 -0
- moonshot/integrations/web_api/routes/benchmark_result.py +175 -0
- moonshot/integrations/web_api/routes/context_strategy.py +129 -0
- moonshot/integrations/web_api/routes/cookbook.py +225 -0
- moonshot/integrations/web_api/routes/dataset.py +120 -0
- moonshot/integrations/web_api/routes/endpoint.py +282 -0
- moonshot/integrations/web_api/routes/metric.py +78 -0
- moonshot/integrations/web_api/routes/prompt_template.py +128 -0
- moonshot/integrations/web_api/routes/recipe.py +219 -0
- moonshot/integrations/web_api/routes/redteam.py +609 -0
- moonshot/integrations/web_api/routes/runner.py +239 -0
- moonshot/integrations/web_api/schemas/__init__.py +0 -0
- moonshot/integrations/web_api/schemas/benchmark_runner_dto.py +13 -0
- moonshot/integrations/web_api/schemas/cookbook_create_dto.py +19 -0
- moonshot/integrations/web_api/schemas/cookbook_response_model.py +9 -0
- moonshot/integrations/web_api/schemas/dataset_response_dto.py +9 -0
- moonshot/integrations/web_api/schemas/endpoint_create_dto.py +21 -0
- moonshot/integrations/web_api/schemas/endpoint_response_model.py +11 -0
- moonshot/integrations/web_api/schemas/prompt_response_model.py +14 -0
- moonshot/integrations/web_api/schemas/prompt_template_response_model.py +10 -0
- moonshot/integrations/web_api/schemas/recipe_create_dto.py +32 -0
- moonshot/integrations/web_api/schemas/recipe_response_model.py +7 -0
- moonshot/integrations/web_api/schemas/session_create_dto.py +16 -0
- moonshot/integrations/web_api/schemas/session_prompt_dto.py +7 -0
- moonshot/integrations/web_api/schemas/session_response_model.py +38 -0
- moonshot/integrations/web_api/services/__init__.py +0 -0
- moonshot/integrations/web_api/services/attack_module_service.py +34 -0
- moonshot/integrations/web_api/services/auto_red_team_test_manager.py +86 -0
- moonshot/integrations/web_api/services/auto_red_team_test_state.py +57 -0
- moonshot/integrations/web_api/services/base_service.py +8 -0
- moonshot/integrations/web_api/services/benchmark_result_service.py +25 -0
- moonshot/integrations/web_api/services/benchmark_test_manager.py +106 -0
- moonshot/integrations/web_api/services/benchmark_test_state.py +56 -0
- moonshot/integrations/web_api/services/benchmarking_service.py +31 -0
- moonshot/integrations/web_api/services/context_strategy_service.py +22 -0
- moonshot/integrations/web_api/services/cookbook_service.py +194 -0
- moonshot/integrations/web_api/services/dataset_service.py +20 -0
- moonshot/integrations/web_api/services/endpoint_service.py +65 -0
- moonshot/integrations/web_api/services/metric_service.py +14 -0
- moonshot/integrations/web_api/services/prompt_template_service.py +39 -0
- moonshot/integrations/web_api/services/recipe_service.py +155 -0
- moonshot/integrations/web_api/services/runner_service.py +147 -0
- moonshot/integrations/web_api/services/session_service.py +350 -0
- moonshot/integrations/web_api/services/utils/exceptions_handler.py +41 -0
- moonshot/integrations/web_api/services/utils/results_formatter.py +47 -0
- moonshot/integrations/web_api/status_updater/interface/benchmark_progress_callback.py +14 -0
- moonshot/integrations/web_api/status_updater/interface/redteam_progress_callback.py +14 -0
- moonshot/integrations/web_api/status_updater/moonshot_ui_webhook.py +72 -0
- moonshot/integrations/web_api/types/types.py +99 -0
- moonshot/src/__init__.py +0 -0
- moonshot/src/api/__init__.py +0 -0
- moonshot/src/api/api_connector.py +58 -0
- moonshot/src/api/api_connector_endpoint.py +162 -0
- moonshot/src/api/api_context_strategy.py +57 -0
- moonshot/src/api/api_cookbook.py +160 -0
- moonshot/src/api/api_dataset.py +46 -0
- moonshot/src/api/api_environment_variables.py +17 -0
- moonshot/src/api/api_metrics.py +51 -0
- moonshot/src/api/api_prompt_template.py +43 -0
- moonshot/src/api/api_recipe.py +182 -0
- moonshot/src/api/api_red_teaming.py +59 -0
- moonshot/src/api/api_result.py +84 -0
- moonshot/src/api/api_run.py +74 -0
- moonshot/src/api/api_runner.py +132 -0
- moonshot/src/api/api_session.py +290 -0
- moonshot/src/configs/__init__.py +0 -0
- moonshot/src/configs/env_variables.py +187 -0
- moonshot/src/connectors/__init__.py +0 -0
- moonshot/src/connectors/connector.py +327 -0
- moonshot/src/connectors/connector_prompt_arguments.py +17 -0
- moonshot/src/connectors_endpoints/__init__.py +0 -0
- moonshot/src/connectors_endpoints/connector_endpoint.py +211 -0
- moonshot/src/connectors_endpoints/connector_endpoint_arguments.py +54 -0
- moonshot/src/cookbooks/__init__.py +0 -0
- moonshot/src/cookbooks/cookbook.py +225 -0
- moonshot/src/cookbooks/cookbook_arguments.py +34 -0
- moonshot/src/datasets/__init__.py +0 -0
- moonshot/src/datasets/dataset.py +255 -0
- moonshot/src/datasets/dataset_arguments.py +50 -0
- moonshot/src/metrics/__init__.py +0 -0
- moonshot/src/metrics/metric.py +192 -0
- moonshot/src/metrics/metric_interface.py +95 -0
- moonshot/src/prompt_templates/__init__.py +0 -0
- moonshot/src/prompt_templates/prompt_template.py +103 -0
- moonshot/src/recipes/__init__.py +0 -0
- moonshot/src/recipes/recipe.py +340 -0
- moonshot/src/recipes/recipe_arguments.py +111 -0
- moonshot/src/redteaming/__init__.py +0 -0
- moonshot/src/redteaming/attack/__init__.py +0 -0
- moonshot/src/redteaming/attack/attack_module.py +618 -0
- moonshot/src/redteaming/attack/attack_module_arguments.py +44 -0
- moonshot/src/redteaming/attack/context_strategy.py +131 -0
- moonshot/src/redteaming/context_strategy/__init__.py +0 -0
- moonshot/src/redteaming/context_strategy/context_strategy_interface.py +46 -0
- moonshot/src/redteaming/session/__init__.py +0 -0
- moonshot/src/redteaming/session/chat.py +209 -0
- moonshot/src/redteaming/session/red_teaming_progress.py +128 -0
- moonshot/src/redteaming/session/red_teaming_type.py +6 -0
- moonshot/src/redteaming/session/session.py +775 -0
- moonshot/src/results/__init__.py +0 -0
- moonshot/src/results/result.py +119 -0
- moonshot/src/results/result_arguments.py +44 -0
- moonshot/src/runners/__init__.py +0 -0
- moonshot/src/runners/runner.py +476 -0
- moonshot/src/runners/runner_arguments.py +46 -0
- moonshot/src/runners/runner_type.py +6 -0
- moonshot/src/runs/__init__.py +0 -0
- moonshot/src/runs/run.py +344 -0
- moonshot/src/runs/run_arguments.py +162 -0
- moonshot/src/runs/run_progress.py +145 -0
- moonshot/src/runs/run_status.py +10 -0
- moonshot/src/storage/__init__.py +0 -0
- moonshot/src/storage/db_interface.py +128 -0
- moonshot/src/storage/io_interface.py +31 -0
- moonshot/src/storage/storage.py +525 -0
- moonshot/src/utils/__init__.py +0 -0
- moonshot/src/utils/import_modules.py +96 -0
- 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
|