kiln-ai 0.19.0__py3-none-any.whl → 0.21.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.
- kiln_ai/adapters/__init__.py +8 -2
- kiln_ai/adapters/adapter_registry.py +43 -208
- kiln_ai/adapters/chat/chat_formatter.py +8 -12
- kiln_ai/adapters/chat/test_chat_formatter.py +6 -2
- kiln_ai/adapters/chunkers/__init__.py +13 -0
- kiln_ai/adapters/chunkers/base_chunker.py +42 -0
- kiln_ai/adapters/chunkers/chunker_registry.py +16 -0
- kiln_ai/adapters/chunkers/fixed_window_chunker.py +39 -0
- kiln_ai/adapters/chunkers/helpers.py +23 -0
- kiln_ai/adapters/chunkers/test_base_chunker.py +63 -0
- kiln_ai/adapters/chunkers/test_chunker_registry.py +28 -0
- kiln_ai/adapters/chunkers/test_fixed_window_chunker.py +346 -0
- kiln_ai/adapters/chunkers/test_helpers.py +75 -0
- kiln_ai/adapters/data_gen/test_data_gen_task.py +9 -3
- kiln_ai/adapters/docker_model_runner_tools.py +119 -0
- kiln_ai/adapters/embedding/__init__.py +0 -0
- kiln_ai/adapters/embedding/base_embedding_adapter.py +44 -0
- kiln_ai/adapters/embedding/embedding_registry.py +32 -0
- kiln_ai/adapters/embedding/litellm_embedding_adapter.py +199 -0
- kiln_ai/adapters/embedding/test_base_embedding_adapter.py +283 -0
- kiln_ai/adapters/embedding/test_embedding_registry.py +166 -0
- kiln_ai/adapters/embedding/test_litellm_embedding_adapter.py +1149 -0
- kiln_ai/adapters/eval/base_eval.py +2 -2
- kiln_ai/adapters/eval/eval_runner.py +9 -3
- kiln_ai/adapters/eval/g_eval.py +2 -2
- kiln_ai/adapters/eval/test_base_eval.py +2 -4
- kiln_ai/adapters/eval/test_g_eval.py +4 -5
- kiln_ai/adapters/extractors/__init__.py +18 -0
- kiln_ai/adapters/extractors/base_extractor.py +72 -0
- kiln_ai/adapters/extractors/encoding.py +20 -0
- kiln_ai/adapters/extractors/extractor_registry.py +44 -0
- kiln_ai/adapters/extractors/extractor_runner.py +112 -0
- kiln_ai/adapters/extractors/litellm_extractor.py +386 -0
- kiln_ai/adapters/extractors/test_base_extractor.py +244 -0
- kiln_ai/adapters/extractors/test_encoding.py +54 -0
- kiln_ai/adapters/extractors/test_extractor_registry.py +181 -0
- kiln_ai/adapters/extractors/test_extractor_runner.py +181 -0
- kiln_ai/adapters/extractors/test_litellm_extractor.py +1192 -0
- kiln_ai/adapters/fine_tune/__init__.py +1 -1
- kiln_ai/adapters/fine_tune/openai_finetune.py +14 -4
- kiln_ai/adapters/fine_tune/test_dataset_formatter.py +2 -2
- kiln_ai/adapters/fine_tune/test_fireworks_tinetune.py +2 -6
- kiln_ai/adapters/fine_tune/test_openai_finetune.py +108 -111
- kiln_ai/adapters/fine_tune/test_together_finetune.py +2 -6
- kiln_ai/adapters/ml_embedding_model_list.py +192 -0
- kiln_ai/adapters/ml_model_list.py +761 -37
- kiln_ai/adapters/model_adapters/base_adapter.py +51 -21
- kiln_ai/adapters/model_adapters/litellm_adapter.py +380 -138
- kiln_ai/adapters/model_adapters/test_base_adapter.py +193 -17
- kiln_ai/adapters/model_adapters/test_litellm_adapter.py +407 -2
- kiln_ai/adapters/model_adapters/test_litellm_adapter_tools.py +1103 -0
- kiln_ai/adapters/model_adapters/test_saving_adapter_results.py +5 -5
- kiln_ai/adapters/model_adapters/test_structured_output.py +113 -5
- kiln_ai/adapters/ollama_tools.py +69 -12
- kiln_ai/adapters/parsers/__init__.py +1 -1
- kiln_ai/adapters/provider_tools.py +205 -47
- kiln_ai/adapters/rag/deduplication.py +49 -0
- kiln_ai/adapters/rag/progress.py +252 -0
- kiln_ai/adapters/rag/rag_runners.py +844 -0
- kiln_ai/adapters/rag/test_deduplication.py +195 -0
- kiln_ai/adapters/rag/test_progress.py +785 -0
- kiln_ai/adapters/rag/test_rag_runners.py +2376 -0
- kiln_ai/adapters/remote_config.py +80 -8
- kiln_ai/adapters/repair/test_repair_task.py +12 -9
- kiln_ai/adapters/run_output.py +3 -0
- kiln_ai/adapters/test_adapter_registry.py +657 -85
- kiln_ai/adapters/test_docker_model_runner_tools.py +305 -0
- kiln_ai/adapters/test_ml_embedding_model_list.py +429 -0
- kiln_ai/adapters/test_ml_model_list.py +251 -1
- kiln_ai/adapters/test_ollama_tools.py +340 -1
- kiln_ai/adapters/test_prompt_adaptors.py +13 -6
- kiln_ai/adapters/test_prompt_builders.py +1 -1
- kiln_ai/adapters/test_provider_tools.py +254 -8
- kiln_ai/adapters/test_remote_config.py +651 -58
- kiln_ai/adapters/vector_store/__init__.py +1 -0
- kiln_ai/adapters/vector_store/base_vector_store_adapter.py +83 -0
- kiln_ai/adapters/vector_store/lancedb_adapter.py +389 -0
- kiln_ai/adapters/vector_store/test_base_vector_store.py +160 -0
- kiln_ai/adapters/vector_store/test_lancedb_adapter.py +1841 -0
- kiln_ai/adapters/vector_store/test_vector_store_registry.py +199 -0
- kiln_ai/adapters/vector_store/vector_store_registry.py +33 -0
- kiln_ai/datamodel/__init__.py +39 -34
- kiln_ai/datamodel/basemodel.py +170 -1
- kiln_ai/datamodel/chunk.py +158 -0
- kiln_ai/datamodel/datamodel_enums.py +28 -0
- kiln_ai/datamodel/embedding.py +64 -0
- kiln_ai/datamodel/eval.py +1 -1
- kiln_ai/datamodel/external_tool_server.py +298 -0
- kiln_ai/datamodel/extraction.py +303 -0
- kiln_ai/datamodel/json_schema.py +25 -10
- kiln_ai/datamodel/project.py +40 -1
- kiln_ai/datamodel/rag.py +79 -0
- kiln_ai/datamodel/registry.py +0 -15
- kiln_ai/datamodel/run_config.py +62 -0
- kiln_ai/datamodel/task.py +2 -77
- kiln_ai/datamodel/task_output.py +6 -1
- kiln_ai/datamodel/task_run.py +41 -0
- kiln_ai/datamodel/test_attachment.py +649 -0
- kiln_ai/datamodel/test_basemodel.py +4 -4
- kiln_ai/datamodel/test_chunk_models.py +317 -0
- kiln_ai/datamodel/test_dataset_split.py +1 -1
- kiln_ai/datamodel/test_embedding_models.py +448 -0
- kiln_ai/datamodel/test_eval_model.py +6 -6
- kiln_ai/datamodel/test_example_models.py +175 -0
- kiln_ai/datamodel/test_external_tool_server.py +691 -0
- kiln_ai/datamodel/test_extraction_chunk.py +206 -0
- kiln_ai/datamodel/test_extraction_model.py +470 -0
- kiln_ai/datamodel/test_rag.py +641 -0
- kiln_ai/datamodel/test_registry.py +8 -3
- kiln_ai/datamodel/test_task.py +15 -47
- kiln_ai/datamodel/test_tool_id.py +320 -0
- kiln_ai/datamodel/test_vector_store.py +320 -0
- kiln_ai/datamodel/tool_id.py +105 -0
- kiln_ai/datamodel/vector_store.py +141 -0
- kiln_ai/tools/__init__.py +8 -0
- kiln_ai/tools/base_tool.py +82 -0
- kiln_ai/tools/built_in_tools/__init__.py +13 -0
- kiln_ai/tools/built_in_tools/math_tools.py +124 -0
- kiln_ai/tools/built_in_tools/test_math_tools.py +204 -0
- kiln_ai/tools/mcp_server_tool.py +95 -0
- kiln_ai/tools/mcp_session_manager.py +246 -0
- kiln_ai/tools/rag_tools.py +157 -0
- kiln_ai/tools/test_base_tools.py +199 -0
- kiln_ai/tools/test_mcp_server_tool.py +457 -0
- kiln_ai/tools/test_mcp_session_manager.py +1585 -0
- kiln_ai/tools/test_rag_tools.py +848 -0
- kiln_ai/tools/test_tool_registry.py +562 -0
- kiln_ai/tools/tool_registry.py +85 -0
- kiln_ai/utils/__init__.py +3 -0
- kiln_ai/utils/async_job_runner.py +62 -17
- kiln_ai/utils/config.py +24 -2
- kiln_ai/utils/env.py +15 -0
- kiln_ai/utils/filesystem.py +14 -0
- kiln_ai/utils/filesystem_cache.py +60 -0
- kiln_ai/utils/litellm.py +94 -0
- kiln_ai/utils/lock.py +100 -0
- kiln_ai/utils/mime_type.py +38 -0
- kiln_ai/utils/open_ai_types.py +94 -0
- kiln_ai/utils/pdf_utils.py +38 -0
- kiln_ai/utils/project_utils.py +17 -0
- kiln_ai/utils/test_async_job_runner.py +151 -35
- kiln_ai/utils/test_config.py +138 -1
- kiln_ai/utils/test_env.py +142 -0
- kiln_ai/utils/test_filesystem_cache.py +316 -0
- kiln_ai/utils/test_litellm.py +206 -0
- kiln_ai/utils/test_lock.py +185 -0
- kiln_ai/utils/test_mime_type.py +66 -0
- kiln_ai/utils/test_open_ai_types.py +131 -0
- kiln_ai/utils/test_pdf_utils.py +73 -0
- kiln_ai/utils/test_uuid.py +111 -0
- kiln_ai/utils/test_validation.py +524 -0
- kiln_ai/utils/uuid.py +9 -0
- kiln_ai/utils/validation.py +90 -0
- {kiln_ai-0.19.0.dist-info → kiln_ai-0.21.0.dist-info}/METADATA +12 -5
- kiln_ai-0.21.0.dist-info/RECORD +211 -0
- kiln_ai-0.19.0.dist-info/RECORD +0 -115
- {kiln_ai-0.19.0.dist-info → kiln_ai-0.21.0.dist-info}/WHEEL +0 -0
- {kiln_ai-0.19.0.dist-info → kiln_ai-0.21.0.dist-info}/licenses/LICENSE.txt +0 -0
kiln_ai/datamodel/test_task.py
CHANGED
|
@@ -3,22 +3,18 @@ from pydantic import ValidationError
|
|
|
3
3
|
|
|
4
4
|
from kiln_ai.datamodel.datamodel_enums import StructuredOutputMode, TaskOutputRatingType
|
|
5
5
|
from kiln_ai.datamodel.prompt_id import PromptGenerators
|
|
6
|
-
from kiln_ai.datamodel.task import
|
|
6
|
+
from kiln_ai.datamodel.task import RunConfigProperties, Task, TaskRunConfig
|
|
7
7
|
from kiln_ai.datamodel.task_output import normalize_rating
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def test_runconfig_valid_creation():
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
config = RunConfig(
|
|
14
|
-
task=task,
|
|
11
|
+
config = RunConfigProperties(
|
|
15
12
|
model_name="gpt-4",
|
|
16
13
|
model_provider_name="openai",
|
|
17
14
|
prompt_id=PromptGenerators.SIMPLE,
|
|
18
15
|
structured_output_mode="json_schema",
|
|
19
16
|
)
|
|
20
17
|
|
|
21
|
-
assert config.task == task
|
|
22
18
|
assert config.model_name == "gpt-4"
|
|
23
19
|
assert config.model_provider_name == "openai"
|
|
24
20
|
assert config.prompt_id == PromptGenerators.SIMPLE # Check default value
|
|
@@ -26,13 +22,12 @@ def test_runconfig_valid_creation():
|
|
|
26
22
|
|
|
27
23
|
def test_runconfig_missing_required_fields():
|
|
28
24
|
with pytest.raises(ValidationError) as exc_info:
|
|
29
|
-
|
|
25
|
+
RunConfigProperties() # type: ignore
|
|
30
26
|
|
|
31
27
|
errors = exc_info.value.errors()
|
|
32
28
|
assert (
|
|
33
|
-
len(errors) ==
|
|
29
|
+
len(errors) == 4
|
|
34
30
|
) # task, model_name, model_provider_name, and prompt_id are required
|
|
35
|
-
assert any(error["loc"][0] == "task" for error in errors)
|
|
36
31
|
assert any(error["loc"][0] == "model_name" for error in errors)
|
|
37
32
|
assert any(error["loc"][0] == "model_provider_name" for error in errors)
|
|
38
33
|
assert any(error["loc"][0] == "prompt_id" for error in errors)
|
|
@@ -40,10 +35,7 @@ def test_runconfig_missing_required_fields():
|
|
|
40
35
|
|
|
41
36
|
|
|
42
37
|
def test_runconfig_custom_prompt_id():
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
config = RunConfig(
|
|
46
|
-
task=task,
|
|
38
|
+
config = RunConfigProperties(
|
|
47
39
|
model_name="gpt-4",
|
|
48
40
|
model_provider_name="openai",
|
|
49
41
|
prompt_id=PromptGenerators.SIMPLE_CHAIN_OF_THOUGHT,
|
|
@@ -100,30 +92,18 @@ def test_task_run_config_missing_required_fields(sample_task):
|
|
|
100
92
|
with pytest.raises(ValidationError) as exc_info:
|
|
101
93
|
TaskRunConfig(
|
|
102
94
|
run_config_properties=RunConfigProperties(
|
|
103
|
-
|
|
104
|
-
),
|
|
95
|
+
model_name="gpt-4", model_provider_name="openai"
|
|
96
|
+
), # type: ignore
|
|
105
97
|
parent=sample_task,
|
|
106
|
-
)
|
|
98
|
+
) # type: ignore
|
|
107
99
|
assert "Field required" in str(exc_info.value)
|
|
108
100
|
|
|
109
101
|
# Test missing run_config
|
|
110
102
|
with pytest.raises(ValidationError) as exc_info:
|
|
111
|
-
TaskRunConfig(name="Test Config", parent=sample_task)
|
|
103
|
+
TaskRunConfig(name="Test Config", parent=sample_task) # type: ignore
|
|
112
104
|
assert "Field required" in str(exc_info.value)
|
|
113
105
|
|
|
114
106
|
|
|
115
|
-
def test_task_run_config_missing_task_in_run_config(sample_task):
|
|
116
|
-
with pytest.raises(
|
|
117
|
-
ValidationError, match="Input should be a valid dictionary or instance of Task"
|
|
118
|
-
):
|
|
119
|
-
# Create a run config without a task
|
|
120
|
-
RunConfig(
|
|
121
|
-
model_name="gpt-4",
|
|
122
|
-
model_provider_name="openai",
|
|
123
|
-
task=None, # type: ignore
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
107
|
@pytest.mark.parametrize(
|
|
128
108
|
"rating_type,rating,expected",
|
|
129
109
|
[
|
|
@@ -165,10 +145,8 @@ def test_normalize_rating_errors(rating_type, rating):
|
|
|
165
145
|
|
|
166
146
|
def test_run_config_defaults():
|
|
167
147
|
"""RunConfig should require top_p, temperature, and structured_output_mode to be set."""
|
|
168
|
-
task = Task(id="task1", name="Test Task", instruction="Do something")
|
|
169
148
|
|
|
170
|
-
config =
|
|
171
|
-
task=task,
|
|
149
|
+
config = RunConfigProperties(
|
|
172
150
|
model_name="gpt-4",
|
|
173
151
|
model_provider_name="openai",
|
|
174
152
|
prompt_id=PromptGenerators.SIMPLE,
|
|
@@ -180,11 +158,9 @@ def test_run_config_defaults():
|
|
|
180
158
|
|
|
181
159
|
def test_run_config_valid_ranges():
|
|
182
160
|
"""RunConfig should accept valid ranges for top_p and temperature."""
|
|
183
|
-
task = Task(id="task1", name="Test Task", instruction="Do something")
|
|
184
161
|
|
|
185
162
|
# Test valid values
|
|
186
|
-
config =
|
|
187
|
-
task=task,
|
|
163
|
+
config = RunConfigProperties(
|
|
188
164
|
model_name="gpt-4",
|
|
189
165
|
model_provider_name="openai",
|
|
190
166
|
prompt_id=PromptGenerators.SIMPLE,
|
|
@@ -201,10 +177,8 @@ def test_run_config_valid_ranges():
|
|
|
201
177
|
@pytest.mark.parametrize("top_p", [0.0, 0.5, 1.0])
|
|
202
178
|
def test_run_config_valid_top_p(top_p):
|
|
203
179
|
"""Test that RunConfig accepts valid top_p values (0-1)."""
|
|
204
|
-
task = Task(id="task1", name="Test Task", instruction="Do something")
|
|
205
180
|
|
|
206
|
-
config =
|
|
207
|
-
task=task,
|
|
181
|
+
config = RunConfigProperties(
|
|
208
182
|
model_name="gpt-4",
|
|
209
183
|
model_provider_name="openai",
|
|
210
184
|
prompt_id=PromptGenerators.SIMPLE,
|
|
@@ -219,11 +193,9 @@ def test_run_config_valid_top_p(top_p):
|
|
|
219
193
|
@pytest.mark.parametrize("top_p", [-0.1, 1.1, 2.0])
|
|
220
194
|
def test_run_config_invalid_top_p(top_p):
|
|
221
195
|
"""Test that RunConfig rejects invalid top_p values."""
|
|
222
|
-
task = Task(id="task1", name="Test Task", instruction="Do something")
|
|
223
196
|
|
|
224
197
|
with pytest.raises(ValueError, match="top_p must be between 0 and 1"):
|
|
225
|
-
|
|
226
|
-
task=task,
|
|
198
|
+
RunConfigProperties(
|
|
227
199
|
model_name="gpt-4",
|
|
228
200
|
model_provider_name="openai",
|
|
229
201
|
prompt_id=PromptGenerators.SIMPLE,
|
|
@@ -236,10 +208,8 @@ def test_run_config_invalid_top_p(top_p):
|
|
|
236
208
|
@pytest.mark.parametrize("temperature", [0.0, 1.0, 2.0])
|
|
237
209
|
def test_run_config_valid_temperature(temperature):
|
|
238
210
|
"""Test that RunConfig accepts valid temperature values (0-2)."""
|
|
239
|
-
task = Task(id="task1", name="Test Task", instruction="Do something")
|
|
240
211
|
|
|
241
|
-
config =
|
|
242
|
-
task=task,
|
|
212
|
+
config = RunConfigProperties(
|
|
243
213
|
model_name="gpt-4",
|
|
244
214
|
model_provider_name="openai",
|
|
245
215
|
prompt_id=PromptGenerators.SIMPLE,
|
|
@@ -254,11 +224,9 @@ def test_run_config_valid_temperature(temperature):
|
|
|
254
224
|
@pytest.mark.parametrize("temperature", [-0.1, 2.1, 3.0])
|
|
255
225
|
def test_run_config_invalid_temperature(temperature):
|
|
256
226
|
"""Test that RunConfig rejects invalid temperature values."""
|
|
257
|
-
task = Task(id="task1", name="Test Task", instruction="Do something")
|
|
258
227
|
|
|
259
228
|
with pytest.raises(ValueError, match="temperature must be between 0 and 2"):
|
|
260
|
-
|
|
261
|
-
task=task,
|
|
229
|
+
RunConfigProperties(
|
|
262
230
|
model_name="gpt-4",
|
|
263
231
|
model_provider_name="openai",
|
|
264
232
|
prompt_id=PromptGenerators.SIMPLE,
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pydantic import BaseModel, ValidationError
|
|
3
|
+
|
|
4
|
+
from kiln_ai.datamodel.tool_id import (
|
|
5
|
+
MCP_LOCAL_TOOL_ID_PREFIX,
|
|
6
|
+
MCP_REMOTE_TOOL_ID_PREFIX,
|
|
7
|
+
RAG_TOOL_ID_PREFIX,
|
|
8
|
+
KilnBuiltInToolId,
|
|
9
|
+
ToolId,
|
|
10
|
+
_check_tool_id,
|
|
11
|
+
mcp_server_and_tool_name_from_id,
|
|
12
|
+
rag_config_id_from_id,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestKilnBuiltInToolId:
|
|
17
|
+
"""Test the KilnBuiltInToolId enum."""
|
|
18
|
+
|
|
19
|
+
def test_enum_values(self):
|
|
20
|
+
"""Test that enum has expected values."""
|
|
21
|
+
assert KilnBuiltInToolId.ADD_NUMBERS == "kiln_tool::add_numbers"
|
|
22
|
+
assert KilnBuiltInToolId.SUBTRACT_NUMBERS == "kiln_tool::subtract_numbers"
|
|
23
|
+
assert KilnBuiltInToolId.MULTIPLY_NUMBERS == "kiln_tool::multiply_numbers"
|
|
24
|
+
assert KilnBuiltInToolId.DIVIDE_NUMBERS == "kiln_tool::divide_numbers"
|
|
25
|
+
for enum_value in KilnBuiltInToolId.__members__.values():
|
|
26
|
+
assert _check_tool_id(enum_value) == enum_value
|
|
27
|
+
|
|
28
|
+
def test_enum_membership(self):
|
|
29
|
+
"""Test enum membership checks."""
|
|
30
|
+
assert "kiln_tool::add_numbers" in KilnBuiltInToolId.__members__.values()
|
|
31
|
+
assert "invalid_tool" not in KilnBuiltInToolId.__members__.values()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestCheckToolId:
|
|
35
|
+
"""Test the _check_tool_id validation function."""
|
|
36
|
+
|
|
37
|
+
def test_valid_builtin_tools(self):
|
|
38
|
+
"""Test validation of valid built-in tools."""
|
|
39
|
+
for tool_id in KilnBuiltInToolId:
|
|
40
|
+
result = _check_tool_id(tool_id.value)
|
|
41
|
+
assert result == tool_id.value
|
|
42
|
+
|
|
43
|
+
def test_valid_mcp_remote_tools(self):
|
|
44
|
+
"""Test validation of valid MCP remote tools."""
|
|
45
|
+
valid_ids = [
|
|
46
|
+
"mcp::remote::server1::tool1",
|
|
47
|
+
"mcp::remote::my_server::my_tool",
|
|
48
|
+
"mcp::remote::test::function_name",
|
|
49
|
+
]
|
|
50
|
+
for tool_id in valid_ids:
|
|
51
|
+
result = _check_tool_id(tool_id)
|
|
52
|
+
assert result == tool_id
|
|
53
|
+
|
|
54
|
+
def test_valid_mcp_local_tools(self):
|
|
55
|
+
"""Test validation of valid MCP local tools."""
|
|
56
|
+
valid_ids = [
|
|
57
|
+
"mcp::local::server1::tool1",
|
|
58
|
+
"mcp::local::my_server::my_tool",
|
|
59
|
+
"mcp::local::test::function_name",
|
|
60
|
+
]
|
|
61
|
+
for tool_id in valid_ids:
|
|
62
|
+
result = _check_tool_id(tool_id)
|
|
63
|
+
assert result == tool_id
|
|
64
|
+
|
|
65
|
+
def test_invalid_empty_or_none(self):
|
|
66
|
+
"""Test validation fails for empty or None values."""
|
|
67
|
+
with pytest.raises(ValueError, match="Invalid tool ID"):
|
|
68
|
+
_check_tool_id("")
|
|
69
|
+
|
|
70
|
+
with pytest.raises(ValueError, match="Invalid tool ID"):
|
|
71
|
+
_check_tool_id(None) # type: ignore
|
|
72
|
+
|
|
73
|
+
def test_invalid_non_string(self):
|
|
74
|
+
"""Test validation fails for non-string values."""
|
|
75
|
+
with pytest.raises(ValueError, match="Invalid tool ID"):
|
|
76
|
+
_check_tool_id(123) # type: ignore
|
|
77
|
+
|
|
78
|
+
with pytest.raises(ValueError, match="Invalid tool ID"):
|
|
79
|
+
_check_tool_id(["tool"]) # type: ignore
|
|
80
|
+
|
|
81
|
+
def test_invalid_unknown_tool(self):
|
|
82
|
+
"""Test validation fails for unknown tool IDs."""
|
|
83
|
+
with pytest.raises(ValueError, match="Invalid tool ID: unknown_tool"):
|
|
84
|
+
_check_tool_id("unknown_tool")
|
|
85
|
+
|
|
86
|
+
def test_invalid_mcp_format(self):
|
|
87
|
+
"""Test validation fails for invalid MCP tool formats."""
|
|
88
|
+
# These IDs start with the MCP remote prefix but have invalid formats
|
|
89
|
+
mcp_remote_invalid_ids = [
|
|
90
|
+
"mcp::remote::", # Missing server and tool
|
|
91
|
+
"mcp::remote::server", # Missing tool
|
|
92
|
+
"mcp::remote::server::", # Empty tool name
|
|
93
|
+
"mcp::remote::::tool", # Empty server name
|
|
94
|
+
"mcp::remote::server::tool::extra", # Too many parts
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
for invalid_id in mcp_remote_invalid_ids:
|
|
98
|
+
with pytest.raises(ValueError, match="Invalid remote MCP tool ID"):
|
|
99
|
+
_check_tool_id(invalid_id)
|
|
100
|
+
|
|
101
|
+
# These IDs start with the MCP local prefix but have invalid formats
|
|
102
|
+
mcp_local_invalid_ids = [
|
|
103
|
+
"mcp::local::", # Missing server and tool
|
|
104
|
+
"mcp::local::server", # Missing tool
|
|
105
|
+
"mcp::local::server::", # Empty tool name
|
|
106
|
+
"mcp::local::::tool", # Empty server name
|
|
107
|
+
"mcp::local::server::tool::extra", # Too many parts
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
for invalid_id in mcp_local_invalid_ids:
|
|
111
|
+
with pytest.raises(ValueError, match="Invalid local MCP tool ID"):
|
|
112
|
+
_check_tool_id(invalid_id)
|
|
113
|
+
|
|
114
|
+
# This ID doesn't start with MCP prefix so gets generic error
|
|
115
|
+
with pytest.raises(ValueError, match="Invalid tool ID"):
|
|
116
|
+
_check_tool_id("mcp::wrong::server::tool")
|
|
117
|
+
|
|
118
|
+
def test_valid_rag_tools(self):
|
|
119
|
+
"""Test validation of valid RAG tools."""
|
|
120
|
+
valid_ids = [
|
|
121
|
+
"kiln_tool::rag::config1",
|
|
122
|
+
"kiln_tool::rag::my_rag_config",
|
|
123
|
+
"kiln_tool::rag::test_config_123",
|
|
124
|
+
]
|
|
125
|
+
for tool_id in valid_ids:
|
|
126
|
+
result = _check_tool_id(tool_id)
|
|
127
|
+
assert result == tool_id
|
|
128
|
+
|
|
129
|
+
def test_invalid_rag_format(self):
|
|
130
|
+
"""Test validation fails for invalid RAG tool formats."""
|
|
131
|
+
# These IDs start with the RAG prefix but have invalid formats
|
|
132
|
+
rag_invalid_ids = [
|
|
133
|
+
"kiln_tool::rag::", # Missing config ID
|
|
134
|
+
"kiln_tool::rag::config::extra", # Too many parts
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
for invalid_id in rag_invalid_ids:
|
|
138
|
+
with pytest.raises(ValueError, match="Invalid RAG tool ID"):
|
|
139
|
+
_check_tool_id(invalid_id)
|
|
140
|
+
|
|
141
|
+
def test_rag_tool_empty_config_id(self):
|
|
142
|
+
"""Test that RAG tool with empty config ID is handled properly."""
|
|
143
|
+
# This tests the case where rag_config_id_from_id returns empty string
|
|
144
|
+
# which should trigger line 66 in the source
|
|
145
|
+
with pytest.raises(ValueError, match="Invalid RAG tool ID"):
|
|
146
|
+
_check_tool_id("kiln_tool::rag::")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestMcpServerAndToolNameFromId:
|
|
150
|
+
"""Test the mcp_server_and_tool_name_from_id function."""
|
|
151
|
+
|
|
152
|
+
def test_valid_mcp_ids(self):
|
|
153
|
+
"""Test parsing valid MCP tool IDs."""
|
|
154
|
+
test_cases = [
|
|
155
|
+
# Remote MCP tools
|
|
156
|
+
("mcp::remote::server1::tool1", ("server1", "tool1")),
|
|
157
|
+
("mcp::remote::my_server::my_tool", ("my_server", "my_tool")),
|
|
158
|
+
("mcp::remote::test::function_name", ("test", "function_name")),
|
|
159
|
+
# Local MCP tools
|
|
160
|
+
("mcp::local::server1::tool1", ("server1", "tool1")),
|
|
161
|
+
("mcp::local::my_server::my_tool", ("my_server", "my_tool")),
|
|
162
|
+
("mcp::local::test::function_name", ("test", "function_name")),
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
for tool_id, expected in test_cases:
|
|
166
|
+
result = mcp_server_and_tool_name_from_id(tool_id)
|
|
167
|
+
assert result == expected
|
|
168
|
+
|
|
169
|
+
def test_invalid_mcp_ids(self):
|
|
170
|
+
"""Test parsing fails for invalid MCP tool IDs."""
|
|
171
|
+
# Test remote MCP tool ID errors
|
|
172
|
+
remote_invalid_ids = [
|
|
173
|
+
"mcp::remote::", # Only 3 parts
|
|
174
|
+
"mcp::remote::server", # Only 3 parts
|
|
175
|
+
"mcp::remote::server::tool::extra", # 5 parts
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
for invalid_id in remote_invalid_ids:
|
|
179
|
+
with pytest.raises(ValueError, match="Invalid remote MCP tool ID"):
|
|
180
|
+
mcp_server_and_tool_name_from_id(invalid_id)
|
|
181
|
+
|
|
182
|
+
# Test local MCP tool ID errors
|
|
183
|
+
local_invalid_ids = [
|
|
184
|
+
"mcp::local::", # Only 3 parts
|
|
185
|
+
"mcp::local::server", # Only 3 parts
|
|
186
|
+
"mcp::local::server::tool::extra", # 5 parts
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
for invalid_id in local_invalid_ids:
|
|
190
|
+
with pytest.raises(ValueError, match="Invalid local MCP tool ID"):
|
|
191
|
+
mcp_server_and_tool_name_from_id(invalid_id)
|
|
192
|
+
|
|
193
|
+
# Test generic MCP tool ID errors (not remote or local)
|
|
194
|
+
generic_invalid_ids = [
|
|
195
|
+
"not_mcp_format", # Only 1 part
|
|
196
|
+
"single_part", # Only 1 part
|
|
197
|
+
"", # Empty string
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
for invalid_id in generic_invalid_ids:
|
|
201
|
+
with pytest.raises(ValueError, match="Invalid MCP tool ID"):
|
|
202
|
+
mcp_server_and_tool_name_from_id(invalid_id)
|
|
203
|
+
|
|
204
|
+
def test_mcp_ids_with_wrong_prefix_still_parse(self):
|
|
205
|
+
"""Test that IDs with wrong prefix but correct structure still parse (validation happens elsewhere)."""
|
|
206
|
+
# This function only checks structure (4 parts), not content
|
|
207
|
+
result = mcp_server_and_tool_name_from_id("mcp::wrong::server::tool")
|
|
208
|
+
assert result == ("server", "tool")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestToolIdPydanticType:
|
|
212
|
+
"""Test the ToolId pydantic type annotation."""
|
|
213
|
+
|
|
214
|
+
class _ModelWithToolId(BaseModel):
|
|
215
|
+
tool_id: ToolId
|
|
216
|
+
|
|
217
|
+
def test_valid_builtin_tools(self):
|
|
218
|
+
"""Test ToolId validates built-in tools."""
|
|
219
|
+
for tool_id in KilnBuiltInToolId:
|
|
220
|
+
model = self._ModelWithToolId(tool_id=tool_id.value)
|
|
221
|
+
assert model.tool_id == tool_id.value
|
|
222
|
+
|
|
223
|
+
def test_valid_mcp_tools(self):
|
|
224
|
+
"""Test ToolId validates MCP remote and local tools."""
|
|
225
|
+
valid_ids = [
|
|
226
|
+
# Remote MCP tools
|
|
227
|
+
"mcp::remote::server1::tool1",
|
|
228
|
+
"mcp::remote::my_server::my_tool",
|
|
229
|
+
# Local MCP tools
|
|
230
|
+
"mcp::local::server1::tool1",
|
|
231
|
+
"mcp::local::my_server::my_tool",
|
|
232
|
+
# RAG tools
|
|
233
|
+
"kiln_tool::rag::config1",
|
|
234
|
+
"kiln_tool::rag::my_rag_config",
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
for tool_id in valid_ids:
|
|
238
|
+
model = self._ModelWithToolId(tool_id=tool_id)
|
|
239
|
+
assert model.tool_id == tool_id
|
|
240
|
+
|
|
241
|
+
def test_invalid_tools_raise_validation_error(self):
|
|
242
|
+
"""Test ToolId raises ValidationError for invalid tools."""
|
|
243
|
+
invalid_ids = [
|
|
244
|
+
"",
|
|
245
|
+
"unknown_tool",
|
|
246
|
+
"mcp::remote::",
|
|
247
|
+
"mcp::remote::server",
|
|
248
|
+
"mcp::local::",
|
|
249
|
+
"mcp::local::server",
|
|
250
|
+
"kiln_tool::rag::",
|
|
251
|
+
"kiln_tool::rag::config::extra",
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
for invalid_id in invalid_ids:
|
|
255
|
+
with pytest.raises(ValidationError):
|
|
256
|
+
self._ModelWithToolId(tool_id=invalid_id)
|
|
257
|
+
|
|
258
|
+
def test_non_string_raises_validation_error(self):
|
|
259
|
+
"""Test ToolId raises ValidationError for non-string values."""
|
|
260
|
+
with pytest.raises(ValidationError):
|
|
261
|
+
self._ModelWithToolId(tool_id=123) # type: ignore
|
|
262
|
+
|
|
263
|
+
with pytest.raises(ValidationError):
|
|
264
|
+
self._ModelWithToolId(tool_id=None) # type: ignore
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class TestConstants:
|
|
268
|
+
"""Test module constants."""
|
|
269
|
+
|
|
270
|
+
def test_mcp_remote_tool_id_prefix(self):
|
|
271
|
+
"""Test the MCP remote tool ID prefix constant."""
|
|
272
|
+
assert MCP_REMOTE_TOOL_ID_PREFIX == "mcp::remote::"
|
|
273
|
+
|
|
274
|
+
def test_mcp_local_tool_id_prefix(self):
|
|
275
|
+
"""Test the MCP local tool ID prefix constant."""
|
|
276
|
+
assert MCP_LOCAL_TOOL_ID_PREFIX == "mcp::local::"
|
|
277
|
+
|
|
278
|
+
def test_rag_tool_id_prefix(self):
|
|
279
|
+
"""Test the RAG tool ID prefix constant."""
|
|
280
|
+
assert RAG_TOOL_ID_PREFIX == "kiln_tool::rag::"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class TestRagConfigIdFromId:
|
|
284
|
+
"""Test the rag_config_id_from_id function."""
|
|
285
|
+
|
|
286
|
+
def test_valid_rag_ids(self):
|
|
287
|
+
"""Test parsing valid RAG tool IDs."""
|
|
288
|
+
test_cases = [
|
|
289
|
+
("kiln_tool::rag::config1", "config1"),
|
|
290
|
+
("kiln_tool::rag::my_rag_config", "my_rag_config"),
|
|
291
|
+
("kiln_tool::rag::test_config_123", "test_config_123"),
|
|
292
|
+
("kiln_tool::rag::a", "a"), # Minimal valid case
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
for tool_id, expected in test_cases:
|
|
296
|
+
result = rag_config_id_from_id(tool_id)
|
|
297
|
+
assert result == expected
|
|
298
|
+
|
|
299
|
+
def test_invalid_rag_ids(self):
|
|
300
|
+
"""Test parsing fails for invalid RAG tool IDs."""
|
|
301
|
+
# Test various invalid formats that should trigger line 104
|
|
302
|
+
invalid_ids = [
|
|
303
|
+
"kiln_tool::rag::config::extra", # Too many parts (4 parts)
|
|
304
|
+
"wrong::rag::config", # Wrong prefix
|
|
305
|
+
"kiln_tool::wrong::config", # Wrong middle part
|
|
306
|
+
"rag::config", # Too few parts (2 parts)
|
|
307
|
+
"", # Empty string
|
|
308
|
+
"single_part", # Only 1 part
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
for invalid_id in invalid_ids:
|
|
312
|
+
with pytest.raises(ValueError, match="Invalid RAG tool ID"):
|
|
313
|
+
rag_config_id_from_id(invalid_id)
|
|
314
|
+
|
|
315
|
+
def test_rag_id_with_empty_config_id(self):
|
|
316
|
+
"""Test that RAG tool ID with empty config ID returns empty string."""
|
|
317
|
+
# This is actually valid according to the parser - it returns empty string
|
|
318
|
+
# The validation for empty config ID happens in _check_tool_id
|
|
319
|
+
result = rag_config_id_from_id("kiln_tool::rag::")
|
|
320
|
+
assert result == ""
|