kubrick-cli 0.1.2__tar.gz
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-0.1.2/LICENSE +21 -0
- kubrick_cli-0.1.2/PKG-INFO +86 -0
- kubrick_cli-0.1.2/README.md +64 -0
- kubrick_cli-0.1.2/kubrick_cli/__init__.py +47 -0
- kubrick_cli-0.1.2/kubrick_cli/agent_loop.py +307 -0
- kubrick_cli-0.1.2/kubrick_cli/classifier.py +198 -0
- kubrick_cli-0.1.2/kubrick_cli/config.py +248 -0
- kubrick_cli-0.1.2/kubrick_cli/display.py +165 -0
- kubrick_cli-0.1.2/kubrick_cli/execution_strategy.py +197 -0
- kubrick_cli-0.1.2/kubrick_cli/main.py +797 -0
- kubrick_cli-0.1.2/kubrick_cli/planning.py +328 -0
- kubrick_cli-0.1.2/kubrick_cli/progress.py +172 -0
- kubrick_cli-0.1.2/kubrick_cli/providers/__init__.py +6 -0
- kubrick_cli-0.1.2/kubrick_cli/providers/anthropic_provider.py +224 -0
- kubrick_cli-0.1.2/kubrick_cli/providers/base.py +137 -0
- kubrick_cli-0.1.2/kubrick_cli/providers/factory.py +183 -0
- kubrick_cli-0.1.2/kubrick_cli/providers/openai_provider.py +190 -0
- kubrick_cli-0.1.2/kubrick_cli/providers/triton_provider.py +96 -0
- kubrick_cli-0.1.2/kubrick_cli/safety.py +216 -0
- kubrick_cli-0.1.2/kubrick_cli/scheduler.py +197 -0
- kubrick_cli-0.1.2/kubrick_cli/setup_wizard.py +172 -0
- kubrick_cli-0.1.2/kubrick_cli/tools.py +407 -0
- kubrick_cli-0.1.2/kubrick_cli/triton_client.py +192 -0
- kubrick_cli-0.1.2/kubrick_cli.egg-info/PKG-INFO +86 -0
- kubrick_cli-0.1.2/kubrick_cli.egg-info/SOURCES.txt +33 -0
- kubrick_cli-0.1.2/kubrick_cli.egg-info/dependency_links.txt +1 -0
- kubrick_cli-0.1.2/kubrick_cli.egg-info/entry_points.txt +2 -0
- kubrick_cli-0.1.2/kubrick_cli.egg-info/requires.txt +9 -0
- kubrick_cli-0.1.2/kubrick_cli.egg-info/top_level.txt +1 -0
- kubrick_cli-0.1.2/pyproject.toml +40 -0
- kubrick_cli-0.1.2/setup.cfg +4 -0
- kubrick_cli-0.1.2/tests/test_client.py +74 -0
- kubrick_cli-0.1.2/tests/test_config.py +101 -0
- kubrick_cli-0.1.2/tests/test_tool_calling.py +126 -0
- kubrick_cli-0.1.2/tests/test_triton.py +127 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2026] [Russell Land]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kubrick-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A CLI tool for AI-assisted coding using Triton LLM backend
|
|
5
|
+
Author-email: Russell Land <rcland12@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rcland12/kubrick-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/rcland12/kubrick-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/rcland12/kubrick-cli/issues
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: rich>=13.0.0
|
|
14
|
+
Requires-Dist: prompt_toolkit>=3.0.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
18
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
19
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# Kubrick CLI
|
|
24
|
+
|
|
25
|
+
An AI-assisted coding CLI tool powered by your own Triton LLM backend. Like Claude Code, but self-hosted.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Local Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install -e .
|
|
33
|
+
kubrick
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Docker
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd /path/to/your/project
|
|
40
|
+
docker run --rm -it \
|
|
41
|
+
--network host \
|
|
42
|
+
-v ${HOME}:/home/kubrick \
|
|
43
|
+
-v ${PWD}:/workspace \
|
|
44
|
+
-v /etc/localtime:/etc/localtime:ro \
|
|
45
|
+
-v /etc/timezone:/etc/timezone:ro \
|
|
46
|
+
rcland12/kubrick-cli
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Basic usage
|
|
53
|
+
kubrick
|
|
54
|
+
|
|
55
|
+
# Custom Triton server
|
|
56
|
+
kubrick --triton-url my-server:8000
|
|
57
|
+
|
|
58
|
+
# Load previous conversation
|
|
59
|
+
kubrick --load 20240118_143022
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Requirements
|
|
63
|
+
|
|
64
|
+
- Python 3.8+
|
|
65
|
+
- Triton Inference Server with streaming LLM model
|
|
66
|
+
- Default endpoint: `http://localhost:8000`
|
|
67
|
+
- Default model: `llm_decoupled`
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
- **[WIKI.md](docs/WIKI.md)** - Complete features, commands, and usage guide
|
|
72
|
+
- **[TRITON.md](docs/TRITON.md)** - Triton backend setup and requirements
|
|
73
|
+
- **[DOCKER.md](docs/DOCKER.md)** - Docker setup and troubleshooting
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
Config stored at `~/.kubrick/config.json`. Override with environment variables:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
export TRITON_URL=localhost:8000
|
|
81
|
+
export TRITON_MODEL_NAME=llm_decoupled
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT License - See [LICENSE](LICENSE)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Kubrick CLI
|
|
2
|
+
|
|
3
|
+
An AI-assisted coding CLI tool powered by your own Triton LLM backend. Like Claude Code, but self-hosted.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Local Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install -e .
|
|
11
|
+
kubrick
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Docker
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd /path/to/your/project
|
|
18
|
+
docker run --rm -it \
|
|
19
|
+
--network host \
|
|
20
|
+
-v ${HOME}:/home/kubrick \
|
|
21
|
+
-v ${PWD}:/workspace \
|
|
22
|
+
-v /etc/localtime:/etc/localtime:ro \
|
|
23
|
+
-v /etc/timezone:/etc/timezone:ro \
|
|
24
|
+
rcland12/kubrick-cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Basic usage
|
|
31
|
+
kubrick
|
|
32
|
+
|
|
33
|
+
# Custom Triton server
|
|
34
|
+
kubrick --triton-url my-server:8000
|
|
35
|
+
|
|
36
|
+
# Load previous conversation
|
|
37
|
+
kubrick --load 20240118_143022
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- Python 3.8+
|
|
43
|
+
- Triton Inference Server with streaming LLM model
|
|
44
|
+
- Default endpoint: `http://localhost:8000`
|
|
45
|
+
- Default model: `llm_decoupled`
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
- **[WIKI.md](docs/WIKI.md)** - Complete features, commands, and usage guide
|
|
50
|
+
- **[TRITON.md](docs/TRITON.md)** - Triton backend setup and requirements
|
|
51
|
+
- **[DOCKER.md](docs/DOCKER.md)** - Docker setup and troubleshooting
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
Config stored at `~/.kubrick/config.json`. Override with environment variables:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export TRITON_URL=localhost:8000
|
|
59
|
+
export TRITON_MODEL_NAME=llm_decoupled
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT License - See [LICENSE](LICENSE)
|
|
@@ -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
|
+
}
|