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

@@ -0,0 +1,244 @@
1
+ from pathlib import Path
2
+ from unittest import mock
3
+
4
+ import pytest
5
+ from pydantic import BaseModel
6
+
7
+ from libs.core.kiln_ai.datamodel.model_cache import ModelCache
8
+
9
+
10
+ # Define a simple Pydantic model for testing
11
+ class ModelTest(BaseModel):
12
+ name: str
13
+ value: int
14
+
15
+
16
+ @pytest.fixture
17
+ def model_cache():
18
+ return ModelCache()
19
+
20
+
21
+ def should_skip_test(model_cache):
22
+ return not model_cache._enabled
23
+
24
+
25
+ @pytest.fixture
26
+ def test_path(tmp_path):
27
+ # Create a temporary file path for testing
28
+ test_file = tmp_path / "test_model.kiln"
29
+ test_file.touch() # Create the file
30
+ return test_file
31
+
32
+
33
+ def test_set_and_get_model(model_cache, test_path):
34
+ if not model_cache._enabled:
35
+ pytest.skip("Cache is disabled on this fs")
36
+
37
+ model = ModelTest(name="test", value=123)
38
+ mtime_ns = test_path.stat().st_mtime_ns
39
+
40
+ model_cache.set_model(test_path, model, mtime_ns)
41
+ cached_model = model_cache.get_model(test_path, ModelTest)
42
+
43
+ assert cached_model is not None
44
+ assert cached_model.name == "test"
45
+ assert cached_model.value == 123
46
+
47
+
48
+ def test_invalidate_model(model_cache, test_path):
49
+ model = ModelTest(name="test", value=123)
50
+ mtime = test_path.stat().st_mtime
51
+
52
+ model_cache.set_model(test_path, model, mtime)
53
+ model_cache.invalidate(test_path)
54
+ cached_model = model_cache.get_model(test_path, ModelTest)
55
+
56
+ assert cached_model is None
57
+
58
+
59
+ def test_clear_cache(model_cache, test_path):
60
+ model = ModelTest(name="test", value=123)
61
+ mtime = test_path.stat().st_mtime
62
+
63
+ model_cache.set_model(test_path, model, mtime)
64
+ model_cache.clear()
65
+ cached_model = model_cache.get_model(test_path, ModelTest)
66
+
67
+ assert cached_model is None
68
+
69
+
70
+ def test_cache_invalid_due_to_mtime_change(model_cache, test_path):
71
+ model = ModelTest(name="test", value=123)
72
+ mtime = test_path.stat().st_mtime
73
+
74
+ model_cache.set_model(test_path, model, mtime)
75
+
76
+ # Simulate a file modification by updating the mtime
77
+ test_path.touch()
78
+ cached_model = model_cache.get_model(test_path, ModelTest)
79
+
80
+ assert cached_model is None
81
+
82
+
83
+ def test_get_model_wrong_type(model_cache, test_path):
84
+ if not model_cache._enabled:
85
+ pytest.skip("Cache is disabled on this fs")
86
+
87
+ class AnotherModel(BaseModel):
88
+ other_field: str
89
+
90
+ model = ModelTest(name="test", value=123)
91
+ mtime_ns = test_path.stat().st_mtime_ns
92
+
93
+ model_cache.set_model(test_path, model, mtime_ns)
94
+
95
+ with pytest.raises(ValueError):
96
+ model_cache.get_model(test_path, AnotherModel)
97
+
98
+ # Test that the cache invalidates
99
+ cached_model = model_cache.get_model(test_path, ModelTest)
100
+ assert cached_model is None
101
+
102
+
103
+ def test_is_cache_valid_true(model_cache, test_path):
104
+ mtime_ns = test_path.stat().st_mtime_ns
105
+ assert model_cache._is_cache_valid(test_path, mtime_ns) is True
106
+
107
+
108
+ def test_is_cache_valid_false_due_to_mtime_change(model_cache, test_path):
109
+ if not model_cache._enabled:
110
+ pytest.skip("Cache is disabled on this fs")
111
+
112
+ mtime_ns = test_path.stat().st_mtime_ns
113
+ # Simulate a file modification by updating the mtime
114
+ test_path.touch()
115
+ assert model_cache._is_cache_valid(test_path, mtime_ns) is False
116
+
117
+
118
+ def test_is_cache_valid_false_due_to_missing_file(model_cache):
119
+ non_existent_path = Path("/non/existent/path")
120
+ assert model_cache._is_cache_valid(non_existent_path, 0) is False
121
+
122
+
123
+ def test_benchmark_get_model(benchmark, model_cache, test_path):
124
+ model = ModelTest(name="test", value=123)
125
+ mtime = test_path.stat().st_mtime
126
+
127
+ # Set the model in the cache
128
+ model_cache.set_model(test_path, model, mtime)
129
+
130
+ # Benchmark the get_model method
131
+ def get_model():
132
+ return model_cache.get_model(test_path, ModelTest)
133
+
134
+ benchmark(get_model)
135
+ stats = benchmark.stats.stats
136
+
137
+ # 25k ops per second is the target. Getting 250k on Macbook, but CI will be slower
138
+ target = 1 / 25000
139
+ if stats.mean > target:
140
+ pytest.fail(
141
+ f"Average time per iteration: {stats.mean}, expected less than {target}"
142
+ )
143
+
144
+
145
+ def test_get_model_returns_copy(model_cache, test_path):
146
+ if not model_cache._enabled:
147
+ pytest.skip("Cache is disabled on this fs")
148
+
149
+ model = ModelTest(name="test", value=123)
150
+ mtime_ns = test_path.stat().st_mtime_ns
151
+
152
+ # Set the model in the cache
153
+ model_cache.set_model(test_path, model, mtime_ns)
154
+
155
+ # Get a copy of the model from the cache
156
+ cached_model = model_cache.get_model(test_path, ModelTest)
157
+
158
+ # Different instance (is), same data (==)
159
+ assert cached_model is not model
160
+ assert cached_model == model
161
+
162
+ # Mutate the cached model
163
+ cached_model.name = "mutated"
164
+
165
+ # Get the model again from the cache
166
+ new_cached_model = model_cache.get_model(test_path, ModelTest)
167
+
168
+ # Assert that the new cached model has the original values
169
+ assert new_cached_model == model
170
+ assert new_cached_model.name == "test"
171
+
172
+ # Save the mutated model back to the cache
173
+ model_cache.set_model(test_path, cached_model, mtime_ns)
174
+
175
+ # Get the model again from the cache
176
+ updated_cached_model = model_cache.get_model(test_path, ModelTest)
177
+
178
+ # Assert that the updated cached model has the mutated values
179
+ assert updated_cached_model.name == "mutated"
180
+ assert updated_cached_model.value == 123
181
+
182
+
183
+ def test_no_cache_when_no_fine_granularity(model_cache, test_path):
184
+ model = ModelTest(name="test", value=123)
185
+ mtime_ns = test_path.stat().st_mtime_ns
186
+
187
+ model_cache._enabled = False
188
+ model_cache.set_model(test_path, model, mtime_ns)
189
+ cached_model = model_cache.get_model(test_path, ModelTest)
190
+
191
+ # Assert that the model is not cached
192
+ assert cached_model is None
193
+ assert model_cache.model_cache == {}
194
+ assert model_cache._enabled is False
195
+
196
+
197
+ def test_check_timestamp_granularity_macos():
198
+ with mock.patch("sys.platform", "darwin"):
199
+ cache = ModelCache()
200
+ assert cache._check_timestamp_granularity() is True
201
+ assert cache._enabled is True
202
+
203
+
204
+ def test_check_timestamp_granularity_windows():
205
+ with mock.patch("sys.platform", "win32"):
206
+ cache = ModelCache()
207
+ assert cache._check_timestamp_granularity() is True
208
+ assert cache._enabled is True
209
+
210
+
211
+ def test_check_timestamp_granularity_linux_good():
212
+ mock_stats = mock.Mock()
213
+ mock_stats.f_timespec = 9 # nanosecond precision
214
+
215
+ with (
216
+ mock.patch("sys.platform", "linux"),
217
+ mock.patch("os.statvfs", return_value=mock_stats),
218
+ ):
219
+ cache = ModelCache()
220
+ assert cache._check_timestamp_granularity() is True
221
+ assert cache._enabled is True
222
+
223
+
224
+ def test_check_timestamp_granularity_linux_poor():
225
+ mock_stats = mock.Mock()
226
+ mock_stats.f_timespec = 3 # millisecond precision
227
+
228
+ with (
229
+ mock.patch("sys.platform", "linux"),
230
+ mock.patch("os.statvfs", return_value=mock_stats),
231
+ ):
232
+ cache = ModelCache()
233
+ assert cache._check_timestamp_granularity() is False
234
+ assert cache._enabled is False
235
+
236
+
237
+ def test_check_timestamp_granularity_linux_error():
238
+ with (
239
+ mock.patch("sys.platform", "linux"),
240
+ mock.patch("os.statvfs", side_effect=OSError("Mock filesystem error")),
241
+ ):
242
+ cache = ModelCache()
243
+ assert cache._check_timestamp_granularity() is False
244
+ assert cache._enabled is False
@@ -1,4 +1,6 @@
1
1
  import json
2
+ import os
3
+ from unittest.mock import patch
2
4
 
3
5
  import pytest
4
6
  from pydantic import ValidationError
@@ -82,6 +84,7 @@ def test_task_serialization(test_project_file):
82
84
  instruction="Test Base Task Instruction",
83
85
  thinking_instruction="Test Thinking Instruction",
84
86
  )
87
+ assert task._loaded_from_file is False
85
88
 
86
89
  task.save_to_file()
87
90
 
@@ -90,6 +93,11 @@ def test_task_serialization(test_project_file):
90
93
  assert parsed_task.description == "Test Description"
91
94
  assert parsed_task.instruction == "Test Base Task Instruction"
92
95
  assert parsed_task.thinking_instruction == "Test Thinking Instruction"
96
+ assert parsed_task._loaded_from_file is True
97
+
98
+ # Confirm the local property is not persisted to disk
99
+ json_data = json.loads(parsed_task.path.read_text())
100
+ assert "_loaded_from_file" not in json_data
93
101
 
94
102
 
95
103
  def test_save_to_file_without_path():
@@ -315,3 +323,119 @@ def test_finetune_parameters_validation():
315
323
  base_model_id="gpt-3.5-turbo",
316
324
  parameters={"invalid": [1, 2, 3]}, # Lists are not allowed
317
325
  )
326
+
327
+
328
+ def test_task_run_input_source_validation(tmp_path):
329
+ # Setup basic output for TaskRun creation
330
+ output = TaskOutput(
331
+ output="test output",
332
+ source=DataSource(
333
+ type=DataSourceType.synthetic,
334
+ properties={
335
+ "model_name": "test-model",
336
+ "model_provider": "test-provider",
337
+ "adapter_name": "test-adapter",
338
+ },
339
+ ),
340
+ )
341
+
342
+ project_path = tmp_path / "project.kiln"
343
+ project = Project(name="Test Project", path=project_path)
344
+ project.save_to_file()
345
+ task = Task(name="Test Task", instruction="Test Instruction", parent=project)
346
+ task.save_to_file()
347
+
348
+ # Test 1: Creating without input_source should work when strict mode is off
349
+ task_run = TaskRun(
350
+ input="test input",
351
+ output=output,
352
+ )
353
+ task_run.parent = task
354
+ assert task_run.input_source is None
355
+
356
+ # Save for later usage
357
+ task_run.save_to_file()
358
+ task_missing_input_source = task_run.path
359
+
360
+ # Test 2: Creating with input_source should work when strict mode is off
361
+ task_run = TaskRun(
362
+ input="test input 2",
363
+ input_source=DataSource(
364
+ type=DataSourceType.human,
365
+ properties={"created_by": "test-user"},
366
+ ),
367
+ output=output,
368
+ )
369
+ assert task_run.input_source is not None
370
+
371
+ # Test 3: Creating without input_source should fail when strict mode is on
372
+ with patch("kiln_ai.datamodel.strict_mode", return_value=True):
373
+ with pytest.raises(ValueError) as exc_info:
374
+ task_run = TaskRun(
375
+ input="test input 3",
376
+ output=output,
377
+ )
378
+ assert "input_source is required when strict mode is enabled" in str(
379
+ exc_info.value
380
+ )
381
+
382
+ # Test 4: Loading from disk should work without input_source, even with strict mode on
383
+ assert os.path.exists(task_missing_input_source)
384
+ task_run = TaskRun.load_from_file(task_missing_input_source)
385
+ assert task_run.input_source is None
386
+
387
+
388
+ def test_task_output_source_validation(tmp_path):
389
+ # Setup basic output source for validation
390
+ output_source = DataSource(
391
+ type=DataSourceType.synthetic,
392
+ properties={
393
+ "model_name": "test-model",
394
+ "model_provider": "test-provider",
395
+ "adapter_name": "test-adapter",
396
+ },
397
+ )
398
+
399
+ project_path = tmp_path / "project.kiln"
400
+ project = Project(name="Test Project", path=project_path)
401
+ project.save_to_file()
402
+ task = Task(name="Test Task", instruction="Test Instruction", parent=project)
403
+ task.save_to_file()
404
+
405
+ # Test 1: Creating without source should work when strict mode is off
406
+ task_output = TaskOutput(
407
+ output="test output",
408
+ )
409
+ assert task_output.source is None
410
+
411
+ # Save for later usage
412
+ task_run = TaskRun(
413
+ input="test input",
414
+ input_source=output_source,
415
+ output=task_output,
416
+ )
417
+ task_run.parent = task
418
+ task_run.save_to_file()
419
+ task_missing_output_source = task_run.path
420
+
421
+ # Test 2: Creating with source should work when strict mode is off
422
+ task_output = TaskOutput(
423
+ output="test output 2",
424
+ source=output_source,
425
+ )
426
+ assert task_output.source is not None
427
+
428
+ # Test 3: Creating without source should fail when strict mode is on
429
+ with patch("kiln_ai.datamodel.strict_mode", return_value=True):
430
+ with pytest.raises(ValueError) as exc_info:
431
+ task_output = TaskOutput(
432
+ output="test output 3",
433
+ )
434
+ assert "Output source is required when strict mode is enabled" in str(
435
+ exc_info.value
436
+ )
437
+
438
+ # Test 4: Loading from disk should work without source, even with strict mode on
439
+ assert os.path.exists(task_missing_output_source)
440
+ task_run = TaskRun.load_from_file(task_missing_output_source)
441
+ assert task_run.output.source is None
kiln_ai/utils/config.py CHANGED
@@ -80,6 +80,10 @@ class Config:
80
80
  list,
81
81
  default_lambda=lambda: [],
82
82
  ),
83
+ "custom_models": ConfigProperty(
84
+ list,
85
+ default_lambda=lambda: [],
86
+ ),
83
87
  }
84
88
  self._settings = self.load_settings()
85
89
 
@@ -145,7 +149,7 @@ class Config:
145
149
  settings = yaml.safe_load(f.read()) or {}
146
150
  return settings
147
151
 
148
- def settings(self, hide_sensitive=False):
152
+ def settings(self, hide_sensitive=False) -> Dict[str, Any]:
149
153
  if hide_sensitive:
150
154
  return {
151
155
  k: "[hidden]"
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.4
2
+ Name: kiln-ai
3
+ Version: 0.7.1
4
+ Summary: Kiln AI
5
+ Project-URL: Homepage, https://getkiln.ai
6
+ Project-URL: Repository, https://github.com/Kiln-AI/kiln
7
+ Project-URL: Documentation, https://kiln-ai.github.io/Kiln/kiln_core_docs/kiln_ai.html
8
+ Project-URL: Issues, https://github.com/Kiln-AI/kiln/issues
9
+ Author-email: "Steve Cosman, Chesterfield Laboratories Inc" <scosman@users.noreply.github.com>
10
+ License-File: LICENSE.txt
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: coverage>=7.6.4
18
+ Requires-Dist: jsonschema>=4.23.0
19
+ Requires-Dist: langchain-aws>=0.2.4
20
+ Requires-Dist: langchain-fireworks>=0.2.5
21
+ Requires-Dist: langchain-groq>=0.2.0
22
+ Requires-Dist: langchain-ollama>=0.2.0
23
+ Requires-Dist: langchain-openai>=0.2.4
24
+ Requires-Dist: langchain>=0.3.5
25
+ Requires-Dist: openai>=1.53.0
26
+ Requires-Dist: pdoc>=15.0.0
27
+ Requires-Dist: pydantic>=2.9.2
28
+ Requires-Dist: pytest-benchmark>=5.1.0
29
+ Requires-Dist: pytest-cov>=6.0.0
30
+ Requires-Dist: pyyaml>=6.0.2
31
+ Requires-Dist: typing-extensions>=4.12.2
32
+ Description-Content-Type: text/markdown
33
+
34
+ # Kiln AI Core Library
35
+
36
+ <p align="center">
37
+ <picture>
38
+ <img width="205" alt="Kiln AI Logo" src="https://github.com/user-attachments/assets/5fbcbdf7-1feb-45c9-bd73-99a46dd0a47f">
39
+ </picture>
40
+ </p>
41
+
42
+ [![PyPI - Version](https://img.shields.io/pypi/v/kiln-ai.svg?logo=pypi&label=PyPI&logoColor=gold)](https://pypi.org/project/kiln-ai)
43
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/kiln-ai.svg)](https://pypi.org/project/kiln-ai)
44
+ [![Docs](https://img.shields.io/badge/docs-pdoc-blue)](https://kiln-ai.github.io/Kiln/kiln_core_docs/index.html)
45
+
46
+ ---
47
+
48
+ ## Installation
49
+
50
+ ```console
51
+ pip install kiln_ai
52
+ ```
53
+
54
+ ## About
55
+
56
+ This package is the Kiln AI core library. There is also a separate desktop application and server package. Learn more about Kiln AI at [getkiln.ai](https://getkiln.ai) and on Github: [github.com/Kiln-AI/kiln](https://github.com/Kiln-AI/kiln).
57
+
58
+ # Guide: Using the Kiln Python Library
59
+
60
+ In this guide we'll walk common examples of how to use the library.
61
+
62
+ ## Documentation
63
+
64
+ The library has a [comprehensive set of docs](https://kiln-ai.github.io/Kiln/kiln_core_docs/index.html).
65
+
66
+ ## Table of Contents
67
+
68
+ - [Using the Kiln Data Model](#using-the-kiln-data-model)
69
+ - [Understanding the Kiln Data Model](#understanding-the-kiln-data-model)
70
+ - [Datamodel Overview](#datamodel-overview)
71
+ - [Load a Project](#load-a-project)
72
+ - [Load an Existing Dataset into a Kiln Task Dataset](#load-an-existing-dataset-into-a-kiln-task-dataset)
73
+ - [Using your Kiln Dataset in a Notebook or Project](#using-your-kiln-dataset-in-a-notebook-or-project)
74
+ - [Using Kiln Dataset in Pandas](#using-kiln-dataset-in-pandas)
75
+ - [Advanced Usage](#advanced-usage)
76
+
77
+ ## Installation
78
+
79
+ ```bash
80
+ pip install kiln-ai
81
+ ```
82
+
83
+ ## Using the Kiln Data Model
84
+
85
+ ### Understanding the Kiln Data Model
86
+
87
+ Kiln projects are simply a directory of files (mostly JSON files with the extension `.kiln`) that describe your project, including tasks, runs, and other data.
88
+
89
+ This dataset design was chosen for several reasons:
90
+
91
+ - Git compatibility: Kiln project folders are easy to collaborate on in git. The filenames use unique IDs to avoid conflicts and allow many people to work in parallel. The files are small and easy to compare using standard diff tools.
92
+ - JSON allows you to easily load and manipulate the data using standard tools (pandas, polars, etc)
93
+
94
+ The Kiln Python library provides a set of Python classes that which help you easily interact with your Kiln dataset. Using the library to load and manipulate your dataset is the fastest way to get started, and will guarantees you don't insert any invalid data into your dataset. There's extensive validation when using the library, so we recommend using it to load and manipulate your dataset over direct JSON manipulation.
95
+
96
+ ### Datamodel Overview
97
+
98
+ - Project: a Kiln Project that organizes related tasks
99
+ - Task: a specific task including prompt instructions, input/output schemas, and requirements
100
+ - TaskRun: a sample (run) of a task including input, output and human rating information
101
+ - DatasetSplit: a frozen collection of task runs divided into train/test/validation splits
102
+ - Finetune: configuration and status tracking for fine-tuning models on task data
103
+
104
+ ### Load a Project
105
+
106
+ Assuming you've created a project in the Kiln UI, you'll have a `project.kiln` file in your `~/Kiln Projects/Project Name` directory.
107
+
108
+ ```python
109
+ from kiln_ai.datamodel import Project
110
+
111
+ project = Project.load_from_file("path/to/your/project.kiln")
112
+ print("Project: ", project.name, " - ", project.description)
113
+
114
+ # List all tasks in the project, and their dataset sizes
115
+ tasks = project.tasks()
116
+ for task in tasks:
117
+ print("Task: ", task.name, " - ", task.description)
118
+ print("Total dataset size:", len(task.runs()))
119
+ ```
120
+
121
+ ### Load an Existing Dataset into a Kiln Task Dataset
122
+
123
+ If you already have a dataset in a file, you can load it into a Kiln project.
124
+
125
+ **Important**: Kiln will validate the input and output schemas, and ensure that each datapoint in the dataset is valid for this task.
126
+
127
+ - Plaintext input/output: ensure "output_json_schema" and "input_json_schema" not set in your Task definition.
128
+ - JSON input/output: ensure "output_json_schema" and "input_json_schema" are valid JSON schemas in your Task definition. Every datapoint in the dataset must be valid JSON fitting the schema.
129
+
130
+ Here's a simple example of how to load a dataset into a Kiln task:
131
+
132
+ ```python
133
+
134
+ import kiln_ai
135
+ import kiln_ai.datamodel
136
+
137
+ # Created a project and task via the UI and put its path here
138
+ task_path = "/Users/youruser/Kiln Projects/test project/tasks/632780983478 - Joke Generator/task.kiln"
139
+ task = kiln_ai.datamodel.Task.load_from_file(task_path)
140
+
141
+ # Add data to the task - loop over you dataset and run this for each item
142
+ item = kiln_ai.datamodel.TaskRun(
143
+ parent=task,
144
+ input='{"topic": "AI"}',
145
+ output=kiln_ai.datamodel.TaskOutput(
146
+ output='{"setup": "What is AI?", "punchline": "content_here"}',
147
+ ),
148
+ )
149
+ item.save_to_file()
150
+ print("Saved item to file: ", item.path)
151
+ ```
152
+
153
+ And here's a more complex example of how to load a dataset into a Kiln task. This example sets the source of the data (human in this case, but you can also set it be be synthetic), the created_by property, and a 5-star rating.
154
+
155
+ ```python
156
+ import kiln_ai
157
+ import kiln_ai.datamodel
158
+
159
+ # Created a project and task via the UI and put its path here
160
+ task_path = "/Users/youruser/Kiln Projects/test project/tasks/632780983478 - Joke Generator/task.kiln"
161
+ task = kiln_ai.datamodel.Task.load_from_file(task_path)
162
+
163
+ # Add data to the task - loop over you dataset and run this for each item
164
+ item = kiln_ai.datamodel.TaskRun(
165
+ parent=task,
166
+ input='{"topic": "AI"}',
167
+ input_source=kiln_ai.datamodel.DataSource(
168
+ type=kiln_ai.datamodel.DataSourceType.human,
169
+ properties={"created_by": "John Doe"},
170
+ ),
171
+ output=kiln_ai.datamodel.TaskOutput(
172
+ output='{"setup": "What is AI?", "punchline": "content_here"}',
173
+ source=kiln_ai.datamodel.DataSource(
174
+ type=kiln_ai.datamodel.DataSourceType.human,
175
+ properties={"created_by": "Jane Doe"},
176
+ ),
177
+ rating=kiln_ai.datamodel.TaskOutputRating(score=5,type="five_star"),
178
+ ),
179
+ )
180
+ item.save_to_file()
181
+ print("Saved item to file: ", item.path)
182
+ ```
183
+
184
+ ### Using your Kiln Dataset in a Notebook or Project
185
+
186
+ You can use your Kiln dataset in a notebook or project by loading the dataset into a pandas dataframe.
187
+
188
+ ```python
189
+ import kiln_ai
190
+ import kiln_ai.datamodel
191
+
192
+ # Created a project and task via the UI and put its path here
193
+ task_path = "/Users/youruser/Kiln Projects/test project/tasks/632780983478 - Joke Generator/task.kiln"
194
+ task = kiln_ai.datamodel.Task.load_from_file(task_path)
195
+
196
+ runs = task.runs()
197
+ for run in runs:
198
+ print(f"Input: {run.input}")
199
+ print(f"Output: {run.output.output}")
200
+
201
+ print(f"Total runs: {len(runs)}")
202
+ ```
203
+
204
+ ### Using Kiln Dataset in Pandas
205
+
206
+ You can also use your Kiln dataset in a pandas dataframe, or a similar script for other tools like polars.
207
+
208
+ ```python
209
+ import glob
210
+ import json
211
+ import pandas as pd
212
+ from pathlib import Path
213
+
214
+ task_dir = "/Users/youruser/Kiln Projects/test project/tasks/632780983478 - Joke Generator"
215
+ dataitem_glob = task_dir + "/runs/*/task_run.kiln"
216
+
217
+ dfs = []
218
+ for file in glob.glob(dataitem_glob):
219
+ js = json.loads(Path(file).read_text())
220
+
221
+ df = pd.DataFrame([{
222
+ "input": js["input"],
223
+ "output": js["output"]["output"],
224
+ }])
225
+
226
+ # Alternatively: you can use pd.json_normalize(js) to get the full json structure
227
+ # df = pd.json_normalize(js)
228
+ dfs.append(df)
229
+ final_df = pd.concat(dfs, ignore_index=True)
230
+ print(final_df)
231
+ ```
232
+
233
+ ### Advanced Usage
234
+
235
+ The library can do a lot more than the examples we've shown here.
236
+
237
+ See the [docs](https://kiln-ai.github.io/Kiln/kiln_core_docs/index.html) for more information.