| """ |
| Utility for loading prompts and instructions from external JSON files. |
| """ |
|
|
| import os |
| import json |
| from pathlib import Path |
| from typing import Dict, Optional, List, Any |
| import logging |
|
|
| logger = logging.getLogger(__name__) |
|
|
| class PromptLoader: |
| """Loads prompts and instructions from external JSON files.""" |
| |
| def __init__(self, base_dir: Optional[Path] = None): |
| """Initialize with base directory.""" |
| if base_dir is None: |
| |
| self.base_dir = Path(__file__).parent.parent |
| else: |
| self.base_dir = Path(base_dir) |
| |
| self.prompts_dir = self.base_dir / "prompts" |
| self.instructions_dir = self.base_dir / "instructions" |
| |
| |
| self._cache: Dict[str, Dict[str, Any]] = {} |
| |
| def load_prompt(self, prompt_name: str, **kwargs) -> str: |
| """ |
| Load a prompt from the prompts directory. |
| Supports both .txt (plain text) and .json formats. |
| |
| Args: |
| prompt_name: Name of the prompt file (without extension) |
| **kwargs: Variables to substitute in the prompt |
| |
| Returns: |
| The loaded prompt with variables substituted |
| """ |
| |
| txt_path = self.prompts_dir / f"{prompt_name}.txt" |
| json_path = self.prompts_dir / f"{prompt_name}.json" |
| |
| if txt_path.exists(): |
| |
| logger.debug(f"Loading prompt from .txt file: {txt_path}") |
| with open(txt_path, 'r', encoding='utf-8') as f: |
| prompt_text = f.read().strip() |
| elif json_path.exists(): |
| |
| logger.debug(f"Loading prompt from .json file: {json_path}") |
| data = self._load_json_file(json_path) |
| prompt_data = data.get("prompt", "") |
| |
| |
| if isinstance(prompt_data, list): |
| |
| prompt_text = "\n".join(prompt_data) |
| else: |
| prompt_text = prompt_data |
| else: |
| raise FileNotFoundError(f"Prompt file not found: {prompt_name} (checked .txt and .json)") |
| |
| |
| if kwargs: |
| try: |
| logger.debug(f"Formatting prompt {prompt_name} with variables: {list(kwargs.keys())}") |
| prompt_text = prompt_text.format(**kwargs) |
| logger.debug(f"Successfully formatted prompt {prompt_name}") |
| except KeyError as e: |
| logger.warning(f"Missing variable {e} in prompt {prompt_name}") |
| except Exception as e: |
| logger.error(f"Error formatting prompt {prompt_name}: {e}") |
| logger.error(f"Available variables: {list(kwargs.keys())}") |
| |
| return prompt_text |
| |
| def load_instruction(self, instruction_name: str) -> str: |
| """ |
| Load instructions from the instructions directory as a single string. |
| |
| Args: |
| instruction_name: Name of the instruction file (without .json extension) |
| |
| Returns: |
| The loaded instructions as a joined string |
| """ |
| instructions_list = self.load_instructions_as_list(instruction_name) |
| return "\n".join(instructions_list) |
| |
| def load_instructions_as_list(self, instruction_name: str) -> List[str]: |
| """ |
| Load instructions and return as a list of strings. |
| |
| Args: |
| instruction_name: Name of the instruction file (without .json extension) |
| |
| Returns: |
| List of instruction strings |
| """ |
| instruction_path = self.instructions_dir / f"{instruction_name}.json" |
| data = self._load_json_file(instruction_path) |
| |
| instructions = data.get("instructions", []) |
| |
| |
| return [instruction for instruction in instructions if instruction.strip()] |
| |
| def _load_json_file(self, file_path: Path) -> Dict[str, Any]: |
| """Load JSON file content with caching.""" |
| cache_key = str(file_path) |
| |
| |
| if cache_key in self._cache: |
| return self._cache[cache_key] |
| |
| try: |
| if not file_path.exists(): |
| raise FileNotFoundError(f"File not found: {file_path}") |
| |
| with open(file_path, 'r', encoding='utf-8') as f: |
| data = json.load(f) |
| |
| |
| self._cache[cache_key] = data |
| logger.debug(f"Loaded {file_path.name}: {type(data)} with {len(data)} keys") |
| |
| return data |
| |
| except json.JSONDecodeError as e: |
| logger.error(f"Invalid JSON in file {file_path}: {e}") |
| raise |
| except Exception as e: |
| logger.error(f"Error loading file {file_path}: {e}") |
| raise |
| |
| def clear_cache(self): |
| """Clear the file cache.""" |
| self._cache.clear() |
| logger.debug("Prompt loader cache cleared") |
| |
| def list_prompts(self) -> List[str]: |
| """List all available prompt files.""" |
| if not self.prompts_dir.exists(): |
| return [] |
| |
| prompts = [] |
| for file_path in self.prompts_dir.rglob("*.json"): |
| |
| rel_path = file_path.relative_to(self.prompts_dir) |
| |
| prompt_name = str(rel_path.with_suffix('')) |
| prompts.append(prompt_name) |
| |
| return sorted(prompts) |
| |
| def list_instructions(self) -> List[str]: |
| """List all available instruction files.""" |
| if not self.instructions_dir.exists(): |
| return [] |
| |
| instructions = [] |
| for file_path in self.instructions_dir.rglob("*.json"): |
| |
| rel_path = file_path.relative_to(self.instructions_dir) |
| |
| instruction_name = str(rel_path.with_suffix('')) |
| instructions.append(instruction_name) |
| |
| return sorted(instructions) |
| |
| def get_info(self) -> dict: |
| """Get information about the prompt loader.""" |
| return { |
| "base_dir": str(self.base_dir), |
| "prompts_dir": str(self.prompts_dir), |
| "instructions_dir": str(self.instructions_dir), |
| "prompts_dir_exists": self.prompts_dir.exists(), |
| "instructions_dir_exists": self.instructions_dir.exists(), |
| "available_prompts": self.list_prompts(), |
| "available_instructions": self.list_instructions(), |
| "cache_size": len(self._cache) |
| } |
|
|
| |
| prompt_loader = PromptLoader() |