654ab7b6d9
Aggiunge HypothesisAgent che invoca LLMClient con system/user template parametrizzati sul genoma e sul MarketSummary, poi estrae la S-expression (da fence markdown lisp/scheme/sexp o testo nudo), la parsa e la valida. Restituisce HypothesisProposal con strategy=None + parse_error in caso di output malformato, mantenendo sempre il CompletionResult per accounting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
94 lines
2.9 KiB
Python
94 lines
2.9 KiB
Python
from multi_swarm.agents.hypothesis import HypothesisAgent, MarketSummary
|
|
from multi_swarm.genome.hypothesis import HypothesisAgentGenome, ModelTier
|
|
from multi_swarm.llm.client import CompletionResult
|
|
|
|
|
|
def make_summary() -> MarketSummary:
|
|
return MarketSummary(
|
|
symbol="BTC/USDT",
|
|
timeframe="1h",
|
|
n_bars=1000,
|
|
return_mean=0.0001,
|
|
return_std=0.01,
|
|
skew=0.1,
|
|
kurtosis=3.5,
|
|
volatility_regime="high",
|
|
)
|
|
|
|
|
|
def test_hypothesis_agent_calls_llm_and_parses(mocker): # type: ignore[no-untyped-def]
|
|
fake_llm = mocker.MagicMock()
|
|
fake_llm.complete.return_value = CompletionResult(
|
|
text="(strategy (when (gt (indicator rsi 14) 70.0) (entry-short)))",
|
|
input_tokens=200,
|
|
output_tokens=80,
|
|
tier=ModelTier.C,
|
|
model="qwen",
|
|
)
|
|
g = HypothesisAgentGenome(
|
|
system_prompt="Pensa come un fisico.",
|
|
feature_access=["close"],
|
|
temperature=0.9,
|
|
top_p=0.95,
|
|
model_tier=ModelTier.C,
|
|
lookback_window=200,
|
|
cognitive_style="physicist",
|
|
)
|
|
agent = HypothesisAgent(llm=fake_llm)
|
|
proposal = agent.propose(g, make_summary())
|
|
assert proposal.strategy is not None
|
|
assert proposal.raw_text.startswith("(strategy")
|
|
assert proposal.completion.input_tokens == 200
|
|
fake_llm.complete.assert_called_once()
|
|
|
|
|
|
def test_hypothesis_agent_returns_none_on_parse_error(mocker): # type: ignore[no-untyped-def]
|
|
fake_llm = mocker.MagicMock()
|
|
fake_llm.complete.return_value = CompletionResult(
|
|
text="this is not s-expression",
|
|
input_tokens=200,
|
|
output_tokens=80,
|
|
tier=ModelTier.C,
|
|
model="qwen",
|
|
)
|
|
g = HypothesisAgentGenome(
|
|
system_prompt="x",
|
|
feature_access=["close"],
|
|
temperature=0.9,
|
|
top_p=0.95,
|
|
model_tier=ModelTier.C,
|
|
lookback_window=200,
|
|
cognitive_style="physicist",
|
|
)
|
|
agent = HypothesisAgent(llm=fake_llm)
|
|
proposal = agent.propose(g, make_summary())
|
|
assert proposal.strategy is None
|
|
assert proposal.parse_error is not None
|
|
|
|
|
|
def test_hypothesis_agent_extracts_sexp_from_markdown_fence(mocker): # type: ignore[no-untyped-def]
|
|
fake_llm = mocker.MagicMock()
|
|
fake_llm.complete.return_value = CompletionResult(
|
|
text=(
|
|
"Ecco la strategia:\n```lisp\n"
|
|
"(strategy (when (lt (indicator rsi 14) 30.0) (entry-long)))\n"
|
|
"```\nFatta."
|
|
),
|
|
input_tokens=200,
|
|
output_tokens=80,
|
|
tier=ModelTier.C,
|
|
model="qwen",
|
|
)
|
|
g = HypothesisAgentGenome(
|
|
system_prompt="x",
|
|
feature_access=["close"],
|
|
temperature=0.9,
|
|
top_p=0.95,
|
|
model_tier=ModelTier.C,
|
|
lookback_window=200,
|
|
cognitive_style="physicist",
|
|
)
|
|
agent = HypothesisAgent(llm=fake_llm)
|
|
proposal = agent.propose(g, make_summary())
|
|
assert proposal.strategy is not None
|