Source code for prompt_recipe_smith.models

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from prompt_recipe_smith.exceptions import TooManyBranchesError, TooManyQuestionsError

MAX_BRANCHES = 3
MAX_LAYERED_QUESTIONS = 3


[docs] @dataclass(frozen=True) class PromptTemplate: """A fixed prompt form with named variables.""" template: str
[docs] def render(self, variables: dict[str, Any]) -> str: return self.template.format_map(_SafeFormatDict(variables))
[docs] @dataclass(frozen=True) class PromptBranch: """One simple conditional path in a prompt recipe.""" name: str description: str keyword: str | None = None keywords: tuple[str, ...] = () template: PromptTemplate | None = None
[docs] def matches(self, user_input: str) -> bool: keywords = self.keywords if self.keyword is not None: keywords = (self.keyword, *keywords) normalized_input = user_input.casefold() return any(keyword.casefold() in normalized_input for keyword in keywords)
[docs] @dataclass(frozen=True) class PromptQuestion: """One clarification question asked before rendering a final prompt.""" key: str text: str
[docs] @dataclass(frozen=True) class PromptRecipe: """A reusable conditional prompt-building procedure.""" name: str description: str final_template: PromptTemplate steps: tuple[str, ...] = () branches: tuple[PromptBranch, ...] = () questions: tuple[PromptQuestion, ...] = () layered_final_template: PromptTemplate | None = None examples: tuple[str, ...] = () defaults: dict[str, str] = field(default_factory=dict) def __post_init__(self) -> None: if len(self.branches) > MAX_BRANCHES: msg = f"PromptRecipe supports up to {MAX_BRANCHES} branches." raise TooManyBranchesError(msg) if len(self.questions) > MAX_LAYERED_QUESTIONS: msg = ( "PromptRecipe supports up to " f"{MAX_LAYERED_QUESTIONS} layered questions." ) raise TooManyQuestionsError(msg)
[docs] @dataclass(frozen=True) class PromptSession: """One prompt-building interaction.""" recipe_name: str user_input: str selected_branch: str | None = None answers: tuple[tuple[str, str], ...] = () next_question: PromptQuestion | None = None complete: bool = False
[docs] @dataclass(frozen=True) class PromptResult: """The result of applying a recipe to user input.""" prompt: str recipe_name: str user_input: str selected_branch: str | None = None steps: tuple[str, ...] = () provider: str = "chatgpt" answers: tuple[tuple[str, str], ...] = ()
[docs] def to_dict(self) -> dict[str, str | list[str] | dict[str, str] | None]: return { "prompt": self.prompt, "recipe_name": self.recipe_name, "user_input": self.user_input, "selected_branch": self.selected_branch, "steps": list(self.steps), "provider": self.provider, "answers": dict(self.answers), }
class _SafeFormatDict(dict[str, Any]): def __missing__(self, key: str) -> str: return "{" + key + "}"