llm-annotator 0.2.0__tar.gz → 0.2.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llm-annotator
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: An easy-to-extend LLM annotator for robust, resumable data annotation.
5
5
  Author-email: Bram Vanroy <2779410+BramVanroy@users.noreply.github.com>
6
6
  License-Expression: Apache-2.0
@@ -47,7 +47,7 @@ select = ["C", "E", "F", "W", "I"]
47
47
 
48
48
  [tool.ruff.lint.isort]
49
49
  lines-after-imports = 2
50
- known-first-party = ["c5"]
50
+ known-first-party = ["llm_annotator"]
51
51
 
52
52
  [tool.ruff.format]
53
53
  # Like Black, use double quotes for strings.
@@ -98,10 +98,11 @@ class Annotator:
98
98
  def _load_dataset(
99
99
  self,
100
100
  *,
101
- dataset_name: str,
102
101
  prompt_template: str,
103
102
  pdout: Path,
104
103
  idx_column: str,
104
+ dataset_name: str | None = None,
105
+ dataset: Dataset | None = None,
105
106
  dataset_config: str = None,
106
107
  data_dir: str | None = None,
107
108
  dataset_split: str | None = None,
@@ -120,6 +121,7 @@ class Annotator:
120
121
 
121
122
  Args:
122
123
  dataset_name: Name or path of the dataset to load.
124
+ dataset: Pre-loaded dataset to use instead of loading from name/path.
123
125
  pdout: Output directory for caching and results.
124
126
  dataset_config: Dataset configuration name (optional).
125
127
  data_dir: Data directory for local datasets (optional).
@@ -144,37 +146,37 @@ class Annotator:
144
146
  if max_num_samples is not None and max_num_samples <= 0:
145
147
  raise ValueError("'max_num_samples' must be a positive integer or None")
146
148
 
147
- dataset_config = dataset_config
148
- dataset_split = dataset_split
149
+ if not dataset_name and dataset is None:
150
+ raise ValueError("Either 'dataset_name' or 'dataset' must be provided")
149
151
 
150
152
  # Split verification and defaulting
151
- split_names = get_dataset_split_names(dataset_name)
152
- if not dataset_split:
153
- if len(split_names) == 1:
154
- dataset_split = split_names[0]
155
- else:
156
- raise ValueError(
157
- f"Dataset '{dataset_name}' has multiple splits {split_names}. "
158
- "Please specify a split using the 'dataset_split' argument."
159
- )
160
- elif dataset_split not in split_names:
161
- raise ValueError(f"Dataset '{dataset_name}' does not have a split named '{dataset_split}'")
153
+ if dataset_name:
154
+ split_names = get_dataset_split_names(dataset_name)
155
+ if not dataset_split:
156
+ if len(split_names) == 1:
157
+ dataset_split = split_names[0]
158
+ else:
159
+ raise ValueError(
160
+ f"Dataset '{dataset_name}' has multiple splits {split_names}. "
161
+ "Please specify a split using the 'dataset_split' argument."
162
+ )
163
+ elif dataset_split not in split_names:
164
+ raise ValueError(f"Dataset '{dataset_name}' does not have a split named '{dataset_split}'")
162
165
 
163
166
  pdout = Path(pdout)
164
167
  p_cached_input_ds = pdout / f"{prefix}cached_input_dataset"
165
168
 
166
- dataset = None
167
-
168
169
  # If exists and not empty, try to load from cache. If loading the
169
170
  # cached dataset fails (corrupted cache), fall back to loading from
170
171
  # the original source.
172
+ loaded_ds = None
171
173
  if use_cached_input_dataset and p_cached_input_ds.exists() and p_cached_input_ds.stat().st_size > 0:
172
- try:
173
- dataset = Dataset.load_from_disk(p_cached_input_ds)
174
- except Exception:
175
- dataset = None
174
+ loaded_ds = Dataset.load_from_disk(p_cached_input_ds)
176
175
 
177
- if dataset is None:
176
+ # Always prefer a locally cached dataset if available
177
+ if loaded_ds is not None:
178
+ dataset = loaded_ds
179
+ else:
178
180
  if streaming and not max_num_samples:
179
181
  raise ValueError(
180
182
  "Streaming mode requires max_num_samples to be set."
@@ -182,7 +184,8 @@ class Annotator:
182
184
  " the requested number of samples."
183
185
  )
184
186
 
185
- if streaming:
187
+ # No dataset provided, so got to load it from dataset_name
188
+ if dataset is None and streaming:
186
189
  ds_iter: IterableDataset = load_dataset(
187
190
  dataset_name, name=dataset_config, data_dir=data_dir, split=dataset_split, streaming=True
188
191
  )
@@ -206,7 +209,12 @@ class Annotator:
206
209
  # Convert to Dataset
207
210
  dataset = Dataset.from_generator(yield_fn, split=dataset_split)
208
211
  else:
209
- dataset = load_dataset(dataset_name, name=dataset_config, data_dir=data_dir, split=dataset_split)
212
+ # Use the provided dataset if available
213
+ if dataset is not None:
214
+ dataset = dataset
215
+ else:
216
+ dataset = load_dataset(dataset_name, name=dataset_config, data_dir=data_dir, split=dataset_split)
217
+
210
218
  if shuffle_seed is not None:
211
219
  dataset = dataset.shuffle(seed=shuffle_seed)
212
220
 
@@ -224,24 +232,18 @@ class Annotator:
224
232
  dataset = self._preprocess_dataset(dataset=dataset)
225
233
 
226
234
  dataset = dataset.map(
227
- lambda sample, idx: {
228
- f"{prefix}prompted": self.tokenizer.apply_chat_template(
229
- [
230
- {
231
- "role": "user",
232
- "content": prompt_template.format(**{fld: sample[fld] for fld in prompt_fields}),
233
- }
234
- ],
235
- tokenize=False,
236
- add_generation_template=True,
237
- enable_thinking=self.enable_thinking,
238
- ),
239
- idx_column: idx,
240
- },
235
+ self.apply_prompt_template,
241
236
  with_indices=True,
242
237
  num_proc=self.num_proc,
238
+ fn_kwargs={
239
+ "prompt_fields": prompt_fields,
240
+ "prompt_template": prompt_template,
241
+ "idx_column": idx_column,
242
+ "prefix": prefix,
243
+ },
243
244
  desc="Applying prompt template",
244
245
  )
246
+
245
247
  if cache_input_dataset:
246
248
  dataset.save_to_disk(p_cached_input_ds)
247
249
 
@@ -265,6 +267,38 @@ class Annotator:
265
267
  dataset = self._postprocess_dataset(dataset=dataset)
266
268
  return dataset, processed_n_samples
267
269
 
270
+ def apply_prompt_template(
271
+ self, sample: dict, idx: int, prompt_fields: Iterable[str], prompt_template: str, idx_column: str, prefix: str
272
+ ) -> dict[str, str | int]:
273
+ """Apply the prompt template to a single dataset sample. Fills in the prompt template with values from the sample,
274
+ based on the prompt_fields.
275
+
276
+ Args:
277
+ sample: The dataset sample to process.
278
+ idx: The index of the sample in the dataset.
279
+ prompt_fields: Fields required by the prompt template.
280
+ prompt_template: The prompt template string with placeholders.
281
+ idx_column: Column name to use as unique identifier.
282
+ prefix: String prefix to use for internal column names.
283
+
284
+ Returns:
285
+ A dictionary with the filled-in prompt and the sample index.
286
+ """
287
+ return {
288
+ f"{prefix}prompted": self.tokenizer.apply_chat_template(
289
+ [
290
+ {
291
+ "role": "user",
292
+ "content": prompt_template.format(**{fld: sample[fld] for fld in prompt_fields}),
293
+ }
294
+ ],
295
+ tokenize=False,
296
+ add_generation_template=True,
297
+ enable_thinking=self.enable_thinking,
298
+ ),
299
+ idx_column: idx,
300
+ }
301
+
268
302
  def _preprocess_dataset(self, *, dataset: Dataset) -> Dataset:
269
303
  """Preprocess the dataset before applying prompt templates.
270
304
 
@@ -433,9 +467,10 @@ class Annotator:
433
467
 
434
468
  def annotate_dataset(
435
469
  self,
436
- dataset_name: str,
437
470
  output_dir: str | Path,
438
471
  *,
472
+ dataset_name: str | None = None,
473
+ dataset: Dataset | None = None,
439
474
  new_hub_id: str | None = None,
440
475
  overwrite: bool = False,
441
476
  dataset_config: str | None = None,
@@ -465,8 +500,9 @@ class Annotator:
465
500
  from dataset loading through model inference to output generation.
466
501
 
467
502
  Args:
468
- dataset_name: Name or path of the dataset to annotate.
469
503
  output_dir: Directory to save annotation results.
504
+ dataset_name: Name or path of the dataset to annotate.
505
+ dataset: Pre-loaded dataset to use instead of loading from name/path.
470
506
  new_hub_id: Optional Hugging Face dataset ID for uploads.
471
507
  overwrite: Whether to overwrite existing output directory.
472
508
  dataset_config: Dataset configuration name (optional).
@@ -564,10 +600,11 @@ class Annotator:
564
600
 
565
601
  self._load_tokenizer()
566
602
  dataset, processed_n_samples = self._load_dataset(
567
- dataset_name=dataset_name,
568
603
  prompt_template=prompt_template,
569
- pdout=pdout,
570
604
  idx_column=idx_column,
605
+ pdout=pdout,
606
+ dataset_name=dataset_name,
607
+ dataset=dataset,
571
608
  dataset_config=dataset_config,
572
609
  data_dir=data_dir,
573
610
  dataset_split=dataset_split,
File without changes
File without changes
File without changes
File without changes