- backend/services/calendar_service.py: genera 13 slot con distribuzione PN (4v+2s+2n+3r+1c+1p) e Schwartz (L5=3,L4=3,L3=4,L2=2,L1=1), ordina per funnel, ruota nicchie, calcola date
- backend/services/prompt_service.py: carica/compila/elenca prompt {{variabile}}, ValueError per variabili mancanti
- backend/data/prompts/system_prompt.txt: sistema prompt esperto content marketing B2B italiano
- backend/data/prompts/topic_generator.txt: generazione topic per slot calendario
- backend/data/prompts/pas_valore.txt: formato PAS per post valore educativo
- backend/data/prompts/listicle_valore.txt: formato Listicle per post valore
- backend/data/prompts/bab_storytelling.txt: formato BAB per post storytelling
- backend/data/prompts/aida_promozione.txt: formato AIDA per post promozionale
- backend/data/prompts/dato_news.txt: formato Dato+Implicazione per post news
170 lines
5.7 KiB
Python
170 lines
5.7 KiB
Python
"""PromptService — carica, lista e compila prompt .txt con variabili.
|
|
|
|
Gestisce i file .txt dei prompt LLM nella directory PROMPTS_PATH.
|
|
Usa la sintassi {{variabile}} per i placeholder (doppia graffa).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
# Pattern per trovare le variabili {{nome}} nei template
|
|
_VARIABLE_PATTERN = re.compile(r"\{\{(\w+)\}\}")
|
|
|
|
|
|
class PromptService:
|
|
"""Servizio per gestire i prompt .txt del sistema di generazione.
|
|
|
|
Fornisce metodi per:
|
|
- Elencare i prompt disponibili
|
|
- Caricare il contenuto di un prompt
|
|
- Compilare un prompt sostituendo le variabili {{...}}
|
|
- Salvare un prompt (per l'editor di Phase 2)
|
|
- Estrarre la lista di variabili richieste da un template
|
|
"""
|
|
|
|
def __init__(self, prompts_dir: Path) -> None:
|
|
"""Inizializza il servizio con la directory dei prompt.
|
|
|
|
Args:
|
|
prompts_dir: Path alla directory contenente i file .txt dei prompt.
|
|
Tipicamente PROMPTS_PATH da backend.config.
|
|
|
|
Raises:
|
|
FileNotFoundError: Se la directory non esiste.
|
|
"""
|
|
if not prompts_dir.exists():
|
|
raise FileNotFoundError(
|
|
f"Directory prompt non trovata: {prompts_dir}. "
|
|
"Verifica che PROMPTS_PATH sia configurato correttamente."
|
|
)
|
|
if not prompts_dir.is_dir():
|
|
raise NotADirectoryError(
|
|
f"Il percorso non è una directory: {prompts_dir}"
|
|
)
|
|
self._prompts_dir = prompts_dir
|
|
|
|
def list_prompts(self) -> list[str]:
|
|
"""Elenca tutti i prompt .txt disponibili nella directory.
|
|
|
|
Returns:
|
|
Lista di nomi file senza estensione, ordinata alfabeticamente.
|
|
Es: ['aida_promozione', 'bab_storytelling', 'system_prompt', ...]
|
|
"""
|
|
return sorted(
|
|
p.stem for p in self._prompts_dir.glob("*.txt") if p.is_file()
|
|
)
|
|
|
|
def load_prompt(self, name: str) -> str:
|
|
"""Carica il contenuto grezzo di un prompt .txt.
|
|
|
|
Args:
|
|
name: Nome del prompt senza estensione (es. "pas_valore")
|
|
|
|
Returns:
|
|
Contenuto testuale del file prompt
|
|
|
|
Raises:
|
|
FileNotFoundError: Se il file non esiste
|
|
"""
|
|
path = self._get_path(name)
|
|
if not path.exists():
|
|
available = self.list_prompts()
|
|
raise FileNotFoundError(
|
|
f"Prompt '{name}' non trovato in {self._prompts_dir}. "
|
|
f"Prompt disponibili: {available}"
|
|
)
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
def compile_prompt(self, name: str, variables: dict[str, str]) -> str:
|
|
"""Carica un prompt e sostituisce tutte le variabili {{nome}} con i valori forniti.
|
|
|
|
Args:
|
|
name: Nome del prompt senza estensione
|
|
variables: Dizionario { nome_variabile: valore }
|
|
|
|
Returns:
|
|
Testo del prompt con tutte le variabili sostituite
|
|
|
|
Raises:
|
|
FileNotFoundError: Se il prompt non esiste
|
|
ValueError: Se una variabile nel template non ha corrispondenza nel dict
|
|
"""
|
|
template = self.load_prompt(name)
|
|
|
|
# Verifica che tutte le variabili del template siano nel dict
|
|
required = set(_VARIABLE_PATTERN.findall(template))
|
|
provided = set(variables.keys())
|
|
missing = required - provided
|
|
if missing:
|
|
raise ValueError(
|
|
f"Variabili mancanti per il prompt '{name}': {sorted(missing)}. "
|
|
f"Fornire: {sorted(required)}"
|
|
)
|
|
|
|
def replace_var(match: re.Match) -> str:
|
|
var_name = match.group(1)
|
|
return variables[var_name]
|
|
|
|
return _VARIABLE_PATTERN.sub(replace_var, template)
|
|
|
|
def save_prompt(self, name: str, content: str) -> None:
|
|
"""Salva il contenuto di un prompt nel file .txt.
|
|
|
|
Usato dall'editor di prompt in Phase 2.
|
|
|
|
Args:
|
|
name: Nome del prompt senza estensione
|
|
content: Contenuto testuale da salvare
|
|
|
|
Raises:
|
|
ValueError: Se il nome contiene caratteri non sicuri
|
|
"""
|
|
# Sicurezza: validazione nome file (solo lettere, cifre, underscore, trattino)
|
|
if not re.match(r"^[\w\-]+$", name):
|
|
raise ValueError(
|
|
f"Nome prompt non valido: '{name}'. "
|
|
"Usa solo lettere, cifre, underscore e trattino."
|
|
)
|
|
path = self._get_path(name)
|
|
path.write_text(content, encoding="utf-8")
|
|
|
|
def get_required_variables(self, name: str) -> list[str]:
|
|
"""Analizza il template e ritorna la lista delle variabili richieste.
|
|
|
|
Args:
|
|
name: Nome del prompt senza estensione
|
|
|
|
Returns:
|
|
Lista ordinata di nomi variabile (senza doppie graffe)
|
|
Es: ['brand_name', 'livello_schwartz', 'obiettivo_campagna', 'target_nicchia', 'topic']
|
|
|
|
Raises:
|
|
FileNotFoundError: Se il prompt non esiste
|
|
"""
|
|
template = self.load_prompt(name)
|
|
variables = sorted(set(_VARIABLE_PATTERN.findall(template)))
|
|
return variables
|
|
|
|
def prompt_exists(self, name: str) -> bool:
|
|
"""Verifica se un prompt esiste.
|
|
|
|
Args:
|
|
name: Nome del prompt senza estensione
|
|
|
|
Returns:
|
|
True se il file esiste
|
|
"""
|
|
return self._get_path(name).exists()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Metodi privati
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _get_path(self, name: str) -> Path:
|
|
"""Costruisce il percorso completo per un file prompt."""
|
|
return self._prompts_dir / f"{name}.txt"
|