| | """ |
| | 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() |