Skip to content

Commit e5fc4f3

Browse files
feat: implement CLI model tier configuration
- Introduce model tiers (small, medium, large) for CLI tools - Update agent.py to resolve model names based on tier - Modify chore_implement.py and prompt.py to accept tier options - Enhance README.md and overview.md with model tier documentation
1 parent 01442ce commit e5fc4f3

7 files changed

Lines changed: 176 additions & 13 deletions

File tree

.env.example

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,24 @@ PORT=8000
4141

4242
# Anthropic API key for Claude
4343
# ANTHROPIC_API_KEY=sk-ant-...
44+
45+
# ============================================================================
46+
# CLI Model Tier Configuration (optional)
47+
# ============================================================================
48+
# Override the default model for each tier per CLI tool.
49+
# Tiers: small (fast/cheap), medium (balanced), large (complex)
50+
51+
# Claude Code models
52+
# CLI_MODEL_CLAUDE_SMALL=haiku
53+
# CLI_MODEL_CLAUDE_MEDIUM=sonnet
54+
# CLI_MODEL_CLAUDE_LARGE=opus
55+
56+
# GitHub Copilot models
57+
# CLI_MODEL_COPILOT_SMALL=gpt-4o-mini
58+
# CLI_MODEL_COPILOT_MEDIUM=gpt-4o
59+
# CLI_MODEL_COPILOT_LARGE=o1
60+
61+
# Gemini CLI models
62+
# CLI_MODEL_GEMINI_SMALL=gemini-2.0-flash
63+
# CLI_MODEL_GEMINI_MEDIUM=gemini-2.0-flash-thinking
64+
# CLI_MODEL_GEMINI_LARGE=gemini-2.5-pro

agentic/README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,29 @@ agentic/
2121
uv run agentic/workflows/chore_implement.py "your task description"
2222

2323
# Options
24-
--model [haiku|sonnet|opus] # Default: opus
24+
--model [small|medium|large] # Default: large
2525
--cli [claude|copilot|gemini] # Default: claude
2626
--working-dir PATH # Default: current directory
27+
```
28+
29+
## Model Tiers
30+
31+
Model tiers abstract away CLI-specific model names, allowing the same command to work across different AI tools.
32+
33+
| Tier | Purpose | Claude | Copilot | Gemini |
34+
|------|---------|--------|---------|--------|
35+
| small | Fast/cheap tasks | haiku | gpt-4o-mini | gemini-2.0-flash |
36+
| medium | Balanced tasks | sonnet | gpt-4o | gemini-2.0-flash-thinking |
37+
| large | Complex tasks | opus | o1 | gemini-2.5-pro |
38+
39+
### Override Mappings
40+
41+
Override the default model for any tier via environment variables:
42+
43+
```bash
44+
# Use a specific Claude model for the "large" tier
45+
CLI_MODEL_CLAUDE_LARGE=claude-3-5-sonnet-20240620
46+
47+
# Use a different Copilot model for the "medium" tier
48+
CLI_MODEL_COPILOT_MEDIUM=gpt-4-turbo
2749
```

agentic/workflows/chore_implement.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# requires-python = ">=3.10"
44
# dependencies = [
55
# "pydantic",
6+
# "pydantic-settings",
67
# "python-dotenv",
78
# "click",
89
# "rich",
@@ -24,7 +25,7 @@
2425
2526
Examples:
2627
# Run with specific model
27-
./agentic/workflows/chore_implement.py "Add logging to agent.py" --model sonnet
28+
./agentic/workflows/chore_implement.py "Add logging to agent.py" --model medium
2829
2930
# Run from a different working directory
3031
./agentic/workflows/chore_implement.py "Update documentation" --working-dir /path/to/project
@@ -130,9 +131,9 @@ def extract_plan_path(output: str) -> str:
130131
@click.argument("prompt", required=True)
131132
@click.option(
132133
"--model",
133-
type=click.Choice(["haiku", "sonnet", "opus"]),
134-
default="opus",
135-
help="Claude model to use",
134+
type=click.Choice(["small", "medium", "large"]),
135+
default="large",
136+
help="Model tier (maps to CLI-specific models)",
136137
)
137138
@click.option(
138139
"--working-dir",

agentic/workflows/modules/agent.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@
1313
from pydantic import BaseModel
1414
from dotenv import load_dotenv
1515

16+
# Add project root to path for imports
17+
_project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
18+
if _project_root not in sys.path:
19+
sys.path.insert(0, _project_root)
20+
21+
# Import settings directly from the module file to avoid core/__init__.py
22+
# which has dependencies (PIL) not available in uv script environments
23+
import importlib.util
24+
_config_path = os.path.join(_project_root, "core", "config.py")
25+
_spec = importlib.util.spec_from_file_location("core_config", _config_path)
26+
_core_config = importlib.util.module_from_spec(_spec)
27+
_spec.loader.exec_module(_core_config)
28+
settings = _core_config.settings
29+
1630

1731
# Retry codes for Claude Code execution errors
1832
class RetryCode(str, Enum):
@@ -29,7 +43,7 @@ class AgentPromptRequest(BaseModel):
2943
prompt: str
3044
run_id: str
3145
agent_name: str = "ops"
32-
model: Literal["haiku", "sonnet", "opus"] = "sonnet"
46+
model: Literal["small", "medium", "large"] = "medium"
3347
cli: Literal["claude", "copilot", "gemini"] = "claude"
3448
dangerously_skip_permissions: bool = False
3549
output_file: str
@@ -50,7 +64,7 @@ class AgentTemplateRequest(BaseModel):
5064
slash_command: str
5165
args: List[str]
5266
run_id: str
53-
model: Literal["haiku", "sonnet", "opus"] = "sonnet"
67+
model: Literal["small", "medium", "large"] = "medium"
5468
cli: Literal["claude", "copilot", "gemini"] = "claude"
5569
working_dir: Optional[str] = None
5670

@@ -485,12 +499,15 @@ def prompt_claude_code(request: AgentPromptRequest) -> AgentPromptResponse:
485499
if os.path.exists(potential_mcp_path):
486500
mcp_config_path = potential_mcp_path
487501

502+
# Resolve model tier to actual model name for the CLI
503+
actual_model = settings.resolve_model(request.cli, request.model)
504+
488505
# Build CLI-specific command
489506
cmd = build_cli_command(
490507
cli=request.cli,
491508
cli_path=cli_path,
492509
prompt=request.prompt,
493-
model=request.model,
510+
model=actual_model,
494511
dangerously_skip_permissions=request.dangerously_skip_permissions,
495512
mcp_config_path=mcp_config_path,
496513
)
@@ -707,7 +724,7 @@ def execute_template(request: AgentTemplateRequest) -> AgentPromptResponse:
707724
slash_command="/implement",
708725
args=["plan.md"],
709726
run_id="abc12345",
710-
model="sonnet" # Explicitly set model
727+
model="medium" # Model tier (small/medium/large)
711728
)
712729
response = execute_template(request)
713730
"""

agentic/workflows/prompt.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# requires-python = ">=3.10"
44
# dependencies = [
55
# "pydantic",
6+
# "pydantic-settings",
67
# "python-dotenv",
78
# "click",
89
# "rich",
@@ -23,7 +24,7 @@
2324
2425
Examples:
2526
# Run with specific model
26-
./adw_prompt.py "Explain this code" --model opus
27+
./adw_prompt.py "Explain this code" --model large
2728
2829
# Run with custom output file
2930
./adw_prompt.py "Create a FastAPI app" --output my_result.jsonl
@@ -71,9 +72,9 @@
7172
@click.argument("prompt", required=True)
7273
@click.option(
7374
"--model",
74-
type=click.Choice(["haiku", "sonnet", "opus"]),
75-
default="sonnet",
76-
help="Claude model to use",
75+
type=click.Choice(["small", "medium", "large"]),
76+
default="medium",
77+
help="Model tier (maps to CLI-specific models)",
7778
)
7879
@click.option(
7980
"--output",

core/config.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,39 @@ class Settings(BaseSettings):
9797
claude_review_max_tokens: int = 2000
9898
"""Maximum tokens for Claude review responses"""
9999

100+
# =============================================================================
101+
# CLI MODEL TIER CONFIGURATION
102+
# =============================================================================
103+
# Model tiers (small/medium/large) map to CLI-specific model names.
104+
# Override these to use different models for each tier.
105+
106+
cli_model_claude_small: str = "haiku"
107+
"""Claude model for 'small' tier (fast/cheap tasks)"""
108+
109+
cli_model_claude_medium: str = "sonnet"
110+
"""Claude model for 'medium' tier (balanced tasks)"""
111+
112+
cli_model_claude_large: str = "opus"
113+
"""Claude model for 'large' tier (complex tasks)"""
114+
115+
cli_model_copilot_small: str = "gpt-4o-mini"
116+
"""Copilot model for 'small' tier"""
117+
118+
cli_model_copilot_medium: str = "gpt-4o"
119+
"""Copilot model for 'medium' tier"""
120+
121+
cli_model_copilot_large: str = "o1"
122+
"""Copilot model for 'large' tier"""
123+
124+
cli_model_gemini_small: str = "gemini-2.0-flash"
125+
"""Gemini model for 'small' tier"""
126+
127+
cli_model_gemini_medium: str = "gemini-2.0-flash-thinking"
128+
"""Gemini model for 'medium' tier"""
129+
130+
cli_model_gemini_large: str = "gemini-2.5-pro"
131+
"""Gemini model for 'large' tier"""
132+
100133
# =============================================================================
101134
# CACHE
102135
# =============================================================================
@@ -154,6 +187,36 @@ def has_google_cloud(self) -> bool:
154187
"""Check if Google Cloud is configured."""
155188
return bool(self.google_application_credentials or self.instance_connection_name)
156189

190+
def resolve_model(self, cli: str, tier: str) -> str:
191+
"""Resolve a model tier (small/medium/large) to the actual model name for a CLI.
192+
193+
Args:
194+
cli: The CLI tool name ("claude", "copilot", or "gemini")
195+
tier: The model tier ("small", "medium", or "large")
196+
197+
Returns:
198+
The actual model name for the specified CLI and tier.
199+
If the tier is not recognized, returns the tier unchanged (pass-through).
200+
"""
201+
mapping = {
202+
"claude": {
203+
"small": self.cli_model_claude_small,
204+
"medium": self.cli_model_claude_medium,
205+
"large": self.cli_model_claude_large,
206+
},
207+
"copilot": {
208+
"small": self.cli_model_copilot_small,
209+
"medium": self.cli_model_copilot_medium,
210+
"large": self.cli_model_copilot_large,
211+
},
212+
"gemini": {
213+
"small": self.cli_model_gemini_small,
214+
"medium": self.cli_model_gemini_medium,
215+
"large": self.cli_model_gemini_large,
216+
},
217+
}
218+
return mapping.get(cli, {}).get(tier, tier)
219+
157220

158221
# Global settings instance
159222
# Import this from other modules: from core.config import settings

docs/workflows/overview.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,41 @@ gh workflow run bulk-generate.yml -f specification_id=all -f library=matplotlib
182182
```
183183

184184
**Concurrency limit**: Max 3 parallel implementations globally.
185+
186+
---
187+
188+
## CLI Model Tiers
189+
190+
The agentic workflows use abstract model tiers (`small`, `medium`, `large`) instead of CLI-specific model names. This allows the same command to work across different AI tools.
191+
192+
| Tier | Purpose | Claude | Copilot | Gemini |
193+
|------|---------|--------|---------|--------|
194+
| small | Fast/cheap tasks | haiku | gpt-4o-mini | gemini-2.0-flash |
195+
| medium | Balanced tasks | sonnet | gpt-4o | gemini-2.0-flash-thinking |
196+
| large | Complex tasks | opus | o1 | gemini-2.5-pro |
197+
198+
### Usage
199+
200+
```bash
201+
# Use large tier (default for chore_implement)
202+
uv run agentic/workflows/chore_implement.py "Add feature X" --model large
203+
204+
# Use medium tier with Copilot
205+
uv run agentic/workflows/prompt.py "Quick fix" --model medium --cli copilot
206+
```
207+
208+
### Override Mappings
209+
210+
Override the default model for any tier via environment variables:
211+
212+
```bash
213+
# Linux/WSL
214+
export CLI_MODEL_CLAUDE_LARGE=claude-3-5-sonnet-20240620
215+
export CLI_MODEL_COPILOT_MEDIUM=gpt-4-turbo
216+
217+
# Windows PowerShell
218+
$env:CLI_MODEL_CLAUDE_LARGE = "claude-3-5-sonnet-20240620"
219+
$env:CLI_MODEL_COPILOT_MEDIUM = "gpt-4-turbo"
220+
```
221+
222+
All environment variable names follow the pattern: `CLI_MODEL_{CLI}_{TIER}`

0 commit comments

Comments
 (0)