kiln-ai 0.12.0__py3-none-any.whl → 0.13.2__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.

Potentially problematic release.


This version of kiln-ai might be problematic. Click here for more details.

Files changed (49) hide show
  1. kiln_ai/adapters/__init__.py +4 -0
  2. kiln_ai/adapters/adapter_registry.py +157 -28
  3. kiln_ai/adapters/eval/__init__.py +28 -0
  4. kiln_ai/adapters/eval/eval_runner.py +4 -1
  5. kiln_ai/adapters/eval/g_eval.py +19 -3
  6. kiln_ai/adapters/eval/test_base_eval.py +1 -0
  7. kiln_ai/adapters/eval/test_eval_runner.py +1 -0
  8. kiln_ai/adapters/eval/test_g_eval.py +13 -7
  9. kiln_ai/adapters/fine_tune/base_finetune.py +16 -2
  10. kiln_ai/adapters/fine_tune/finetune_registry.py +2 -0
  11. kiln_ai/adapters/fine_tune/fireworks_finetune.py +8 -1
  12. kiln_ai/adapters/fine_tune/test_fireworks_tinetune.py +19 -0
  13. kiln_ai/adapters/fine_tune/test_together_finetune.py +533 -0
  14. kiln_ai/adapters/fine_tune/together_finetune.py +327 -0
  15. kiln_ai/adapters/ml_model_list.py +638 -155
  16. kiln_ai/adapters/model_adapters/__init__.py +2 -4
  17. kiln_ai/adapters/model_adapters/base_adapter.py +14 -11
  18. kiln_ai/adapters/model_adapters/litellm_adapter.py +391 -0
  19. kiln_ai/adapters/model_adapters/litellm_config.py +13 -0
  20. kiln_ai/adapters/model_adapters/test_litellm_adapter.py +407 -0
  21. kiln_ai/adapters/model_adapters/test_structured_output.py +23 -5
  22. kiln_ai/adapters/ollama_tools.py +3 -2
  23. kiln_ai/adapters/parsers/r1_parser.py +19 -14
  24. kiln_ai/adapters/parsers/test_r1_parser.py +17 -5
  25. kiln_ai/adapters/provider_tools.py +52 -60
  26. kiln_ai/adapters/repair/test_repair_task.py +3 -3
  27. kiln_ai/adapters/run_output.py +1 -1
  28. kiln_ai/adapters/test_adapter_registry.py +17 -20
  29. kiln_ai/adapters/test_generate_docs.py +2 -2
  30. kiln_ai/adapters/test_prompt_adaptors.py +30 -19
  31. kiln_ai/adapters/test_provider_tools.py +27 -82
  32. kiln_ai/datamodel/basemodel.py +2 -0
  33. kiln_ai/datamodel/datamodel_enums.py +2 -0
  34. kiln_ai/datamodel/json_schema.py +1 -1
  35. kiln_ai/datamodel/task_output.py +13 -6
  36. kiln_ai/datamodel/test_basemodel.py +9 -0
  37. kiln_ai/datamodel/test_datasource.py +19 -0
  38. kiln_ai/utils/config.py +46 -0
  39. kiln_ai/utils/dataset_import.py +232 -0
  40. kiln_ai/utils/test_dataset_import.py +596 -0
  41. {kiln_ai-0.12.0.dist-info → kiln_ai-0.13.2.dist-info}/METADATA +51 -7
  42. {kiln_ai-0.12.0.dist-info → kiln_ai-0.13.2.dist-info}/RECORD +44 -41
  43. kiln_ai/adapters/model_adapters/langchain_adapters.py +0 -309
  44. kiln_ai/adapters/model_adapters/openai_compatible_config.py +0 -10
  45. kiln_ai/adapters/model_adapters/openai_model_adapter.py +0 -289
  46. kiln_ai/adapters/model_adapters/test_langchain_adapter.py +0 -343
  47. kiln_ai/adapters/model_adapters/test_openai_model_adapter.py +0 -216
  48. {kiln_ai-0.12.0.dist-info → kiln_ai-0.13.2.dist-info}/WHEEL +0 -0
  49. {kiln_ai-0.12.0.dist-info → kiln_ai-0.13.2.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,327 @@
1
+ from typing import Literal, Tuple
2
+
3
+ from together import Together
4
+ from together.types.files import FilePurpose
5
+ from together.types.finetune import FinetuneJobStatus as TogetherFinetuneJobStatus
6
+
7
+ from kiln_ai.adapters.fine_tune.base_finetune import (
8
+ BaseFinetuneAdapter,
9
+ FineTuneParameter,
10
+ FineTuneStatus,
11
+ FineTuneStatusType,
12
+ )
13
+ from kiln_ai.adapters.fine_tune.dataset_formatter import DatasetFormat, DatasetFormatter
14
+ from kiln_ai.datamodel import DatasetSplit, StructuredOutputMode, Task
15
+ from kiln_ai.datamodel import Finetune as FinetuneModel
16
+ from kiln_ai.utils.config import Config
17
+
18
+ _pending_statuses = [
19
+ TogetherFinetuneJobStatus.STATUS_PENDING,
20
+ TogetherFinetuneJobStatus.STATUS_QUEUED,
21
+ ]
22
+ _running_statuses = [
23
+ TogetherFinetuneJobStatus.STATUS_RUNNING,
24
+ TogetherFinetuneJobStatus.STATUS_COMPRESSING,
25
+ TogetherFinetuneJobStatus.STATUS_UPLOADING,
26
+ ]
27
+ _completed_statuses = [TogetherFinetuneJobStatus.STATUS_COMPLETED]
28
+ _failed_statuses = [
29
+ TogetherFinetuneJobStatus.STATUS_CANCELLED,
30
+ TogetherFinetuneJobStatus.STATUS_CANCEL_REQUESTED,
31
+ TogetherFinetuneJobStatus.STATUS_ERROR,
32
+ TogetherFinetuneJobStatus.STATUS_USER_ERROR,
33
+ ]
34
+
35
+
36
+ class TogetherFinetune(BaseFinetuneAdapter):
37
+ """
38
+ A fine-tuning adapter for Together.ai.
39
+ """
40
+
41
+ def __init__(self, datamodel: FinetuneModel):
42
+ super().__init__(datamodel)
43
+ api_key = Config.shared().together_api_key
44
+ if not api_key:
45
+ raise ValueError("Together.ai API key not set")
46
+ self.client = Together(api_key=api_key)
47
+
48
+ async def status(self) -> FineTuneStatus:
49
+ status, _ = await self._status()
50
+ # update the datamodel if the status has changed
51
+ if self.datamodel.latest_status != status.status:
52
+ self.datamodel.latest_status = status.status
53
+ if self.datamodel.path:
54
+ self.datamodel.save_to_file()
55
+ return status
56
+
57
+ async def _status(self) -> Tuple[FineTuneStatus, str | None]:
58
+ try:
59
+ fine_tuning_job_id = self.datamodel.provider_id
60
+ if not fine_tuning_job_id:
61
+ return FineTuneStatus(
62
+ status=FineTuneStatusType.unknown,
63
+ message="Fine-tuning job ID not set. Can not retrieve status.",
64
+ ), None
65
+
66
+ # retrieve the fine-tuning job
67
+ together_finetune = self.client.fine_tuning.retrieve(id=fine_tuning_job_id)
68
+
69
+ status = together_finetune.status
70
+ if status in _pending_statuses:
71
+ return FineTuneStatus(
72
+ status=FineTuneStatusType.pending,
73
+ message=f"Fine-tuning job is pending [{status}]",
74
+ ), fine_tuning_job_id
75
+ elif status in _running_statuses:
76
+ return FineTuneStatus(
77
+ status=FineTuneStatusType.running,
78
+ message=f"Fine-tuning job is running [{status}]",
79
+ ), fine_tuning_job_id
80
+ elif status in _completed_statuses:
81
+ return FineTuneStatus(
82
+ status=FineTuneStatusType.completed,
83
+ message="Fine-tuning job completed",
84
+ ), fine_tuning_job_id
85
+ elif status in _failed_statuses:
86
+ return FineTuneStatus(
87
+ status=FineTuneStatusType.failed,
88
+ message=f"Fine-tuning job failed [{status}]",
89
+ ), fine_tuning_job_id
90
+ else:
91
+ return FineTuneStatus(
92
+ status=FineTuneStatusType.unknown,
93
+ message=f"Unknown fine-tuning job status [{status}]",
94
+ ), fine_tuning_job_id
95
+ except Exception as e:
96
+ return FineTuneStatus(
97
+ status=FineTuneStatusType.unknown,
98
+ message=f"Error retrieving fine-tuning job status: {e}",
99
+ ), None
100
+
101
+ async def _start(self, dataset: DatasetSplit) -> None:
102
+ task = self.datamodel.parent_task()
103
+ if not task:
104
+ raise ValueError("Task is required to start a fine-tune")
105
+
106
+ format = DatasetFormat.OPENAI_CHAT_JSONL
107
+ if task.output_json_schema:
108
+ # This formatter will check it's valid JSON, and normalize the output (chat format just uses exact string).
109
+ format = DatasetFormat.OPENAI_CHAT_JSON_SCHEMA_JSONL
110
+ # Together doesn't support JSON-mode for fine-tunes, so we use JSON instructions in the system message. However not our standard json_instructions mode.
111
+ # Instead we augment the system message with custom JSON instructions for a fine-tune (see augment_system_message). A nice simple instructions.
112
+ # Why: Fine-tunes tend to need less coaching to get JSON format correct, as they have seen examples. And they are often on smaller models that have trouble following longer/complex JSON-schema prompts so our default is a poor choice.
113
+ # We save json_custom_instructions mode so it knows what to do at call time.
114
+ self.datamodel.structured_output_mode = (
115
+ StructuredOutputMode.json_custom_instructions
116
+ )
117
+
118
+ train_file_id = await self.generate_and_upload_jsonl(
119
+ dataset, self.datamodel.train_split_name, task, format
120
+ )
121
+
122
+ # Their library defaults to "" and not None, so we follow that convention.
123
+ validation_file_id = ""
124
+ if self.datamodel.validation_split_name:
125
+ validation_file_id = await self.generate_and_upload_jsonl(
126
+ dataset, self.datamodel.validation_split_name, task, format
127
+ )
128
+
129
+ together_finetune = self.client.fine_tuning.create(
130
+ training_file=train_file_id,
131
+ validation_file=validation_file_id,
132
+ model=self.datamodel.base_model_id,
133
+ wandb_api_key=Config.shared().wandb_api_key,
134
+ wandb_project_name="Kiln_AI" if Config.shared().wandb_api_key else None,
135
+ **self._build_finetune_parameters(),
136
+ )
137
+
138
+ # 2 different IDs, output_name is the name of the model that results from the fine-tune job, the finetune_job_id is the ID of the fine-tune job
139
+ self.datamodel.provider_id = together_finetune.id
140
+ self.datamodel.fine_tune_model_id = together_finetune.output_name
141
+
142
+ if self.datamodel.path:
143
+ self.datamodel.save_to_file()
144
+
145
+ def _build_finetune_parameters(self) -> dict:
146
+ """
147
+ Build the parameters dictionary for fine-tuning with Together.ai.
148
+ Only includes parameters that exist in the datamodel and have valid types.
149
+
150
+ Args:
151
+ train_file_id: The ID of the uploaded training file
152
+ display_name: The display name for the fine-tune job
153
+
154
+ Returns:
155
+ Dictionary of parameters to pass to Together's fine-tuning API
156
+ """
157
+ parameters = self.datamodel.parameters
158
+
159
+ # Start with required parameters
160
+ properties = {
161
+ # Force LoRA for now. We only support serverless Loras at the moment. We can remove this later when we add support for full-finetunes.
162
+ "lora": True,
163
+ # Suffix must be truncated to 40 characters
164
+ "suffix": f"kiln_ai_{self.datamodel.id}",
165
+ }
166
+
167
+ # Add optional parameters only if they exist and have correct types
168
+ if "epochs" in parameters and isinstance(parameters["epochs"], int):
169
+ properties["n_epochs"] = parameters["epochs"]
170
+
171
+ if "learning_rate" in parameters and isinstance(
172
+ parameters["learning_rate"], float
173
+ ):
174
+ properties["learning_rate"] = parameters["learning_rate"]
175
+
176
+ if "batch_size" in parameters and isinstance(parameters["batch_size"], int):
177
+ properties["batch_size"] = parameters["batch_size"]
178
+
179
+ if "num_checkpoints" in parameters and isinstance(
180
+ parameters["num_checkpoints"], int
181
+ ):
182
+ properties["n_checkpoints"] = parameters["num_checkpoints"]
183
+
184
+ if "min_lr_ratio" in parameters and isinstance(
185
+ parameters["min_lr_ratio"], float
186
+ ):
187
+ properties["min_lr_ratio"] = parameters["min_lr_ratio"]
188
+
189
+ if "warmup_ratio" in parameters and isinstance(
190
+ parameters["warmup_ratio"], float
191
+ ):
192
+ properties["warmup_ratio"] = parameters["warmup_ratio"]
193
+
194
+ if "max_grad_norm" in parameters and isinstance(
195
+ parameters["max_grad_norm"], float
196
+ ):
197
+ properties["max_grad_norm"] = parameters["max_grad_norm"]
198
+
199
+ if "weight_decay" in parameters and isinstance(
200
+ parameters["weight_decay"], float
201
+ ):
202
+ properties["weight_decay"] = parameters["weight_decay"]
203
+
204
+ if "lora_rank" in parameters and isinstance(parameters["lora_rank"], int):
205
+ properties["lora_r"] = parameters["lora_rank"]
206
+
207
+ if "lora_dropout" in parameters and isinstance(
208
+ parameters["lora_dropout"], float
209
+ ):
210
+ properties["lora_dropout"] = parameters["lora_dropout"]
211
+
212
+ if "lora_alpha" in parameters and isinstance(parameters["lora_alpha"], float):
213
+ properties["lora_alpha"] = parameters["lora_alpha"]
214
+
215
+ return properties
216
+
217
+ @classmethod
218
+ def augment_system_message(cls, system_message: str, task: Task) -> str:
219
+ """
220
+ Augment the system message with custom JSON instructions for a fine-tune.
221
+
222
+ This is a shorter version of the JSON instructions, as fine-tunes tend to need less coaching to get JSON format correct. Plus smaller models are often finetuned, and don't do well following our detailed JSON-schema instructions from json_instructions.
223
+
224
+ Together doesn't support JSON-mode for fine-tunes, so this is needed where it isn't needed with other providers.
225
+ """
226
+ if task.output_json_schema:
227
+ return (
228
+ system_message
229
+ + "\n\nReturn only JSON. Do not include any non JSON text.\n"
230
+ )
231
+ return system_message
232
+
233
+ async def generate_and_upload_jsonl(
234
+ self, dataset: DatasetSplit, split_name: str, task: Task, format: DatasetFormat
235
+ ) -> str:
236
+ formatter = DatasetFormatter(
237
+ dataset=dataset,
238
+ system_message=self.datamodel.system_message,
239
+ thinking_instructions=self.datamodel.thinking_instructions,
240
+ )
241
+ path = formatter.dump_to_file(split_name, format, self.datamodel.data_strategy)
242
+
243
+ try:
244
+ together_file = self.client.files.upload(
245
+ file=path,
246
+ purpose=FilePurpose.FineTune,
247
+ check=True,
248
+ )
249
+ return together_file.id
250
+ except Exception as e:
251
+ raise ValueError(f"Failed to upload dataset: {e}")
252
+
253
+ @classmethod
254
+ def available_parameters(cls) -> list[FineTuneParameter]:
255
+ return [
256
+ FineTuneParameter(
257
+ name="epochs",
258
+ description="Number of epochs to fine-tune for. Default: 1, Min: 1, Max: 20.",
259
+ type="int",
260
+ optional=True,
261
+ ),
262
+ FineTuneParameter(
263
+ name="learning_rate",
264
+ description="Learning rate to use for fine-tuning. Defaults to 0.00001",
265
+ type="float",
266
+ optional=True,
267
+ ),
268
+ FineTuneParameter(
269
+ name="batch_size",
270
+ description="Batch size used in training. Can be configured with a positive value less than 1024 and in power of 2. If not specified, defaults to the max supported value for this model. See together model pages for min/max values for each model.",
271
+ type="int",
272
+ optional=True,
273
+ ),
274
+ FineTuneParameter(
275
+ name="num_checkpoints",
276
+ description="Number of checkpoints to save during training. Defaults to 1.",
277
+ type="int",
278
+ optional=True,
279
+ ),
280
+ FineTuneParameter(
281
+ name="min_lr_ratio",
282
+ description="The ratio of the final learning rate to the peak learning rate. Default: 0.0, Min: 0.0, Max: 1.0.",
283
+ type="float",
284
+ optional=True,
285
+ ),
286
+ FineTuneParameter(
287
+ name="warmup_ratio",
288
+ description="The percent of steps at the start of training to linearly increase the learning rate. Defaults to 0.0, Min: 0.0, Max: 1.0.",
289
+ type="float",
290
+ optional=True,
291
+ ),
292
+ FineTuneParameter(
293
+ name="max_grad_norm",
294
+ description="Max gradient norm for gradient clipping. Set to 0 to disable. Defaults to 1.0, Min: 0.0.",
295
+ type="float",
296
+ optional=True,
297
+ ),
298
+ FineTuneParameter(
299
+ name="weight_decay",
300
+ description="Weight decay. Defaults to 0.0.",
301
+ type="float",
302
+ optional=True,
303
+ ),
304
+ FineTuneParameter(
305
+ name="lora_rank",
306
+ description="Rank of LoRA adapters. Default: 8, Min: 1, Max: 64",
307
+ type="int",
308
+ optional=True,
309
+ ),
310
+ FineTuneParameter(
311
+ name="lora_dropout",
312
+ description="Dropout rate for LoRA adapters. Default: 0.0, Min: 0.0, Max: 1.0.",
313
+ type="float",
314
+ optional=True,
315
+ ),
316
+ FineTuneParameter(
317
+ name="lora_alpha",
318
+ description="Alpha value for LoRA adapter training. Default: 8. Min: 1. If a value less than 1 is given, it will default to lora_rank value to follow the recommendation of 1:1 scaling.",
319
+ type="float",
320
+ optional=True,
321
+ ),
322
+ ]
323
+
324
+ async def _deploy(self) -> bool:
325
+ # Together is awesome. Auto deploys!
326
+ # If I add support for non-Lora serverless I'll have to modify this, but good for now.
327
+ return True