feat(prompt): PromptLibrary v3.1 — anti_patterns + output_priorities

Estende il compositor del SYSTEM con 2 sezioni opzionali iniettate
DOPO i VINCOLI core e PRIMA dell'EXAMPLE:

  ANTI-PATTERN DA EVITARE: lista esplicita di cose da evitare (overfitting,
    correlazione=causalita, > 4 AND, singolo evento estremo, ecc.)
  PRIORITA' DI OUTPUT: trade-off come "robustezza > ottimalita su singolo
    regime", "semplicita > complessita raffinata", "selettivita > attivita"

Razionale: ridurre la varianza non-utile nelle strategie generate
quando il LLM affronta trade-off, e prevenire overfitting nel design.
Entrambi i campi sono opzionali (skip se "") -> backward-compatible
con prompts.json v3.0.

PromptLibrary v3.1: +2 fields top-level (default "").
_build_system_prompt: 2 sezioni condizionali post-VINCOLI.

Test: +3 unit (compositor inject/skip + from_json parsing).
Tot: 235 test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-05-15 21:38:35 +00:00
parent 5202eb517b
commit 19a3592a20
4 changed files with 88 additions and 1 deletions
@@ -197,7 +197,16 @@ def _build_system_prompt(lib: PromptLibrary, genome: HypothesisAgentGenome) -> s
parts.append("") parts.append("")
# 6. Vincoli (core scaffold) # 6. Vincoli (core scaffold)
parts.append(_SYSTEM_CONSTRAINTS) parts.append(_SYSTEM_CONSTRAINTS)
# 7. Esempio (core scaffold) # 7. NEW v3.1: anti-pattern e output priorities (da prompts.json, opzionali)
if lib.anti_patterns:
parts.append("\nANTI-PATTERN DA EVITARE:\n")
parts.append(lib.anti_patterns)
parts.append("")
if lib.output_priorities:
parts.append("\nPRIORITA' DI OUTPUT (trade-off):\n")
parts.append(lib.output_priorities)
parts.append("")
# 8. Esempio (core scaffold)
parts.append(_SYSTEM_EXAMPLE) parts.append(_SYSTEM_EXAMPLE)
return "\n".join(parts) return "\n".join(parts)
@@ -89,6 +89,7 @@ class PromptLibrary:
v3.0: aggiunge 4 campi top-level strategy-specific iniettabili nel prompt v3.0: aggiunge 4 campi top-level strategy-specific iniettabili nel prompt
dal compositor ``_build_system_prompt``. Tutti opzionali con default sensati dal compositor ``_build_system_prompt``. Tutti opzionali con default sensati
per backcompat. per backcompat.
v3.1: aggiunge anti_patterns e output_priorities iniettati dopo i VINCOLI core.
""" """
styles: dict[str, str] styles: dict[str, str]
@@ -98,6 +99,8 @@ class PromptLibrary:
pattern_guidance: str = field(default="") # sezione PATTERN GUIDANCE del SYSTEM pattern_guidance: str = field(default="") # sezione PATTERN GUIDANCE del SYSTEM
instruction: str = field(default="") # frase finale USER ("Genera una strategia...") instruction: str = field(default="") # frase finale USER ("Genera una strategia...")
domain_warnings: str = field(default="") # opzionale: warning di dominio (es. crypto 24/7) domain_warnings: str = field(default="") # opzionale: warning di dominio (es. crypto 24/7)
anti_patterns: str = field(default="") # NEW v3.1: lista esplicita di pattern da evitare
output_priorities: str = field(default="") # NEW v3.1: trade-off espliciti di output
def __post_init__(self) -> None: def __post_init__(self) -> None:
if not self.styles: if not self.styles:
@@ -115,6 +118,8 @@ class PromptLibrary:
("pattern_guidance", self.pattern_guidance), ("pattern_guidance", self.pattern_guidance),
("instruction", self.instruction), ("instruction", self.instruction),
("domain_warnings", self.domain_warnings), ("domain_warnings", self.domain_warnings),
("anti_patterns", self.anti_patterns),
("output_priorities", self.output_priorities),
): ):
if not isinstance(value, str): if not isinstance(value, str):
raise PromptLibraryError( raise PromptLibraryError(
@@ -135,6 +140,8 @@ class PromptLibrary:
pattern_guidance=_DEFAULT_PATTERN_GUIDANCE, pattern_guidance=_DEFAULT_PATTERN_GUIDANCE,
instruction=_DEFAULT_INSTRUCTION, instruction=_DEFAULT_INSTRUCTION,
domain_warnings="", domain_warnings="",
anti_patterns="",
output_priorities="",
) )
@classmethod @classmethod
@@ -190,6 +197,13 @@ class PromptLibrary:
pattern_guidance = data.get("pattern_guidance", "") pattern_guidance = data.get("pattern_guidance", "")
instruction = data.get("instruction", "") instruction = data.get("instruction", "")
domain_warnings = data.get("domain_warnings", "") domain_warnings = data.get("domain_warnings", "")
# Parse new optional top-level fields (v3.1)
anti_patterns_raw = data.get("anti_patterns", "")
output_priorities_raw = data.get("output_priorities", "")
if not isinstance(anti_patterns_raw, str):
raise PromptLibraryError(f"anti_patterns deve essere stringa, non {type(anti_patterns_raw)}")
if not isinstance(output_priorities_raw, str):
raise PromptLibraryError(f"output_priorities deve essere stringa, non {type(output_priorities_raw)}")
return cls( return cls(
styles=styles, styles=styles,
@@ -198,6 +212,8 @@ class PromptLibrary:
pattern_guidance=pattern_guidance, pattern_guidance=pattern_guidance,
instruction=instruction, instruction=instruction,
domain_warnings=domain_warnings, domain_warnings=domain_warnings,
anti_patterns=anti_patterns_raw,
output_priorities=output_priorities_raw,
) )
@property @property
@@ -370,3 +370,53 @@ def test_user_template_uses_instruction_from_library(mocker): # type: ignore[no
user_msg = call_kwargs["user"] user_msg = call_kwargs["user"]
assert "INSTR_X" in user_msg assert "INSTR_X" in user_msg
assert "Genera una strategia che cerchi anomalie sfruttabili in questo regime." not in user_msg assert "Genera una strategia che cerchi anomalie sfruttabili in questo regime." not in user_msg
def test_build_system_prompt_includes_anti_patterns_and_priorities(mocker): # type: ignore[no-untyped-def]
"""anti_patterns e output_priorities da PromptLibrary appaiono nel SYSTEM prompt."""
fake_llm = mocker.MagicMock()
fake_llm.complete.return_value = CompletionResult(
text=VALID_STRATEGY_JSON,
input_tokens=200,
output_tokens=80,
tier=ModelTier.C,
model="qwen",
)
lib = PromptLibrary(
styles={"physicist": "Pensa come un fisico."},
focus={},
anti_patterns="EVITA_X",
output_priorities="PRIORI_X",
)
agent = HypothesisAgent(llm=fake_llm, prompt_library=lib)
agent.propose(make_genome(), make_summary())
call_kwargs = fake_llm.complete.call_args.kwargs
system_msg = call_kwargs["system"]
assert "ANTI-PATTERN DA EVITARE" in system_msg
assert "EVITA_X" in system_msg
assert "PRIORITA' DI OUTPUT" in system_msg
assert "PRIORI_X" in system_msg
def test_build_system_prompt_skips_anti_patterns_and_priorities_when_empty(mocker): # type: ignore[no-untyped-def]
"""anti_patterns='' e output_priorities='' -> sezioni opzionali assenti nel SYSTEM."""
fake_llm = mocker.MagicMock()
fake_llm.complete.return_value = CompletionResult(
text=VALID_STRATEGY_JSON,
input_tokens=200,
output_tokens=80,
tier=ModelTier.C,
model="qwen",
)
lib = PromptLibrary(
styles={"physicist": "Pensa come un fisico."},
focus={},
anti_patterns="",
output_priorities="",
)
agent = HypothesisAgent(llm=fake_llm, prompt_library=lib)
agent.propose(make_genome(), make_summary())
call_kwargs = fake_llm.complete.call_args.kwargs
system_msg = call_kwargs["system"]
assert "ANTI-PATTERN" not in system_msg
assert "PRIORITA' DI OUTPUT" not in system_msg
@@ -76,3 +76,15 @@ def test_prompt_library_accepts_empty_string_for_optional_fields() -> None:
) )
assert lib.agent_role == "" assert lib.agent_role == ""
assert lib.domain_warnings == "" assert lib.domain_warnings == ""
def test_from_json_loads_anti_patterns_and_output_priorities(tmp_path: Path) -> None:
"""from_json() legge anti_patterns e output_priorities (v3.1)."""
data = {
"styles": {"physicist": {"directive": "Cerca leggi conservative."}},
"anti_patterns": "Evita overfitting.",
"output_priorities": "Robustezza > ottimalita.",
}
lib = PromptLibrary.from_json(_write_json(data, tmp_path))
assert lib.anti_patterns == "Evita overfitting."
assert lib.output_priorities == "Robustezza > ottimalita."