kiln_ai.adapters.prompt_builders

  1from __future__ import annotations
  2
  3from abc import ABCMeta, abstractmethod
  4from dataclasses import dataclass
  5from typing import TYPE_CHECKING
  6
  7from kiln_ai.datamodel import PromptGenerators, PromptId, Task, TaskRun
  8from kiln_ai.utils.exhaustive_error import raise_exhaustive_enum_error
  9
 10if TYPE_CHECKING:
 11    from kiln_ai.datamodel.skill import Skill
 12
 13
 14@dataclass
 15class PromptExample:
 16    """A simple input/output example for use in prompt building."""
 17
 18    input: str
 19    output: str
 20
 21
 22def build_skills_prompt_section(skills: list[Skill] | None) -> str | None:
 23    """Build a system prompt section listing available skills.
 24
 25    This is a standalone function so both the inference adapter and the
 26    fine-tune pipeline can include the same skills instructions in the
 27    system prompt.
 28    """
 29    if not skills:
 30        return None
 31
 32    skill_lines = "\n".join(f"- `{s.name}`\n  {s.description}" for s in skills)
 33
 34    return (
 35        "# Skills\n\n"
 36        "When a Skill is relevant, load it with `skill(name)` and follow its instructions.\n"
 37        "Load additional Skill resources only if needed with `skill(name, resource)`.\n"
 38        "## Available Skills\n\n"
 39        f"{skill_lines}"
 40    )
 41
 42
 43class BasePromptBuilder(metaclass=ABCMeta):
 44    """Base class for building prompts from tasks.
 45
 46    Provides the core interface and basic functionality for prompt builders.
 47    """
 48
 49    def __init__(self, task: Task):
 50        """Initialize the prompt builder with a task.
 51
 52        Args:
 53            task (Task): The task containing instructions and requirements.
 54        """
 55        self.task = task
 56
 57    def prompt_id(self) -> str | None:
 58        """Returns the ID of the prompt, scoped to this builder.
 59
 60        Returns:
 61            str | None: The ID of the prompt, or None if not set.
 62        """
 63        return None
 64
 65    def build_prompt(
 66        self,
 67        include_json_instructions: bool,
 68        skills: list[Skill] | None = None,
 69    ) -> str:
 70        """Build and return the complete prompt string.
 71
 72        Args:
 73            include_json_instructions: Whether to include JSON schema formatting instructions.
 74            skills: Optional list of skills to include in the prompt. When provided,
 75                a skills instruction section is appended so the model knows which
 76                skills are available.
 77
 78        Returns:
 79            str: The constructed prompt.
 80        """
 81        prompt = self.build_base_prompt()
 82
 83        if include_json_instructions and self.task.output_schema():
 84            prompt = (
 85                prompt
 86                + f"\n\n# Format Instructions\n\nReturn a JSON object conforming to the following schema:\n```\n{self.task.output_schema()}\n```"
 87            )
 88
 89        skills_section = build_skills_prompt_section(skills)
 90        if skills_section:
 91            prompt = prompt + "\n\n" + skills_section
 92
 93        return prompt
 94
 95    @abstractmethod
 96    def build_base_prompt(self) -> str:
 97        """Build and return the complete prompt string.
 98
 99        Returns:
100            str: The constructed prompt.
101        """
102        pass
103
104    def chain_of_thought_prompt(self) -> str | None:
105        """Build and return the chain of thought prompt string.
106
107        Returns:
108            str: The constructed chain of thought prompt.
109        """
110        return None
111
112    def build_prompt_for_ui(self, skills: list[Skill] | None = None) -> str:
113        """Build a prompt for the UI. It includes additional instructions (like chain of thought), even if they are passed to the model in stages.
114
115        Designed for end-user consumption, not for model consumption.
116
117        Args:
118            skills: Optional list of skills to include in the prompt display.
119
120        Returns:
121            str: The constructed prompt string.
122        """
123        base_prompt = self.build_prompt(include_json_instructions=False, skills=skills)
124        cot_prompt = self.chain_of_thought_prompt()
125        if cot_prompt:
126            base_prompt += "\n\n# Thinking Instructions\n\n" + cot_prompt
127        return base_prompt
128
129
130class SimplePromptBuilder(BasePromptBuilder):
131    """A basic prompt builder that combines task instruction with requirements."""
132
133    def build_base_prompt(self) -> str:
134        """Build a simple prompt with instruction and requirements.
135
136        Returns:
137            str: The constructed prompt string.
138        """
139        base_prompt = self.task.instruction
140
141        if len(self.task.requirements) > 0:
142            base_prompt += (
143                "\n\nYour response should respect the following requirements:\n"
144            )
145            # iterate requirements, formatting them in numbered list like 1) task.instruction\n2)...
146            for i, requirement in enumerate(self.task.requirements):
147                base_prompt += f"{i + 1}) {requirement.instruction}\n"
148
149        return base_prompt
150
151
152class MultiShotPromptBuilder(BasePromptBuilder):
153    """A prompt builder that includes multiple examples in the prompt."""
154
155    @classmethod
156    def example_count(cls) -> int:
157        """Get the maximum number of examples to include in the prompt.
158
159        Returns:
160            int: The maximum number of examples (default 25).
161        """
162        return 25
163
164    def build_instruction_and_requirements(self) -> str:
165        """Build the instruction and requirements section of the prompt.
166
167        Returns:
168            str: The instruction and requirements sections.
169        """
170        base_prompt = f"# Instruction\n\n{self.task.instruction}\n\n"
171
172        if len(self.task.requirements) > 0:
173            base_prompt += "# Requirements\n\nYour response should respect the following requirements:\n"
174            for i, requirement in enumerate(self.task.requirements):
175                base_prompt += f"{i + 1}) {requirement.instruction}\n"
176            base_prompt += "\n"
177
178        return base_prompt
179
180    def build_base_prompt(self) -> str:
181        """Build a prompt with instruction, requirements, and multiple examples.
182
183        Returns:
184            str: The constructed prompt string with examples.
185        """
186        base_prompt = self.build_instruction_and_requirements()
187
188        valid_examples = self.collect_examples()
189
190        if len(valid_examples) == 0:
191            return base_prompt
192
193        base_prompt += "# Example Outputs\n\n"
194        for i, example in enumerate(valid_examples):
195            base_prompt += self.prompt_section_for_example(i, example)
196
197        return base_prompt
198
199    def prompt_section_for_example(self, index: int, example: TaskRun) -> str:
200        # Prefer repaired output if it exists, otherwise use the regular output
201        output = example.repaired_output or example.output
202        return f"## Example {index + 1}\n\nInput: {example.input}\nOutput: {output.output}\n\n"
203
204    def collect_examples(self) -> list[TaskRun]:
205        valid_examples: list[TaskRun] = []
206        runs = self.task.runs(readonly=True)
207
208        # first pass, we look for repaired outputs. These are the best examples.
209        for run in runs:
210            if len(valid_examples) >= self.__class__.example_count():
211                break
212            if run.repaired_output is not None:
213                valid_examples.append(run)
214
215        # second pass, we look for high quality outputs (rating based)
216        # Minimum is "high_quality" (4 star in star rating scale), then sort by rating
217        # exclude repaired outputs as they were used above
218        runs_with_rating = [
219            run
220            for run in runs
221            if run.output.rating is not None
222            and run.output.rating.value is not None
223            and run.output.rating.is_high_quality()
224            and run.repaired_output is None
225        ]
226        runs_with_rating.sort(
227            key=lambda x: (x.output.rating and x.output.rating.value) or 0, reverse=True
228        )
229        for run in runs_with_rating:
230            if len(valid_examples) >= self.__class__.example_count():
231                break
232            valid_examples.append(run)
233        return valid_examples
234
235
236class FewShotPromptBuilder(MultiShotPromptBuilder):
237    """A prompt builder that includes a small number of examples in the prompt."""
238
239    @classmethod
240    def example_count(cls) -> int:
241        """Get the maximum number of examples to include in the prompt.
242
243        Returns:
244            int: The maximum number of examples (4).
245        """
246        return 4
247
248
249class CustomExamplePromptBuilder(FewShotPromptBuilder):
250    """A prompt builder that uses custom examples instead of collecting from the dataset.
251
252    The base prompt is normally built from the task's instruction and requirements. A
253    base_prompt_override can be supplied (e.g. the task's default run config prompt) to
254    use the real production prompt as the base instead, with the custom examples appended.
255    """
256
257    def __init__(
258        self,
259        task: Task,
260        examples: list[PromptExample] | None = None,
261        base_prompt_override: str | None = None,
262    ):
263        super().__init__(task)
264        self._custom_examples = examples or []
265        self._base_prompt_override = base_prompt_override
266
267    def collect_examples(self) -> list[TaskRun]:
268        """Override to return an empty list - we handle examples separately."""
269        return []
270
271    def build_base_prompt(self) -> str:
272        """Build a prompt with instruction (or override), requirements, and custom examples."""
273        if self._base_prompt_override is not None:
274            base_prompt = self._base_prompt_override
275        else:
276            base_prompt = self.build_instruction_and_requirements()
277
278        if self._custom_examples:
279            if not base_prompt.endswith("\n\n"):
280                base_prompt = base_prompt.rstrip("\n") + "\n\n"
281            base_prompt += "# Example Outputs\n\n"
282            for i, example in enumerate(self._custom_examples):
283                base_prompt += f"## Example {i + 1}\n\nInput: {example.input}\nOutput: {example.output}\n\n"
284
285        return base_prompt
286
287
288class RepairsPromptBuilder(MultiShotPromptBuilder):
289    """A prompt builder that includes multiple examples in the prompt, including repaired instructions describing what was wrong, and how it was fixed."""
290
291    def prompt_section_for_example(self, index: int, example: TaskRun) -> str:
292        if (
293            not example.repaired_output
294            or not example.repair_instructions
295            or not example.repaired_output.output
296        ):
297            return super().prompt_section_for_example(index, example)
298
299        prompt_section = f"## Example {index + 1}\n\nInput: {example.input}\n\n"
300        prompt_section += (
301            f"Initial Output Which Was Insufficient: {example.output.output}\n\n"
302        )
303        prompt_section += f"Instructions On How to Improve the Initial Output: {example.repair_instructions}\n\n"
304        prompt_section += (
305            f"Repaired Output Which is Sufficient: {example.repaired_output.output}\n\n"
306        )
307        return prompt_section
308
309
310def chain_of_thought_prompt(task: Task) -> str:
311    """Standard implementation to build and return the chain of thought prompt string.
312
313    Returns:
314        str: The constructed chain of thought prompt.
315    """
316
317    cot_instruction = task.thinking_instruction
318    if not cot_instruction:
319        cot_instruction = "Think step by step, explaining your reasoning."
320
321    return cot_instruction
322
323
324class SimpleChainOfThoughtPromptBuilder(SimplePromptBuilder):
325    """A prompt builder that includes a chain of thought prompt on top of the simple prompt."""
326
327    def chain_of_thought_prompt(self) -> str | None:
328        return chain_of_thought_prompt(self.task)
329
330
331class FewShotChainOfThoughtPromptBuilder(FewShotPromptBuilder):
332    """A prompt builder that includes a chain of thought prompt on top of the few shot prompt."""
333
334    def chain_of_thought_prompt(self) -> str | None:
335        return chain_of_thought_prompt(self.task)
336
337
338class MultiShotChainOfThoughtPromptBuilder(MultiShotPromptBuilder):
339    """A prompt builder that includes a chain of thought prompt on top of the multi shot prompt."""
340
341    def chain_of_thought_prompt(self) -> str | None:
342        return chain_of_thought_prompt(self.task)
343
344
345class SavedPromptBuilder(BasePromptBuilder):
346    """A prompt builder that looks up a static prompt."""
347
348    def __init__(self, task: Task, prompt_id: str):
349        super().__init__(task)
350        prompt_model = next(
351            (
352                prompt
353                for prompt in task.prompts(readonly=True)
354                if prompt.id == prompt_id
355            ),
356            None,
357        )
358        if not prompt_model:
359            raise ValueError(f"Prompt ID not found: {prompt_id}")
360        self.prompt_model = prompt_model
361
362    def prompt_id(self) -> str | None:
363        return self.prompt_model.id
364
365    def build_base_prompt(self) -> str:
366        """Returns a saved prompt.
367
368        Returns:
369            str: The prompt string.
370        """
371        return self.prompt_model.prompt
372
373    def chain_of_thought_prompt(self) -> str | None:
374        return self.prompt_model.chain_of_thought_instructions
375
376
377class TaskRunConfigPromptBuilder(BasePromptBuilder):
378    """A prompt builder that looks up a static prompt in a task run config."""
379
380    def __init__(self, task: Task, run_config_prompt_id: str):
381        parts = run_config_prompt_id.split("::")
382        if len(parts) != 4:
383            raise ValueError(
384                f"Invalid task run config prompt ID: {run_config_prompt_id}. Expected format: 'task_run_config::[project_id]::[task_id]::[run_config_id]'."
385            )
386
387        task_id = parts[2]
388        if task_id != task.id:
389            raise ValueError(
390                f"Task run config prompt ID: {run_config_prompt_id}. Task ID mismatch. Expected: {task.id}, got: {task_id}."
391            )
392
393        run_config_id = parts[3]
394        run_config = next(
395            (
396                run_config
397                for run_config in task.run_configs(readonly=True)
398                if run_config.id == run_config_id
399            ),
400            None,
401        )
402        if not run_config:
403            raise ValueError(
404                f"Task run config ID not found: {run_config_id} for prompt id {run_config_prompt_id}"
405            )
406        if run_config.prompt is None:
407            raise ValueError(
408                f"Task run config ID {run_config_id} does not have a stored prompt. Used as prompt id {run_config_prompt_id}"
409            )
410
411        # Load the prompt from the model
412        self.prompt = run_config.prompt.prompt
413        self.cot_prompt = run_config.prompt.chain_of_thought_instructions
414        self.id = run_config_prompt_id
415
416        super().__init__(task)
417
418    def prompt_id(self) -> str | None:
419        return self.id
420
421    def build_base_prompt(self) -> str:
422        return self.prompt
423
424    def chain_of_thought_prompt(self) -> str | None:
425        return self.cot_prompt
426
427
428class FineTunePromptBuilder(BasePromptBuilder):
429    """A prompt builder that looks up a fine-tune prompt."""
430
431    def __init__(self, task: Task, nested_fine_tune_id: str):
432        super().__init__(task)
433
434        # IDs are in project_id::task_id::fine_tune_id format
435        self.full_fine_tune_id = nested_fine_tune_id
436        parts = nested_fine_tune_id.split("::")
437        if len(parts) != 3:
438            raise ValueError(
439                f"Invalid fine-tune ID format. Expected 'project_id::task_id::fine_tune_id', got: {nested_fine_tune_id}"
440            )
441        fine_tune_id = parts[2]
442
443        fine_tune_model = next(
444            (
445                fine_tune
446                for fine_tune in task.finetunes(readonly=True)
447                if fine_tune.id == fine_tune_id
448            ),
449            None,
450        )
451        if not fine_tune_model:
452            raise ValueError(f"Fine-tune ID not found: {fine_tune_id}")
453        self.fine_tune_model = fine_tune_model
454
455    def prompt_id(self) -> str | None:
456        return self.full_fine_tune_id
457
458    def build_base_prompt(self) -> str:
459        return self.fine_tune_model.system_message
460
461    def chain_of_thought_prompt(self) -> str | None:
462        return self.fine_tune_model.thinking_instructions
463
464
465# Our UI has some names that are not the same as the class names, which also hint parameters.
466def prompt_builder_from_id(prompt_id: PromptId, task: Task) -> BasePromptBuilder:
467    """Convert a name used in the UI to the corresponding prompt builder class.
468
469    Args:
470        prompt_id (PromptId): The prompt ID.
471
472    Returns:
473        type[BasePromptBuilder]: The corresponding prompt builder class.
474
475    Raises:
476        ValueError: If the UI name is not recognized.
477    """
478
479    # Saved prompts are prefixed with "id::"
480    if prompt_id.startswith("id::"):
481        prompt_id = prompt_id[4:]
482        return SavedPromptBuilder(task, prompt_id)
483
484    # Task run config prompts are prefixed with "task_run_config::"
485    # task_run_config::[project_id]::[task_id]::[run_config_id]
486    if prompt_id.startswith("task_run_config::"):
487        return TaskRunConfigPromptBuilder(task, prompt_id)
488
489    # Fine-tune prompts are prefixed with "fine_tune_prompt::"
490    if prompt_id.startswith("fine_tune_prompt::"):
491        prompt_id = prompt_id[18:]
492        return FineTunePromptBuilder(task, prompt_id)
493
494    # Check if the prompt_id matches any enum value
495    if prompt_id not in [member.value for member in PromptGenerators]:
496        raise ValueError(f"Unknown prompt generator: {prompt_id}")
497    typed_prompt_generator = PromptGenerators(prompt_id)
498
499    match typed_prompt_generator:
500        case PromptGenerators.SIMPLE:
501            return SimplePromptBuilder(task)
502        case PromptGenerators.FEW_SHOT:
503            return FewShotPromptBuilder(task)
504        case PromptGenerators.MULTI_SHOT:
505            return MultiShotPromptBuilder(task)
506        case PromptGenerators.REPAIRS:
507            return RepairsPromptBuilder(task)
508        case PromptGenerators.SIMPLE_CHAIN_OF_THOUGHT:
509            return SimpleChainOfThoughtPromptBuilder(task)
510        case PromptGenerators.FEW_SHOT_CHAIN_OF_THOUGHT:
511            return FewShotChainOfThoughtPromptBuilder(task)
512        case PromptGenerators.MULTI_SHOT_CHAIN_OF_THOUGHT:
513            return MultiShotChainOfThoughtPromptBuilder(task)
514        case _:
515            # Type checking will find missing cases
516            raise_exhaustive_enum_error(typed_prompt_generator)
@dataclass
class PromptExample:
15@dataclass
16class PromptExample:
17    """A simple input/output example for use in prompt building."""
18
19    input: str
20    output: str

A simple input/output example for use in prompt building.

PromptExample(input: str, output: str)
input: str
output: str
def build_skills_prompt_section(skills: list[kiln_ai.datamodel.Skill] | None) -> str | None:
23def build_skills_prompt_section(skills: list[Skill] | None) -> str | None:
24    """Build a system prompt section listing available skills.
25
26    This is a standalone function so both the inference adapter and the
27    fine-tune pipeline can include the same skills instructions in the
28    system prompt.
29    """
30    if not skills:
31        return None
32
33    skill_lines = "\n".join(f"- `{s.name}`\n  {s.description}" for s in skills)
34
35    return (
36        "# Skills\n\n"
37        "When a Skill is relevant, load it with `skill(name)` and follow its instructions.\n"
38        "Load additional Skill resources only if needed with `skill(name, resource)`.\n"
39        "## Available Skills\n\n"
40        f"{skill_lines}"
41    )

Build a system prompt section listing available skills.

This is a standalone function so both the inference adapter and the fine-tune pipeline can include the same skills instructions in the system prompt.

class BasePromptBuilder:
 44class BasePromptBuilder(metaclass=ABCMeta):
 45    """Base class for building prompts from tasks.
 46
 47    Provides the core interface and basic functionality for prompt builders.
 48    """
 49
 50    def __init__(self, task: Task):
 51        """Initialize the prompt builder with a task.
 52
 53        Args:
 54            task (Task): The task containing instructions and requirements.
 55        """
 56        self.task = task
 57
 58    def prompt_id(self) -> str | None:
 59        """Returns the ID of the prompt, scoped to this builder.
 60
 61        Returns:
 62            str | None: The ID of the prompt, or None if not set.
 63        """
 64        return None
 65
 66    def build_prompt(
 67        self,
 68        include_json_instructions: bool,
 69        skills: list[Skill] | None = None,
 70    ) -> str:
 71        """Build and return the complete prompt string.
 72
 73        Args:
 74            include_json_instructions: Whether to include JSON schema formatting instructions.
 75            skills: Optional list of skills to include in the prompt. When provided,
 76                a skills instruction section is appended so the model knows which
 77                skills are available.
 78
 79        Returns:
 80            str: The constructed prompt.
 81        """
 82        prompt = self.build_base_prompt()
 83
 84        if include_json_instructions and self.task.output_schema():
 85            prompt = (
 86                prompt
 87                + f"\n\n# Format Instructions\n\nReturn a JSON object conforming to the following schema:\n```\n{self.task.output_schema()}\n```"
 88            )
 89
 90        skills_section = build_skills_prompt_section(skills)
 91        if skills_section:
 92            prompt = prompt + "\n\n" + skills_section
 93
 94        return prompt
 95
 96    @abstractmethod
 97    def build_base_prompt(self) -> str:
 98        """Build and return the complete prompt string.
 99
100        Returns:
101            str: The constructed prompt.
102        """
103        pass
104
105    def chain_of_thought_prompt(self) -> str | None:
106        """Build and return the chain of thought prompt string.
107
108        Returns:
109            str: The constructed chain of thought prompt.
110        """
111        return None
112
113    def build_prompt_for_ui(self, skills: list[Skill] | None = None) -> str:
114        """Build a prompt for the UI. It includes additional instructions (like chain of thought), even if they are passed to the model in stages.
115
116        Designed for end-user consumption, not for model consumption.
117
118        Args:
119            skills: Optional list of skills to include in the prompt display.
120
121        Returns:
122            str: The constructed prompt string.
123        """
124        base_prompt = self.build_prompt(include_json_instructions=False, skills=skills)
125        cot_prompt = self.chain_of_thought_prompt()
126        if cot_prompt:
127            base_prompt += "\n\n# Thinking Instructions\n\n" + cot_prompt
128        return base_prompt

Base class for building prompts from tasks.

Provides the core interface and basic functionality for prompt builders.

BasePromptBuilder(task: kiln_ai.datamodel.Task)
50    def __init__(self, task: Task):
51        """Initialize the prompt builder with a task.
52
53        Args:
54            task (Task): The task containing instructions and requirements.
55        """
56        self.task = task

Initialize the prompt builder with a task.

Args: task (Task): The task containing instructions and requirements.

task
def prompt_id(self) -> str | None:
58    def prompt_id(self) -> str | None:
59        """Returns the ID of the prompt, scoped to this builder.
60
61        Returns:
62            str | None: The ID of the prompt, or None if not set.
63        """
64        return None

Returns the ID of the prompt, scoped to this builder.

Returns: str | None: The ID of the prompt, or None if not set.

def build_prompt( self, include_json_instructions: bool, skills: list[kiln_ai.datamodel.Skill] | None = None) -> str:
66    def build_prompt(
67        self,
68        include_json_instructions: bool,
69        skills: list[Skill] | None = None,
70    ) -> str:
71        """Build and return the complete prompt string.
72
73        Args:
74            include_json_instructions: Whether to include JSON schema formatting instructions.
75            skills: Optional list of skills to include in the prompt. When provided,
76                a skills instruction section is appended so the model knows which
77                skills are available.
78
79        Returns:
80            str: The constructed prompt.
81        """
82        prompt = self.build_base_prompt()
83
84        if include_json_instructions and self.task.output_schema():
85            prompt = (
86                prompt
87                + f"\n\n# Format Instructions\n\nReturn a JSON object conforming to the following schema:\n```\n{self.task.output_schema()}\n```"
88            )
89
90        skills_section = build_skills_prompt_section(skills)
91        if skills_section:
92            prompt = prompt + "\n\n" + skills_section
93
94        return prompt

Build and return the complete prompt string.

Args: include_json_instructions: Whether to include JSON schema formatting instructions. skills: Optional list of skills to include in the prompt. When provided, a skills instruction section is appended so the model knows which skills are available.

Returns: str: The constructed prompt.

@abstractmethod
def build_base_prompt(self) -> str:
 96    @abstractmethod
 97    def build_base_prompt(self) -> str:
 98        """Build and return the complete prompt string.
 99
100        Returns:
101            str: The constructed prompt.
102        """
103        pass

Build and return the complete prompt string.

Returns: str: The constructed prompt.

def chain_of_thought_prompt(self) -> str | None:
105    def chain_of_thought_prompt(self) -> str | None:
106        """Build and return the chain of thought prompt string.
107
108        Returns:
109            str: The constructed chain of thought prompt.
110        """
111        return None

Build and return the chain of thought prompt string.

Returns: str: The constructed chain of thought prompt.

def build_prompt_for_ui(self, skills: list[kiln_ai.datamodel.Skill] | None = None) -> str:
113    def build_prompt_for_ui(self, skills: list[Skill] | None = None) -> str:
114        """Build a prompt for the UI. It includes additional instructions (like chain of thought), even if they are passed to the model in stages.
115
116        Designed for end-user consumption, not for model consumption.
117
118        Args:
119            skills: Optional list of skills to include in the prompt display.
120
121        Returns:
122            str: The constructed prompt string.
123        """
124        base_prompt = self.build_prompt(include_json_instructions=False, skills=skills)
125        cot_prompt = self.chain_of_thought_prompt()
126        if cot_prompt:
127            base_prompt += "\n\n# Thinking Instructions\n\n" + cot_prompt
128        return base_prompt

Build a prompt for the UI. It includes additional instructions (like chain of thought), even if they are passed to the model in stages.

Designed for end-user consumption, not for model consumption.

Args: skills: Optional list of skills to include in the prompt display.

Returns: str: The constructed prompt string.

class SimplePromptBuilder(BasePromptBuilder):
131class SimplePromptBuilder(BasePromptBuilder):
132    """A basic prompt builder that combines task instruction with requirements."""
133
134    def build_base_prompt(self) -> str:
135        """Build a simple prompt with instruction and requirements.
136
137        Returns:
138            str: The constructed prompt string.
139        """
140        base_prompt = self.task.instruction
141
142        if len(self.task.requirements) > 0:
143            base_prompt += (
144                "\n\nYour response should respect the following requirements:\n"
145            )
146            # iterate requirements, formatting them in numbered list like 1) task.instruction\n2)...
147            for i, requirement in enumerate(self.task.requirements):
148                base_prompt += f"{i + 1}) {requirement.instruction}\n"
149
150        return base_prompt

A basic prompt builder that combines task instruction with requirements.

def build_base_prompt(self) -> str:
134    def build_base_prompt(self) -> str:
135        """Build a simple prompt with instruction and requirements.
136
137        Returns:
138            str: The constructed prompt string.
139        """
140        base_prompt = self.task.instruction
141
142        if len(self.task.requirements) > 0:
143            base_prompt += (
144                "\n\nYour response should respect the following requirements:\n"
145            )
146            # iterate requirements, formatting them in numbered list like 1) task.instruction\n2)...
147            for i, requirement in enumerate(self.task.requirements):
148                base_prompt += f"{i + 1}) {requirement.instruction}\n"
149
150        return base_prompt

Build a simple prompt with instruction and requirements.

Returns: str: The constructed prompt string.

class MultiShotPromptBuilder(BasePromptBuilder):
153class MultiShotPromptBuilder(BasePromptBuilder):
154    """A prompt builder that includes multiple examples in the prompt."""
155
156    @classmethod
157    def example_count(cls) -> int:
158        """Get the maximum number of examples to include in the prompt.
159
160        Returns:
161            int: The maximum number of examples (default 25).
162        """
163        return 25
164
165    def build_instruction_and_requirements(self) -> str:
166        """Build the instruction and requirements section of the prompt.
167
168        Returns:
169            str: The instruction and requirements sections.
170        """
171        base_prompt = f"# Instruction\n\n{self.task.instruction}\n\n"
172
173        if len(self.task.requirements) > 0:
174            base_prompt += "# Requirements\n\nYour response should respect the following requirements:\n"
175            for i, requirement in enumerate(self.task.requirements):
176                base_prompt += f"{i + 1}) {requirement.instruction}\n"
177            base_prompt += "\n"
178
179        return base_prompt
180
181    def build_base_prompt(self) -> str:
182        """Build a prompt with instruction, requirements, and multiple examples.
183
184        Returns:
185            str: The constructed prompt string with examples.
186        """
187        base_prompt = self.build_instruction_and_requirements()
188
189        valid_examples = self.collect_examples()
190
191        if len(valid_examples) == 0:
192            return base_prompt
193
194        base_prompt += "# Example Outputs\n\n"
195        for i, example in enumerate(valid_examples):
196            base_prompt += self.prompt_section_for_example(i, example)
197
198        return base_prompt
199
200    def prompt_section_for_example(self, index: int, example: TaskRun) -> str:
201        # Prefer repaired output if it exists, otherwise use the regular output
202        output = example.repaired_output or example.output
203        return f"## Example {index + 1}\n\nInput: {example.input}\nOutput: {output.output}\n\n"
204
205    def collect_examples(self) -> list[TaskRun]:
206        valid_examples: list[TaskRun] = []
207        runs = self.task.runs(readonly=True)
208
209        # first pass, we look for repaired outputs. These are the best examples.
210        for run in runs:
211            if len(valid_examples) >= self.__class__.example_count():
212                break
213            if run.repaired_output is not None:
214                valid_examples.append(run)
215
216        # second pass, we look for high quality outputs (rating based)
217        # Minimum is "high_quality" (4 star in star rating scale), then sort by rating
218        # exclude repaired outputs as they were used above
219        runs_with_rating = [
220            run
221            for run in runs
222            if run.output.rating is not None
223            and run.output.rating.value is not None
224            and run.output.rating.is_high_quality()
225            and run.repaired_output is None
226        ]
227        runs_with_rating.sort(
228            key=lambda x: (x.output.rating and x.output.rating.value) or 0, reverse=True
229        )
230        for run in runs_with_rating:
231            if len(valid_examples) >= self.__class__.example_count():
232                break
233            valid_examples.append(run)
234        return valid_examples

A prompt builder that includes multiple examples in the prompt.

@classmethod
def example_count(cls) -> int:
156    @classmethod
157    def example_count(cls) -> int:
158        """Get the maximum number of examples to include in the prompt.
159
160        Returns:
161            int: The maximum number of examples (default 25).
162        """
163        return 25

Get the maximum number of examples to include in the prompt.

Returns: int: The maximum number of examples (default 25).

def build_instruction_and_requirements(self) -> str:
165    def build_instruction_and_requirements(self) -> str:
166        """Build the instruction and requirements section of the prompt.
167
168        Returns:
169            str: The instruction and requirements sections.
170        """
171        base_prompt = f"# Instruction\n\n{self.task.instruction}\n\n"
172
173        if len(self.task.requirements) > 0:
174            base_prompt += "# Requirements\n\nYour response should respect the following requirements:\n"
175            for i, requirement in enumerate(self.task.requirements):
176                base_prompt += f"{i + 1}) {requirement.instruction}\n"
177            base_prompt += "\n"
178
179        return base_prompt

Build the instruction and requirements section of the prompt.

Returns: str: The instruction and requirements sections.

def build_base_prompt(self) -> str:
181    def build_base_prompt(self) -> str:
182        """Build a prompt with instruction, requirements, and multiple examples.
183
184        Returns:
185            str: The constructed prompt string with examples.
186        """
187        base_prompt = self.build_instruction_and_requirements()
188
189        valid_examples = self.collect_examples()
190
191        if len(valid_examples) == 0:
192            return base_prompt
193
194        base_prompt += "# Example Outputs\n\n"
195        for i, example in enumerate(valid_examples):
196            base_prompt += self.prompt_section_for_example(i, example)
197
198        return base_prompt

Build a prompt with instruction, requirements, and multiple examples.

Returns: str: The constructed prompt string with examples.

def prompt_section_for_example(self, index: int, example: kiln_ai.datamodel.TaskRun) -> str:
200    def prompt_section_for_example(self, index: int, example: TaskRun) -> str:
201        # Prefer repaired output if it exists, otherwise use the regular output
202        output = example.repaired_output or example.output
203        return f"## Example {index + 1}\n\nInput: {example.input}\nOutput: {output.output}\n\n"
def collect_examples(self) -> list[kiln_ai.datamodel.TaskRun]:
205    def collect_examples(self) -> list[TaskRun]:
206        valid_examples: list[TaskRun] = []
207        runs = self.task.runs(readonly=True)
208
209        # first pass, we look for repaired outputs. These are the best examples.
210        for run in runs:
211            if len(valid_examples) >= self.__class__.example_count():
212                break
213            if run.repaired_output is not None:
214                valid_examples.append(run)
215
216        # second pass, we look for high quality outputs (rating based)
217        # Minimum is "high_quality" (4 star in star rating scale), then sort by rating
218        # exclude repaired outputs as they were used above
219        runs_with_rating = [
220            run
221            for run in runs
222            if run.output.rating is not None
223            and run.output.rating.value is not None
224            and run.output.rating.is_high_quality()
225            and run.repaired_output is None
226        ]
227        runs_with_rating.sort(
228            key=lambda x: (x.output.rating and x.output.rating.value) or 0, reverse=True
229        )
230        for run in runs_with_rating:
231            if len(valid_examples) >= self.__class__.example_count():
232                break
233            valid_examples.append(run)
234        return valid_examples
class FewShotPromptBuilder(MultiShotPromptBuilder):
237class FewShotPromptBuilder(MultiShotPromptBuilder):
238    """A prompt builder that includes a small number of examples in the prompt."""
239
240    @classmethod
241    def example_count(cls) -> int:
242        """Get the maximum number of examples to include in the prompt.
243
244        Returns:
245            int: The maximum number of examples (4).
246        """
247        return 4

A prompt builder that includes a small number of examples in the prompt.

@classmethod
def example_count(cls) -> int:
240    @classmethod
241    def example_count(cls) -> int:
242        """Get the maximum number of examples to include in the prompt.
243
244        Returns:
245            int: The maximum number of examples (4).
246        """
247        return 4

Get the maximum number of examples to include in the prompt.

Returns: int: The maximum number of examples (4).

class CustomExamplePromptBuilder(FewShotPromptBuilder):
250class CustomExamplePromptBuilder(FewShotPromptBuilder):
251    """A prompt builder that uses custom examples instead of collecting from the dataset.
252
253    The base prompt is normally built from the task's instruction and requirements. A
254    base_prompt_override can be supplied (e.g. the task's default run config prompt) to
255    use the real production prompt as the base instead, with the custom examples appended.
256    """
257
258    def __init__(
259        self,
260        task: Task,
261        examples: list[PromptExample] | None = None,
262        base_prompt_override: str | None = None,
263    ):
264        super().__init__(task)
265        self._custom_examples = examples or []
266        self._base_prompt_override = base_prompt_override
267
268    def collect_examples(self) -> list[TaskRun]:
269        """Override to return an empty list - we handle examples separately."""
270        return []
271
272    def build_base_prompt(self) -> str:
273        """Build a prompt with instruction (or override), requirements, and custom examples."""
274        if self._base_prompt_override is not None:
275            base_prompt = self._base_prompt_override
276        else:
277            base_prompt = self.build_instruction_and_requirements()
278
279        if self._custom_examples:
280            if not base_prompt.endswith("\n\n"):
281                base_prompt = base_prompt.rstrip("\n") + "\n\n"
282            base_prompt += "# Example Outputs\n\n"
283            for i, example in enumerate(self._custom_examples):
284                base_prompt += f"## Example {i + 1}\n\nInput: {example.input}\nOutput: {example.output}\n\n"
285
286        return base_prompt

A prompt builder that uses custom examples instead of collecting from the dataset.

The base prompt is normally built from the task's instruction and requirements. A base_prompt_override can be supplied (e.g. the task's default run config prompt) to use the real production prompt as the base instead, with the custom examples appended.

CustomExamplePromptBuilder( task: kiln_ai.datamodel.Task, examples: list[PromptExample] | None = None, base_prompt_override: str | None = None)
258    def __init__(
259        self,
260        task: Task,
261        examples: list[PromptExample] | None = None,
262        base_prompt_override: str | None = None,
263    ):
264        super().__init__(task)
265        self._custom_examples = examples or []
266        self._base_prompt_override = base_prompt_override

Initialize the prompt builder with a task.

Args: task (Task): The task containing instructions and requirements.

def collect_examples(self) -> list[kiln_ai.datamodel.TaskRun]:
268    def collect_examples(self) -> list[TaskRun]:
269        """Override to return an empty list - we handle examples separately."""
270        return []

Override to return an empty list - we handle examples separately.

def build_base_prompt(self) -> str:
272    def build_base_prompt(self) -> str:
273        """Build a prompt with instruction (or override), requirements, and custom examples."""
274        if self._base_prompt_override is not None:
275            base_prompt = self._base_prompt_override
276        else:
277            base_prompt = self.build_instruction_and_requirements()
278
279        if self._custom_examples:
280            if not base_prompt.endswith("\n\n"):
281                base_prompt = base_prompt.rstrip("\n") + "\n\n"
282            base_prompt += "# Example Outputs\n\n"
283            for i, example in enumerate(self._custom_examples):
284                base_prompt += f"## Example {i + 1}\n\nInput: {example.input}\nOutput: {example.output}\n\n"
285
286        return base_prompt

Build a prompt with instruction (or override), requirements, and custom examples.

class RepairsPromptBuilder(MultiShotPromptBuilder):
289class RepairsPromptBuilder(MultiShotPromptBuilder):
290    """A prompt builder that includes multiple examples in the prompt, including repaired instructions describing what was wrong, and how it was fixed."""
291
292    def prompt_section_for_example(self, index: int, example: TaskRun) -> str:
293        if (
294            not example.repaired_output
295            or not example.repair_instructions
296            or not example.repaired_output.output
297        ):
298            return super().prompt_section_for_example(index, example)
299
300        prompt_section = f"## Example {index + 1}\n\nInput: {example.input}\n\n"
301        prompt_section += (
302            f"Initial Output Which Was Insufficient: {example.output.output}\n\n"
303        )
304        prompt_section += f"Instructions On How to Improve the Initial Output: {example.repair_instructions}\n\n"
305        prompt_section += (
306            f"Repaired Output Which is Sufficient: {example.repaired_output.output}\n\n"
307        )
308        return prompt_section

A prompt builder that includes multiple examples in the prompt, including repaired instructions describing what was wrong, and how it was fixed.

def prompt_section_for_example(self, index: int, example: kiln_ai.datamodel.TaskRun) -> str:
292    def prompt_section_for_example(self, index: int, example: TaskRun) -> str:
293        if (
294            not example.repaired_output
295            or not example.repair_instructions
296            or not example.repaired_output.output
297        ):
298            return super().prompt_section_for_example(index, example)
299
300        prompt_section = f"## Example {index + 1}\n\nInput: {example.input}\n\n"
301        prompt_section += (
302            f"Initial Output Which Was Insufficient: {example.output.output}\n\n"
303        )
304        prompt_section += f"Instructions On How to Improve the Initial Output: {example.repair_instructions}\n\n"
305        prompt_section += (
306            f"Repaired Output Which is Sufficient: {example.repaired_output.output}\n\n"
307        )
308        return prompt_section
def chain_of_thought_prompt(task: kiln_ai.datamodel.Task) -> str:
311def chain_of_thought_prompt(task: Task) -> str:
312    """Standard implementation to build and return the chain of thought prompt string.
313
314    Returns:
315        str: The constructed chain of thought prompt.
316    """
317
318    cot_instruction = task.thinking_instruction
319    if not cot_instruction:
320        cot_instruction = "Think step by step, explaining your reasoning."
321
322    return cot_instruction

Standard implementation to build and return the chain of thought prompt string.

Returns: str: The constructed chain of thought prompt.

class SimpleChainOfThoughtPromptBuilder(SimplePromptBuilder):
325class SimpleChainOfThoughtPromptBuilder(SimplePromptBuilder):
326    """A prompt builder that includes a chain of thought prompt on top of the simple prompt."""
327
328    def chain_of_thought_prompt(self) -> str | None:
329        return chain_of_thought_prompt(self.task)

A prompt builder that includes a chain of thought prompt on top of the simple prompt.

def chain_of_thought_prompt(self) -> str | None:
328    def chain_of_thought_prompt(self) -> str | None:
329        return chain_of_thought_prompt(self.task)

Build and return the chain of thought prompt string.

Returns: str: The constructed chain of thought prompt.

class FewShotChainOfThoughtPromptBuilder(FewShotPromptBuilder):
332class FewShotChainOfThoughtPromptBuilder(FewShotPromptBuilder):
333    """A prompt builder that includes a chain of thought prompt on top of the few shot prompt."""
334
335    def chain_of_thought_prompt(self) -> str | None:
336        return chain_of_thought_prompt(self.task)

A prompt builder that includes a chain of thought prompt on top of the few shot prompt.

def chain_of_thought_prompt(self) -> str | None:
335    def chain_of_thought_prompt(self) -> str | None:
336        return chain_of_thought_prompt(self.task)

Build and return the chain of thought prompt string.

Returns: str: The constructed chain of thought prompt.

class MultiShotChainOfThoughtPromptBuilder(MultiShotPromptBuilder):
339class MultiShotChainOfThoughtPromptBuilder(MultiShotPromptBuilder):
340    """A prompt builder that includes a chain of thought prompt on top of the multi shot prompt."""
341
342    def chain_of_thought_prompt(self) -> str | None:
343        return chain_of_thought_prompt(self.task)

A prompt builder that includes a chain of thought prompt on top of the multi shot prompt.

def chain_of_thought_prompt(self) -> str | None:
342    def chain_of_thought_prompt(self) -> str | None:
343        return chain_of_thought_prompt(self.task)

Build and return the chain of thought prompt string.

Returns: str: The constructed chain of thought prompt.

class SavedPromptBuilder(BasePromptBuilder):
346class SavedPromptBuilder(BasePromptBuilder):
347    """A prompt builder that looks up a static prompt."""
348
349    def __init__(self, task: Task, prompt_id: str):
350        super().__init__(task)
351        prompt_model = next(
352            (
353                prompt
354                for prompt in task.prompts(readonly=True)
355                if prompt.id == prompt_id
356            ),
357            None,
358        )
359        if not prompt_model:
360            raise ValueError(f"Prompt ID not found: {prompt_id}")
361        self.prompt_model = prompt_model
362
363    def prompt_id(self) -> str | None:
364        return self.prompt_model.id
365
366    def build_base_prompt(self) -> str:
367        """Returns a saved prompt.
368
369        Returns:
370            str: The prompt string.
371        """
372        return self.prompt_model.prompt
373
374    def chain_of_thought_prompt(self) -> str | None:
375        return self.prompt_model.chain_of_thought_instructions

A prompt builder that looks up a static prompt.

SavedPromptBuilder(task: kiln_ai.datamodel.Task, prompt_id: str)
349    def __init__(self, task: Task, prompt_id: str):
350        super().__init__(task)
351        prompt_model = next(
352            (
353                prompt
354                for prompt in task.prompts(readonly=True)
355                if prompt.id == prompt_id
356            ),
357            None,
358        )
359        if not prompt_model:
360            raise ValueError(f"Prompt ID not found: {prompt_id}")
361        self.prompt_model = prompt_model

Initialize the prompt builder with a task.

Args: task (Task): The task containing instructions and requirements.

prompt_model
def prompt_id(self) -> str | None:
363    def prompt_id(self) -> str | None:
364        return self.prompt_model.id

Returns the ID of the prompt, scoped to this builder.

Returns: str | None: The ID of the prompt, or None if not set.

def build_base_prompt(self) -> str:
366    def build_base_prompt(self) -> str:
367        """Returns a saved prompt.
368
369        Returns:
370            str: The prompt string.
371        """
372        return self.prompt_model.prompt

Returns a saved prompt.

Returns: str: The prompt string.

def chain_of_thought_prompt(self) -> str | None:
374    def chain_of_thought_prompt(self) -> str | None:
375        return self.prompt_model.chain_of_thought_instructions

Build and return the chain of thought prompt string.

Returns: str: The constructed chain of thought prompt.

class TaskRunConfigPromptBuilder(BasePromptBuilder):
378class TaskRunConfigPromptBuilder(BasePromptBuilder):
379    """A prompt builder that looks up a static prompt in a task run config."""
380
381    def __init__(self, task: Task, run_config_prompt_id: str):
382        parts = run_config_prompt_id.split("::")
383        if len(parts) != 4:
384            raise ValueError(
385                f"Invalid task run config prompt ID: {run_config_prompt_id}. Expected format: 'task_run_config::[project_id]::[task_id]::[run_config_id]'."
386            )
387
388        task_id = parts[2]
389        if task_id != task.id:
390            raise ValueError(
391                f"Task run config prompt ID: {run_config_prompt_id}. Task ID mismatch. Expected: {task.id}, got: {task_id}."
392            )
393
394        run_config_id = parts[3]
395        run_config = next(
396            (
397                run_config
398                for run_config in task.run_configs(readonly=True)
399                if run_config.id == run_config_id
400            ),
401            None,
402        )
403        if not run_config:
404            raise ValueError(
405                f"Task run config ID not found: {run_config_id} for prompt id {run_config_prompt_id}"
406            )
407        if run_config.prompt is None:
408            raise ValueError(
409                f"Task run config ID {run_config_id} does not have a stored prompt. Used as prompt id {run_config_prompt_id}"
410            )
411
412        # Load the prompt from the model
413        self.prompt = run_config.prompt.prompt
414        self.cot_prompt = run_config.prompt.chain_of_thought_instructions
415        self.id = run_config_prompt_id
416
417        super().__init__(task)
418
419    def prompt_id(self) -> str | None:
420        return self.id
421
422    def build_base_prompt(self) -> str:
423        return self.prompt
424
425    def chain_of_thought_prompt(self) -> str | None:
426        return self.cot_prompt

A prompt builder that looks up a static prompt in a task run config.

TaskRunConfigPromptBuilder(task: kiln_ai.datamodel.Task, run_config_prompt_id: str)
381    def __init__(self, task: Task, run_config_prompt_id: str):
382        parts = run_config_prompt_id.split("::")
383        if len(parts) != 4:
384            raise ValueError(
385                f"Invalid task run config prompt ID: {run_config_prompt_id}. Expected format: 'task_run_config::[project_id]::[task_id]::[run_config_id]'."
386            )
387
388        task_id = parts[2]
389        if task_id != task.id:
390            raise ValueError(
391                f"Task run config prompt ID: {run_config_prompt_id}. Task ID mismatch. Expected: {task.id}, got: {task_id}."
392            )
393
394        run_config_id = parts[3]
395        run_config = next(
396            (
397                run_config
398                for run_config in task.run_configs(readonly=True)
399                if run_config.id == run_config_id
400            ),
401            None,
402        )
403        if not run_config:
404            raise ValueError(
405                f"Task run config ID not found: {run_config_id} for prompt id {run_config_prompt_id}"
406            )
407        if run_config.prompt is None:
408            raise ValueError(
409                f"Task run config ID {run_config_id} does not have a stored prompt. Used as prompt id {run_config_prompt_id}"
410            )
411
412        # Load the prompt from the model
413        self.prompt = run_config.prompt.prompt
414        self.cot_prompt = run_config.prompt.chain_of_thought_instructions
415        self.id = run_config_prompt_id
416
417        super().__init__(task)

Initialize the prompt builder with a task.

Args: task (Task): The task containing instructions and requirements.

prompt
cot_prompt
id
def prompt_id(self) -> str | None:
419    def prompt_id(self) -> str | None:
420        return self.id

Returns the ID of the prompt, scoped to this builder.

Returns: str | None: The ID of the prompt, or None if not set.

def build_base_prompt(self) -> str:
422    def build_base_prompt(self) -> str:
423        return self.prompt

Build and return the complete prompt string.

Returns: str: The constructed prompt.

def chain_of_thought_prompt(self) -> str | None:
425    def chain_of_thought_prompt(self) -> str | None:
426        return self.cot_prompt

Build and return the chain of thought prompt string.

Returns: str: The constructed chain of thought prompt.

class FineTunePromptBuilder(BasePromptBuilder):
429class FineTunePromptBuilder(BasePromptBuilder):
430    """A prompt builder that looks up a fine-tune prompt."""
431
432    def __init__(self, task: Task, nested_fine_tune_id: str):
433        super().__init__(task)
434
435        # IDs are in project_id::task_id::fine_tune_id format
436        self.full_fine_tune_id = nested_fine_tune_id
437        parts = nested_fine_tune_id.split("::")
438        if len(parts) != 3:
439            raise ValueError(
440                f"Invalid fine-tune ID format. Expected 'project_id::task_id::fine_tune_id', got: {nested_fine_tune_id}"
441            )
442        fine_tune_id = parts[2]
443
444        fine_tune_model = next(
445            (
446                fine_tune
447                for fine_tune in task.finetunes(readonly=True)
448                if fine_tune.id == fine_tune_id
449            ),
450            None,
451        )
452        if not fine_tune_model:
453            raise ValueError(f"Fine-tune ID not found: {fine_tune_id}")
454        self.fine_tune_model = fine_tune_model
455
456    def prompt_id(self) -> str | None:
457        return self.full_fine_tune_id
458
459    def build_base_prompt(self) -> str:
460        return self.fine_tune_model.system_message
461
462    def chain_of_thought_prompt(self) -> str | None:
463        return self.fine_tune_model.thinking_instructions

A prompt builder that looks up a fine-tune prompt.

FineTunePromptBuilder(task: kiln_ai.datamodel.Task, nested_fine_tune_id: str)
432    def __init__(self, task: Task, nested_fine_tune_id: str):
433        super().__init__(task)
434
435        # IDs are in project_id::task_id::fine_tune_id format
436        self.full_fine_tune_id = nested_fine_tune_id
437        parts = nested_fine_tune_id.split("::")
438        if len(parts) != 3:
439            raise ValueError(
440                f"Invalid fine-tune ID format. Expected 'project_id::task_id::fine_tune_id', got: {nested_fine_tune_id}"
441            )
442        fine_tune_id = parts[2]
443
444        fine_tune_model = next(
445            (
446                fine_tune
447                for fine_tune in task.finetunes(readonly=True)
448                if fine_tune.id == fine_tune_id
449            ),
450            None,
451        )
452        if not fine_tune_model:
453            raise ValueError(f"Fine-tune ID not found: {fine_tune_id}")
454        self.fine_tune_model = fine_tune_model

Initialize the prompt builder with a task.

Args: task (Task): The task containing instructions and requirements.

full_fine_tune_id
fine_tune_model
def prompt_id(self) -> str | None:
456    def prompt_id(self) -> str | None:
457        return self.full_fine_tune_id

Returns the ID of the prompt, scoped to this builder.

Returns: str | None: The ID of the prompt, or None if not set.

def build_base_prompt(self) -> str:
459    def build_base_prompt(self) -> str:
460        return self.fine_tune_model.system_message

Build and return the complete prompt string.

Returns: str: The constructed prompt.

def chain_of_thought_prompt(self) -> str | None:
462    def chain_of_thought_prompt(self) -> str | None:
463        return self.fine_tune_model.thinking_instructions

Build and return the chain of thought prompt string.

Returns: str: The constructed chain of thought prompt.

def prompt_builder_from_id( prompt_id: Annotated[str, AfterValidator(func=<function <lambda>>)], task: kiln_ai.datamodel.Task) -> BasePromptBuilder:
467def prompt_builder_from_id(prompt_id: PromptId, task: Task) -> BasePromptBuilder:
468    """Convert a name used in the UI to the corresponding prompt builder class.
469
470    Args:
471        prompt_id (PromptId): The prompt ID.
472
473    Returns:
474        type[BasePromptBuilder]: The corresponding prompt builder class.
475
476    Raises:
477        ValueError: If the UI name is not recognized.
478    """
479
480    # Saved prompts are prefixed with "id::"
481    if prompt_id.startswith("id::"):
482        prompt_id = prompt_id[4:]
483        return SavedPromptBuilder(task, prompt_id)
484
485    # Task run config prompts are prefixed with "task_run_config::"
486    # task_run_config::[project_id]::[task_id]::[run_config_id]
487    if prompt_id.startswith("task_run_config::"):
488        return TaskRunConfigPromptBuilder(task, prompt_id)
489
490    # Fine-tune prompts are prefixed with "fine_tune_prompt::"
491    if prompt_id.startswith("fine_tune_prompt::"):
492        prompt_id = prompt_id[18:]
493        return FineTunePromptBuilder(task, prompt_id)
494
495    # Check if the prompt_id matches any enum value
496    if prompt_id not in [member.value for member in PromptGenerators]:
497        raise ValueError(f"Unknown prompt generator: {prompt_id}")
498    typed_prompt_generator = PromptGenerators(prompt_id)
499
500    match typed_prompt_generator:
501        case PromptGenerators.SIMPLE:
502            return SimplePromptBuilder(task)
503        case PromptGenerators.FEW_SHOT:
504            return FewShotPromptBuilder(task)
505        case PromptGenerators.MULTI_SHOT:
506            return MultiShotPromptBuilder(task)
507        case PromptGenerators.REPAIRS:
508            return RepairsPromptBuilder(task)
509        case PromptGenerators.SIMPLE_CHAIN_OF_THOUGHT:
510            return SimpleChainOfThoughtPromptBuilder(task)
511        case PromptGenerators.FEW_SHOT_CHAIN_OF_THOUGHT:
512            return FewShotChainOfThoughtPromptBuilder(task)
513        case PromptGenerators.MULTI_SHOT_CHAIN_OF_THOUGHT:
514            return MultiShotChainOfThoughtPromptBuilder(task)
515        case _:
516            # Type checking will find missing cases
517            raise_exhaustive_enum_error(typed_prompt_generator)

Convert a name used in the UI to the corresponding prompt builder class.

Args: prompt_id (PromptId): The prompt ID.

Returns: type[BasePromptBuilder]: The corresponding prompt builder class.

Raises: ValueError: If the UI name is not recognized.