Initial commit of integrated agent system
This commit is contained in:
153
backend/agents/prompt_loader.py
Normal file
153
backend/agents/prompt_loader.py
Normal file
@@ -0,0 +1,153 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Prompt Loader - Unified management and loading of Agent Prompts
|
||||
Supports Markdown and YAML formats
|
||||
Uses simple string replacement, does not depend on Jinja2
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
# Singleton instance
|
||||
_prompt_loader_instance: Optional["PromptLoader"] = None
|
||||
|
||||
|
||||
def get_prompt_loader() -> "PromptLoader":
|
||||
"""Get the singleton PromptLoader instance."""
|
||||
global _prompt_loader_instance
|
||||
if _prompt_loader_instance is None:
|
||||
_prompt_loader_instance = PromptLoader()
|
||||
return _prompt_loader_instance
|
||||
|
||||
|
||||
class PromptLoader:
|
||||
"""Unified Prompt loader"""
|
||||
|
||||
def __init__(self, prompts_dir: Optional[Path] = None):
|
||||
"""
|
||||
Initialize Prompt loader
|
||||
|
||||
Args:
|
||||
prompts_dir: Prompts directory path,
|
||||
defaults to prompts/ directory of current file
|
||||
"""
|
||||
if prompts_dir is None:
|
||||
self.prompts_dir = Path(__file__).parent / "prompts"
|
||||
else:
|
||||
self.prompts_dir = Path(prompts_dir)
|
||||
|
||||
def load_prompt(
|
||||
self,
|
||||
agent_type: str,
|
||||
prompt_name: str,
|
||||
variables: Optional[Dict[str, Any]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Load and render Prompt.
|
||||
|
||||
No caching — always reads fresh from disk (CoPaw-style).
|
||||
"""
|
||||
prompt_path = self.prompts_dir / agent_type / f"{prompt_name}.md"
|
||||
|
||||
if not prompt_path.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Prompt file not found: {prompt_path}\n"
|
||||
f"Please create the prompt file or check the path.",
|
||||
)
|
||||
|
||||
with open(prompt_path, "r", encoding="utf-8") as f:
|
||||
prompt_template = f.read()
|
||||
|
||||
# If variables provided, use simple string replacement
|
||||
if variables:
|
||||
rendered = self._render_template(prompt_template, variables)
|
||||
else:
|
||||
rendered = prompt_template
|
||||
|
||||
return rendered
|
||||
|
||||
def _render_template(
|
||||
self,
|
||||
template: str,
|
||||
variables: Dict[str, Any],
|
||||
) -> str:
|
||||
"""
|
||||
Render template using simple string replacement
|
||||
Supports {{ variable }} syntax (compatible with previous Jinja2 format)
|
||||
|
||||
Args:
|
||||
template: Template string
|
||||
variables: Variable dictionary
|
||||
|
||||
Returns:
|
||||
Rendered string
|
||||
"""
|
||||
rendered = template
|
||||
|
||||
# Replace {{ variable }} format
|
||||
for key, value in variables.items():
|
||||
# Support both {{ key }} and {{key}} formats
|
||||
pattern1 = f"{{{{ {key} }}}}"
|
||||
pattern2 = f"{{{{{key}}}}}"
|
||||
rendered = rendered.replace(pattern1, str(value))
|
||||
rendered = rendered.replace(pattern2, str(value))
|
||||
|
||||
return rendered
|
||||
|
||||
def _escape_json_braces(self, text: str) -> str:
|
||||
"""
|
||||
Escape braces in JSON code blocks, treating them as literals
|
||||
|
||||
Args:
|
||||
text: Text to process
|
||||
|
||||
Returns:
|
||||
Processed text
|
||||
"""
|
||||
|
||||
def replace_code_block(match):
|
||||
code_content = match.group(1)
|
||||
# Escape all braces within code block
|
||||
escaped = code_content.replace("{", "{{").replace("}", "}}")
|
||||
return f"```json\n{escaped}\n```"
|
||||
|
||||
# Replace all braces in JSON code blocks
|
||||
text = re.sub(
|
||||
r"```json\n(.*?)\n```",
|
||||
replace_code_block,
|
||||
text,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
return text
|
||||
|
||||
def load_yaml_config(
|
||||
self,
|
||||
agent_type: str,
|
||||
config_name: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Load YAML configuration file.
|
||||
|
||||
No caching — always reads fresh from disk (CoPaw-style).
|
||||
"""
|
||||
yaml_path = self.prompts_dir / agent_type / f"{config_name}.yaml"
|
||||
|
||||
if not yaml_path.exists():
|
||||
raise FileNotFoundError(f"YAML config not found: {yaml_path}")
|
||||
|
||||
with open(yaml_path, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
|
||||
def clear_cache(self):
|
||||
"""No-op — caching removed (CoPaw-style, always fresh reads)."""
|
||||
pass
|
||||
|
||||
def reload_prompt(self, agent_type: str, prompt_name: str):
|
||||
"""No-op — caching removed."""
|
||||
pass
|
||||
|
||||
def reload_config(self, agent_type: str, config_name: str):
|
||||
"""No-op — caching removed."""
|
||||
pass
|
||||
Reference in New Issue
Block a user