kiln-ai 0.11.1__py3-none-any.whl → 0.13.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.

Potentially problematic release.


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

Files changed (80) hide show
  1. kiln_ai/adapters/__init__.py +4 -0
  2. kiln_ai/adapters/adapter_registry.py +163 -39
  3. kiln_ai/adapters/data_gen/data_gen_task.py +18 -0
  4. kiln_ai/adapters/eval/__init__.py +28 -0
  5. kiln_ai/adapters/eval/base_eval.py +164 -0
  6. kiln_ai/adapters/eval/eval_runner.py +270 -0
  7. kiln_ai/adapters/eval/g_eval.py +368 -0
  8. kiln_ai/adapters/eval/registry.py +16 -0
  9. kiln_ai/adapters/eval/test_base_eval.py +325 -0
  10. kiln_ai/adapters/eval/test_eval_runner.py +641 -0
  11. kiln_ai/adapters/eval/test_g_eval.py +498 -0
  12. kiln_ai/adapters/eval/test_g_eval_data.py +4 -0
  13. kiln_ai/adapters/fine_tune/base_finetune.py +16 -2
  14. kiln_ai/adapters/fine_tune/finetune_registry.py +2 -0
  15. kiln_ai/adapters/fine_tune/test_dataset_formatter.py +4 -1
  16. kiln_ai/adapters/fine_tune/test_fireworks_tinetune.py +1 -1
  17. kiln_ai/adapters/fine_tune/test_openai_finetune.py +1 -1
  18. kiln_ai/adapters/fine_tune/test_together_finetune.py +531 -0
  19. kiln_ai/adapters/fine_tune/together_finetune.py +325 -0
  20. kiln_ai/adapters/ml_model_list.py +758 -163
  21. kiln_ai/adapters/model_adapters/__init__.py +2 -4
  22. kiln_ai/adapters/model_adapters/base_adapter.py +61 -43
  23. kiln_ai/adapters/model_adapters/litellm_adapter.py +391 -0
  24. kiln_ai/adapters/model_adapters/litellm_config.py +13 -0
  25. kiln_ai/adapters/model_adapters/test_base_adapter.py +22 -13
  26. kiln_ai/adapters/model_adapters/test_litellm_adapter.py +407 -0
  27. kiln_ai/adapters/model_adapters/test_saving_adapter_results.py +41 -19
  28. kiln_ai/adapters/model_adapters/test_structured_output.py +59 -35
  29. kiln_ai/adapters/ollama_tools.py +3 -3
  30. kiln_ai/adapters/parsers/r1_parser.py +19 -14
  31. kiln_ai/adapters/parsers/test_r1_parser.py +17 -5
  32. kiln_ai/adapters/prompt_builders.py +80 -42
  33. kiln_ai/adapters/provider_tools.py +50 -58
  34. kiln_ai/adapters/repair/repair_task.py +9 -21
  35. kiln_ai/adapters/repair/test_repair_task.py +6 -6
  36. kiln_ai/adapters/run_output.py +3 -0
  37. kiln_ai/adapters/test_adapter_registry.py +26 -29
  38. kiln_ai/adapters/test_generate_docs.py +4 -4
  39. kiln_ai/adapters/test_ollama_tools.py +0 -1
  40. kiln_ai/adapters/test_prompt_adaptors.py +47 -33
  41. kiln_ai/adapters/test_prompt_builders.py +91 -31
  42. kiln_ai/adapters/test_provider_tools.py +26 -81
  43. kiln_ai/datamodel/__init__.py +50 -952
  44. kiln_ai/datamodel/basemodel.py +2 -0
  45. kiln_ai/datamodel/datamodel_enums.py +60 -0
  46. kiln_ai/datamodel/dataset_filters.py +114 -0
  47. kiln_ai/datamodel/dataset_split.py +170 -0
  48. kiln_ai/datamodel/eval.py +298 -0
  49. kiln_ai/datamodel/finetune.py +105 -0
  50. kiln_ai/datamodel/json_schema.py +7 -1
  51. kiln_ai/datamodel/project.py +23 -0
  52. kiln_ai/datamodel/prompt.py +37 -0
  53. kiln_ai/datamodel/prompt_id.py +83 -0
  54. kiln_ai/datamodel/strict_mode.py +24 -0
  55. kiln_ai/datamodel/task.py +181 -0
  56. kiln_ai/datamodel/task_output.py +328 -0
  57. kiln_ai/datamodel/task_run.py +164 -0
  58. kiln_ai/datamodel/test_basemodel.py +19 -11
  59. kiln_ai/datamodel/test_dataset_filters.py +71 -0
  60. kiln_ai/datamodel/test_dataset_split.py +32 -8
  61. kiln_ai/datamodel/test_datasource.py +22 -2
  62. kiln_ai/datamodel/test_eval_model.py +635 -0
  63. kiln_ai/datamodel/test_example_models.py +9 -13
  64. kiln_ai/datamodel/test_json_schema.py +23 -0
  65. kiln_ai/datamodel/test_models.py +2 -2
  66. kiln_ai/datamodel/test_prompt_id.py +129 -0
  67. kiln_ai/datamodel/test_task.py +159 -0
  68. kiln_ai/utils/config.py +43 -1
  69. kiln_ai/utils/dataset_import.py +232 -0
  70. kiln_ai/utils/test_dataset_import.py +596 -0
  71. {kiln_ai-0.11.1.dist-info → kiln_ai-0.13.0.dist-info}/METADATA +86 -6
  72. kiln_ai-0.13.0.dist-info/RECORD +103 -0
  73. kiln_ai/adapters/model_adapters/langchain_adapters.py +0 -302
  74. kiln_ai/adapters/model_adapters/openai_compatible_config.py +0 -11
  75. kiln_ai/adapters/model_adapters/openai_model_adapter.py +0 -246
  76. kiln_ai/adapters/model_adapters/test_langchain_adapter.py +0 -350
  77. kiln_ai/adapters/model_adapters/test_openai_model_adapter.py +0 -225
  78. kiln_ai-0.11.1.dist-info/RECORD +0 -76
  79. {kiln_ai-0.11.1.dist-info → kiln_ai-0.13.0.dist-info}/WHEEL +0 -0
  80. {kiln_ai-0.11.1.dist-info → kiln_ai-0.13.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,105 @@
1
+ from typing import TYPE_CHECKING, Dict, Union
2
+
3
+ from pydantic import Field, model_validator
4
+ from typing_extensions import Self
5
+
6
+ from kiln_ai.datamodel.basemodel import NAME_FIELD, KilnParentedModel
7
+ from kiln_ai.datamodel.datamodel_enums import (
8
+ FinetuneDataStrategy,
9
+ FineTuneStatusType,
10
+ StructuredOutputMode,
11
+ )
12
+
13
+ if TYPE_CHECKING:
14
+ from kiln_ai.datamodel.task import Task
15
+
16
+
17
+ class Finetune(KilnParentedModel):
18
+ """
19
+ The Kiln fine-tune datamodel.
20
+
21
+ Initially holds a reference to a training job, with needed identifiers to update the status. When complete, contains the new model ID.
22
+ """
23
+
24
+ name: str = NAME_FIELD
25
+ description: str | None = Field(
26
+ default=None,
27
+ description="A description of the fine-tune for you and your team. Not used in training.",
28
+ )
29
+ structured_output_mode: StructuredOutputMode | None = Field(
30
+ default=None,
31
+ description="The mode to use to train the model for structured output, if it was trained with structured output. Will determine how we call the tuned model, so we call with the matching mode.",
32
+ )
33
+ provider: str = Field(
34
+ description="The provider to use for the fine-tune (e.g. 'openai')."
35
+ )
36
+ base_model_id: str = Field(
37
+ description="The id of the base model to use for the fine-tune. This string relates to the provider's IDs for their own models, not Kiln IDs."
38
+ )
39
+ provider_id: str | None = Field(
40
+ default=None,
41
+ description="The ID of the fine-tune job on the provider's side. May not be the same as the fine_tune_model_id.",
42
+ )
43
+ fine_tune_model_id: str | None = Field(
44
+ default=None,
45
+ description="The ID of the fine-tuned model on the provider's side. May not be the same as the provider_id.",
46
+ )
47
+ dataset_split_id: str = Field(
48
+ description="The ID of the dataset split to use for this fine-tune.",
49
+ )
50
+ train_split_name: str = Field(
51
+ default="train",
52
+ description="The name of the training split to use for this fine-tune.",
53
+ )
54
+ validation_split_name: str | None = Field(
55
+ default=None,
56
+ description="The name of the validation split to use for this fine-tune. Optional.",
57
+ )
58
+ parameters: dict[str, str | int | float | bool] = Field(
59
+ default={},
60
+ description="The parameters to use for this fine-tune. These are provider-specific.",
61
+ )
62
+ # These two fields are saved exactly used for training. Even if they map exactly to a custom prompt or generator, those can change, so we want to keep a record of the training prompt.
63
+ system_message: str = Field(
64
+ description="The system message to use for this fine-tune.",
65
+ )
66
+ thinking_instructions: str | None = Field(
67
+ default=None,
68
+ description="The thinking instructions to use for this fine-tune. Only used when data_strategy is final_and_intermediate.",
69
+ )
70
+ latest_status: FineTuneStatusType = Field(
71
+ default=FineTuneStatusType.unknown,
72
+ description="The latest known status of this fine-tune. Not updated in real time.",
73
+ )
74
+ properties: Dict[str, str | int | float] = Field(
75
+ default={},
76
+ description="Properties of the fine-tune. Different providers may use different properties.",
77
+ )
78
+ data_strategy: FinetuneDataStrategy = Field(
79
+ default=FinetuneDataStrategy.final_only,
80
+ description="The strategy to use for training the model. 'final_only' will only train on the final response. 'final_and_intermediate' will train on the final response and intermediate outputs (chain of thought or reasoning).",
81
+ )
82
+
83
+ # Workaround to return typed parent without importing Task
84
+ def parent_task(self) -> Union["Task", None]:
85
+ if self.parent is None or self.parent.__class__.__name__ != "Task":
86
+ return None
87
+ return self.parent # type: ignore
88
+
89
+ @model_validator(mode="after")
90
+ def validate_thinking_instructions(self) -> Self:
91
+ if (
92
+ self.thinking_instructions is not None
93
+ and self.data_strategy != FinetuneDataStrategy.final_and_intermediate
94
+ ):
95
+ raise ValueError(
96
+ "Thinking instructions can only be used when data_strategy is final_and_intermediate"
97
+ )
98
+ if (
99
+ self.thinking_instructions is None
100
+ and self.data_strategy == FinetuneDataStrategy.final_and_intermediate
101
+ ):
102
+ raise ValueError(
103
+ "Thinking instructions are required when data_strategy is final_and_intermediate"
104
+ )
105
+ return self
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import re
2
3
  from typing import Annotated, Dict
3
4
 
4
5
  import jsonschema
@@ -48,7 +49,7 @@ def validate_schema(instance: Dict, schema_str: str) -> None:
48
49
  v.validate(instance)
49
50
  except jsonschema.exceptions.ValidationError as e:
50
51
  raise ValueError(
51
- f"This task requires a specific output schema. While the model produced JSON, that JSON didn't meet the schema. Search 'Troubleshooting Structured Data Issues' in our docs for more information. The error from the schema check was: {e.message}"
52
+ f"This task requires a specific output schema. While the model produced JSON, that JSON didn't meet the schema. Search 'Troubleshooting Structured Data Issues' in our docs for more information. The error from the schema check was: {e.message}. The JSON was: \n```json\n{instance}\n```"
52
53
  ) from e
53
54
 
54
55
 
@@ -83,3 +84,8 @@ def schema_from_json_str(v: str) -> Dict:
83
84
  raise ValueError(f"Invalid JSON: {v}\n {e}")
84
85
  except Exception as e:
85
86
  raise ValueError(f"Unexpected error parsing JSON schema: {v}\n {e}")
87
+
88
+
89
+ def string_to_json_key(s: str) -> str:
90
+ """Convert a string to a valid JSON key."""
91
+ return re.sub(r"[^a-z0-9_]", "", s.strip().lower().replace(" ", "_"))
@@ -0,0 +1,23 @@
1
+ from pydantic import Field
2
+
3
+ from kiln_ai.datamodel.basemodel import NAME_FIELD, KilnParentModel
4
+ from kiln_ai.datamodel.task import Task
5
+
6
+
7
+ class Project(KilnParentModel, parent_of={"tasks": Task}):
8
+ """
9
+ A collection of related tasks.
10
+
11
+ Projects organize tasks into logical groups and provide high-level descriptions
12
+ of the overall goals.
13
+ """
14
+
15
+ name: str = NAME_FIELD
16
+ description: str | None = Field(
17
+ default=None,
18
+ description="A description of the project for you and your team. Will not be used in prompts/training/validation.",
19
+ )
20
+
21
+ # Needed for typechecking. TODO P2: fix this in KilnParentModel
22
+ def tasks(self) -> list[Task]:
23
+ return super().tasks() # type: ignore
@@ -0,0 +1,37 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+ from kiln_ai.datamodel.basemodel import NAME_FIELD, KilnParentedModel
4
+
5
+
6
+ class BasePrompt(BaseModel):
7
+ """
8
+ A prompt for a task. This is the basic data storage format which can be used throughout a project.
9
+
10
+ The "Prompt" model name is reserved for the custom prompts parented by a task.
11
+ """
12
+
13
+ name: str = NAME_FIELD
14
+ description: str | None = Field(
15
+ default=None,
16
+ description="A more detailed description of the prompt.",
17
+ )
18
+ generator_id: str | None = Field(
19
+ default=None,
20
+ description="The id of the generator that created this prompt.",
21
+ )
22
+ prompt: str = Field(
23
+ description="The prompt for the task.",
24
+ min_length=1,
25
+ )
26
+ chain_of_thought_instructions: str | None = Field(
27
+ default=None,
28
+ description="Instructions for the model 'thinking' about the requirement prior to answering. Used for chain of thought style prompting. COT will not be used unless this is provided.",
29
+ )
30
+
31
+
32
+ class Prompt(KilnParentedModel, BasePrompt):
33
+ """
34
+ A prompt for a task. This is the custom prompt parented by a task.
35
+ """
36
+
37
+ pass
@@ -0,0 +1,83 @@
1
+ from enum import Enum
2
+ from typing import Annotated
3
+
4
+ from pydantic import AfterValidator
5
+
6
+
7
+ # Generators that can take any task and build a prompt
8
+ class PromptGenerators(str, Enum):
9
+ SIMPLE = "simple_prompt_builder"
10
+ MULTI_SHOT = "multi_shot_prompt_builder"
11
+ FEW_SHOT = "few_shot_prompt_builder"
12
+ REPAIRS = "repairs_prompt_builder"
13
+ SIMPLE_CHAIN_OF_THOUGHT = "simple_chain_of_thought_prompt_builder"
14
+ FEW_SHOT_CHAIN_OF_THOUGHT = "few_shot_chain_of_thought_prompt_builder"
15
+ MULTI_SHOT_CHAIN_OF_THOUGHT = "multi_shot_chain_of_thought_prompt_builder"
16
+
17
+
18
+ prompt_generator_values = [pg.value for pg in PromptGenerators]
19
+
20
+
21
+ PromptId = Annotated[
22
+ str,
23
+ AfterValidator(lambda v: _check_prompt_id(v)),
24
+ ]
25
+ """
26
+ A pydantic type that validates strings containing a valid prompt ID.
27
+
28
+ Prompt IDs can be one of:
29
+ - A saved prompt ID
30
+ - A fine-tune prompt ID
31
+ - A task run config ID
32
+ - A prompt generator name
33
+ """
34
+
35
+
36
+ def _check_prompt_id(id: str) -> str:
37
+ """
38
+ Check that the prompt ID is valid.
39
+ """
40
+ if id in prompt_generator_values:
41
+ return id
42
+
43
+ if id.startswith("id::"):
44
+ # check it has 4 parts divided by :: -- 'id::project_id::task_id::prompt_id'
45
+ parts = id.split("::")
46
+ if len(parts) != 2 or len(parts[1]) == 0:
47
+ raise ValueError(
48
+ f"Invalid saved prompt ID: {id}. Expected format: 'id::[prompt_id]'."
49
+ )
50
+ return id
51
+
52
+ if id.startswith("task_run_config::"):
53
+ # check it had a eval_id after the :: -- 'project_id::task_id::task_run_config_id'
54
+ parts = id.split("::")
55
+ if len(parts) != 4:
56
+ raise ValueError(
57
+ f"Invalid task run config prompt ID: {id}. Expected format: 'task_run_config::[project_id]::[task_id]::[task_run_config_id]'."
58
+ )
59
+ return id
60
+
61
+ if id.startswith("fine_tune_prompt::"):
62
+ # check it had a fine_tune_id after the :: -- 'fine_tune_prompt::fine_tune_id'
63
+ fine_tune_id = id[18:]
64
+ if len(fine_tune_id) == 0:
65
+ raise ValueError(
66
+ f"Invalid fine-tune prompt ID: {id}. Expected format: 'fine_tune_prompt::[fine_tune_id]'."
67
+ )
68
+ return id
69
+
70
+ raise ValueError(f"Invalid prompt ID: {id}")
71
+
72
+
73
+ def is_frozen_prompt(id: PromptId) -> bool:
74
+ """
75
+ Check if the prompt ID is a frozen prompt.
76
+ """
77
+ if id.startswith("id::"):
78
+ return True
79
+ if id.startswith("task_run_config::"):
80
+ return True
81
+ if id.startswith("fine_tune_prompt::"):
82
+ return True
83
+ return False
@@ -0,0 +1,24 @@
1
+ """
2
+ Strict mode is a feature that enables extra validations that we want to enforce in Kiln App, ensuring everything follows the ideal schema.
3
+
4
+ It's off by default when used through the library. Enable it by calling `set_strict_mode(True)`.
5
+ """
6
+
7
+ # We want to be hard on ourselves for data completeness generated by the Kiln App, but don't want to make it hard for users to use the datamodel/library.
8
+ # Strict mode enables extra validations that we want to enforce in Kiln App (and any other client that wants best practices), but not in the library (unless they opt in)
9
+ _strict_mode: bool = False
10
+
11
+
12
+ def strict_mode() -> bool:
13
+ """
14
+ Get the current strict mode setting.
15
+ """
16
+ return _strict_mode
17
+
18
+
19
+ def set_strict_mode(value: bool) -> None:
20
+ """
21
+ Set the strict mode setting.
22
+ """
23
+ global _strict_mode
24
+ _strict_mode = value
@@ -0,0 +1,181 @@
1
+ from typing import TYPE_CHECKING, Dict, List, Union
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from kiln_ai.datamodel import Finetune
6
+ from kiln_ai.datamodel.basemodel import (
7
+ ID_FIELD,
8
+ ID_TYPE,
9
+ NAME_FIELD,
10
+ SHORT_NAME_FIELD,
11
+ KilnParentedModel,
12
+ KilnParentModel,
13
+ )
14
+ from kiln_ai.datamodel.datamodel_enums import Priority, TaskOutputRatingType
15
+ from kiln_ai.datamodel.dataset_split import DatasetSplit
16
+ from kiln_ai.datamodel.eval import Eval
17
+ from kiln_ai.datamodel.json_schema import JsonObjectSchema, schema_from_json_str
18
+ from kiln_ai.datamodel.prompt import BasePrompt, Prompt
19
+ from kiln_ai.datamodel.prompt_id import PromptId
20
+ from kiln_ai.datamodel.task_run import TaskRun
21
+
22
+ if TYPE_CHECKING:
23
+ from kiln_ai.datamodel.project import Project
24
+
25
+
26
+ class TaskRequirement(BaseModel):
27
+ """
28
+ Defines a specific requirement that should be met by task outputs.
29
+
30
+ Includes an identifier, name, description, instruction for meeting the requirement,
31
+ priority level, and rating type (five_star, pass_fail, pass_fail_critical, custom).
32
+ """
33
+
34
+ id: ID_TYPE = ID_FIELD
35
+ name: str = SHORT_NAME_FIELD
36
+ description: str | None = Field(default=None)
37
+ instruction: str = Field(min_length=1)
38
+ priority: Priority = Field(default=Priority.p2)
39
+ type: TaskOutputRatingType = Field(default=TaskOutputRatingType.five_star)
40
+
41
+
42
+ class RunConfigProperties(BaseModel):
43
+ """
44
+ A configuration for running a task.
45
+
46
+ This includes everything needed to run a task, except the input and task ID. Running the same RunConfig with the same input should make identical calls to the model (output may vary as models are non-deterministic).
47
+ """
48
+
49
+ model_name: str = Field(description="The model to use for this run config.")
50
+ model_provider_name: str = Field(
51
+ description="The provider to use for this run config."
52
+ )
53
+ prompt_id: PromptId = Field(
54
+ description="The prompt to use for this run config. Defaults to building a simple prompt from the task if not provided.",
55
+ )
56
+
57
+
58
+ class RunConfig(RunConfigProperties):
59
+ """
60
+ A configuration for running a task.
61
+
62
+ This includes everything needed to run a task, except the input. Running the same RunConfig with the same input should make identical calls to the model (output may vary as models are non-deterministic).
63
+
64
+ For example: task, model, provider, prompt, etc.
65
+ """
66
+
67
+ task: "Task" = Field(description="The task to run.")
68
+
69
+
70
+ class TaskRunConfig(KilnParentedModel):
71
+ """
72
+ A Kiln model for persisting a run config in a Kiln Project, nested under a task.
73
+
74
+ Typically used to save a method of running a task for evaluation.
75
+
76
+ A run config includes everything needed to run a task, except the input. Running the same RunConfig with the same input should make identical calls to the model (output may vary as models are non-deterministic).
77
+ """
78
+
79
+ name: str = NAME_FIELD
80
+ description: str | None = Field(
81
+ default=None, description="The description of the task run config."
82
+ )
83
+ run_config_properties: RunConfigProperties = Field(
84
+ description="The run config properties to use for this task run."
85
+ )
86
+ # The prompt_id in the run_config_properties is the prompt ID to use for this task run.
87
+ # However, we want the prompt to be perfectly consistent, and some prompt_ids are dynamic.
88
+ # If we need to "freeze" a prompt, we can do so here (then point the prompt_id to this frozen prompt).
89
+ prompt: BasePrompt | None = Field(
90
+ default=None,
91
+ description="A prompt to use for run config.",
92
+ )
93
+
94
+ # Workaround to return typed parent without importing Task
95
+ def parent_task(self) -> Union["Task", None]:
96
+ if self.parent is None or self.parent.__class__.__name__ != "Task":
97
+ return None
98
+ return self.parent # type: ignore
99
+
100
+ def run_config(self) -> RunConfig:
101
+ parent_task = self.parent_task()
102
+ if parent_task is None:
103
+ raise ValueError("Run config must be parented to a task")
104
+ return RunConfig(
105
+ task=parent_task,
106
+ model_name=self.run_config_properties.model_name,
107
+ model_provider_name=self.run_config_properties.model_provider_name,
108
+ prompt_id=self.run_config_properties.prompt_id,
109
+ )
110
+
111
+
112
+ class Task(
113
+ KilnParentedModel,
114
+ KilnParentModel,
115
+ parent_of={
116
+ "runs": TaskRun,
117
+ "dataset_splits": DatasetSplit,
118
+ "finetunes": Finetune,
119
+ "prompts": Prompt,
120
+ "evals": Eval,
121
+ "run_configs": TaskRunConfig,
122
+ },
123
+ ):
124
+ """
125
+ Represents a specific task to be performed, with associated requirements and validation rules.
126
+
127
+ Contains the task definition, requirements, input/output schemas, and maintains
128
+ a collection of task runs.
129
+ """
130
+
131
+ name: str = NAME_FIELD
132
+ description: str | None = Field(
133
+ default=None,
134
+ description="A description of the task for you and your team. Will not be used in prompts/training/validation.",
135
+ )
136
+ instruction: str = Field(
137
+ min_length=1,
138
+ description="The instructions for the task. Will be used in prompts/training/validation.",
139
+ )
140
+ requirements: List[TaskRequirement] = Field(default=[])
141
+ output_json_schema: JsonObjectSchema | None = None
142
+ input_json_schema: JsonObjectSchema | None = None
143
+ thinking_instruction: str | None = Field(
144
+ default=None,
145
+ description="Instructions for the model 'thinking' about the requirement prior to answering. Used for chain of thought style prompting.",
146
+ )
147
+
148
+ def output_schema(self) -> Dict | None:
149
+ if self.output_json_schema is None:
150
+ return None
151
+ return schema_from_json_str(self.output_json_schema)
152
+
153
+ def input_schema(self) -> Dict | None:
154
+ if self.input_json_schema is None:
155
+ return None
156
+ return schema_from_json_str(self.input_json_schema)
157
+
158
+ # These wrappers help for typechecking. TODO P2: fix this in KilnParentModel
159
+ def runs(self, readonly: bool = False) -> list[TaskRun]:
160
+ return super().runs(readonly=readonly) # type: ignore
161
+
162
+ def dataset_splits(self, readonly: bool = False) -> list[DatasetSplit]:
163
+ return super().dataset_splits(readonly=readonly) # type: ignore
164
+
165
+ def finetunes(self, readonly: bool = False) -> list[Finetune]:
166
+ return super().finetunes(readonly=readonly) # type: ignore
167
+
168
+ def prompts(self, readonly: bool = False) -> list[Prompt]:
169
+ return super().prompts(readonly=readonly) # type: ignore
170
+
171
+ def evals(self, readonly: bool = False) -> list[Eval]:
172
+ return super().evals(readonly=readonly) # type: ignore
173
+
174
+ def run_configs(self, readonly: bool = False) -> list[TaskRunConfig]:
175
+ return super().run_configs(readonly=readonly) # type: ignore
176
+
177
+ # Workaround to return typed parent without importing Task
178
+ def parent_project(self) -> Union["Project", None]:
179
+ if self.parent is None or self.parent.__class__.__name__ != "Project":
180
+ return None
181
+ return self.parent # type: ignore