kiln-ai 0.14.0__py3-none-any.whl → 0.16.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.
Files changed (58) hide show
  1. kiln_ai/adapters/eval/base_eval.py +7 -2
  2. kiln_ai/adapters/eval/eval_runner.py +5 -64
  3. kiln_ai/adapters/eval/g_eval.py +3 -3
  4. kiln_ai/adapters/fine_tune/base_finetune.py +6 -3
  5. kiln_ai/adapters/fine_tune/dataset_formatter.py +128 -38
  6. kiln_ai/adapters/fine_tune/finetune_registry.py +2 -0
  7. kiln_ai/adapters/fine_tune/fireworks_finetune.py +2 -1
  8. kiln_ai/adapters/fine_tune/test_base_finetune.py +7 -0
  9. kiln_ai/adapters/fine_tune/test_dataset_formatter.py +267 -10
  10. kiln_ai/adapters/fine_tune/test_fireworks_tinetune.py +1 -1
  11. kiln_ai/adapters/fine_tune/test_vertex_finetune.py +586 -0
  12. kiln_ai/adapters/fine_tune/vertex_finetune.py +217 -0
  13. kiln_ai/adapters/ml_model_list.py +817 -62
  14. kiln_ai/adapters/model_adapters/base_adapter.py +33 -10
  15. kiln_ai/adapters/model_adapters/litellm_adapter.py +51 -12
  16. kiln_ai/adapters/model_adapters/test_base_adapter.py +74 -2
  17. kiln_ai/adapters/model_adapters/test_litellm_adapter.py +65 -1
  18. kiln_ai/adapters/model_adapters/test_saving_adapter_results.py +3 -2
  19. kiln_ai/adapters/model_adapters/test_structured_output.py +4 -6
  20. kiln_ai/adapters/parsers/base_parser.py +0 -3
  21. kiln_ai/adapters/parsers/parser_registry.py +5 -3
  22. kiln_ai/adapters/parsers/r1_parser.py +17 -2
  23. kiln_ai/adapters/parsers/request_formatters.py +40 -0
  24. kiln_ai/adapters/parsers/test_parser_registry.py +2 -2
  25. kiln_ai/adapters/parsers/test_r1_parser.py +44 -1
  26. kiln_ai/adapters/parsers/test_request_formatters.py +76 -0
  27. kiln_ai/adapters/prompt_builders.py +14 -1
  28. kiln_ai/adapters/provider_tools.py +25 -1
  29. kiln_ai/adapters/repair/test_repair_task.py +3 -2
  30. kiln_ai/adapters/test_prompt_builders.py +24 -3
  31. kiln_ai/adapters/test_provider_tools.py +86 -1
  32. kiln_ai/datamodel/__init__.py +2 -0
  33. kiln_ai/datamodel/datamodel_enums.py +14 -0
  34. kiln_ai/datamodel/dataset_filters.py +69 -1
  35. kiln_ai/datamodel/dataset_split.py +4 -0
  36. kiln_ai/datamodel/eval.py +8 -0
  37. kiln_ai/datamodel/finetune.py +1 -0
  38. kiln_ai/datamodel/json_schema.py +24 -7
  39. kiln_ai/datamodel/prompt_id.py +1 -0
  40. kiln_ai/datamodel/task_output.py +10 -6
  41. kiln_ai/datamodel/task_run.py +68 -12
  42. kiln_ai/datamodel/test_basemodel.py +3 -7
  43. kiln_ai/datamodel/test_dataset_filters.py +82 -0
  44. kiln_ai/datamodel/test_dataset_split.py +2 -0
  45. kiln_ai/datamodel/test_example_models.py +158 -3
  46. kiln_ai/datamodel/test_json_schema.py +22 -3
  47. kiln_ai/datamodel/test_model_perf.py +3 -2
  48. kiln_ai/datamodel/test_models.py +50 -2
  49. kiln_ai/utils/async_job_runner.py +106 -0
  50. kiln_ai/utils/dataset_import.py +80 -18
  51. kiln_ai/utils/test_async_job_runner.py +199 -0
  52. kiln_ai/utils/test_dataset_import.py +242 -10
  53. {kiln_ai-0.14.0.dist-info → kiln_ai-0.16.0.dist-info}/METADATA +3 -2
  54. kiln_ai-0.16.0.dist-info/RECORD +108 -0
  55. kiln_ai/adapters/test_generate_docs.py +0 -69
  56. kiln_ai-0.14.0.dist-info/RECORD +0 -103
  57. {kiln_ai-0.14.0.dist-info → kiln_ai-0.16.0.dist-info}/WHEEL +0 -0
  58. {kiln_ai-0.14.0.dist-info → kiln_ai-0.16.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,217 @@
1
+ import logging
2
+ import time
3
+
4
+ import vertexai
5
+ from google.cloud import storage
6
+ from google.cloud.aiplatform_v1beta1 import types as gca_types
7
+ from vertexai.tuning import sft
8
+
9
+ from kiln_ai.adapters.fine_tune.base_finetune import (
10
+ BaseFinetuneAdapter,
11
+ FineTuneParameter,
12
+ FineTuneStatus,
13
+ FineTuneStatusType,
14
+ )
15
+ from kiln_ai.adapters.fine_tune.dataset_formatter import DatasetFormat, DatasetFormatter
16
+ from kiln_ai.datamodel import DatasetSplit, StructuredOutputMode, Task
17
+ from kiln_ai.utils.config import Config
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class VertexFinetune(BaseFinetuneAdapter):
23
+ """
24
+ A fine-tuning adapter for Vertex AI.
25
+ """
26
+
27
+ async def status(self) -> FineTuneStatus:
28
+ """
29
+ Get the status of the fine-tune.
30
+ """
31
+
32
+ # Update the datamodel with the latest status if it has changed
33
+ status = await self._status()
34
+ if status.status != self.datamodel.latest_status:
35
+ self.datamodel.latest_status = status.status
36
+ if self.datamodel.path:
37
+ self.datamodel.save_to_file()
38
+ return status
39
+
40
+ async def _status(self) -> FineTuneStatus:
41
+ if not self.datamodel or not self.datamodel.provider_id:
42
+ return FineTuneStatus(
43
+ status=FineTuneStatusType.pending,
44
+ message="This fine-tune has not been started or has not been assigned a provider ID.",
45
+ )
46
+
47
+ response = sft.SupervisedTuningJob(self.datamodel.provider_id)
48
+ # If the fine-tuned model ID has been updated, update the datamodel
49
+ try:
50
+ if self.datamodel.fine_tune_model_id != response.tuned_model_endpoint_name:
51
+ self.datamodel.fine_tune_model_id = response.tuned_model_endpoint_name
52
+ if self.datamodel.path:
53
+ self.datamodel.save_to_file()
54
+ except Exception as e:
55
+ # Don't let this error crash the status call
56
+ logger.warning(f"Error updating fine-tune model ID: {e}")
57
+ pass
58
+
59
+ error = response.error
60
+ if error and error.code != 0:
61
+ return FineTuneStatus(
62
+ status=FineTuneStatusType.failed,
63
+ message=f"Fine Tune Job Error: {error.message} [{error.code}]",
64
+ )
65
+ state = response.state
66
+ if state in [
67
+ gca_types.JobState.JOB_STATE_FAILED,
68
+ gca_types.JobState.JOB_STATE_EXPIRED,
69
+ ]:
70
+ return FineTuneStatus(
71
+ status=FineTuneStatusType.failed,
72
+ message="Fine Tune Job Failed",
73
+ )
74
+ if state in [
75
+ gca_types.JobState.JOB_STATE_CANCELLED,
76
+ gca_types.JobState.JOB_STATE_CANCELLING,
77
+ ]:
78
+ return FineTuneStatus(
79
+ status=FineTuneStatusType.failed, message="Fine Tune Job Cancelled"
80
+ )
81
+ if state in [
82
+ gca_types.JobState.JOB_STATE_PENDING,
83
+ gca_types.JobState.JOB_STATE_QUEUED,
84
+ ]:
85
+ return FineTuneStatus(
86
+ status=FineTuneStatusType.pending, message="Fine Tune Job Pending"
87
+ )
88
+ if state in [
89
+ gca_types.JobState.JOB_STATE_RUNNING,
90
+ ]:
91
+ return FineTuneStatus(
92
+ status=FineTuneStatusType.running,
93
+ message="Fine Tune Job Running",
94
+ )
95
+ if state in [
96
+ gca_types.JobState.JOB_STATE_SUCCEEDED,
97
+ gca_types.JobState.JOB_STATE_PARTIALLY_SUCCEEDED,
98
+ ]:
99
+ return FineTuneStatus(
100
+ status=FineTuneStatusType.completed, message="Fine Tune Job Completed"
101
+ )
102
+
103
+ if state not in [
104
+ gca_types.JobState.JOB_STATE_UPDATING,
105
+ gca_types.JobState.JOB_STATE_UNSPECIFIED,
106
+ gca_types.JobState.JOB_STATE_PAUSED,
107
+ ]:
108
+ # While the above states map to "unknown", they are expected unknowns. Log if some new state appears we aren't expecting
109
+ logger.warning(f"Unknown Vertex AI Fine Tune Status: [{state}]")
110
+
111
+ return FineTuneStatus(
112
+ status=FineTuneStatusType.unknown, message=f"Unknown state: [{state}]"
113
+ )
114
+
115
+ async def _start(self, dataset: DatasetSplit) -> None:
116
+ task = self.datamodel.parent_task()
117
+ if not task:
118
+ raise ValueError("Task is required to start a fine-tune")
119
+
120
+ # Use chat format for unstructured output, and JSON for formatted output
121
+ format = DatasetFormat.VERTEX_GEMINI
122
+ if task.output_json_schema:
123
+ self.datamodel.structured_output_mode = StructuredOutputMode.json_mode
124
+ train_file_id = await self.generate_and_upload_jsonl(
125
+ dataset, self.datamodel.train_split_name, task, format
126
+ )
127
+ validation_file_id = None
128
+ if self.datamodel.validation_split_name:
129
+ validation_file_id = await self.generate_and_upload_jsonl(
130
+ dataset, self.datamodel.validation_split_name, task, format
131
+ )
132
+
133
+ hyperparameters = self.datamodel.parameters
134
+
135
+ project, location = self.get_vertex_provider_location()
136
+ vertexai.init(project=project, location=location)
137
+
138
+ sft_tuning_job = sft.train(
139
+ source_model=self.datamodel.base_model_id,
140
+ train_dataset=train_file_id,
141
+ validation_dataset=validation_file_id,
142
+ tuned_model_display_name=f"kiln_finetune_{self.datamodel.id}",
143
+ # It is recommended to use auto-selection and leave them unset
144
+ epochs=hyperparameters.get("epochs", None), # type: ignore
145
+ adapter_size=hyperparameters.get("adapter_size", None), # type: ignore
146
+ learning_rate_multiplier=hyperparameters.get(
147
+ "learning_rate_multiplier", None
148
+ ), # type: ignore
149
+ labels={
150
+ "source": "kiln",
151
+ "kiln_finetune_id": str(self.datamodel.id),
152
+ "kiln_task_id": str(task.id),
153
+ },
154
+ )
155
+ self.datamodel.provider_id = sft_tuning_job.resource_name
156
+
157
+ return None
158
+
159
+ async def generate_and_upload_jsonl(
160
+ self, dataset: DatasetSplit, split_name: str, task: Task, format: DatasetFormat
161
+ ) -> str:
162
+ formatter = DatasetFormatter(
163
+ dataset, self.datamodel.system_message, self.datamodel.thinking_instructions
164
+ )
165
+ path = formatter.dump_to_file(split_name, format, self.datamodel.data_strategy)
166
+
167
+ project, location = self.get_vertex_provider_location()
168
+ storage_client = storage.Client(project=project)
169
+
170
+ bucket_name = "kiln-ai-data"
171
+
172
+ # Check if bucket exists and create it if it doesn't
173
+ if not storage_client.lookup_bucket(bucket_name):
174
+ bucket = storage_client.create_bucket(bucket_name, location=location)
175
+ else:
176
+ bucket = storage_client.bucket(bucket_name)
177
+
178
+ # Create a blob and upload
179
+ epoch_timestamp = int(time.time())
180
+ blob_name = f"{epoch_timestamp}/{path.name}"
181
+ blob = bucket.blob(blob_name)
182
+ blob.upload_from_filename(path)
183
+
184
+ return f"gs://{bucket.name}/{blob.name}"
185
+
186
+ @classmethod
187
+ def available_parameters(cls) -> list[FineTuneParameter]:
188
+ return [
189
+ FineTuneParameter(
190
+ name="learning_rate_multiplier",
191
+ type="float",
192
+ description="Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting. Defaults to 1.0 (don't scale vertex's learning rate).",
193
+ optional=True,
194
+ ),
195
+ FineTuneParameter(
196
+ name="epochs",
197
+ type="int",
198
+ description="The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. Defaults to 'auto'",
199
+ optional=True,
200
+ ),
201
+ FineTuneParameter(
202
+ name="adapter_size",
203
+ type="int",
204
+ description="The size of the adapter to use for the fine-tune. One of 1, 4, 8, or 16. By default Vertex will auto-select a size.",
205
+ optional=True,
206
+ ),
207
+ ]
208
+
209
+ @classmethod
210
+ def get_vertex_provider_location(cls) -> tuple[str, str]:
211
+ project = Config.shared().vertex_project_id
212
+ location = Config.shared().vertex_location
213
+ if not project or not location:
214
+ raise ValueError(
215
+ "Google Vertex project and location must be set in Kiln settings to fine tune."
216
+ )
217
+ return project, location