Skip to content

Commit d6d2fd5

Browse files
HNicolasNicolas Hervé
andauthored
feat: llm export format (#1716)
Co-authored-by: Nicolas Hervé <nicolas.herve@kili-technology.com>
1 parent 335bf19 commit d6d2fd5

4 files changed

Lines changed: 46 additions & 22 deletions

File tree

src/kili/presentation/client/label.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,7 +1126,7 @@ def export_labels_as_df(
11261126
def export_labels(
11271127
self,
11281128
project_id: str,
1129-
filename: str,
1129+
filename: Optional[str],
11301130
fmt: LabelFormat,
11311131
asset_ids: Optional[List[str]] = None,
11321132
layout: SplitOption = "split",
@@ -1137,7 +1137,7 @@ def export_labels(
11371137
annotation_modifier: Optional[CocoAnnotationModifier] = None,
11381138
asset_filter_kwargs: Optional[Dict[str, object]] = None,
11391139
normalized_coordinates: Optional[bool] = None,
1140-
) -> None:
1140+
) -> Optional[List[Dict[str, Union[List[str], str]]]]:
11411141
# pylint: disable=line-too-long
11421142
"""Export the project labels with the requested format into the requested output path.
11431143
@@ -1225,7 +1225,7 @@ def is_rectangle(coco_annotation, coco_image, kili_annotation):
12251225
resolved_asset_ids = cast(List[AssetId], asset_ids)
12261226

12271227
try:
1228-
export_labels(
1228+
return export_labels(
12291229
self, # pyright: ignore[reportGeneralTypeIssues]
12301230
asset_ids=resolved_asset_ids,
12311231
project_id=ProjectId(project_id),
@@ -1243,3 +1243,4 @@ def is_rectangle(coco_annotation, coco_image, kili_annotation):
12431243
)
12441244
except NoCompatibleJobError as excp:
12451245
warnings.warn(str(excp), stacklevel=2)
1246+
return None

src/kili/services/export/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Service for exporting kili objects."""
22

33
from pathlib import Path
4-
from typing import TYPE_CHECKING, Dict, List, Optional, Type
4+
from typing import TYPE_CHECKING, Dict, List, Optional, Type, Union
55

66
from typing_extensions import get_args
77

@@ -31,14 +31,14 @@ def export_labels( # pylint: disable=too-many-arguments, too-many-locals
3131
label_format: LabelFormat,
3232
split_option: SplitOption,
3333
single_file: bool,
34-
output_file: str,
34+
output_file: Optional[str],
3535
disable_tqdm: Optional[bool],
3636
log_level: LogLevel,
3737
with_assets: bool,
3838
annotation_modifier: Optional[CocoAnnotationModifier],
3939
asset_filter_kwargs: Optional[Dict[str, object]],
4040
normalized_coordinates: Optional[bool],
41-
) -> None:
41+
) -> Optional[List[Dict[str, Union[List[str], str]]]]:
4242
"""Export the selected assets into the required format, and save it into a file archive."""
4343
kili.kili_api_gateway.get_project(project_id, ["id"])
4444

@@ -49,7 +49,7 @@ def export_labels( # pylint: disable=too-many-arguments, too-many-locals
4949
label_format=label_format,
5050
split_option=split_option,
5151
single_file=single_file,
52-
output_file=Path(output_file),
52+
output_file=Path(output_file) if output_file is not None else None,
5353
with_assets=with_assets,
5454
annotation_modifier=annotation_modifier,
5555
asset_filter_kwargs=asset_filter_kwargs,
@@ -77,8 +77,8 @@ def export_labels( # pylint: disable=too-many-arguments, too-many-locals
7777
get_args(LabelFormat)
7878
) # ensures full mapping
7979
exporter_class = format_exporter_selector_mapping[label_format]
80-
exporter_class(
80+
return exporter_class(
8181
export_params, kili, logger, disable_tqdm, content_repository
8282
).export_project()
83-
else:
84-
raise ValueError(f'Label format "{label_format}" is not implemented or does not exist.')
83+
84+
raise ValueError(f'Label format "{label_format}" is not implemented or does not exist.')

src/kili/services/export/format/base.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from abc import ABC, abstractmethod
88
from datetime import datetime
99
from pathlib import Path
10-
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple
10+
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple, Union
1111

1212
from kili.domain.asset import AssetId
1313
from kili.domain.project import ProjectId
@@ -41,7 +41,7 @@ class ExportParams(NamedTuple):
4141
label_format: LabelFormat
4242
split_option: SplitOption
4343
single_file: bool
44-
output_file: Path
44+
output_file: Optional[Path]
4545
with_assets: bool
4646
annotation_modifier: Optional[CocoAnnotationModifier]
4747
asset_filter_kwargs: Optional[Dict[str, object]]
@@ -107,6 +107,10 @@ def compatible_jobs(self) -> Tuple[str, ...]:
107107
def process_and_save(self, assets: List[Dict], output_filename: Path) -> None:
108108
"""Converts the asset and save them into an archive file."""
109109

110+
def process(self, assets: List[Dict]) -> List[Dict[str, Union[List[str], str]]]:
111+
"""Converts the asset."""
112+
raise ValueError("Output file is required for this export format.")
113+
110114
def make_archive(self, root_folder: Path, output_filename: Path) -> Path:
111115
"""Make the export archive."""
112116
path_folder = root_folder / self.project_id
@@ -148,7 +152,7 @@ def write_remote_content_file(remote_content: List[str], images_folder: Path) ->
148152

149153
def export_project(
150154
self,
151-
) -> None:
155+
) -> Optional[List[Dict[str, Union[List[str], str]]]]:
152156
"""Export a project to a json.
153157
154158
Return the name of the exported archive file.
@@ -175,7 +179,10 @@ def export_project(
175179

176180
self._check_geotiff_export_compatibility(assets)
177181

178-
self.process_and_save(assets, self.output_file)
182+
if self.output_file is None:
183+
return self.process(assets)
184+
185+
return self.process_and_save(assets, self.output_file)
179186

180187
def _check_and_ensure_asset_access(self) -> None:
181188
"""Check asset access.

src/kili/services/export/format/llm/__init__.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import json
44
import logging
55
from pathlib import Path
6-
from typing import Dict, List, Union
6+
from typing import Dict, List, Optional, Union
77

88
from kili.services.export.exceptions import NotCompatibleInputType
99
from kili.services.export.format.base import AbstractExporter
@@ -25,8 +25,6 @@ def _check_project_compatibility(self) -> None:
2525
f"Project with input type \"{self.project['inputType']}\" not compatible with LLM"
2626
" export format."
2727
)
28-
if not self.single_file:
29-
raise ValueError("LLM export are always single file.")
3028

3129
def _is_job_compatible(self, job: Job) -> bool:
3230
"""Check job compatibility with the LLM format."""
@@ -41,23 +39,41 @@ def _save_assets_export(self, assets: List[Dict], output_filename: Path) -> None
4139
with output_filename.open("wb") as output_file:
4240
output_file.write(export_json.encode("utf-8"))
4341

44-
def process_and_save(self, assets: List[Dict], output_filename: Path) -> None:
42+
def process_and_save(
43+
self, assets: List[Dict], output_filename: Path
44+
) -> Optional[List[Dict[str, Union[List[str], str]]]]:
4545
"""LLM specific process and save."""
46+
result = self._process(assets)
47+
self._save_assets_export(result, output_filename)
48+
49+
def process(self, assets: List[Dict]) -> List[Dict[str, Union[List[str], str]]]:
50+
"""LLM specific process."""
51+
return self._process(assets)
52+
53+
def _process(self, assets: List[Dict]) -> List[Dict[str, Union[List[str], str]]]:
4654
result = []
4755
for asset in assets:
4856
jobs_config = self.project["jsonInterface"]["jobs"]
49-
json_response = asset["latestLabel"]["jsonResponse"]
57+
latest_label = asset["latestLabel"]
5058
result.append(
5159
{
5260
"raw_data": _format_raw_data(asset),
5361
"status": asset["status"],
5462
"external_id": asset["externalId"],
5563
"metadata": asset["jsonMetadata"],
56-
"labels": [_format_json_response(jobs_config, json_response)],
64+
"labels": [
65+
{
66+
"author": latest_label["author"]["email"],
67+
"created_at": latest_label["createdAt"],
68+
"label_type": latest_label["labelType"],
69+
"label": _format_json_response(
70+
jobs_config, latest_label["jsonResponse"]
71+
),
72+
}
73+
],
5774
}
5875
)
59-
60-
self._save_assets_export(result, output_filename)
76+
return result
6177

6278

6379
def _format_json_response(

0 commit comments

Comments
 (0)