feat: model-family-aware token budgets and prompt style
Add ModelFamily enum (config.rs) detected from the model name: - DeepSeekR1: matched on "deepseek-r1", "r1-distill" — R1 thinking blocks consume thousands of output tokens before the JSON; max_output_tokens raised to 32768 and HTTP timeout to 300s; prompt tells the model its <think> output is stripped and only the bare JSON is used - Generic: previous behaviour (8192 tokens, 120s timeout) ClaudeClient stores the detected family and uses it for max_tokens and the request timeout. family() accessor lets the caller (agent.rs) pass it into system_prompt(). prompts::system_prompt() now accepts &ModelFamily and injects a family-specific "output format" section in place of the hardcoded "How to respond" block. New families can be added by extending the enum and the match arms without touching prompt logic elsewhere. Also: log full anyhow cause chain (:#) on JSON extraction failure and show response length alongside the truncated preview, to make future diagnosis easier. Root cause of the 2026-03-09T18:29:22 run failure: R1's thinking tokens counted against max_tokens:8192, leaving only ~500 chars for the actual JSON, which was always truncated mid-object. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,28 @@
|
||||
/// System prompt for the strategy-generation Claude instance.
|
||||
use crate::config::ModelFamily;
|
||||
|
||||
/// System prompt for the strategy-generation model.
|
||||
///
|
||||
/// This is the most important part of the agent — it defines how Claude
|
||||
/// thinks about strategy design, what it knows about the DSL, and how
|
||||
/// it should interpret backtest results.
|
||||
pub fn system_prompt(dsl_schema: &str) -> String {
|
||||
/// Accepts a `ModelFamily` so each family can receive tailored guidance
|
||||
/// while sharing the common DSL schema and strategy evaluation rules.
|
||||
pub fn system_prompt(dsl_schema: &str, family: &ModelFamily) -> String {
|
||||
let output_instructions = match family {
|
||||
ModelFamily::DeepSeekR1 => {
|
||||
"## Output format\n\n\
|
||||
Think through your strategy design carefully before committing to it. \
|
||||
After your thinking, output ONLY a bare JSON object — no markdown fences, \
|
||||
no commentary, no explanation. Start with `{` and end with `}`. \
|
||||
Your thinking will be stripped automatically; only the JSON is used."
|
||||
}
|
||||
ModelFamily::Generic => {
|
||||
"## How to respond\n\n\
|
||||
You must respond with ONLY a valid JSON object — the strategy config.\n\
|
||||
No prose, no markdown explanation, no commentary.\n\
|
||||
Just the raw JSON starting with { and ending with }.\n\n\
|
||||
The JSON must be a valid strategy with \"type\": \"rule_based\".\n\
|
||||
Use \"usdc\" (not \"usdt\") as the quote asset for balance expressions."
|
||||
}
|
||||
};
|
||||
|
||||
format!(
|
||||
r##"You are a quantitative trading strategy researcher. Your task is to design,
|
||||
evaluate, and iteratively refine trading strategies expressed in the swym JSON DSL.
|
||||
@@ -88,14 +107,7 @@ Every strategy MUST have:
|
||||
- A time-based exit: use bars_since_entry to avoid holding losers indefinitely
|
||||
- Reasonable position sizing: prefer ATR-based or percent-of-balance over fixed quantity
|
||||
|
||||
## How to respond
|
||||
|
||||
You must respond with ONLY a valid JSON object — the strategy config.
|
||||
No prose, no markdown explanation, no commentary.
|
||||
Just the raw JSON starting with {{ and ending with }}.
|
||||
|
||||
The JSON must be a valid strategy with "type": "rule_based".
|
||||
Use "usdc" (not "usdt") as the quote asset for balance expressions.
|
||||
{output_instructions}
|
||||
|
||||
## Interpreting backtest results
|
||||
|
||||
|
||||
Reference in New Issue
Block a user