kubrick-cli 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kubrick_cli/__init__.py +47 -0
- kubrick_cli/agent_loop.py +307 -0
- kubrick_cli/classifier.py +198 -0
- kubrick_cli/config.py +248 -0
- kubrick_cli/display.py +165 -0
- kubrick_cli/execution_strategy.py +197 -0
- kubrick_cli/main.py +797 -0
- kubrick_cli/planning.py +328 -0
- kubrick_cli/progress.py +172 -0
- kubrick_cli/providers/__init__.py +6 -0
- kubrick_cli/providers/anthropic_provider.py +224 -0
- kubrick_cli/providers/base.py +137 -0
- kubrick_cli/providers/factory.py +183 -0
- kubrick_cli/providers/openai_provider.py +190 -0
- kubrick_cli/providers/triton_provider.py +96 -0
- kubrick_cli/safety.py +216 -0
- kubrick_cli/scheduler.py +197 -0
- kubrick_cli/setup_wizard.py +172 -0
- kubrick_cli/tools.py +407 -0
- kubrick_cli/triton_client.py +192 -0
- kubrick_cli-0.1.2.dist-info/METADATA +86 -0
- kubrick_cli-0.1.2.dist-info/RECORD +26 -0
- kubrick_cli-0.1.2.dist-info/WHEEL +5 -0
- kubrick_cli-0.1.2.dist-info/entry_points.txt +2 -0
- kubrick_cli-0.1.2.dist-info/licenses/LICENSE +21 -0
- kubrick_cli-0.1.2.dist-info/top_level.txt +1 -0
kubrick_cli/config.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Configuration management for Kubrick CLI."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class KubrickConfig:
|
|
10
|
+
"""Manages Kubrick configuration and data directories."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, skip_wizard: bool = False):
|
|
13
|
+
"""
|
|
14
|
+
Initialize config manager and ensure directories exist.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
skip_wizard: Skip setup wizard even if config doesn't exist (for testing)
|
|
18
|
+
"""
|
|
19
|
+
# Use ~/.kubrick in all environments (Docker and non-Docker)
|
|
20
|
+
# In Docker, this resolves to /home/kubrick/.kubrick
|
|
21
|
+
# Outside Docker, this resolves to the current user's home directory
|
|
22
|
+
self.kubrick_dir = Path.home() / ".kubrick"
|
|
23
|
+
self.config_file = self.kubrick_dir / "config.json"
|
|
24
|
+
self.conversations_dir = self.kubrick_dir / "conversations"
|
|
25
|
+
|
|
26
|
+
# Ensure directories exist
|
|
27
|
+
self._ensure_directories()
|
|
28
|
+
|
|
29
|
+
# Load or create config (with optional setup wizard)
|
|
30
|
+
self.config = self._load_config(skip_wizard=skip_wizard)
|
|
31
|
+
|
|
32
|
+
def _ensure_directories(self):
|
|
33
|
+
"""Create necessary directories if they don't exist."""
|
|
34
|
+
self.kubrick_dir.mkdir(exist_ok=True)
|
|
35
|
+
self.conversations_dir.mkdir(exist_ok=True)
|
|
36
|
+
|
|
37
|
+
def _load_config(self, skip_wizard: bool = False) -> Dict[str, Any]:
|
|
38
|
+
"""
|
|
39
|
+
Load configuration from file or create default.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
skip_wizard: Skip setup wizard even if config doesn't exist
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Configuration dictionary
|
|
46
|
+
"""
|
|
47
|
+
if self.config_file.exists():
|
|
48
|
+
try:
|
|
49
|
+
with open(self.config_file, "r") as f:
|
|
50
|
+
loaded_config = json.load(f)
|
|
51
|
+
|
|
52
|
+
# Merge with defaults to ensure all keys exist
|
|
53
|
+
default_config = self._get_default_config()
|
|
54
|
+
default_config.update(loaded_config)
|
|
55
|
+
return default_config
|
|
56
|
+
|
|
57
|
+
except (json.JSONDecodeError, IOError):
|
|
58
|
+
# If config is corrupted, start fresh
|
|
59
|
+
return self._get_default_config()
|
|
60
|
+
else:
|
|
61
|
+
# First time setup - run wizard unless skipped
|
|
62
|
+
if not skip_wizard:
|
|
63
|
+
from .setup_wizard import SetupWizard
|
|
64
|
+
|
|
65
|
+
wizard_config = SetupWizard.run()
|
|
66
|
+
|
|
67
|
+
# Merge wizard config with defaults
|
|
68
|
+
config = self._get_default_config()
|
|
69
|
+
config.update(wizard_config)
|
|
70
|
+
|
|
71
|
+
self._save_config(config)
|
|
72
|
+
return config
|
|
73
|
+
else:
|
|
74
|
+
# Create default config without wizard
|
|
75
|
+
config = self._get_default_config()
|
|
76
|
+
self._save_config(config)
|
|
77
|
+
return config
|
|
78
|
+
|
|
79
|
+
def _get_default_config(self) -> Dict[str, Any]:
|
|
80
|
+
"""Get default configuration values."""
|
|
81
|
+
return {
|
|
82
|
+
# Provider settings
|
|
83
|
+
"provider": "triton",
|
|
84
|
+
# Triton settings
|
|
85
|
+
"triton_url": "localhost:8000",
|
|
86
|
+
"triton_model": "llm_decoupled",
|
|
87
|
+
# OpenAI settings
|
|
88
|
+
"openai_api_key": None,
|
|
89
|
+
"openai_model": "gpt-4",
|
|
90
|
+
# Anthropic settings
|
|
91
|
+
"anthropic_api_key": None,
|
|
92
|
+
"anthropic_model": "claude-sonnet-4-5-20250929",
|
|
93
|
+
# Legacy compatibility
|
|
94
|
+
"model_name": "llm_decoupled",
|
|
95
|
+
"use_openai": False,
|
|
96
|
+
"default_working_dir": None,
|
|
97
|
+
# Agent loop settings
|
|
98
|
+
"max_iterations": 15,
|
|
99
|
+
"max_tools_per_turn": 5,
|
|
100
|
+
"total_timeout_seconds": 600,
|
|
101
|
+
"enable_parallel_tools": True,
|
|
102
|
+
"max_parallel_workers": 3,
|
|
103
|
+
# Safety settings
|
|
104
|
+
"require_dangerous_command_confirmation": True,
|
|
105
|
+
"tool_timeout_seconds": 30,
|
|
106
|
+
"max_file_size_mb": 10,
|
|
107
|
+
# Display settings
|
|
108
|
+
"display_mode": "natural", # natural, json, verbose
|
|
109
|
+
"show_tool_results": True,
|
|
110
|
+
"show_progress": True,
|
|
111
|
+
# Task classification settings
|
|
112
|
+
"enable_task_classification": True,
|
|
113
|
+
"enable_planning_phase": True,
|
|
114
|
+
# Conversation settings
|
|
115
|
+
"auto_save_conversations": True,
|
|
116
|
+
"max_conversations": 100,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def _save_config(self, config: Dict[str, Any]):
|
|
120
|
+
"""Save configuration to file."""
|
|
121
|
+
with open(self.config_file, "w") as f:
|
|
122
|
+
json.dump(config, f, indent=2)
|
|
123
|
+
|
|
124
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
125
|
+
"""Get a configuration value."""
|
|
126
|
+
return self.config.get(key, default)
|
|
127
|
+
|
|
128
|
+
def set(self, key: str, value: Any):
|
|
129
|
+
"""Set a configuration value and save."""
|
|
130
|
+
self.config[key] = value
|
|
131
|
+
self._save_config(self.config)
|
|
132
|
+
|
|
133
|
+
def get_all(self) -> Dict[str, Any]:
|
|
134
|
+
"""Get all configuration values."""
|
|
135
|
+
return self.config.copy()
|
|
136
|
+
|
|
137
|
+
def save_conversation(
|
|
138
|
+
self, conversation_id: str, messages: list, metadata: Dict = None
|
|
139
|
+
):
|
|
140
|
+
"""
|
|
141
|
+
Save a conversation to disk.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
conversation_id: Unique identifier for the conversation (e.g., timestamp)
|
|
145
|
+
messages: List of message dictionaries
|
|
146
|
+
metadata: Optional metadata (working_dir, triton_url, etc.)
|
|
147
|
+
"""
|
|
148
|
+
conversation_file = self.conversations_dir / f"{conversation_id}.json"
|
|
149
|
+
|
|
150
|
+
data = {
|
|
151
|
+
"id": conversation_id,
|
|
152
|
+
"messages": messages,
|
|
153
|
+
"metadata": metadata or {},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
with open(conversation_file, "w") as f:
|
|
157
|
+
json.dump(data, f, indent=2)
|
|
158
|
+
|
|
159
|
+
# Clean up old conversations if we exceed max
|
|
160
|
+
self._cleanup_old_conversations()
|
|
161
|
+
|
|
162
|
+
def load_conversation(self, conversation_id: str) -> Optional[Dict]:
|
|
163
|
+
"""
|
|
164
|
+
Load a conversation from disk.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
conversation_id: Unique identifier for the conversation
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Dictionary with 'id', 'messages', and 'metadata', or None if not found
|
|
171
|
+
"""
|
|
172
|
+
conversation_file = self.conversations_dir / f"{conversation_id}.json"
|
|
173
|
+
|
|
174
|
+
if not conversation_file.exists():
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
with open(conversation_file, "r") as f:
|
|
179
|
+
return json.load(f)
|
|
180
|
+
except (json.JSONDecodeError, IOError):
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def list_conversations(self, limit: int = None) -> list:
|
|
184
|
+
"""
|
|
185
|
+
List all saved conversations.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
limit: Optional limit on number of conversations to return
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
List of conversation metadata sorted by modification time (newest first)
|
|
192
|
+
"""
|
|
193
|
+
conversations = []
|
|
194
|
+
|
|
195
|
+
for conv_file in self.conversations_dir.glob("*.json"):
|
|
196
|
+
try:
|
|
197
|
+
with open(conv_file, "r") as f:
|
|
198
|
+
data = json.load(f)
|
|
199
|
+
|
|
200
|
+
conversations.append(
|
|
201
|
+
{
|
|
202
|
+
"id": data.get("id", conv_file.stem),
|
|
203
|
+
"metadata": data.get("metadata", {}),
|
|
204
|
+
"message_count": len(data.get("messages", [])),
|
|
205
|
+
"modified": conv_file.stat().st_mtime,
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
except (json.JSONDecodeError, IOError):
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
# Sort by modification time (newest first)
|
|
212
|
+
conversations.sort(key=lambda x: x["modified"], reverse=True)
|
|
213
|
+
|
|
214
|
+
if limit:
|
|
215
|
+
conversations = conversations[:limit]
|
|
216
|
+
|
|
217
|
+
return conversations
|
|
218
|
+
|
|
219
|
+
def delete_conversation(self, conversation_id: str) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Delete a conversation.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
conversation_id: Unique identifier for the conversation
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
True if deleted, False if not found
|
|
228
|
+
"""
|
|
229
|
+
conversation_file = self.conversations_dir / f"{conversation_id}.json"
|
|
230
|
+
|
|
231
|
+
if conversation_file.exists():
|
|
232
|
+
conversation_file.unlink()
|
|
233
|
+
return True
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
def _cleanup_old_conversations(self):
|
|
237
|
+
"""Remove oldest conversations if we exceed max_conversations."""
|
|
238
|
+
max_conversations = self.config.get("max_conversations", 100)
|
|
239
|
+
|
|
240
|
+
conversations = list(self.conversations_dir.glob("*.json"))
|
|
241
|
+
|
|
242
|
+
if len(conversations) > max_conversations:
|
|
243
|
+
# Sort by modification time (oldest first)
|
|
244
|
+
conversations.sort(key=lambda x: x.stat().st_mtime)
|
|
245
|
+
|
|
246
|
+
# Delete oldest conversations
|
|
247
|
+
for conv_file in conversations[: len(conversations) - max_conversations]:
|
|
248
|
+
conv_file.unlink()
|
kubrick_cli/display.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Display manager for natural language tool output."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.syntax import Syntax
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DisplayManager:
|
|
14
|
+
"""
|
|
15
|
+
Manages display of tool calls and results in natural language.
|
|
16
|
+
|
|
17
|
+
Supports multiple display modes:
|
|
18
|
+
- natural: Human-friendly descriptions (default)
|
|
19
|
+
- json: Raw JSON panels (old behavior)
|
|
20
|
+
- verbose: Both natural + JSON
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: Dict):
|
|
24
|
+
"""
|
|
25
|
+
Initialize display manager.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
config: Configuration dict with display settings
|
|
29
|
+
"""
|
|
30
|
+
self.display_mode = config.get("display_mode", "natural")
|
|
31
|
+
self.show_tool_results = config.get("show_tool_results", True)
|
|
32
|
+
|
|
33
|
+
def display_tool_call(self, tool_name: str, parameters: Dict[str, Any]):
|
|
34
|
+
"""
|
|
35
|
+
Display a tool call in the configured mode.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
tool_name: Name of the tool being called
|
|
39
|
+
parameters: Tool parameters
|
|
40
|
+
"""
|
|
41
|
+
if self.display_mode == "natural":
|
|
42
|
+
self._display_natural_tool_call(tool_name, parameters)
|
|
43
|
+
elif self.display_mode == "json":
|
|
44
|
+
self._display_json_tool_call(tool_name, parameters)
|
|
45
|
+
elif self.display_mode == "verbose":
|
|
46
|
+
self._display_natural_tool_call(tool_name, parameters)
|
|
47
|
+
self._display_json_tool_call(tool_name, parameters)
|
|
48
|
+
|
|
49
|
+
def display_tool_result(
|
|
50
|
+
self, tool_name: str, result: Dict[str, Any], success: bool
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Display a tool execution result.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
tool_name: Name of the tool that was executed
|
|
57
|
+
result: Result dictionary
|
|
58
|
+
success: Whether the execution succeeded
|
|
59
|
+
"""
|
|
60
|
+
if not self.show_tool_results:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
if self.display_mode == "natural":
|
|
64
|
+
self._display_natural_result(tool_name, result, success)
|
|
65
|
+
elif self.display_mode in ("json", "verbose"):
|
|
66
|
+
self._display_json_result(tool_name, result, success)
|
|
67
|
+
|
|
68
|
+
def _display_natural_tool_call(
|
|
69
|
+
self, tool_name: str, parameters: Dict[str, Any]
|
|
70
|
+
):
|
|
71
|
+
"""Display tool call in natural language."""
|
|
72
|
+
description = self._get_natural_description(tool_name, parameters)
|
|
73
|
+
console.print(f"[cyan]→ {description}[/cyan]")
|
|
74
|
+
|
|
75
|
+
def _display_json_tool_call(
|
|
76
|
+
self, tool_name: str, parameters: Dict[str, Any]
|
|
77
|
+
):
|
|
78
|
+
"""Display tool call as JSON panel."""
|
|
79
|
+
tool_data = {"tool": tool_name, "parameters": parameters}
|
|
80
|
+
json_str = json.dumps(tool_data, indent=2)
|
|
81
|
+
syntax = Syntax(json_str, "json", theme="monokai", line_numbers=False)
|
|
82
|
+
console.print(Panel(syntax, title="Tool Call", border_style="cyan"))
|
|
83
|
+
|
|
84
|
+
def _display_natural_result(
|
|
85
|
+
self, tool_name: str, result: Dict[str, Any], success: bool
|
|
86
|
+
):
|
|
87
|
+
"""Display result in natural language."""
|
|
88
|
+
if success:
|
|
89
|
+
# Extract key information from result
|
|
90
|
+
result_text = result.get("result", "")
|
|
91
|
+
|
|
92
|
+
# Truncate long results for display
|
|
93
|
+
if len(result_text) > 200:
|
|
94
|
+
preview = result_text[:200] + "..."
|
|
95
|
+
console.print(
|
|
96
|
+
f"[green]✓ {tool_name} succeeded[/green] [dim](output truncated)[/dim]"
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
console.print(f"[green]✓ {tool_name} succeeded[/green]")
|
|
100
|
+
else:
|
|
101
|
+
error = result.get("error", "Unknown error")
|
|
102
|
+
console.print(f"[red]✗ {tool_name} failed: {error}[/red]")
|
|
103
|
+
|
|
104
|
+
def _display_json_result(
|
|
105
|
+
self, tool_name: str, result: Dict[str, Any], success: bool
|
|
106
|
+
):
|
|
107
|
+
"""Display result as JSON panel."""
|
|
108
|
+
json_str = json.dumps(result, indent=2)
|
|
109
|
+
syntax = Syntax(json_str, "json", theme="monokai", line_numbers=False)
|
|
110
|
+
border_style = "green" if success else "red"
|
|
111
|
+
title = f"Result: {tool_name}"
|
|
112
|
+
console.print(Panel(syntax, title=title, border_style=border_style))
|
|
113
|
+
|
|
114
|
+
def _get_natural_description(
|
|
115
|
+
self, tool_name: str, parameters: Dict[str, Any]
|
|
116
|
+
) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Generate natural language description for a tool call.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
tool_name: Name of the tool
|
|
122
|
+
parameters: Tool parameters
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Human-friendly description
|
|
126
|
+
"""
|
|
127
|
+
if tool_name == "read_file":
|
|
128
|
+
file_path = parameters.get("file_path", "unknown")
|
|
129
|
+
return f"Reading {file_path}"
|
|
130
|
+
|
|
131
|
+
elif tool_name == "write_file":
|
|
132
|
+
file_path = parameters.get("file_path", "unknown")
|
|
133
|
+
content_length = len(parameters.get("content", ""))
|
|
134
|
+
return (
|
|
135
|
+
f"Writing {content_length} characters to {file_path}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
elif tool_name == "edit_file":
|
|
139
|
+
file_path = parameters.get("file_path", "unknown")
|
|
140
|
+
return f"Editing {file_path}"
|
|
141
|
+
|
|
142
|
+
elif tool_name == "list_files":
|
|
143
|
+
pattern = parameters.get("pattern", "*")
|
|
144
|
+
directory = parameters.get("directory", ".")
|
|
145
|
+
return f"Listing files matching '{pattern}' in {directory}"
|
|
146
|
+
|
|
147
|
+
elif tool_name == "search_files":
|
|
148
|
+
pattern = parameters.get("pattern", "")
|
|
149
|
+
file_pattern = parameters.get("file_pattern", "**/*")
|
|
150
|
+
return f"Searching for '{pattern}' in files matching '{file_pattern}'"
|
|
151
|
+
|
|
152
|
+
elif tool_name == "run_bash":
|
|
153
|
+
command = parameters.get("command", "")
|
|
154
|
+
# Truncate long commands
|
|
155
|
+
if len(command) > 60:
|
|
156
|
+
command = command[:60] + "..."
|
|
157
|
+
return f"Running bash command: {command}"
|
|
158
|
+
|
|
159
|
+
elif tool_name == "create_directory":
|
|
160
|
+
path = parameters.get("path", "unknown")
|
|
161
|
+
return f"Creating directory {path}"
|
|
162
|
+
|
|
163
|
+
else:
|
|
164
|
+
# Fallback for unknown tools
|
|
165
|
+
return f"Calling {tool_name}"
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Execution strategy configuration based on task complexity."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
from .classifier import TaskClassification
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ExecutionConfig:
|
|
11
|
+
"""Configuration for task execution."""
|
|
12
|
+
|
|
13
|
+
mode: str # "conversational", "agentic_simple", "agentic_complex"
|
|
14
|
+
max_iterations: int
|
|
15
|
+
use_agent_loop: bool
|
|
16
|
+
use_planning: bool
|
|
17
|
+
model_tier: str # "fast", "balanced", "best"
|
|
18
|
+
temperature: float
|
|
19
|
+
max_tokens: Optional[int]
|
|
20
|
+
hyperparameters: Dict
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ExecutionStrategy:
|
|
24
|
+
"""
|
|
25
|
+
Determines execution strategy based on task classification.
|
|
26
|
+
|
|
27
|
+
Optimizes for:
|
|
28
|
+
- Cost (use smaller models for simple tasks)
|
|
29
|
+
- Speed (fewer iterations for simple tasks)
|
|
30
|
+
- Quality (best models for complex tasks)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# Model tiers by provider
|
|
34
|
+
MODEL_TIERS = {
|
|
35
|
+
"openai": {
|
|
36
|
+
"fast": "gpt-3.5-turbo",
|
|
37
|
+
"balanced": "gpt-4",
|
|
38
|
+
"best": "gpt-4",
|
|
39
|
+
},
|
|
40
|
+
"anthropic": {
|
|
41
|
+
"fast": "claude-haiku-4-5-20251001", # 4-5x faster, 1/3 cost
|
|
42
|
+
"balanced": "claude-sonnet-4-5-20250929", # Claude Sonnet 4.5 (best balance, workhorse)
|
|
43
|
+
"best": "claude-opus-4-1-20250805", # Claude Opus 4.1 (most capable)
|
|
44
|
+
},
|
|
45
|
+
"triton": {
|
|
46
|
+
# Triton uses same model for all tiers
|
|
47
|
+
"fast": None, # Use configured model
|
|
48
|
+
"balanced": None,
|
|
49
|
+
"best": None,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def get_execution_config(
|
|
55
|
+
classification: Optional[TaskClassification],
|
|
56
|
+
provider_name: str,
|
|
57
|
+
default_model: str,
|
|
58
|
+
) -> ExecutionConfig:
|
|
59
|
+
"""
|
|
60
|
+
Get execution configuration based on task classification.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
classification: Task classification result (None for fallback)
|
|
64
|
+
provider_name: Provider name (triton, openai, anthropic)
|
|
65
|
+
default_model: Default model name from config
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
ExecutionConfig with optimized settings
|
|
69
|
+
"""
|
|
70
|
+
# Handle None classification (fallback to simple)
|
|
71
|
+
if classification is None:
|
|
72
|
+
return ExecutionStrategy._simple_config(provider_name, default_model)
|
|
73
|
+
|
|
74
|
+
complexity = classification.complexity
|
|
75
|
+
|
|
76
|
+
if complexity == "CONVERSATIONAL":
|
|
77
|
+
return ExecutionStrategy._conversational_config(
|
|
78
|
+
provider_name, default_model
|
|
79
|
+
)
|
|
80
|
+
elif complexity == "SIMPLE":
|
|
81
|
+
return ExecutionStrategy._simple_config(provider_name, default_model)
|
|
82
|
+
elif complexity == "COMPLEX":
|
|
83
|
+
return ExecutionStrategy._complex_config(provider_name, default_model)
|
|
84
|
+
else:
|
|
85
|
+
# Fallback to simple
|
|
86
|
+
return ExecutionStrategy._simple_config(provider_name, default_model)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _conversational_config(
|
|
90
|
+
provider_name: str, default_model: str
|
|
91
|
+
) -> ExecutionConfig:
|
|
92
|
+
"""
|
|
93
|
+
Configuration for conversational tasks.
|
|
94
|
+
|
|
95
|
+
- Single-turn response
|
|
96
|
+
- No agent loop
|
|
97
|
+
- Fast/cheap model
|
|
98
|
+
- Higher temperature for creativity
|
|
99
|
+
"""
|
|
100
|
+
# Get fast model for provider
|
|
101
|
+
model_tier = ExecutionStrategy.MODEL_TIERS.get(
|
|
102
|
+
provider_name, ExecutionStrategy.MODEL_TIERS["triton"]
|
|
103
|
+
)
|
|
104
|
+
fast_model = model_tier.get("fast") or default_model
|
|
105
|
+
|
|
106
|
+
return ExecutionConfig(
|
|
107
|
+
mode="conversational",
|
|
108
|
+
max_iterations=1,
|
|
109
|
+
use_agent_loop=False,
|
|
110
|
+
use_planning=False,
|
|
111
|
+
model_tier="fast",
|
|
112
|
+
temperature=0.7, # Higher for natural conversation
|
|
113
|
+
max_tokens=1000, # Shorter responses
|
|
114
|
+
hyperparameters={
|
|
115
|
+
"model": fast_model,
|
|
116
|
+
"temperature": 0.7,
|
|
117
|
+
"max_tokens": 1000,
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _simple_config(provider_name: str, default_model: str) -> ExecutionConfig:
|
|
123
|
+
"""
|
|
124
|
+
Configuration for simple tasks.
|
|
125
|
+
|
|
126
|
+
- Agent loop with low iterations
|
|
127
|
+
- No planning phase
|
|
128
|
+
- Balanced model
|
|
129
|
+
- Medium temperature
|
|
130
|
+
"""
|
|
131
|
+
model_tier = ExecutionStrategy.MODEL_TIERS.get(
|
|
132
|
+
provider_name, ExecutionStrategy.MODEL_TIERS["triton"]
|
|
133
|
+
)
|
|
134
|
+
balanced_model = model_tier.get("balanced") or default_model
|
|
135
|
+
|
|
136
|
+
return ExecutionConfig(
|
|
137
|
+
mode="agentic_simple",
|
|
138
|
+
max_iterations=5, # Lower for simple tasks
|
|
139
|
+
use_agent_loop=True,
|
|
140
|
+
use_planning=False, # Skip planning for simple tasks
|
|
141
|
+
model_tier="balanced",
|
|
142
|
+
temperature=0.4, # Lower for accuracy
|
|
143
|
+
max_tokens=2000,
|
|
144
|
+
hyperparameters={
|
|
145
|
+
"model": balanced_model,
|
|
146
|
+
"temperature": 0.4,
|
|
147
|
+
"max_tokens": 2000,
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def _complex_config(provider_name: str, default_model: str) -> ExecutionConfig:
|
|
153
|
+
"""
|
|
154
|
+
Configuration for complex tasks.
|
|
155
|
+
|
|
156
|
+
- Full agent loop
|
|
157
|
+
- Planning phase available
|
|
158
|
+
- Best model
|
|
159
|
+
- Low temperature for consistency
|
|
160
|
+
"""
|
|
161
|
+
model_tier = ExecutionStrategy.MODEL_TIERS.get(
|
|
162
|
+
provider_name, ExecutionStrategy.MODEL_TIERS["triton"]
|
|
163
|
+
)
|
|
164
|
+
best_model = model_tier.get("best") or default_model
|
|
165
|
+
|
|
166
|
+
return ExecutionConfig(
|
|
167
|
+
mode="agentic_complex",
|
|
168
|
+
max_iterations=15, # Full iterations
|
|
169
|
+
use_agent_loop=True,
|
|
170
|
+
use_planning=True, # Enable planning for complex tasks
|
|
171
|
+
model_tier="best",
|
|
172
|
+
temperature=0.3, # Lower for consistency
|
|
173
|
+
max_tokens=4000,
|
|
174
|
+
hyperparameters={
|
|
175
|
+
"model": best_model,
|
|
176
|
+
"temperature": 0.3,
|
|
177
|
+
"max_tokens": 4000,
|
|
178
|
+
},
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def get_model_for_tier(provider_name: str, tier: str, default_model: str) -> str:
|
|
183
|
+
"""
|
|
184
|
+
Get model name for a specific tier.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
provider_name: Provider name
|
|
188
|
+
tier: Model tier (fast, balanced, best)
|
|
189
|
+
default_model: Fallback model
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Model name
|
|
193
|
+
"""
|
|
194
|
+
model_tier = ExecutionStrategy.MODEL_TIERS.get(
|
|
195
|
+
provider_name, ExecutionStrategy.MODEL_TIERS["triton"]
|
|
196
|
+
)
|
|
197
|
+
return model_tier.get(tier) or default_model
|