feat(agents): hypothesis agent with prompt template + s-expr extraction
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>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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
|
||||
Reference in New Issue
Block a user