fix(orchestrator): definisci prompt_library PRIMA di istanziare HypothesisAgent

Bug introdotto in b6f48e4: HypothesisAgent(prompt_library=prompt_library) era
chiamato a riga 109, ma prompt_library veniva definito a riga 123 -> NameError
a runtime quando run_phase1 viene eseguito.

Spostato il blocco di setup prompt_library + set_cognitive_styles PRIMA della
istanziazione di HypothesisAgent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-05-15 21:05:39 +00:00
parent b6f48e46fc
commit 898b24b6a3
5 changed files with 64 additions and 35 deletions
+26
View File
@@ -1,16 +1,24 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import importlib.resources
from datetime import datetime from datetime import datetime
from pathlib import Path
from multi_swarm_core.cerbero.client import CerberoClient from multi_swarm_core.cerbero.client import CerberoClient
from multi_swarm_core.config import load_settings from multi_swarm_core.config import load_settings
from multi_swarm_core.data.cerbero_ohlcv import CerberoOHLCVLoader, OHLCVRequest from multi_swarm_core.data.cerbero_ohlcv import CerberoOHLCVLoader, OHLCVRequest
from multi_swarm_core.genome.hypothesis import ModelTier from multi_swarm_core.genome.hypothesis import ModelTier
from multi_swarm_core.genome.prompt_library import PromptLibrary
from multi_swarm_core.llm.client import LLMClient from multi_swarm_core.llm.client import LLMClient
from multi_swarm_core.orchestrator.run import RunConfig, run_phase1 from multi_swarm_core.orchestrator.run import RunConfig, run_phase1
def _default_prompt_library_path() -> Path:
"""Default: prompts.json shippato col package strategy_crypto."""
return Path(str(importlib.resources.files("strategy_crypto") / "prompts.json"))
def parse_args() -> argparse.Namespace: def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Multi-Swarm Phase 1 runner") p = argparse.ArgumentParser(description="Multi-Swarm Phase 1 runner")
p.add_argument("--name", default="phase1-spike-001") p.add_argument("--name", default="phase1-spike-001")
@@ -96,6 +104,16 @@ def parse_args() -> argparse.Namespace:
default=0.5, default=0.5,
help="Multi-objective: peso IS (1-alpha = OOS). 1.0=solo IS, 0.5=bilanciato, 0.0=solo OOS", help="Multi-objective: peso IS (1-alpha = OOS). 1.0=solo IS, 0.5=bilanciato, 0.0=solo OOS",
) )
p.add_argument(
"--prompt-library",
type=Path,
default=None,
help=(
"Path al file JSON con stili cognitivi + direttive system_prompt. "
"Default: strategy_crypto/prompts.json shippato col package. "
"Schema: {styles: {<name>: {directive: <testo>}}}"
),
)
return p.parse_args() return p.parse_args()
@@ -103,6 +121,13 @@ def main() -> None:
args = parse_args() args = parse_args()
settings = load_settings() settings = load_settings()
prompt_lib_path = args.prompt_library or _default_prompt_library_path()
prompt_library = PromptLibrary.from_json(prompt_lib_path)
print(
f"PromptLibrary loaded from {prompt_lib_path}: "
f"{len(prompt_library.styles)} stili ({', '.join(prompt_library.cognitive_styles)})"
)
token = ( token = (
settings.cerbero_mainnet_token.get_secret_value() settings.cerbero_mainnet_token.get_secret_value()
if settings.cerbero_mainnet_token if settings.cerbero_mainnet_token
@@ -161,6 +186,7 @@ def main() -> None:
wfa_top_k=args.wfa_top_k, wfa_top_k=args.wfa_top_k,
eval_oos_during_loop=args.eval_oos_during_loop, eval_oos_during_loop=args.eval_oos_during_loop,
fitness_combined_alpha=args.fitness_combined_alpha, fitness_combined_alpha=args.fitness_combined_alpha,
prompt_library=prompt_library,
) )
run_id = run_phase1(cfg, ohlcv=ohlcv, llm=llm) run_id = run_phase1(cfg, ohlcv=ohlcv, llm=llm)
@@ -3,34 +3,12 @@ from __future__ import annotations
import random import random
from ..genome.hypothesis import HypothesisAgentGenome, ModelTier from ..genome.hypothesis import HypothesisAgentGenome, ModelTier
from ..genome.mutation import COGNITIVE_STYLES from ..genome.prompt_library import PromptLibrary
STYLE_PROMPTS: dict[str, str] = { # Mantenuto come alias backcompat: equivalente a PromptLibrary.default().styles.
"physicist": ( # Nuovi caller dovrebbero usare PromptLibrary direttamente per supportare
"Cerca leggi conservative, simmetrie, regimi di scala. " # l'override via prompts.json di una strategia.
"Pensa in termini di flussi e potenziali." STYLE_PROMPTS: dict[str, str] = PromptLibrary.default().styles
),
"biologist": (
"Cerca pattern adattivi, nicchie ecologiche, "
"predator-prey dynamics tra partecipanti del mercato."
),
"historian": (
"Cerca pattern ricorrenti su scale temporali multiple, "
"analogie con regimi storici, mean reversion strutturali."
),
"meteorologist": (
"Cerca regimi di volatilità che si autoalimentano, "
"transizioni di stato come fronti, persistenza locale."
),
"ecologist": (
"Cerca interazioni multi-asset, correlazioni cluster, "
"segnali di stress sistemico nelle dinamiche di flusso."
),
"engineer": (
"Cerca segnali con rapporto S/N favorevole, filtri causali, "
"robustezza a perturbazioni di calibrazione."
),
}
def build_initial_population( def build_initial_population(
@@ -38,15 +16,22 @@ def build_initial_population(
model_tier: ModelTier, model_tier: ModelTier,
rng: random.Random, rng: random.Random,
feature_pool: tuple[str, ...] = ("close", "high", "low", "volume"), feature_pool: tuple[str, ...] = ("close", "high", "low", "volume"),
prompt_library: PromptLibrary | None = None,
) -> list[HypothesisAgentGenome]: ) -> list[HypothesisAgentGenome]:
"""Costruisce una popolazione iniziale K varia per stile cognitivo + parametri.""" """Costruisce una popolazione iniziale K varia per stile cognitivo + parametri.
``prompt_library`` controlla quali stili sono disponibili e quale system_prompt
iniziale viene assegnato. Default = builtin 6 stili (physicist, biologist, ...).
Override tipico: ``PromptLibrary.from_json(strategy_crypto/prompts.json)``.
"""
lib = prompt_library or PromptLibrary.default()
population: list[HypothesisAgentGenome] = [] population: list[HypothesisAgentGenome] = []
for i in range(k): for i in range(k):
style = COGNITIVE_STYLES[i % len(COGNITIVE_STYLES)] style = lib.style_at(i)
n_features = rng.randint(1, len(feature_pool)) n_features = rng.randint(1, len(feature_pool))
feats = sorted(rng.sample(feature_pool, k=n_features)) feats = sorted(rng.sample(feature_pool, k=n_features))
g = HypothesisAgentGenome( g = HypothesisAgentGenome(
system_prompt=STYLE_PROMPTS[style], system_prompt=lib.directive(style),
feature_access=feats, feature_access=feats,
temperature=round(rng.uniform(0.7, 1.2), 2), temperature=round(rng.uniform(0.7, 1.2), 2),
top_p=0.95, top_p=0.95,
@@ -7,6 +7,10 @@ from .hypothesis import HypothesisAgentGenome
FEATURE_POOL: tuple[str, ...] = ("open", "high", "low", "close", "volume") FEATURE_POOL: tuple[str, ...] = ("open", "high", "low", "close", "volume")
# Lista di default builtin (allineata con PromptLibrary.default()).
# Il dispatcher run_phase1 sovrascrive `COGNITIVE_STYLES` con la lista letta
# da prompts.json prima del loop GA, cosi' `mutate_cognitive_style` pesca
# dai candidati corretti per la strategia in corso.
COGNITIVE_STYLES: tuple[str, ...] = ( COGNITIVE_STYLES: tuple[str, ...] = (
"physicist", "physicist",
"biologist", "biologist",
@@ -17,6 +21,18 @@ COGNITIVE_STYLES: tuple[str, ...] = (
) )
def set_cognitive_styles(styles: tuple[str, ...]) -> None:
"""Sovrascrive la lista globale di stili candidati per la mutation.
Da chiamare PRIMA del GA loop (es. in run_phase1 dopo aver caricato la
PromptLibrary). Non thread-safe: pensata per uno script CLI.
"""
global COGNITIVE_STYLES
if not styles:
raise ValueError("set_cognitive_styles: lista vuota")
COGNITIVE_STYLES = tuple(styles)
def _clone_with(g: HypothesisAgentGenome, **overrides: Any) -> HypothesisAgentGenome: def _clone_with(g: HypothesisAgentGenome, **overrides: Any) -> HypothesisAgentGenome:
payload: dict[str, Any] = g.to_dict() payload: dict[str, Any] = g.to_dict()
payload.update(overrides) payload.update(overrides)
@@ -106,6 +106,12 @@ def run_phase1(
market = build_market_summary(train_ohlcv, symbol=cfg.symbol, timeframe=cfg.timeframe) market = build_market_summary(train_ohlcv, symbol=cfg.symbol, timeframe=cfg.timeframe)
# Propaga la libreria di stili al modulo mutation (cosi' mutate_cognitive_style
# pesca dai candidati coerenti col JSON della strategia in corso). Va FATTO
# PRIMA di istanziare HypothesisAgent (che la riceve in costruttore).
prompt_library = cfg.prompt_library or PromptLibrary.default()
set_cognitive_styles(prompt_library.cognitive_styles)
hypothesis_agent = HypothesisAgent(llm=llm, prompt_library=prompt_library) hypothesis_agent = HypothesisAgent(llm=llm, prompt_library=prompt_library)
falsification_agent = FalsificationAgent( falsification_agent = FalsificationAgent(
fees_bp=cfg.fees_bp, n_trials_dsr=cfg.n_trials_dsr fees_bp=cfg.fees_bp, n_trials_dsr=cfg.n_trials_dsr
@@ -118,11 +124,6 @@ def run_phase1(
) )
cost_tracker = CostTracker() cost_tracker = CostTracker()
# Propaga la libreria di stili al modulo mutation (cosi' mutate_cognitive_style
# pesca dai candidati coerenti col JSON della strategia in corso).
prompt_library = cfg.prompt_library or PromptLibrary.default()
set_cognitive_styles(prompt_library.cognitive_styles)
population = build_initial_population( population = build_initial_population(
k=cfg.population_size, k=cfg.population_size,
model_tier=cfg.model_tier, model_tier=cfg.model_tier,
+1
View File
@@ -18,3 +18,4 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel.force-include] [tool.hatch.build.targets.wheel.force-include]
"strategy_crypto/strategies" = "strategy_crypto/strategies" "strategy_crypto/strategies" = "strategy_crypto/strategies"
"strategy_crypto/prompts.json" = "strategy_crypto/prompts.json"