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/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Kubrick CLI - AI-assisted coding tool with agentic capabilities."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.2.0"
|
|
4
|
+
|
|
5
|
+
from .agent_loop import AgentLoop, CompletionDetector
|
|
6
|
+
from .classifier import TaskClassification, TaskClassifier
|
|
7
|
+
from .config import KubrickConfig
|
|
8
|
+
from .display import DisplayManager
|
|
9
|
+
from .execution_strategy import ExecutionConfig, ExecutionStrategy
|
|
10
|
+
from .main import KubrickCLI
|
|
11
|
+
from .planning import PlanningPhase
|
|
12
|
+
from .progress import ProgressTracker
|
|
13
|
+
from .providers.base import ProviderAdapter
|
|
14
|
+
from .providers.factory import ProviderFactory
|
|
15
|
+
from .providers.triton_provider import TritonProvider
|
|
16
|
+
from .providers.openai_provider import OpenAIProvider
|
|
17
|
+
from .providers.anthropic_provider import AnthropicProvider
|
|
18
|
+
from .safety import SafetyConfig, SafetyManager
|
|
19
|
+
from .scheduler import ToolScheduler
|
|
20
|
+
from .setup_wizard import SetupWizard
|
|
21
|
+
from .tools import ToolExecutor
|
|
22
|
+
from .triton_client import TritonLLMClient
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"AgentLoop",
|
|
26
|
+
"AnthropicProvider",
|
|
27
|
+
"CompletionDetector",
|
|
28
|
+
"DisplayManager",
|
|
29
|
+
"ExecutionConfig",
|
|
30
|
+
"ExecutionStrategy",
|
|
31
|
+
"KubrickCLI",
|
|
32
|
+
"KubrickConfig",
|
|
33
|
+
"OpenAIProvider",
|
|
34
|
+
"PlanningPhase",
|
|
35
|
+
"ProgressTracker",
|
|
36
|
+
"ProviderAdapter",
|
|
37
|
+
"ProviderFactory",
|
|
38
|
+
"SafetyConfig",
|
|
39
|
+
"SafetyManager",
|
|
40
|
+
"SetupWizard",
|
|
41
|
+
"TaskClassification",
|
|
42
|
+
"TaskClassifier",
|
|
43
|
+
"ToolExecutor",
|
|
44
|
+
"ToolScheduler",
|
|
45
|
+
"TritonLLMClient",
|
|
46
|
+
"TritonProvider",
|
|
47
|
+
]
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""Multi-step agentic execution loop with completion detection."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict, Iterator, List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
# Import display manager (will be None if not available)
|
|
11
|
+
try:
|
|
12
|
+
from .display import DisplayManager
|
|
13
|
+
except ImportError:
|
|
14
|
+
DisplayManager = None
|
|
15
|
+
|
|
16
|
+
# Import scheduler (will be None if not available)
|
|
17
|
+
try:
|
|
18
|
+
from .scheduler import ToolScheduler
|
|
19
|
+
except ImportError:
|
|
20
|
+
ToolScheduler = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CompletionDetector:
|
|
24
|
+
"""Detects when an agent has completed its task."""
|
|
25
|
+
|
|
26
|
+
# Explicit completion markers the agent can use
|
|
27
|
+
COMPLETION_MARKERS = [
|
|
28
|
+
"TASK_COMPLETE",
|
|
29
|
+
"PLAN_COMPLETE",
|
|
30
|
+
"[COMPLETE]",
|
|
31
|
+
"[DONE]",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def is_complete(
|
|
36
|
+
response_text: str,
|
|
37
|
+
has_tool_calls: bool,
|
|
38
|
+
iteration: int,
|
|
39
|
+
max_iterations: int,
|
|
40
|
+
) -> Tuple[bool, str]:
|
|
41
|
+
"""
|
|
42
|
+
Determine if the agent has completed its task.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
response_text: The agent's response text
|
|
46
|
+
has_tool_calls: Whether the response contains tool calls
|
|
47
|
+
iteration: Current iteration number
|
|
48
|
+
max_iterations: Maximum allowed iterations
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Tuple of (is_complete, reason)
|
|
52
|
+
"""
|
|
53
|
+
# Check for explicit completion markers
|
|
54
|
+
for marker in CompletionDetector.COMPLETION_MARKERS:
|
|
55
|
+
if marker in response_text:
|
|
56
|
+
return True, f"explicit_marker:{marker}"
|
|
57
|
+
|
|
58
|
+
# Max iterations reached (fallback safety)
|
|
59
|
+
if iteration >= max_iterations:
|
|
60
|
+
return True, "max_iterations_reached"
|
|
61
|
+
|
|
62
|
+
# If no tool calls and response looks conclusive
|
|
63
|
+
if not has_tool_calls:
|
|
64
|
+
# Check if response appears to be a final summary/conclusion
|
|
65
|
+
if CompletionDetector._looks_conclusive(response_text):
|
|
66
|
+
return True, "conclusive_response"
|
|
67
|
+
|
|
68
|
+
# Otherwise, continue
|
|
69
|
+
return False, "continuing"
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _looks_conclusive(text: str) -> bool:
|
|
73
|
+
"""
|
|
74
|
+
Check if text looks like a conclusive response.
|
|
75
|
+
|
|
76
|
+
This is a heuristic to detect when the agent is done without
|
|
77
|
+
explicitly saying so.
|
|
78
|
+
"""
|
|
79
|
+
text_lower = text.lower()
|
|
80
|
+
|
|
81
|
+
# Look for summary/conclusion indicators
|
|
82
|
+
conclusive_patterns = [
|
|
83
|
+
r"\b(done|completed|finished|ready)\b",
|
|
84
|
+
r"\b(successfully|all set|good to go)\b",
|
|
85
|
+
r"\bhere(?:'s| is) (?:the |a )?(?:summary|result)",
|
|
86
|
+
r"\b(?:task|work|changes) (?:is |are )?(?:complete|done|finished)",
|
|
87
|
+
r"\blet me know if you need",
|
|
88
|
+
r"\bthat(?:'s| should do it)",
|
|
89
|
+
r"\beverything(?:'s| is) (?:set|ready|done)",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
for pattern in conclusive_patterns:
|
|
93
|
+
if re.search(pattern, text_lower):
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AgentLoop:
|
|
100
|
+
"""
|
|
101
|
+
Multi-step agentic execution loop.
|
|
102
|
+
|
|
103
|
+
Runs iteratively until the task is complete or max iterations reached.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
llm_client,
|
|
109
|
+
tool_executor,
|
|
110
|
+
max_iterations: int = 15,
|
|
111
|
+
max_tools_per_turn: int = 5,
|
|
112
|
+
timeout_seconds: int = 600,
|
|
113
|
+
stream_options: Dict = None,
|
|
114
|
+
display_manager=None,
|
|
115
|
+
tool_scheduler=None,
|
|
116
|
+
):
|
|
117
|
+
"""
|
|
118
|
+
Initialize the agent loop.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
llm_client: LLM client instance (e.g., TritonLLMClient)
|
|
122
|
+
tool_executor: Tool executor instance
|
|
123
|
+
max_iterations: Maximum number of iterations
|
|
124
|
+
max_tools_per_turn: Maximum tools per turn
|
|
125
|
+
timeout_seconds: Total timeout in seconds
|
|
126
|
+
stream_options: Optional streaming options
|
|
127
|
+
display_manager: Optional DisplayManager instance for natural language display
|
|
128
|
+
tool_scheduler: Optional ToolScheduler for parallel execution
|
|
129
|
+
"""
|
|
130
|
+
self.llm_client = llm_client
|
|
131
|
+
self.tool_executor = tool_executor
|
|
132
|
+
self.max_iterations = max_iterations
|
|
133
|
+
self.max_tools_per_turn = max_tools_per_turn
|
|
134
|
+
self.timeout_seconds = timeout_seconds
|
|
135
|
+
self.stream_options = stream_options or {}
|
|
136
|
+
self.display_manager = display_manager
|
|
137
|
+
self.tool_scheduler = tool_scheduler
|
|
138
|
+
|
|
139
|
+
def run(
|
|
140
|
+
self,
|
|
141
|
+
messages: List[Dict],
|
|
142
|
+
tool_parser,
|
|
143
|
+
display_callback=None,
|
|
144
|
+
) -> Dict:
|
|
145
|
+
"""
|
|
146
|
+
Run the agentic loop.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
messages: Conversation messages (will be modified in-place)
|
|
150
|
+
tool_parser: Function to parse tool calls from text
|
|
151
|
+
display_callback: Optional callback for displaying streaming response
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dict with execution results and metadata
|
|
155
|
+
"""
|
|
156
|
+
iteration = 0
|
|
157
|
+
total_tool_calls = 0
|
|
158
|
+
|
|
159
|
+
while iteration < self.max_iterations:
|
|
160
|
+
iteration += 1
|
|
161
|
+
|
|
162
|
+
console.print(
|
|
163
|
+
f"\n[dim]→ Agent iteration {iteration}/{self.max_iterations}[/dim]"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Get LLM response
|
|
167
|
+
console.print("[bold cyan]Assistant:[/bold cyan]")
|
|
168
|
+
chunks = []
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
for chunk in self.llm_client.generate_streaming(
|
|
172
|
+
messages, stream_options=self.stream_options
|
|
173
|
+
):
|
|
174
|
+
console.print(chunk, end="")
|
|
175
|
+
chunks.append(chunk)
|
|
176
|
+
|
|
177
|
+
console.print("\n")
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
console.print(f"\n[red]Error during LLM generation: {e}[/red]")
|
|
181
|
+
return {
|
|
182
|
+
"success": False,
|
|
183
|
+
"error": str(e),
|
|
184
|
+
"iterations": iteration,
|
|
185
|
+
"tool_calls": total_tool_calls,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Get full response
|
|
189
|
+
response_text = "".join(chunks)
|
|
190
|
+
|
|
191
|
+
# Add to conversation
|
|
192
|
+
messages.append({"role": "assistant", "content": response_text})
|
|
193
|
+
|
|
194
|
+
# Display with custom callback if provided
|
|
195
|
+
if display_callback:
|
|
196
|
+
display_callback(response_text)
|
|
197
|
+
|
|
198
|
+
# Parse tool calls
|
|
199
|
+
tool_calls = tool_parser(response_text)
|
|
200
|
+
|
|
201
|
+
# Check completion
|
|
202
|
+
is_complete, reason = CompletionDetector.is_complete(
|
|
203
|
+
response_text=response_text,
|
|
204
|
+
has_tool_calls=len(tool_calls) > 0,
|
|
205
|
+
iteration=iteration,
|
|
206
|
+
max_iterations=self.max_iterations,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if is_complete:
|
|
210
|
+
console.print(f"\n[green]✓ Task complete ({reason})[/green]")
|
|
211
|
+
return {
|
|
212
|
+
"success": True,
|
|
213
|
+
"completion_reason": reason,
|
|
214
|
+
"iterations": iteration,
|
|
215
|
+
"tool_calls": total_tool_calls,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# Execute tool calls if any
|
|
219
|
+
if tool_calls:
|
|
220
|
+
# Enforce max tools per turn
|
|
221
|
+
if len(tool_calls) > self.max_tools_per_turn:
|
|
222
|
+
console.print(
|
|
223
|
+
f"[yellow]⚠ Too many tool calls ({len(tool_calls)}), "
|
|
224
|
+
f"limiting to {self.max_tools_per_turn}[/yellow]"
|
|
225
|
+
)
|
|
226
|
+
tool_calls = tool_calls[: self.max_tools_per_turn]
|
|
227
|
+
|
|
228
|
+
console.print(
|
|
229
|
+
f"\n[yellow]Executing {len(tool_calls)} tool(s)...[/yellow]\n"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Execute tools (with or without scheduler)
|
|
233
|
+
if self.tool_scheduler and len(tool_calls) > 1:
|
|
234
|
+
# Use scheduler for parallel execution
|
|
235
|
+
execution_results = self.tool_scheduler.execute_tools(
|
|
236
|
+
tool_calls
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
# Execute sequentially
|
|
240
|
+
execution_results = [
|
|
241
|
+
self.tool_executor.execute(tool_name, params)
|
|
242
|
+
for tool_name, params in tool_calls
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
# Display and collect results
|
|
246
|
+
tool_results = []
|
|
247
|
+
for (tool_name, parameters), result in zip(
|
|
248
|
+
tool_calls, execution_results
|
|
249
|
+
):
|
|
250
|
+
# Display tool call and result
|
|
251
|
+
if self.display_manager:
|
|
252
|
+
self.display_manager.display_tool_call(
|
|
253
|
+
tool_name, parameters
|
|
254
|
+
)
|
|
255
|
+
self.display_manager.display_tool_result(
|
|
256
|
+
tool_name, result, result["success"]
|
|
257
|
+
)
|
|
258
|
+
else:
|
|
259
|
+
console.print(f"[cyan]→ Called {tool_name}[/cyan]")
|
|
260
|
+
if result["success"]:
|
|
261
|
+
console.print(
|
|
262
|
+
f"[green]✓ {tool_name} succeeded[/green]"
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
console.print(
|
|
266
|
+
f"[red]✗ {tool_name} failed: {result['error']}[/red]"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Add to results
|
|
270
|
+
if result["success"]:
|
|
271
|
+
tool_results.append(
|
|
272
|
+
f"Tool: {tool_name}\nResult: {result['result']}"
|
|
273
|
+
)
|
|
274
|
+
else:
|
|
275
|
+
tool_results.append(
|
|
276
|
+
f"Tool: {tool_name}\nError: {result['error']}"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
total_tool_calls += 1
|
|
280
|
+
|
|
281
|
+
# Add tool results to conversation
|
|
282
|
+
tool_results_text = "\n\n".join(tool_results)
|
|
283
|
+
messages.append(
|
|
284
|
+
{
|
|
285
|
+
"role": "user",
|
|
286
|
+
"content": f"Tool execution results:\n\n{tool_results_text}",
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Continue to next iteration
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
# If no tool calls and not complete, something might be wrong
|
|
294
|
+
console.print(
|
|
295
|
+
"[yellow]⚠ No tool calls and task not marked complete. Continuing...[/yellow]"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Max iterations reached
|
|
299
|
+
console.print(
|
|
300
|
+
f"\n[yellow]⚠ Max iterations ({self.max_iterations}) reached[/yellow]"
|
|
301
|
+
)
|
|
302
|
+
return {
|
|
303
|
+
"success": True,
|
|
304
|
+
"completion_reason": "max_iterations",
|
|
305
|
+
"iterations": iteration,
|
|
306
|
+
"tool_calls": total_tool_calls,
|
|
307
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Task complexity classification for intelligent routing."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TaskClassification:
|
|
14
|
+
"""Result of task classification."""
|
|
15
|
+
|
|
16
|
+
complexity: str # CONVERSATIONAL, SIMPLE, COMPLEX
|
|
17
|
+
reasoning: str
|
|
18
|
+
estimated_tool_calls: int
|
|
19
|
+
requires_tools: bool
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TaskClassifier:
|
|
23
|
+
"""
|
|
24
|
+
Classifies tasks into three tiers to determine execution strategy.
|
|
25
|
+
|
|
26
|
+
CONVERSATIONAL:
|
|
27
|
+
- Greetings, questions, general chat
|
|
28
|
+
- No tool calls needed
|
|
29
|
+
- Single-turn response sufficient
|
|
30
|
+
|
|
31
|
+
SIMPLE:
|
|
32
|
+
- Single file operations
|
|
33
|
+
- Clear, specific scope
|
|
34
|
+
- Estimated 1-5 tool calls
|
|
35
|
+
- Low iteration count (3-5)
|
|
36
|
+
|
|
37
|
+
COMPLEX:
|
|
38
|
+
- Multi-file operations
|
|
39
|
+
- Architectural changes
|
|
40
|
+
- Uncertain scope
|
|
41
|
+
- Estimated >5 tool calls
|
|
42
|
+
- Full iteration count (15)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, llm_client):
|
|
46
|
+
"""
|
|
47
|
+
Initialize task classifier.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
llm_client: LLM client instance for classification
|
|
51
|
+
"""
|
|
52
|
+
self.llm_client = llm_client
|
|
53
|
+
|
|
54
|
+
def classify(
|
|
55
|
+
self, user_message: str, conversation_history: List[Dict] = None
|
|
56
|
+
) -> TaskClassification:
|
|
57
|
+
"""
|
|
58
|
+
Classify a task into CONVERSATIONAL, SIMPLE, or COMPLEX.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
user_message: The user's task request
|
|
62
|
+
conversation_history: Optional conversation context
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
TaskClassification object with detailed classification
|
|
66
|
+
"""
|
|
67
|
+
# Build classification prompt
|
|
68
|
+
classification_messages = [
|
|
69
|
+
{
|
|
70
|
+
"role": "system",
|
|
71
|
+
"content": """You are a task complexity classifier for a coding assistant.
|
|
72
|
+
|
|
73
|
+
Your job is to classify tasks into three tiers: CONVERSATIONAL, SIMPLE, or COMPLEX.
|
|
74
|
+
|
|
75
|
+
# Classification Criteria
|
|
76
|
+
|
|
77
|
+
**CONVERSATIONAL:**
|
|
78
|
+
- Greetings, questions, general chat
|
|
79
|
+
- No file operations or code changes needed
|
|
80
|
+
- No tools required
|
|
81
|
+
- Estimated 0 tool calls
|
|
82
|
+
- Examples:
|
|
83
|
+
- "Hi", "Hello", "How are you?"
|
|
84
|
+
- "What can you do?"
|
|
85
|
+
- "Explain what async/await means"
|
|
86
|
+
- "Tell me about this project"
|
|
87
|
+
|
|
88
|
+
**SIMPLE:**
|
|
89
|
+
- Single file operations (read, write, edit one file)
|
|
90
|
+
- Clear, specific scope with well-defined requirements
|
|
91
|
+
- Estimated 1-5 tool calls
|
|
92
|
+
- Examples:
|
|
93
|
+
- "Read config.py"
|
|
94
|
+
- "Create a hello world script"
|
|
95
|
+
- "Fix the typo in line 42 of main.py"
|
|
96
|
+
- "List all Python files"
|
|
97
|
+
|
|
98
|
+
**COMPLEX:**
|
|
99
|
+
- Multi-file operations affecting multiple files
|
|
100
|
+
- Architectural changes or refactoring
|
|
101
|
+
- Uncertain scope requiring exploration
|
|
102
|
+
- Estimated >5 tool calls
|
|
103
|
+
- Examples:
|
|
104
|
+
- "Add logging to all Python files"
|
|
105
|
+
- "Refactor the authentication system"
|
|
106
|
+
- "Implement a new feature"
|
|
107
|
+
- "Debug why the tests are failing"
|
|
108
|
+
|
|
109
|
+
# Response Format
|
|
110
|
+
|
|
111
|
+
Respond with ONLY a JSON object:
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"complexity": "CONVERSATIONAL",
|
|
115
|
+
"reasoning": "Brief explanation why",
|
|
116
|
+
"estimated_tool_calls": 0,
|
|
117
|
+
"requires_tools": false
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
OR
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"complexity": "SIMPLE",
|
|
126
|
+
"reasoning": "Brief explanation why",
|
|
127
|
+
"estimated_tool_calls": 2,
|
|
128
|
+
"requires_tools": true
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
OR
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"complexity": "COMPLEX",
|
|
137
|
+
"reasoning": "Brief explanation why",
|
|
138
|
+
"estimated_tool_calls": 10,
|
|
139
|
+
"requires_tools": true
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Respond with ONLY the JSON object, no other text.""",
|
|
144
|
+
},
|
|
145
|
+
{"role": "user", "content": user_message},
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# Get classification from LLM (non-streaming for speed)
|
|
150
|
+
response = self.llm_client.generate(
|
|
151
|
+
classification_messages, stream_options={}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Parse JSON response
|
|
155
|
+
json_start = response.find("{")
|
|
156
|
+
json_end = response.rfind("}") + 1
|
|
157
|
+
|
|
158
|
+
if json_start >= 0 and json_end > json_start:
|
|
159
|
+
json_str = response[json_start:json_end]
|
|
160
|
+
result = json.loads(json_str)
|
|
161
|
+
|
|
162
|
+
complexity = result.get("complexity", "SIMPLE").upper()
|
|
163
|
+
reasoning = result.get("reasoning", "No reasoning provided")
|
|
164
|
+
estimated_tool_calls = result.get("estimated_tool_calls", 0)
|
|
165
|
+
requires_tools = result.get("requires_tools", False)
|
|
166
|
+
|
|
167
|
+
console.print(
|
|
168
|
+
f"[dim]→ Task classified as {complexity}: {reasoning}[/dim]"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return TaskClassification(
|
|
172
|
+
complexity=complexity,
|
|
173
|
+
reasoning=reasoning,
|
|
174
|
+
estimated_tool_calls=estimated_tool_calls,
|
|
175
|
+
requires_tools=requires_tools,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Fallback: default to SIMPLE if parsing fails
|
|
179
|
+
console.print(
|
|
180
|
+
"[yellow]⚠ Classification parsing failed, defaulting to SIMPLE[/yellow]"
|
|
181
|
+
)
|
|
182
|
+
return TaskClassification(
|
|
183
|
+
complexity="SIMPLE",
|
|
184
|
+
reasoning="Parsing failed",
|
|
185
|
+
estimated_tool_calls=3,
|
|
186
|
+
requires_tools=True,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
console.print(
|
|
191
|
+
f"[yellow]⚠ Classification error ({e}), defaulting to SIMPLE[/yellow]"
|
|
192
|
+
)
|
|
193
|
+
return TaskClassification(
|
|
194
|
+
complexity="SIMPLE",
|
|
195
|
+
reasoning=f"Error: {e}",
|
|
196
|
+
estimated_tool_calls=3,
|
|
197
|
+
requires_tools=True,
|
|
198
|
+
)
|