kiln-ai 0.15.0__tar.gz → 0.17.0__tar.gz

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 (151) hide show
  1. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/PKG-INFO +1 -1
  2. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/__init__.py +2 -0
  3. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/adapter_registry.py +22 -44
  4. kiln_ai-0.17.0/kiln_ai/adapters/chat/__init__.py +8 -0
  5. kiln_ai-0.17.0/kiln_ai/adapters/chat/chat_formatter.py +234 -0
  6. kiln_ai-0.17.0/kiln_ai/adapters/chat/test_chat_formatter.py +131 -0
  7. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/data_gen/test_data_gen_task.py +19 -6
  8. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/base_eval.py +8 -6
  9. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/eval_runner.py +9 -65
  10. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/g_eval.py +26 -8
  11. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/test_base_eval.py +166 -15
  12. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/test_eval_runner.py +3 -0
  13. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/test_g_eval.py +1 -0
  14. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/base_finetune.py +2 -2
  15. kiln_ai-0.17.0/kiln_ai/adapters/fine_tune/dataset_formatter.py +388 -0
  16. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/test_base_finetune.py +10 -10
  17. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/test_dataset_formatter.py +402 -211
  18. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/test_fireworks_tinetune.py +3 -3
  19. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/test_openai_finetune.py +6 -6
  20. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/test_together_finetune.py +1 -0
  21. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/test_vertex_finetune.py +4 -4
  22. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/together_finetune.py +12 -1
  23. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/ml_model_list.py +556 -45
  24. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/model_adapters/base_adapter.py +100 -35
  25. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/model_adapters/litellm_adapter.py +116 -100
  26. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/model_adapters/litellm_config.py +3 -2
  27. kiln_ai-0.17.0/kiln_ai/adapters/model_adapters/test_base_adapter.py +446 -0
  28. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/model_adapters/test_litellm_adapter.py +121 -22
  29. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/model_adapters/test_saving_adapter_results.py +44 -2
  30. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/model_adapters/test_structured_output.py +48 -18
  31. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/parsers/base_parser.py +0 -3
  32. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/parsers/parser_registry.py +5 -3
  33. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/parsers/r1_parser.py +17 -2
  34. kiln_ai-0.17.0/kiln_ai/adapters/parsers/request_formatters.py +40 -0
  35. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/parsers/test_parser_registry.py +2 -2
  36. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/parsers/test_r1_parser.py +44 -1
  37. kiln_ai-0.17.0/kiln_ai/adapters/parsers/test_request_formatters.py +76 -0
  38. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/prompt_builders.py +14 -17
  39. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/provider_tools.py +39 -4
  40. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/repair/test_repair_task.py +27 -5
  41. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/test_adapter_registry.py +88 -28
  42. kiln_ai-0.17.0/kiln_ai/adapters/test_ml_model_list.py +158 -0
  43. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/test_prompt_adaptors.py +17 -3
  44. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/test_prompt_builders.py +27 -19
  45. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/test_provider_tools.py +130 -12
  46. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/__init__.py +2 -2
  47. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/datamodel_enums.py +43 -4
  48. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/dataset_filters.py +69 -1
  49. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/dataset_split.py +4 -0
  50. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/eval.py +8 -0
  51. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/finetune.py +13 -7
  52. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/prompt_id.py +1 -0
  53. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/task.py +68 -7
  54. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/task_output.py +1 -1
  55. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/task_run.py +39 -7
  56. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_basemodel.py +5 -8
  57. kiln_ai-0.17.0/kiln_ai/datamodel/test_dataset_filters.py +153 -0
  58. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_dataset_split.py +2 -8
  59. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_example_models.py +54 -0
  60. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_models.py +80 -9
  61. kiln_ai-0.17.0/kiln_ai/datamodel/test_task.py +325 -0
  62. kiln_ai-0.17.0/kiln_ai/utils/async_job_runner.py +106 -0
  63. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/config.py +3 -2
  64. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/dataset_import.py +81 -19
  65. kiln_ai-0.17.0/kiln_ai/utils/logging.py +165 -0
  66. kiln_ai-0.17.0/kiln_ai/utils/test_async_job_runner.py +199 -0
  67. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/test_config.py +23 -0
  68. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/test_dataset_import.py +272 -10
  69. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/pyproject.toml +1 -1
  70. kiln_ai-0.15.0/kiln_ai/adapters/fine_tune/dataset_formatter.py +0 -432
  71. kiln_ai-0.15.0/kiln_ai/adapters/model_adapters/test_base_adapter.py +0 -199
  72. kiln_ai-0.15.0/kiln_ai/datamodel/test_dataset_filters.py +0 -71
  73. kiln_ai-0.15.0/kiln_ai/datamodel/test_task.py +0 -159
  74. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/.gitignore +0 -0
  75. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/.python-version +0 -0
  76. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/LICENSE.txt +0 -0
  77. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/README.md +0 -0
  78. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/index.html +0 -0
  79. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/data_gen/data_gen_task.html +0 -0
  80. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/data_gen.html +0 -0
  81. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/eval/base_eval.html +0 -0
  82. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/eval/eval_runner.html +0 -0
  83. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/eval/g_eval.html +0 -0
  84. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/eval/registry.html +0 -0
  85. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/eval.html +0 -0
  86. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/fine_tune/base_finetune.html +0 -0
  87. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/fine_tune/dataset_formatter.html +0 -0
  88. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/fine_tune/finetune_registry.html +0 -0
  89. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/fine_tune/openai_finetune.html +0 -0
  90. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/fine_tune.html +0 -0
  91. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/ml_model_list.html +0 -0
  92. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/model_adapters/base_adapter.html +0 -0
  93. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/model_adapters/litellm_adapter.html +0 -0
  94. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/model_adapters.html +0 -0
  95. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/prompt_builders.html +0 -0
  96. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/repair/repair_task.html +0 -0
  97. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters/repair.html +0 -0
  98. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/adapters.html +0 -0
  99. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/datamodel/dataset_split.html +0 -0
  100. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/datamodel/eval.html +0 -0
  101. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/datamodel/strict_mode.html +0 -0
  102. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/datamodel.html +0 -0
  103. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/utils/config.html +0 -0
  104. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/utils/formatting.html +0 -0
  105. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai/utils.html +0 -0
  106. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/kiln_ai.html +0 -0
  107. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/docs/kiln_core_docs/search.js +0 -0
  108. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/__init__.py +0 -0
  109. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/data_gen/__init__.py +0 -0
  110. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/data_gen/data_gen_prompts.py +0 -0
  111. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/data_gen/data_gen_task.py +0 -0
  112. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/__init__.py +0 -0
  113. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/registry.py +0 -0
  114. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/eval/test_g_eval_data.py +0 -0
  115. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/__init__.py +0 -0
  116. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/finetune_registry.py +0 -0
  117. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/fireworks_finetune.py +0 -0
  118. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/openai_finetune.py +0 -0
  119. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/fine_tune/vertex_finetune.py +0 -0
  120. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/model_adapters/__init__.py +0 -0
  121. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/ollama_tools.py +0 -0
  122. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/parsers/__init__.py +0 -0
  123. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/parsers/json_parser.py +0 -0
  124. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/parsers/test_json_parser.py +0 -0
  125. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/repair/__init__.py +0 -0
  126. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/repair/repair_task.py +0 -0
  127. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/run_output.py +0 -0
  128. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/adapters/test_ollama_tools.py +0 -0
  129. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/basemodel.py +0 -0
  130. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/json_schema.py +0 -0
  131. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/model_cache.py +0 -0
  132. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/project.py +0 -0
  133. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/prompt.py +0 -0
  134. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/registry.py +0 -0
  135. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/strict_mode.py +0 -0
  136. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_datasource.py +0 -0
  137. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_eval_model.py +0 -0
  138. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_json_schema.py +0 -0
  139. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_model_cache.py +0 -0
  140. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_model_perf.py +0 -0
  141. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_nested_save.py +0 -0
  142. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_output_rating.py +0 -0
  143. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_prompt_id.py +0 -0
  144. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/datamodel/test_registry.py +0 -0
  145. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/__init__.py +0 -0
  146. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/exhaustive_error.py +0 -0
  147. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/formatting.py +0 -0
  148. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/name_generator.py +0 -0
  149. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/kiln_ai/utils/test_name_geneator.py +0 -0
  150. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/setup.cfg +0 -0
  151. {kiln_ai-0.15.0 → kiln_ai-0.17.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiln-ai
3
- Version: 0.15.0
3
+ Version: 0.17.0
4
4
  Summary: Kiln AI
5
5
  Project-URL: Homepage, https://getkiln.ai
6
6
  Project-URL: Repository, https://github.com/Kiln-AI/kiln
@@ -17,6 +17,7 @@ The eval submodule contains the code for evaluating the performance of a model.
17
17
  """
18
18
 
19
19
  from . import (
20
+ chat,
20
21
  data_gen,
21
22
  eval,
22
23
  fine_tune,
@@ -28,6 +29,7 @@ from . import (
28
29
 
29
30
  __all__ = [
30
31
  "model_adapters",
32
+ "chat",
31
33
  "data_gen",
32
34
  "fine_tune",
33
35
  "ml_model_list",
@@ -7,31 +7,33 @@ from kiln_ai.adapters.model_adapters.litellm_adapter import (
7
7
  LiteLlmAdapter,
8
8
  LiteLlmConfig,
9
9
  )
10
- from kiln_ai.adapters.provider_tools import core_provider, lite_llm_config
11
- from kiln_ai.datamodel import PromptId
10
+ from kiln_ai.adapters.provider_tools import (
11
+ core_provider,
12
+ lite_llm_config_for_openai_compatible,
13
+ )
14
+ from kiln_ai.datamodel.task import RunConfigProperties
12
15
  from kiln_ai.utils.config import Config
13
16
  from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error
14
17
 
15
18
 
16
19
  def adapter_for_task(
17
20
  kiln_task: datamodel.Task,
18
- model_name: str,
19
- provider: ModelProviderName,
20
- prompt_id: PromptId | None = None,
21
+ run_config_properties: RunConfigProperties,
21
22
  base_adapter_config: AdapterConfig | None = None,
22
23
  ) -> BaseAdapter:
23
24
  # Get the provider to run. For things like the fine-tune provider, we want to run the underlying provider
24
- core_provider_name = core_provider(model_name, provider)
25
+ core_provider_name = core_provider(
26
+ run_config_properties.model_name, run_config_properties.model_provider_name
27
+ )
25
28
 
26
29
  match core_provider_name:
27
30
  case ModelProviderName.openrouter:
28
31
  return LiteLlmAdapter(
29
32
  kiln_task=kiln_task,
30
33
  config=LiteLlmConfig(
31
- model_name=model_name,
34
+ run_config_properties=run_config_properties,
32
35
  base_url=getenv("OPENROUTER_BASE_URL")
33
36
  or "https://openrouter.ai/api/v1",
34
- provider_name=provider,
35
37
  default_headers={
36
38
  "HTTP-Referer": "https://getkiln.ai/openrouter",
37
39
  "X-Title": "KilnAI",
@@ -40,38 +42,32 @@ def adapter_for_task(
40
42
  "api_key": Config.shared().open_router_api_key,
41
43
  },
42
44
  ),
43
- prompt_id=prompt_id,
44
45
  base_adapter_config=base_adapter_config,
45
46
  )
46
47
  case ModelProviderName.openai:
47
48
  return LiteLlmAdapter(
48
49
  kiln_task=kiln_task,
49
50
  config=LiteLlmConfig(
50
- model_name=model_name,
51
- provider_name=provider,
51
+ run_config_properties=run_config_properties,
52
52
  additional_body_options={
53
53
  "api_key": Config.shared().open_ai_api_key,
54
54
  },
55
55
  ),
56
- prompt_id=prompt_id,
57
56
  base_adapter_config=base_adapter_config,
58
57
  )
59
58
  case ModelProviderName.openai_compatible:
60
- config = lite_llm_config(model_name)
59
+ config = lite_llm_config_for_openai_compatible(run_config_properties)
61
60
  return LiteLlmAdapter(
62
61
  kiln_task=kiln_task,
63
62
  config=config,
64
- prompt_id=prompt_id,
65
63
  base_adapter_config=base_adapter_config,
66
64
  )
67
65
  case ModelProviderName.groq:
68
66
  return LiteLlmAdapter(
69
67
  kiln_task=kiln_task,
70
- prompt_id=prompt_id,
71
68
  base_adapter_config=base_adapter_config,
72
69
  config=LiteLlmConfig(
73
- model_name=model_name,
74
- provider_name=provider,
70
+ run_config_properties=run_config_properties,
75
71
  additional_body_options={
76
72
  "api_key": Config.shared().groq_api_key,
77
73
  },
@@ -80,11 +76,9 @@ def adapter_for_task(
80
76
  case ModelProviderName.amazon_bedrock:
81
77
  return LiteLlmAdapter(
82
78
  kiln_task=kiln_task,
83
- prompt_id=prompt_id,
84
79
  base_adapter_config=base_adapter_config,
85
80
  config=LiteLlmConfig(
86
- model_name=model_name,
87
- provider_name=provider,
81
+ run_config_properties=run_config_properties,
88
82
  additional_body_options={
89
83
  "aws_access_key_id": Config.shared().bedrock_access_key,
90
84
  "aws_secret_access_key": Config.shared().bedrock_secret_key,
@@ -99,11 +93,9 @@ def adapter_for_task(
99
93
  )
100
94
  return LiteLlmAdapter(
101
95
  kiln_task=kiln_task,
102
- prompt_id=prompt_id,
103
96
  base_adapter_config=base_adapter_config,
104
97
  config=LiteLlmConfig(
105
- model_name=model_name,
106
- provider_name=provider,
98
+ run_config_properties=run_config_properties,
107
99
  # Set the Ollama base URL for 2 reasons:
108
100
  # 1. To use the correct base URL
109
101
  # 2. We use Ollama's OpenAI compatible API (/v1), and don't just let litellm use the Ollama API. We use more advanced features like json_schema.
@@ -117,11 +109,9 @@ def adapter_for_task(
117
109
  case ModelProviderName.fireworks_ai:
118
110
  return LiteLlmAdapter(
119
111
  kiln_task=kiln_task,
120
- prompt_id=prompt_id,
121
112
  base_adapter_config=base_adapter_config,
122
113
  config=LiteLlmConfig(
123
- model_name=model_name,
124
- provider_name=provider,
114
+ run_config_properties=run_config_properties,
125
115
  additional_body_options={
126
116
  "api_key": Config.shared().fireworks_api_key,
127
117
  },
@@ -130,11 +120,9 @@ def adapter_for_task(
130
120
  case ModelProviderName.anthropic:
131
121
  return LiteLlmAdapter(
132
122
  kiln_task=kiln_task,
133
- prompt_id=prompt_id,
134
123
  base_adapter_config=base_adapter_config,
135
124
  config=LiteLlmConfig(
136
- model_name=model_name,
137
- provider_name=provider,
125
+ run_config_properties=run_config_properties,
138
126
  additional_body_options={
139
127
  "api_key": Config.shared().anthropic_api_key,
140
128
  },
@@ -143,11 +131,9 @@ def adapter_for_task(
143
131
  case ModelProviderName.gemini_api:
144
132
  return LiteLlmAdapter(
145
133
  kiln_task=kiln_task,
146
- prompt_id=prompt_id,
147
134
  base_adapter_config=base_adapter_config,
148
135
  config=LiteLlmConfig(
149
- model_name=model_name,
150
- provider_name=provider,
136
+ run_config_properties=run_config_properties,
151
137
  additional_body_options={
152
138
  "api_key": Config.shared().gemini_api_key,
153
139
  },
@@ -156,11 +142,9 @@ def adapter_for_task(
156
142
  case ModelProviderName.vertex:
157
143
  return LiteLlmAdapter(
158
144
  kiln_task=kiln_task,
159
- prompt_id=prompt_id,
160
145
  base_adapter_config=base_adapter_config,
161
146
  config=LiteLlmConfig(
162
- model_name=model_name,
163
- provider_name=provider,
147
+ run_config_properties=run_config_properties,
164
148
  additional_body_options={
165
149
  "vertex_project": Config.shared().vertex_project_id,
166
150
  "vertex_location": Config.shared().vertex_location,
@@ -170,11 +154,9 @@ def adapter_for_task(
170
154
  case ModelProviderName.together_ai:
171
155
  return LiteLlmAdapter(
172
156
  kiln_task=kiln_task,
173
- prompt_id=prompt_id,
174
157
  base_adapter_config=base_adapter_config,
175
158
  config=LiteLlmConfig(
176
- model_name=model_name,
177
- provider_name=provider,
159
+ run_config_properties=run_config_properties,
178
160
  additional_body_options={
179
161
  "api_key": Config.shared().together_api_key,
180
162
  },
@@ -183,12 +165,10 @@ def adapter_for_task(
183
165
  case ModelProviderName.azure_openai:
184
166
  return LiteLlmAdapter(
185
167
  kiln_task=kiln_task,
186
- prompt_id=prompt_id,
187
168
  base_adapter_config=base_adapter_config,
188
169
  config=LiteLlmConfig(
189
170
  base_url=Config.shared().azure_openai_endpoint,
190
- model_name=model_name,
191
- provider_name=provider,
171
+ run_config_properties=run_config_properties,
192
172
  additional_body_options={
193
173
  "api_key": Config.shared().azure_openai_api_key,
194
174
  "api_version": "2025-02-01-preview",
@@ -198,11 +178,9 @@ def adapter_for_task(
198
178
  case ModelProviderName.huggingface:
199
179
  return LiteLlmAdapter(
200
180
  kiln_task=kiln_task,
201
- prompt_id=prompt_id,
202
181
  base_adapter_config=base_adapter_config,
203
182
  config=LiteLlmConfig(
204
- model_name=model_name,
205
- provider_name=provider,
183
+ run_config_properties=run_config_properties,
206
184
  additional_body_options={
207
185
  "api_key": Config.shared().huggingface_api_key,
208
186
  },
@@ -0,0 +1,8 @@
1
+ from .chat_formatter import (
2
+ ChatFormatter,
3
+ ChatMessage,
4
+ ChatStrategy,
5
+ get_chat_formatter,
6
+ )
7
+
8
+ __all__ = ["ChatFormatter", "ChatMessage", "ChatStrategy", "get_chat_formatter"]
@@ -0,0 +1,234 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from abc import ABC, abstractmethod
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Dict, List, Literal, Optional
8
+
9
+ from kiln_ai.datamodel.datamodel_enums import ChatStrategy
10
+ from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error
11
+
12
+ COT_FINAL_ANSWER_PROMPT = "Considering the above, return a final result."
13
+
14
+
15
+ @dataclass
16
+ class ChatMessage:
17
+ role: Literal["system", "assistant", "user"]
18
+ content: Optional[str]
19
+
20
+
21
+ @dataclass
22
+ class ChatTurn:
23
+ """
24
+ All data needed to send a chat turn to the model.
25
+ """
26
+
27
+ messages: List[ChatMessage]
28
+ final_call: bool
29
+
30
+
31
+ class ChatFormatter(ABC):
32
+ def __init__(
33
+ self,
34
+ system_message: str,
35
+ user_input: str | Dict,
36
+ thinking_instructions: str | None = None,
37
+ ) -> None:
38
+ self.system_message = system_message
39
+ self.user_input = user_input
40
+ self.thinking_instructions = thinking_instructions
41
+ self._messages: List[ChatMessage] = []
42
+ self._state = "start"
43
+ self._intermediate_outputs: Dict[str, str] = {}
44
+
45
+ @property
46
+ def messages(self) -> List[ChatMessage]:
47
+ return list(self._messages)
48
+
49
+ def message_dicts(self) -> List[dict[str, str | None]]:
50
+ return [{"role": m.role, "content": m.content} for m in self._messages]
51
+
52
+ def intermediate_outputs(self) -> Dict[str, str]:
53
+ """Get the intermediate outputs from the chat formatter."""
54
+ return self._intermediate_outputs
55
+
56
+ @abstractmethod
57
+ def next_turn(self, previous_output: str | None = None) -> Optional[ChatTurn]:
58
+ """Advance the conversation and return the next messages if any."""
59
+ raise NotImplementedError
60
+
61
+
62
+ class SingleTurnFormatter(ChatFormatter):
63
+ def next_turn(self, previous_output: str | None = None) -> Optional[ChatTurn]:
64
+ if self._state == "start":
65
+ msgs = [
66
+ ChatMessage("system", self.system_message),
67
+ ChatMessage("user", format_user_message(self.user_input)),
68
+ ]
69
+ self._state = "awaiting_final"
70
+ self._messages.extend(msgs)
71
+ return ChatTurn(messages=msgs, final_call=True)
72
+
73
+ if self._state == "awaiting_final":
74
+ if previous_output is None:
75
+ raise ValueError("previous_output required for final step")
76
+ self._messages.append(ChatMessage("assistant", previous_output))
77
+ self._state = "done"
78
+ return None
79
+
80
+ return None
81
+
82
+
83
+ class TwoMessageCotLegacyFormatter(ChatFormatter):
84
+ def __init__(
85
+ self,
86
+ system_message: str,
87
+ user_input: str | Dict,
88
+ thinking_instructions: str | None,
89
+ ) -> None:
90
+ super().__init__(system_message, user_input, thinking_instructions)
91
+ if self.thinking_instructions is None:
92
+ raise ValueError(
93
+ "thinking_instructions are required when strategy is final_and_intermediate"
94
+ )
95
+
96
+ def next_turn(self, previous_output: str | None = None) -> Optional[ChatTurn]:
97
+ if self._state == "start":
98
+ msgs = [
99
+ ChatMessage("system", self.system_message),
100
+ ChatMessage("user", format_user_message(self.user_input)),
101
+ ChatMessage("system", self.thinking_instructions),
102
+ ]
103
+ self._state = "awaiting_thinking"
104
+ self._messages.extend(msgs)
105
+ return ChatTurn(messages=msgs, final_call=False)
106
+
107
+ if self._state == "awaiting_thinking":
108
+ if previous_output is None:
109
+ raise ValueError("previous_output required for thinking step")
110
+ msgs = [
111
+ ChatMessage("assistant", previous_output),
112
+ ChatMessage("user", COT_FINAL_ANSWER_PROMPT),
113
+ ]
114
+ self._intermediate_outputs["chain_of_thought"] = previous_output
115
+ self._state = "awaiting_final"
116
+ self._messages.extend(msgs)
117
+ return ChatTurn(messages=msgs, final_call=True)
118
+
119
+ if self._state == "awaiting_final":
120
+ if previous_output is None:
121
+ raise ValueError("previous_output required for final step")
122
+ self._messages.append(ChatMessage("assistant", previous_output))
123
+ self._state = "done"
124
+ return None
125
+
126
+ return None
127
+
128
+
129
+ class TwoMessageCotFormatter(ChatFormatter):
130
+ def __init__(
131
+ self,
132
+ system_message: str,
133
+ user_input: str | Dict,
134
+ thinking_instructions: str | None,
135
+ ) -> None:
136
+ super().__init__(system_message, user_input, thinking_instructions)
137
+ if self.thinking_instructions is None:
138
+ raise ValueError(
139
+ "thinking_instructions are required when strategy is final_and_intermediate"
140
+ )
141
+
142
+ def next_turn(self, previous_output: str | None = None) -> Optional[ChatTurn]:
143
+ if self._state == "start":
144
+ # User message combines the input and the thinking instructions
145
+ formatted_user_message = format_user_message(self.user_input)
146
+ user_message = f"The input is:\n<user_input>\n{formatted_user_message}\n</user_input>\n\n{self.thinking_instructions}"
147
+
148
+ msgs = [
149
+ ChatMessage("system", self.system_message),
150
+ ChatMessage("user", user_message),
151
+ ]
152
+ self._state = "awaiting_thinking"
153
+ self._messages.extend(msgs)
154
+ return ChatTurn(messages=msgs, final_call=False)
155
+
156
+ if self._state == "awaiting_thinking":
157
+ if previous_output is None:
158
+ raise ValueError("previous_output required for thinking step")
159
+ msgs = [
160
+ ChatMessage("assistant", previous_output),
161
+ ChatMessage("user", COT_FINAL_ANSWER_PROMPT),
162
+ ]
163
+ self._intermediate_outputs["chain_of_thought"] = previous_output
164
+ self._state = "awaiting_final"
165
+ self._messages.extend(msgs)
166
+ return ChatTurn(messages=msgs, final_call=True)
167
+
168
+ if self._state == "awaiting_final":
169
+ if previous_output is None:
170
+ raise ValueError("previous_output required for final step")
171
+ self._messages.append(ChatMessage("assistant", previous_output))
172
+ self._state = "done"
173
+ return None
174
+
175
+ return None
176
+
177
+
178
+ class SingleTurnR1ThinkingFormatter(ChatFormatter):
179
+ def next_turn(self, previous_output: str | None = None) -> Optional[ChatTurn]:
180
+ if self._state == "start":
181
+ msgs = [
182
+ ChatMessage("system", self.system_message),
183
+ ChatMessage("user", format_user_message(self.user_input)),
184
+ ]
185
+ self._state = "awaiting_final"
186
+ self._messages.extend(msgs)
187
+ return ChatTurn(messages=msgs, final_call=True)
188
+
189
+ if self._state == "awaiting_final":
190
+ if previous_output is None:
191
+ raise ValueError("previous_output required for final step")
192
+ self._messages.append(ChatMessage("assistant", previous_output))
193
+ self._state = "done"
194
+ return None
195
+
196
+ return None
197
+
198
+
199
+ def get_chat_formatter(
200
+ strategy: ChatStrategy,
201
+ system_message: str,
202
+ user_input: str | Dict,
203
+ thinking_instructions: str | None = None,
204
+ ) -> ChatFormatter:
205
+ match strategy:
206
+ case ChatStrategy.single_turn:
207
+ return SingleTurnFormatter(system_message, user_input)
208
+ case ChatStrategy.two_message_cot_legacy:
209
+ return TwoMessageCotLegacyFormatter(
210
+ system_message, user_input, thinking_instructions
211
+ )
212
+ case ChatStrategy.two_message_cot:
213
+ return TwoMessageCotFormatter(
214
+ system_message, user_input, thinking_instructions
215
+ )
216
+ case ChatStrategy.single_turn_r1_thinking:
217
+ return SingleTurnR1ThinkingFormatter(system_message, user_input)
218
+ case _:
219
+ raise_exhaustive_enum_error(strategy)
220
+
221
+
222
+ def format_user_message(input: Dict | str) -> str:
223
+ """Build a user message from the input.
224
+
225
+ Args:
226
+ input (Union[Dict, str]): The input to format into a message.
227
+
228
+ Returns:
229
+ str: The formatted user message.
230
+ """
231
+ if isinstance(input, dict):
232
+ return json.dumps(input, ensure_ascii=False)
233
+
234
+ return input
@@ -0,0 +1,131 @@
1
+ from kiln_ai.adapters.chat import ChatStrategy, get_chat_formatter
2
+ from kiln_ai.adapters.chat.chat_formatter import (
3
+ COT_FINAL_ANSWER_PROMPT,
4
+ format_user_message,
5
+ )
6
+
7
+
8
+ def test_chat_formatter_final_only():
9
+ expected = [
10
+ {"role": "system", "content": "system message"},
11
+ {"role": "user", "content": "test input"},
12
+ {"role": "assistant", "content": "test output"},
13
+ ]
14
+
15
+ formatter = get_chat_formatter(
16
+ strategy=ChatStrategy.single_turn,
17
+ system_message="system message",
18
+ user_input="test input",
19
+ )
20
+
21
+ first = formatter.next_turn()
22
+ assert [m.__dict__ for m in first.messages] == expected[:2]
23
+ assert first.final_call
24
+ assert formatter.intermediate_outputs() == {}
25
+
26
+ assert formatter.next_turn("test output") is None
27
+ assert formatter.message_dicts() == expected
28
+ assert formatter.intermediate_outputs() == {}
29
+
30
+
31
+ def test_chat_formatter_final_and_intermediate():
32
+ expected = [
33
+ {"role": "system", "content": "system message"},
34
+ {"role": "user", "content": "test input"},
35
+ {"role": "system", "content": "thinking instructions"},
36
+ {"role": "assistant", "content": "thinking output"},
37
+ {"role": "user", "content": COT_FINAL_ANSWER_PROMPT},
38
+ {"role": "assistant", "content": "test output"},
39
+ ]
40
+
41
+ formatter = get_chat_formatter(
42
+ strategy=ChatStrategy.two_message_cot_legacy,
43
+ system_message="system message",
44
+ user_input="test input",
45
+ thinking_instructions="thinking instructions",
46
+ )
47
+
48
+ first = formatter.next_turn()
49
+ assert [m.__dict__ for m in first.messages] == expected[:3]
50
+ assert not first.final_call
51
+ assert formatter.intermediate_outputs() == {}
52
+
53
+ second = formatter.next_turn("thinking output")
54
+ assert [m.__dict__ for m in second.messages] == expected[3:5]
55
+ assert second.final_call
56
+ assert formatter.intermediate_outputs() == {"chain_of_thought": "thinking output"}
57
+
58
+ assert formatter.next_turn("test output") is None
59
+ assert formatter.message_dicts() == expected
60
+ assert formatter.intermediate_outputs() == {"chain_of_thought": "thinking output"}
61
+
62
+
63
+ def test_chat_formatter_two_message_cot():
64
+ user_message = "The input is:\n<user_input>\ntest input\n</user_input>\n\nthinking instructions"
65
+ expected = [
66
+ {"role": "system", "content": "system message"},
67
+ {"role": "user", "content": user_message},
68
+ {"role": "assistant", "content": "thinking output"},
69
+ {"role": "user", "content": COT_FINAL_ANSWER_PROMPT},
70
+ {"role": "assistant", "content": "test output"},
71
+ ]
72
+
73
+ formatter = get_chat_formatter(
74
+ strategy=ChatStrategy.two_message_cot,
75
+ system_message="system message",
76
+ user_input="test input",
77
+ thinking_instructions="thinking instructions",
78
+ )
79
+
80
+ first = formatter.next_turn()
81
+ assert [m.__dict__ for m in first.messages] == expected[:2]
82
+ assert not first.final_call
83
+ assert formatter.intermediate_outputs() == {}
84
+
85
+ second = formatter.next_turn("thinking output")
86
+ assert [m.__dict__ for m in second.messages] == expected[2:4]
87
+ assert second.final_call
88
+ assert formatter.intermediate_outputs() == {"chain_of_thought": "thinking output"}
89
+
90
+ assert formatter.next_turn("test output") is None
91
+ assert formatter.message_dicts() == expected
92
+ assert formatter.intermediate_outputs() == {"chain_of_thought": "thinking output"}
93
+
94
+
95
+ def test_chat_formatter_r1_style():
96
+ thinking_output = "<think>thinking</think> answer"
97
+ expected = [
98
+ {"role": "system", "content": "system message"},
99
+ {"role": "user", "content": "test input"},
100
+ {"role": "assistant", "content": thinking_output},
101
+ ]
102
+
103
+ formatter = get_chat_formatter(
104
+ strategy=ChatStrategy.single_turn_r1_thinking,
105
+ system_message="system message",
106
+ user_input="test input",
107
+ )
108
+
109
+ first = formatter.next_turn()
110
+ assert [m.__dict__ for m in first.messages] == expected[:2]
111
+ assert first.final_call
112
+
113
+ assert formatter.next_turn(thinking_output) is None
114
+ assert formatter.message_dicts() == expected
115
+ assert formatter.intermediate_outputs() == {}
116
+
117
+
118
+ def test_format_user_message():
119
+ # String
120
+ assert format_user_message("test input") == "test input"
121
+ # JSON, preserving order
122
+ assert (
123
+ format_user_message({"test": "input", "a": "b"})
124
+ == '{"test": "input", "a": "b"}'
125
+ )
126
+
127
+
128
+ def test_simple_prompt_builder_structured_input_non_ascii():
129
+ input = {"key": "你好👋"}
130
+ user_msg = format_user_message(input)
131
+ assert "你好👋" in user_msg
@@ -14,6 +14,7 @@ from kiln_ai.adapters.data_gen.data_gen_task import (
14
14
  from kiln_ai.adapters.provider_tools import get_model_and_provider
15
15
  from kiln_ai.adapters.test_prompt_adaptors import get_all_models_and_providers
16
16
  from kiln_ai.datamodel import Project, Task
17
+ from kiln_ai.datamodel.task import RunConfigProperties
17
18
 
18
19
 
19
20
  @pytest.fixture
@@ -110,8 +111,12 @@ async def test_data_gen_all_models_providers(
110
111
 
111
112
  adapter = adapter_for_task(
112
113
  data_gen_task,
113
- model_name=model_name,
114
- provider=provider_name,
114
+ run_config_properties=RunConfigProperties(
115
+ model_name=model_name,
116
+ model_provider_name=provider_name,
117
+ prompt_id="simple_prompt_builder",
118
+ structured_output_mode="unknown",
119
+ ),
115
120
  )
116
121
 
117
122
  input_dict = data_gen_input.model_dump()
@@ -254,8 +259,12 @@ async def test_data_gen_sample_all_models_providers(
254
259
 
255
260
  adapter = adapter_for_task(
256
261
  data_gen_task,
257
- model_name=model_name,
258
- provider=provider_name,
262
+ run_config_properties=RunConfigProperties(
263
+ model_name=model_name,
264
+ model_provider_name=provider_name,
265
+ prompt_id="simple_prompt_builder",
266
+ structured_output_mode="unknown",
267
+ ),
259
268
  )
260
269
 
261
270
  input_dict = data_gen_input.model_dump()
@@ -304,8 +313,12 @@ async def test_data_gen_sample_all_models_providers_with_structured_output(
304
313
 
305
314
  adapter = adapter_for_task(
306
315
  data_gen_task,
307
- model_name=model_name,
308
- provider=provider_name,
316
+ run_config_properties=RunConfigProperties(
317
+ model_name=model_name,
318
+ model_provider_name=provider_name,
319
+ prompt_id="simple_prompt_builder",
320
+ structured_output_mode="unknown",
321
+ ),
309
322
  )
310
323
 
311
324
  input_dict = data_gen_input.model_dump()