BerryDbNew 1.6.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.
- annotation_project/__init__.py +0 -0
- annotation_project/annotation_project.py +677 -0
- berrydb/BerryDB.py +819 -0
- berrydb/IngestFileType.py +7 -0
- berrydb/IngestType.py +6 -0
- berrydb/__init__.py +11 -0
- berrydb/berrydb_settings.py +354 -0
- berrydb/eval_metrics.py +11 -0
- berrydb/llm_agent.py +455 -0
- berrydbnew-1.6.10.dist-info/METADATA +57 -0
- berrydbnew-1.6.10.dist-info/RECORD +32 -0
- berrydbnew-1.6.10.dist-info/WHEEL +4 -0
- constants/__init__.py +1 -0
- constants/constants.py +160 -0
- database/__init__.py +1 -0
- database/database.py +2375 -0
- evaluator/__init__.py +1 -0
- evaluator/berrydb_rag_evaluator.py +134 -0
- fts/__init__.py +1 -0
- fts/fts.py +94 -0
- model_garden/__init__.py +0 -0
- model_garden/annotations_config.py +542 -0
- model_garden/model.py +576 -0
- model_garden/model_config.py +1324 -0
- model_garden/model_provider.py +9 -0
- model_garden/model_repo.py +576 -0
- utils/__init__.py +1 -0
- utils/berrydb_init_exception.py +11 -0
- utils/berrydb_metrics.py +11 -0
- utils/http_client.py +116 -0
- utils/redaction.py +189 -0
- utils/utils.py +161 -0
|
File without changes
|
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from constants import constants as bdb_constants
|
|
7
|
+
from model_garden.annotations_config import AnnotationsConfig
|
|
8
|
+
from model_garden.model import Model
|
|
9
|
+
from utils.utils import Utils
|
|
10
|
+
import requests # noqa: F401 (kept so existing mock.patch("<module>.requests.post") targets resolve)
|
|
11
|
+
from utils import http_client
|
|
12
|
+
from utils.redaction import log_debug, redact, register_secret
|
|
13
|
+
from database.database import Database
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class AnnotationProject:
|
|
18
|
+
__berrydb_api_key: str
|
|
19
|
+
__annotations_api_key: str
|
|
20
|
+
__project_id: int
|
|
21
|
+
__project_name: str
|
|
22
|
+
|
|
23
|
+
def __init__(self, berrydb_api_key:str, annotations_api_key: str, project_id: int, project_name: str):
|
|
24
|
+
if berrydb_api_key is None:
|
|
25
|
+
Utils.print_error_and_exit("BerryDB API key cannot be None")
|
|
26
|
+
if annotations_api_key is None:
|
|
27
|
+
Utils.print_error_and_exit("Annotations API key cannot be None")
|
|
28
|
+
if project_id is None:
|
|
29
|
+
Utils.print_error_and_exit("Project not found")
|
|
30
|
+
if project_name is None:
|
|
31
|
+
Utils.print_error_and_exit("Project name cannot be None")
|
|
32
|
+
|
|
33
|
+
self.__berrydb_api_key = berrydb_api_key
|
|
34
|
+
self.__annotations_api_key = annotations_api_key
|
|
35
|
+
self.__project_id = project_id
|
|
36
|
+
self.__project_name = project_name
|
|
37
|
+
register_secret(berrydb_api_key)
|
|
38
|
+
register_secret(annotations_api_key)
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return (f"AnnotationProject(project_id={self.__project_id!r}, project_name={self.__project_name!r}, "
|
|
42
|
+
f"berrydb_api_key='***', annotations_api_key='***')")
|
|
43
|
+
|
|
44
|
+
__str__ = __repr__
|
|
45
|
+
|
|
46
|
+
def berrydb_api_key(self):
|
|
47
|
+
"""
|
|
48
|
+
Retrieves the BerryDB API key associated with this annotation project.
|
|
49
|
+
|
|
50
|
+
This API key is used for authenticating operations related to BerryDB
|
|
51
|
+
services, such as populating the project with data from a BerryDB database.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
- `str`: The BerryDB API key for the project.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
```python
|
|
58
|
+
# Assuming 'my_annotation_project' is an instance of AnnotationProject
|
|
59
|
+
# project_api_key = my_annotation_project.berrydb_api_key()
|
|
60
|
+
# print(f"The BerryDB API key for this project is: {project_api_key}")
|
|
61
|
+
|
|
62
|
+
# This key might be used internally or for other SDK operations
|
|
63
|
+
# that require authentication with the main BerryDB services.
|
|
64
|
+
```
|
|
65
|
+
---
|
|
66
|
+
"""
|
|
67
|
+
return self.__berrydb_api_key
|
|
68
|
+
|
|
69
|
+
def annotations_api_key(self):
|
|
70
|
+
return self.__annotations_api_key
|
|
71
|
+
|
|
72
|
+
def project_name(self):
|
|
73
|
+
return self.__project_name
|
|
74
|
+
|
|
75
|
+
def project_id(self):
|
|
76
|
+
return self.__project_id
|
|
77
|
+
|
|
78
|
+
def setup_label_config(self, label_config):
|
|
79
|
+
"""
|
|
80
|
+
The `setup_label_config` method configures the label settings for the specified annotation project. This configuration defines the labeling structure and rules to ensure consistent and organized annotation of data within the project. It is essential for tasks such as Named Entity Recognition (NER), image or text classification, and other annotation-based workflows where specific labels are applied to different data points. Without this setup, annotations and predictions added to the project will not be visible in the UI, making it a critical step for visualizing and managing the labeled data.
|
|
81
|
+
|
|
82
|
+
**Parameters**:
|
|
83
|
+
- **label_config** (`str`): A string representing the label configuration. This configuration defines the labels that will be used within the project and their respective properties. The format typically follows a predefined structure that outlines label categories, attributes, and potentially hierarchical relationships between labels.
|
|
84
|
+
|
|
85
|
+
**Returns**:
|
|
86
|
+
- `Dict`: A dict with a message indicating whether the label configuration was successfully applied to the project. In case of an error (e.g., invalid project ID or configuration format), the returned message will provide details about the failure.
|
|
87
|
+
|
|
88
|
+
**Example**
|
|
89
|
+
```python
|
|
90
|
+
# Define your API key and other required parameters
|
|
91
|
+
project_id = 12345 # ID of the project you want to configure
|
|
92
|
+
|
|
93
|
+
# Label config for Topic Modeling
|
|
94
|
+
project_config = '''
|
|
95
|
+
<View>
|
|
96
|
+
<Text name="text" value="$content.text"/>
|
|
97
|
+
<View style="box-shadow: 2px 2px 5px #999; padding: 20px; margin-top: 2em; border-radius: 5px;">
|
|
98
|
+
<Header value="Choose text sentiment"/>
|
|
99
|
+
<Choices name="sentiment" toName="text" choice="single" showInLine="true">
|
|
100
|
+
<Choice value="Introduction"/>
|
|
101
|
+
<Choice value="Plan Information"/>
|
|
102
|
+
<Choice value="Eligibility"/>
|
|
103
|
+
<Choice value="Benefits"/>
|
|
104
|
+
<Choice value="Financial Information"/>
|
|
105
|
+
<Choice value="Provider Information"/>
|
|
106
|
+
<Choice value="Claims and Reimbursements"/>
|
|
107
|
+
<Choice value="Administrative Information"/>
|
|
108
|
+
<Choice value="Legal and Regulatory Information"/>
|
|
109
|
+
<Choice value="Dates and Deadlines"/>
|
|
110
|
+
</Choices>
|
|
111
|
+
</View>
|
|
112
|
+
</View>
|
|
113
|
+
'''
|
|
114
|
+
|
|
115
|
+
# Assume 'my_annotation_project' is an instance of AnnotationProject
|
|
116
|
+
# Setup label for Topic Modeling
|
|
117
|
+
label_config = my_annotation_project.setup_label_config(project_config)
|
|
118
|
+
if label_config:
|
|
119
|
+
print("Label config setup succesful!")
|
|
120
|
+
```
|
|
121
|
+
---
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
url = bdb_constants.LABEL_STUDIO_BASE_URL + bdb_constants.setup_label_config_url.format(self.__project_id)
|
|
125
|
+
payload = json.dumps({"label_config": label_config})
|
|
126
|
+
headers = {
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
"Authorization": f"Token {self.__annotations_api_key}",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if bdb_constants.debug_mode:
|
|
132
|
+
log_debug(logger, "url:", url)
|
|
133
|
+
log_debug(logger, "payload:", payload)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
response = http_client.patch(url, data=payload, headers=headers)
|
|
137
|
+
if response.status_code != 200:
|
|
138
|
+
print("Project label config setup Failed!")
|
|
139
|
+
Utils.handleApiCallFailure(response.json(), response.status_code)
|
|
140
|
+
if bdb_constants.debug_mode:
|
|
141
|
+
log_debug(logger, "Setup config result ", response.json())
|
|
142
|
+
print("Project label config setup successful!")
|
|
143
|
+
return response.json()
|
|
144
|
+
except Exception as e:
|
|
145
|
+
print(f"Failed to setup config: {redact(e)}")
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
def populate(self, database:Database|str):
|
|
149
|
+
"""
|
|
150
|
+
The `populate` method is designed to fill a specific annotation project with data retrieved from a designated database. This process allows users to efficiently import existing data for annotation tasks, enabling faster project setup and reducing the need for manual data entry.
|
|
151
|
+
|
|
152
|
+
**Parameters**:
|
|
153
|
+
- **database** (`Database | str`): The BerryDB database from which data will be sourced. This can be an instance of the `Database` class or a string representing the name of the database. If a string is provided, the SDK will attempt to connect to this database using the `berrydb_api_key` associated with this `AnnotationProject`.
|
|
154
|
+
|
|
155
|
+
**Returns**:
|
|
156
|
+
- `Dict | None`: A dictionary containing the API response upon successful population, or `None` if an error occurs during the process. The dictionary typically includes details about the import task.
|
|
157
|
+
|
|
158
|
+
**Example**
|
|
159
|
+
```python
|
|
160
|
+
# Define your required parameters
|
|
161
|
+
database_name = "PatientDB" # Name of the database from which to populate data
|
|
162
|
+
|
|
163
|
+
# Assume 'my_annotation_project' is an instance of AnnotationProject
|
|
164
|
+
# Option 1: Using database name (string)
|
|
165
|
+
result_from_name = my_annotation_project.populate(database_name)
|
|
166
|
+
if result_from_name:
|
|
167
|
+
print(f"Successfully populated project using database name: {database_name}")
|
|
168
|
+
|
|
169
|
+
# Option 2: Using a Database object (When you either create or connect to a database)
|
|
170
|
+
from berrydb import BerryDB
|
|
171
|
+
my_database_object = BerryDB.connect(api_key="your_api_key", database_name=database_name)
|
|
172
|
+
result_from_object = my_annotation_project.populate(my_database_object)
|
|
173
|
+
```
|
|
174
|
+
---
|
|
175
|
+
"""
|
|
176
|
+
if database is None or not (isinstance(database, Database) or isinstance(database, str)):
|
|
177
|
+
raise ValueError("Error: database is required, it should either be a string or an instance of Database")
|
|
178
|
+
|
|
179
|
+
if isinstance(database, Database):
|
|
180
|
+
database_name = database.database_name()
|
|
181
|
+
else:
|
|
182
|
+
database_name = database
|
|
183
|
+
from berrydb import BerryDB
|
|
184
|
+
BerryDB.connect(self.berrydb_api_key(), database_name)
|
|
185
|
+
|
|
186
|
+
if database_name is None or not isinstance(database_name, str) or database_name.strip() == "":
|
|
187
|
+
raise ValueError("Error: database_name is required and must be of type str")
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
reimport_url = bdb_constants.LABEL_STUDIO_BASE_URL + bdb_constants.reimport_label_studio_project_url.format(self.__project_id)
|
|
191
|
+
headers = {
|
|
192
|
+
"Content-Type": "application/json",
|
|
193
|
+
"Authorization": "Token {}".format(self.__annotations_api_key),
|
|
194
|
+
}
|
|
195
|
+
reimport_data = {
|
|
196
|
+
"file_upload_ids": [],
|
|
197
|
+
"files_as_tasks_list": False,
|
|
198
|
+
"database_name": database_name,
|
|
199
|
+
"bdb_api_key": self.__berrydb_api_key,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if bdb_constants.debug_mode:
|
|
203
|
+
log_debug(logger, "url:", reimport_url)
|
|
204
|
+
log_debug(logger, "payload:", reimport_data)
|
|
205
|
+
|
|
206
|
+
reimport_response = http_client.post(
|
|
207
|
+
reimport_url, data=json.dumps(reimport_data), headers=headers
|
|
208
|
+
)
|
|
209
|
+
if reimport_response.status_code != 201:
|
|
210
|
+
print("Project populated Failed!")
|
|
211
|
+
Utils.handleApiCallFailure(
|
|
212
|
+
reimport_response.json(), reimport_response.status_code
|
|
213
|
+
)
|
|
214
|
+
if bdb_constants.debug_mode:
|
|
215
|
+
log_debug(logger, "Populate project result: ", reimport_response.json())
|
|
216
|
+
print("Project populated successfully!")
|
|
217
|
+
return reimport_response.json()
|
|
218
|
+
except Exception as e:
|
|
219
|
+
print(f"Failed to populate your project: {redact(e)}")
|
|
220
|
+
if bdb_constants.debug_mode:
|
|
221
|
+
raise e
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
def connect_to_ml(self, model:Model|None = None, model_url:str|None = None, model_title:str = "ML Model"):
|
|
225
|
+
"""
|
|
226
|
+
The `connect_to_ml` method establishes a connection between a specified annotation project and a Machine Learning (ML) model backend. This integration enables the project to leverage the capabilities of the ML model for tasks such as predictions, automated annotations, or data analysis, thereby enhancing the annotation workflow.
|
|
227
|
+
|
|
228
|
+
.. tip::
|
|
229
|
+
You can find your BerryDB ML Model URLs for **ml_url** `here <https://app.berrydb.io/ml-models>`_.
|
|
230
|
+
.. #end
|
|
231
|
+
|
|
232
|
+
**Parameters**:
|
|
233
|
+
- **ml_url** (`str`): The URL of the ML backend to which the project will be connected. This URL should point to a deployed ML model or service that is accessible from the BerryDB environment.
|
|
234
|
+
- **ml_title** (`str`, optional): A title for the connection, which provides context for the integration. By default, this is set to "ML Model", but you can customize it to better describe the specific ML model being used.
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
**Returns**:
|
|
238
|
+
- `None`: This method does not return a value. Instead, it establishes the connection and prints a success or failure message indicating the result of the operation.
|
|
239
|
+
|
|
240
|
+
**Example**
|
|
241
|
+
```python
|
|
242
|
+
ml_url = "http://app.berrydb.io/berrydb/model/model-id"
|
|
243
|
+
ml_title = "Text Classification Model"
|
|
244
|
+
|
|
245
|
+
# Assume 'my_annotation_project' is an instance of AnnotationProject
|
|
246
|
+
# Connect the annotation project to the specified ML model
|
|
247
|
+
my_annotation_project.connect_to_ml(ml_url, ml_title)
|
|
248
|
+
```
|
|
249
|
+
---
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
if model is None and model_url is None:
|
|
253
|
+
raise ValueError("Either `model` or `model_url` must be provided.")
|
|
254
|
+
|
|
255
|
+
if model is not None and not (isinstance(model, Model) and model.config._project_url and isinstance(model.config._project_url, str)):
|
|
256
|
+
raise TypeError("`model` must be an instance of Model.")
|
|
257
|
+
|
|
258
|
+
if model_url is not None and not isinstance(model_url, str):
|
|
259
|
+
raise TypeError("`model_url` must be a string.")
|
|
260
|
+
|
|
261
|
+
# TODO: Check for existing connected ML models and use the PATCH method to update the existing ML model connection
|
|
262
|
+
try:
|
|
263
|
+
headers = {
|
|
264
|
+
"Content-Type": "application/json",
|
|
265
|
+
"Authorization": "Token {}".format(self.__annotations_api_key),
|
|
266
|
+
}
|
|
267
|
+
ml_payload = {
|
|
268
|
+
"project": self.__project_id,
|
|
269
|
+
"title": model_title or "ML Model",
|
|
270
|
+
"url": model_url or model.config._project_url,
|
|
271
|
+
"is_interactive": False,
|
|
272
|
+
}
|
|
273
|
+
ml_connect_response = http_client.post(
|
|
274
|
+
bdb_constants.LABEL_STUDIO_BASE_URL + bdb_constants.connect_project_to_ml_url, data=json.dumps(ml_payload), headers=headers
|
|
275
|
+
)
|
|
276
|
+
if ml_connect_response.status_code != 201:
|
|
277
|
+
print("Project Failed to connect to ML Model")
|
|
278
|
+
Utils.handleApiCallFailure(
|
|
279
|
+
ml_connect_response.json(), ml_connect_response.status_code
|
|
280
|
+
)
|
|
281
|
+
if bdb_constants.debug_mode:
|
|
282
|
+
log_debug(logger, "Connect project to ML result: ", ml_connect_response.json())
|
|
283
|
+
print("Project connected to ML Model successfully!")
|
|
284
|
+
except Exception as e:
|
|
285
|
+
print(f"Failed to Connect project to BerryDB ML backend: {redact(e)}")
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
def retrieve_prediction(self, task_ids: List[int]):
|
|
289
|
+
"""
|
|
290
|
+
The `retrieve_prediction` method is used to add a custom prediction to a specific task/record within an annotation project. This allows users to associate automated insights or model outputs with individual task, facilitating the annotation process and improving overall data management.
|
|
291
|
+
|
|
292
|
+
**Parameters**:
|
|
293
|
+
- **task_ids** (`List[int]`): The identifier of the tasks for which the prediction will be retrieved. This IDs should correspond to a valid tasks within the specified project.
|
|
294
|
+
|
|
295
|
+
**Returns**:
|
|
296
|
+
- `None`: This method does not return a value. Instead, it retrieves predictions to the specified tasks asynchronously.
|
|
297
|
+
|
|
298
|
+
**Example**
|
|
299
|
+
```python
|
|
300
|
+
task_ids = [12340, 12341, 12342]
|
|
301
|
+
|
|
302
|
+
# Retrieve predictions for the specified tasks
|
|
303
|
+
ner_proj.retrieve_prediction(task_id)
|
|
304
|
+
```
|
|
305
|
+
---
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
if not self.__annotations_api_key:
|
|
310
|
+
raise ValueError("Error: annotations_api_key is required and must be of type str")
|
|
311
|
+
if not self.__project_id:
|
|
312
|
+
raise ValueError("Error: project_id is required and must be of type int")
|
|
313
|
+
if not (task_ids and isinstance(task_ids, list) and len(task_ids)):
|
|
314
|
+
raise ValueError("Error: task_id is required and must be of type int")
|
|
315
|
+
|
|
316
|
+
url = bdb_constants.LABEL_STUDIO_BASE_URL + bdb_constants.retrieve_predictions_url
|
|
317
|
+
|
|
318
|
+
payload = {
|
|
319
|
+
"selectedItems": {
|
|
320
|
+
"all": False,
|
|
321
|
+
"included": task_ids
|
|
322
|
+
},
|
|
323
|
+
}
|
|
324
|
+
headers = {
|
|
325
|
+
"Content-Type": "application/json",
|
|
326
|
+
"Authorization": f"Token {self.__annotations_api_key}",
|
|
327
|
+
}
|
|
328
|
+
params = {
|
|
329
|
+
"id": "retrieve_tasks_predictions",
|
|
330
|
+
"project": self.__project_id,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if bdb_constants.debug_mode:
|
|
334
|
+
log_debug(logger, "url:", url)
|
|
335
|
+
log_debug(logger, "payload:", payload)
|
|
336
|
+
log_debug(logger, "params:", params)
|
|
337
|
+
|
|
338
|
+
print(f'Retrieving predictions for tasks with IDs: {",".join(task_ids)} in project with ID: {self.__project_id}')
|
|
339
|
+
response = http_client.post(url, json=payload, headers=headers, verify=False, params=params)
|
|
340
|
+
if not (response.status_code == 200 or response.status_code == 201):
|
|
341
|
+
print(f'Failed to retrieve predictions for tasks with IDs: {",".join(task_ids)}')
|
|
342
|
+
Utils.handleApiCallFailure(response.json(), response.status_code)
|
|
343
|
+
if bdb_constants.debug_mode:
|
|
344
|
+
log_debug(logger, "Setup config result ", response.json())
|
|
345
|
+
print(f'Attempting to retrieve predictions for tasks with IDs: {",".join(task_ids)}')
|
|
346
|
+
return response.json()
|
|
347
|
+
except Exception as e:
|
|
348
|
+
print(f"Failed to retrieve predictions: {redact(e)}")
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
def create_prediction(self, task_id, prediction):
|
|
352
|
+
"""
|
|
353
|
+
The `create_prediction` method is used to add a custom prediction to a specific task/record within an annotation project. This allows users to associate automated insights or model outputs with individual task, facilitating the annotation process and improving overall data management.
|
|
354
|
+
|
|
355
|
+
**Parameters**:
|
|
356
|
+
- **task_id** (`int`): The unique identifier of the task to which the prediction will be added. This ID should correspond to a valid task within the specified project.
|
|
357
|
+
- **prediction** (`dict`): A dictionary containing the prediction data to be associated with the task/record. The structure of this dictionary should align with the expected format for predictions in your project based on the setup configuration, and it may include fields such as labels, polygon_labels, confidence scores, etc., and additional metadata.
|
|
358
|
+
|
|
359
|
+
**Returns**:
|
|
360
|
+
- `None`: This method does not return a value. Instead, it adds the prediction to the specified task and prints a success or failure message to indicate the result of the
|
|
361
|
+
|
|
362
|
+
**Example**
|
|
363
|
+
```python
|
|
364
|
+
task_id = 67890
|
|
365
|
+
prediction = {
|
|
366
|
+
"label": "Positive",
|
|
367
|
+
"confidence": 0.95,
|
|
368
|
+
"additional_info": "Predicted based on sentiment analysis model."
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
# Assume 'my_annotation_project' is an instance of AnnotationProject
|
|
372
|
+
# Create a prediction for the specified task
|
|
373
|
+
my_annotation_project.create_prediction(task_id, prediction)
|
|
374
|
+
```
|
|
375
|
+
---
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
import warnings
|
|
380
|
+
|
|
381
|
+
from urllib3.exceptions import InsecureRequestWarning
|
|
382
|
+
|
|
383
|
+
# Suppress only the InsecureRequestWarning from urllib3
|
|
384
|
+
warnings.simplefilter('ignore', InsecureRequestWarning)
|
|
385
|
+
if not self.__annotations_api_key:
|
|
386
|
+
print("Error: annotations_api_key is required and must be of type str")
|
|
387
|
+
return
|
|
388
|
+
if not self.__project_id:
|
|
389
|
+
print("Error: project_id is required and must be of type int")
|
|
390
|
+
return
|
|
391
|
+
if not task_id:
|
|
392
|
+
print("Error: task_id is required and must be of type int")
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
url = bdb_constants.LABEL_STUDIO_BASE_URL + bdb_constants.create_predictions_url.format(task_id)
|
|
396
|
+
|
|
397
|
+
if type(prediction) != dict:
|
|
398
|
+
print("Error: prediction has to be dict type")
|
|
399
|
+
return
|
|
400
|
+
prediction["project"] = self.__project_id
|
|
401
|
+
prediction["task"] = task_id
|
|
402
|
+
|
|
403
|
+
payload = json.dumps(prediction)
|
|
404
|
+
headers = {
|
|
405
|
+
"Content-Type": "application/json",
|
|
406
|
+
"Authorization": f"Token {self.__annotations_api_key}",
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if bdb_constants.debug_mode:
|
|
410
|
+
log_debug(logger, "url:", url)
|
|
411
|
+
log_debug(logger, "payload:", payload)
|
|
412
|
+
|
|
413
|
+
print(f"Adding prediction to task with ID: {task_id} in project with ID: {self.__project_id}")
|
|
414
|
+
response = http_client.post(url, data=payload, headers=headers, verify=False)
|
|
415
|
+
if response.status_code != 201:
|
|
416
|
+
print(f"Failed to add prediction to task with ID: {task_id}")
|
|
417
|
+
Utils.handleApiCallFailure(response.json(), response.status_code)
|
|
418
|
+
if bdb_constants.debug_mode:
|
|
419
|
+
log_debug(logger, "Setup config result ", response.json())
|
|
420
|
+
print(f"Successfully added prediction to task with ID: {task_id}")
|
|
421
|
+
return response.json()
|
|
422
|
+
except Exception as e:
|
|
423
|
+
print(f"Failed to create prediction: {redact(e)}")
|
|
424
|
+
return None
|
|
425
|
+
|
|
426
|
+
def create_annotation(self, task_id, annotation):
|
|
427
|
+
"""
|
|
428
|
+
Create an annotation for a task in the annotation project.
|
|
429
|
+
|
|
430
|
+
**Parameters**:
|
|
431
|
+
- **task_id** (`int`): The unique identifier of the task that the annotation will be associated with. This ID must match an existing task within the specified project.
|
|
432
|
+
- **annotation** (`Dict`): A dictionary containing the details of the annotation to be added to the task. The structure of this dictionary should reflect the required fields and values for your annotation, such as labels, coordinates for bounding boxes, or any relevant metadata.
|
|
433
|
+
|
|
434
|
+
**Returns**:
|
|
435
|
+
- `None`: This method does not return a value. Instead, it adds the annotation to the specified task and prints a success or failure message indicating the result of the operation.
|
|
436
|
+
|
|
437
|
+
**Example**
|
|
438
|
+
```python
|
|
439
|
+
task_id = 67890
|
|
440
|
+
annotation = {
|
|
441
|
+
"label": "Cat",
|
|
442
|
+
"bounding_box": {
|
|
443
|
+
"x": 50,
|
|
444
|
+
"y": 30,
|
|
445
|
+
"width": 100,
|
|
446
|
+
"height": 80
|
|
447
|
+
},
|
|
448
|
+
"confidence": 0.97
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
# Assume 'my_annotation_project' is an instance of AnnotationProject
|
|
452
|
+
# Create an annotation for the specified task
|
|
453
|
+
my_annotation_project.create_annotation(task_id, annotation)
|
|
454
|
+
```
|
|
455
|
+
---
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
import warnings
|
|
460
|
+
|
|
461
|
+
from urllib3.exceptions import InsecureRequestWarning
|
|
462
|
+
|
|
463
|
+
# Suppress only the InsecureRequestWarning from urllib3
|
|
464
|
+
warnings.simplefilter('ignore', InsecureRequestWarning)
|
|
465
|
+
if not self.__annotations_api_key:
|
|
466
|
+
raise ValueError("Error: annotations_api_key is required and must be of type str")
|
|
467
|
+
if self.__project_id is None:
|
|
468
|
+
raise ValueError("Error: project_id is required and must be of type int")
|
|
469
|
+
if not self.__berrydb_api_key:
|
|
470
|
+
raise ValueError("Error: berrydb_api_key is required")
|
|
471
|
+
if task_id is None:
|
|
472
|
+
raise ValueError("Error: task_id is required and must be of type int")
|
|
473
|
+
if not annotation:
|
|
474
|
+
raise ValueError("Error: annotation is required")
|
|
475
|
+
|
|
476
|
+
url = bdb_constants.LABEL_STUDIO_BASE_URL + bdb_constants.create_annotations_url.format(task_id, self.__project_id, self.__berrydb_api_key)
|
|
477
|
+
|
|
478
|
+
if type(annotation) != dict:
|
|
479
|
+
raise ValueError("Error: annotations has to be dict type")
|
|
480
|
+
annotation["project"] = self.__project_id
|
|
481
|
+
|
|
482
|
+
payload = json.dumps(annotation)
|
|
483
|
+
headers = {
|
|
484
|
+
"Content-Type": "application/json",
|
|
485
|
+
"Authorization": f"Token {self.__annotations_api_key}",
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if bdb_constants.debug_mode:
|
|
489
|
+
log_debug(logger, "url:", url)
|
|
490
|
+
log_debug(logger, "payload:", payload)
|
|
491
|
+
|
|
492
|
+
print(f"Adding annotation to task with ID: {task_id} in project with ID: {self.__project_id}")
|
|
493
|
+
response = http_client.post(url, data=payload, headers=headers, verify=False)
|
|
494
|
+
if response.status_code != 201:
|
|
495
|
+
print(f"Failed to add annotation to task with ID: {task_id}")
|
|
496
|
+
Utils.handleApiCallFailure(response.json(), response.status_code)
|
|
497
|
+
if bdb_constants.debug_mode:
|
|
498
|
+
log_debug(logger, "Setup config result ", response.json())
|
|
499
|
+
print(f"Successfully added annotation to task with ID: {task_id}")
|
|
500
|
+
return response.json()
|
|
501
|
+
except Exception as e:
|
|
502
|
+
print(f"Failed to create annotations: {redact(e)}")
|
|
503
|
+
return None
|
|
504
|
+
|
|
505
|
+
def attach_annotations_config(self, annotations_config: AnnotationsConfig):
|
|
506
|
+
"""
|
|
507
|
+
Attaches a predefined annotations configuration to the annotation project.
|
|
508
|
+
|
|
509
|
+
This method links an `AnnotationsConfig` instance to the current project.
|
|
510
|
+
The `AnnotationsConfig` defines how annotations should be generated,
|
|
511
|
+
potentially using LLMs, specific prompts, and data transformations.
|
|
512
|
+
Attaching it to a project enables automated annotation workflows based
|
|
513
|
+
on that configuration.
|
|
514
|
+
|
|
515
|
+
.. note::
|
|
516
|
+
The `annotations_config` parameter must be an instance of
|
|
517
|
+
:class:`~berrydb.model_garden.annotations_config.AnnotationsConfig`.
|
|
518
|
+
Please refer to the :doc:`annotation_config` page for details on how to
|
|
519
|
+
create and save an ``AnnotationsConfig`` object using its builder.
|
|
520
|
+
|
|
521
|
+
Parameters:
|
|
522
|
+
- **annotations_config** (`AnnotationsConfig`): An instance of `AnnotationsConfig`
|
|
523
|
+
that has been previously created and saved. The `name` attribute of this
|
|
524
|
+
config is used to identify it in BerryDB.
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
- `dict`: A dictionary containing the response from BerryDB, typically
|
|
528
|
+
confirming that the configuration has been successfully attached.
|
|
529
|
+
|
|
530
|
+
Raises:
|
|
531
|
+
- `ValueError`: If the `annotations_config` does not have a `name`.
|
|
532
|
+
|
|
533
|
+
Example:
|
|
534
|
+
```python
|
|
535
|
+
from berrydb import AnnotationsConfig
|
|
536
|
+
|
|
537
|
+
# Assume 'my_annotation_project' is an instance of AnnotationProject
|
|
538
|
+
# and 'berrydb_api_key' is your BerryDB API key.
|
|
539
|
+
|
|
540
|
+
# First, create and save an AnnotationsConfig
|
|
541
|
+
ner_config = (
|
|
542
|
+
AnnotationsConfig.builder()
|
|
543
|
+
.name("my-project-ner-config")
|
|
544
|
+
.input_transform_expression("data.text_content")
|
|
545
|
+
.output_transform_expression("annotations.ner_tags")
|
|
546
|
+
.llm_provider("openai")
|
|
547
|
+
.llm_model("gpt-4o-mini")
|
|
548
|
+
.prompt("Extract named entities: {{input}}")
|
|
549
|
+
.build()
|
|
550
|
+
)
|
|
551
|
+
ner_config.save(berrydb_api_key) # Save it to BerryDB
|
|
552
|
+
|
|
553
|
+
# Now, attach this saved config to your annotation project
|
|
554
|
+
try:
|
|
555
|
+
my_annotation_project.attach_annotations_config(ner_config)
|
|
556
|
+
print(f"Successfully attached '{ner_config.name}' to project '{my_annotation_project.project_name()}'.")
|
|
557
|
+
except Exception as e:
|
|
558
|
+
print(f"Error attaching annotations config: {redact(e)}")
|
|
559
|
+
```
|
|
560
|
+
---
|
|
561
|
+
"""
|
|
562
|
+
if not annotations_config.name:
|
|
563
|
+
raise ValueError("Error: annotations_config name is required and must be of type string")
|
|
564
|
+
|
|
565
|
+
url = bdb_constants.LABEL_STUDIO_BASE_URL + bdb_constants.attach_annotations_config_to_project_url.format(self.__project_id)
|
|
566
|
+
payload = json.dumps({"annotation_config_name": annotations_config.name})
|
|
567
|
+
headers = {
|
|
568
|
+
"Content-Type": "application/json",
|
|
569
|
+
"Authorization": f"Token {self.__annotations_api_key}",
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if bdb_constants.debug_mode:
|
|
573
|
+
log_debug(logger, "url:", url)
|
|
574
|
+
log_debug(logger, "payload:", payload)
|
|
575
|
+
|
|
576
|
+
try:
|
|
577
|
+
response = http_client.patch(url, data=payload, headers=headers)
|
|
578
|
+
if response.status_code != 200:
|
|
579
|
+
print("Project label config setup Failed!")
|
|
580
|
+
Utils.handleApiCallFailure(response.json(), response.status_code)
|
|
581
|
+
if bdb_constants.debug_mode:
|
|
582
|
+
log_debug(logger, "Setup config result ", response.json())
|
|
583
|
+
print("Project label config setup successful!")
|
|
584
|
+
return response.json()
|
|
585
|
+
except Exception as e:
|
|
586
|
+
print(f"Failed to setup config: {redact(e)}")
|
|
587
|
+
return None
|
|
588
|
+
|
|
589
|
+
def get_task_data(self):
|
|
590
|
+
"""
|
|
591
|
+
Retrieves a list of all tasks within the annotation project, along with
|
|
592
|
+
their corresponding BerryDB document IDs and database names.
|
|
593
|
+
|
|
594
|
+
This method fetches information for every task in the project. Each task
|
|
595
|
+
typically represents a single data item
|
|
596
|
+
that has been imported into the annotation project. The `task_id` returned
|
|
597
|
+
for each task can then be used with other methods like
|
|
598
|
+
:meth:`retrieve_prediction <annotation_project.annotation_project.AnnotationProject.retrieve_prediction>`,
|
|
599
|
+
:meth:`create_annotation <annotation_project.annotation_project.AnnotationProject.create_annotation>`, or
|
|
600
|
+
:meth:`create_prediction <annotation_project.annotation_project.AnnotationProject.create_prediction>`
|
|
601
|
+
to interact with individual tasks.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
- `List[Dict[str, any]]`: A list of dictionaries, where each dictionary
|
|
605
|
+
represents a task and contains the following keys:
|
|
606
|
+
- **'task_id'** (`int`): The unique identifier of the task within the
|
|
607
|
+
annotation project.
|
|
608
|
+
- **'document_id'** (`str`): The original identifier of the document
|
|
609
|
+
in BerryDB from which this task was created.
|
|
610
|
+
- **'database_name'** (`str`): The name of the BerryDB database from
|
|
611
|
+
which the original document was sourced.
|
|
612
|
+
|
|
613
|
+
Raises:
|
|
614
|
+
- `Exception`: If there's an issue fetching the tasks from BerryDB,
|
|
615
|
+
for example, due to network issues or if the project is not found.
|
|
616
|
+
|
|
617
|
+
Example:
|
|
618
|
+
```python
|
|
619
|
+
# Assuming 'my_annotation_project' is an instance of AnnotationProject
|
|
620
|
+
|
|
621
|
+
try:
|
|
622
|
+
all_tasks_info = my_annotation_project.get_task_data()
|
|
623
|
+
if all_tasks_info:
|
|
624
|
+
print(f"Found {len(all_tasks_info)} tasks in project '{my_annotation_project.project_name()}':")
|
|
625
|
+
for task_info in all_tasks_info:
|
|
626
|
+
task_id = task_info['task_id']
|
|
627
|
+
|
|
628
|
+
Example: Retrieve prediction for this task
|
|
629
|
+
try:
|
|
630
|
+
prediction = my_annotation_project.retrieve_prediction(task_ids=[task_id])
|
|
631
|
+
if prediction:
|
|
632
|
+
print(f" Prediction for task {task_id}: {prediction}")
|
|
633
|
+
except Exception as pred_e:
|
|
634
|
+
print(f" Could not retrieve prediction for task {task_id}: {pred_e}")
|
|
635
|
+
|
|
636
|
+
# Example: Create an example annotation for this task
|
|
637
|
+
ex_annotation_data = {
|
|
638
|
+
"result": [
|
|
639
|
+
{
|
|
640
|
+
"from_name": "label", # Matches your label config
|
|
641
|
+
"to_name": "text", # Matches your label config
|
|
642
|
+
"type": "choices", # Matches your label config
|
|
643
|
+
"value": {"choices": ["SomeLabel"]}
|
|
644
|
+
}
|
|
645
|
+
]
|
|
646
|
+
}
|
|
647
|
+
try:
|
|
648
|
+
my_annotation_project.create_annotation(task_id, ex_annotation_data)
|
|
649
|
+
print(f" Successfully created annotation for task {task_id}")
|
|
650
|
+
except Exception as ann_e:
|
|
651
|
+
print(f" Could not create annotation for task {task_id}: {ann_e}")
|
|
652
|
+
|
|
653
|
+
else:
|
|
654
|
+
print(f"No tasks found in project '{my_annotation_project.project_name()}'.")
|
|
655
|
+
except Exception as e:
|
|
656
|
+
print(f"Error retrieving task data: {redact(e)}")
|
|
657
|
+
```
|
|
658
|
+
"""
|
|
659
|
+
url = f"{bdb_constants.LABEL_STUDIO_BASE_URL}/api/projects/{self.__project_id}/tasks"
|
|
660
|
+
headers = {"Authorization": f"Token {self.__annotations_api_key}"}
|
|
661
|
+
params = {"page": 1, "page_size": -1}
|
|
662
|
+
|
|
663
|
+
task_ids = []
|
|
664
|
+
|
|
665
|
+
try:
|
|
666
|
+
response = http_client.get(url, headers=headers, params=params)
|
|
667
|
+
if response.status_code != 200:
|
|
668
|
+
raise Exception(f"Failed to fetch tasks for project with name: {self.__project_name}")
|
|
669
|
+
|
|
670
|
+
data = response.json()
|
|
671
|
+
tasks = data or []
|
|
672
|
+
|
|
673
|
+
task_ids.extend([{"task_id": task["id"], "document_id": task.get("data", {}).get("_meta", {}).get("objectId", {}),\
|
|
674
|
+
"database_name": task.get("data", {}).get("_meta", {}).get("databaseName", {})} for task in tasks])
|
|
675
|
+
return task_ids
|
|
676
|
+
except:
|
|
677
|
+
raise Exception(f"Failed to fetch tasks for project with name: {self.__project_name}")
|