alpiecode 3.0.0__tar.gz → 4.0.0__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.
- {alpiecode-3.0.0 → alpiecode-4.0.0}/PKG-INFO +1 -1
- {alpiecode-3.0.0 → alpiecode-4.0.0}/pyproject.toml +1 -1
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/__init__.py +5 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/agent.py +16 -2
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/cli.py +2 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/client.py +1 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/config.py +5 -4
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/ipython_ext.py +16 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/orchestrator.py +92 -43
- alpiecode-4.0.0/src/codeagent/prompt.py +404 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/server.py +9 -4
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/tools.py +3 -1
- alpiecode-3.0.0/src/codeagent/prompt.py +0 -454
- {alpiecode-3.0.0 → alpiecode-4.0.0}/README.md +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/setup.cfg +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode/__init__.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/SOURCES.txt +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/backends/base.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/backends/local_backend.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/backends/openai_backend.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/cache.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/compaction.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/context.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/doctor.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/executor.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/github.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/guardian.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/local_model.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/media.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/memory.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/session.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/updater.py +0 -0
- {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/vscode_installer.py +0 -0
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
3
|
+
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
4
|
+
warnings.filterwarnings("ignore", module="jupyter_client.*")
|
|
5
|
+
|
|
1
6
|
"""
|
|
2
7
|
AlpieCode — Autonomous AI Coding Agent powered by local 169Pi GGUF VLM & Online API.
|
|
3
8
|
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
3
|
+
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
4
|
+
warnings.filterwarnings("ignore", module="jupyter_client.*")
|
|
5
|
+
|
|
1
6
|
"""
|
|
2
7
|
CLI agent wrapper and presentation adapter for AlpieCode.
|
|
3
8
|
|
|
@@ -150,6 +155,7 @@ def run_agent(
|
|
|
150
155
|
url: str = None,
|
|
151
156
|
github_repo: str = None,
|
|
152
157
|
server_url: str = None,
|
|
158
|
+
complexity: str = None,
|
|
153
159
|
) -> list:
|
|
154
160
|
"""Run non-interactive agent task with Rich presentation."""
|
|
155
161
|
workdir = workdir.resolve()
|
|
@@ -173,6 +179,7 @@ def run_agent(
|
|
|
173
179
|
video_path=video_path,
|
|
174
180
|
url=url,
|
|
175
181
|
github_repo=github_repo,
|
|
182
|
+
complexity=complexity,
|
|
176
183
|
)
|
|
177
184
|
else:
|
|
178
185
|
backend = resolve_backend(cfg)
|
|
@@ -187,6 +194,7 @@ def run_agent(
|
|
|
187
194
|
video_path=video_path,
|
|
188
195
|
url=url,
|
|
189
196
|
github_repo=github_repo,
|
|
197
|
+
complexity=complexity,
|
|
190
198
|
)
|
|
191
199
|
|
|
192
200
|
_checkpoint(workdir, "checkpoint: start")
|
|
@@ -209,10 +217,12 @@ def run_agent(
|
|
|
209
217
|
console.print(f"📂 Workdir: {workdir}", style="dim")
|
|
210
218
|
if not data["is_offline"]:
|
|
211
219
|
console.print(f"🌐 Mode: [bold green]ONLINE[/bold green]", style="dim")
|
|
212
|
-
console.print(f"🤖 Model: [bold cyan]alpie_9b[/bold cyan]", style="dim")
|
|
213
220
|
else:
|
|
214
221
|
console.print(f"🧠 Mode: [bold yellow]OFFLINE[/bold yellow]", style="dim")
|
|
215
|
-
|
|
222
|
+
comp = data.get("complexity", "low")
|
|
223
|
+
comp_label = {"qa": "Q&A (instant)", "low": "Low (fast)", "medium": "Medium (balanced)", "high": "High (thorough)"}.get(comp, comp)
|
|
224
|
+
comp_color = {"qa": "cyan", "low": "green", "medium": "yellow", "high": "red"}.get(comp, "white")
|
|
225
|
+
console.print(f"⚡ Complexity: [bold {comp_color}]{comp_label}[/bold {comp_color}]", style="dim")
|
|
216
226
|
if cfg.enable_thinking:
|
|
217
227
|
console.print(f"🧠 Reasoning: [bold green]ON[/bold green]", style="dim")
|
|
218
228
|
else:
|
|
@@ -234,6 +244,10 @@ def run_agent(
|
|
|
234
244
|
elif event.type == "compaction" and verbose:
|
|
235
245
|
console.print("🗜️ Compacting context (approaching token limit)...", style="yellow")
|
|
236
246
|
|
|
247
|
+
elif event.type == "wrap_up" and verbose:
|
|
248
|
+
if HAS_RICH:
|
|
249
|
+
console.print(f"⏳ [bold yellow]Wrap-up: {event.data['remaining']} turns remaining[/bold yellow]", style="yellow")
|
|
250
|
+
|
|
237
251
|
elif event.type == "thinking" and verbose:
|
|
238
252
|
_print_reasoning(event.data["content"])
|
|
239
253
|
|
|
@@ -76,6 +76,7 @@ def main():
|
|
|
76
76
|
common.add_argument("--max-turns", type=int, default=None, help="Override max turns")
|
|
77
77
|
common.add_argument("--thinking", "--reasoning", dest="thinking", action="store_true", help="Enable VLM reasoning mode")
|
|
78
78
|
common.add_argument("--no-thinking", "--non-thinking", dest="no_thinking", action="store_true", help="Disable VLM reasoning mode")
|
|
79
|
+
common.add_argument("--complexity", choices=["low", "medium", "high"], default=None, help="Task complexity: low (fast, 10 turns), medium (balanced, 20 turns), high (thorough, 40 turns)")
|
|
79
80
|
common.add_argument("--no-update", action="store_true", help="Skip automatic update check")
|
|
80
81
|
common.add_argument("--quiet", action="store_true", help="Suppress per-turn logging")
|
|
81
82
|
|
|
@@ -166,6 +167,7 @@ def main():
|
|
|
166
167
|
image_path=args.image,
|
|
167
168
|
video_path=getattr(args, "video", None),
|
|
168
169
|
url=getattr(args, "url", None),
|
|
170
|
+
complexity=getattr(args, "complexity", None),
|
|
169
171
|
github_repo=getattr(args, "github", None),
|
|
170
172
|
)
|
|
171
173
|
|
|
@@ -73,6 +73,7 @@ class AlpieCodeClient:
|
|
|
73
73
|
video_path: Optional[str] = None,
|
|
74
74
|
url: Optional[str] = None,
|
|
75
75
|
github_repo: Optional[str] = None,
|
|
76
|
+
complexity: Optional[str] = None,
|
|
76
77
|
) -> Iterator[AgentEvent]:
|
|
77
78
|
"""
|
|
78
79
|
Stream agent events from POST /chat using Server-Sent Events (SSE).
|
|
@@ -18,7 +18,7 @@ CONFIG_DIR = Path.home() / ".alpiecode"
|
|
|
18
18
|
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
19
19
|
|
|
20
20
|
# Config version — bump this when defaults change to trigger auto-migration
|
|
21
|
-
CONFIG_VERSION =
|
|
21
|
+
CONFIG_VERSION = 6 # v6: smart flow, max_turns 20, complexity-aware
|
|
22
22
|
|
|
23
23
|
DEFAULTS = {
|
|
24
24
|
"base_url": "http://20.245.200.125:8000/v1", # Primary endpoint
|
|
@@ -26,7 +26,7 @@ DEFAULTS = {
|
|
|
26
26
|
"model_repo": "169Pi/Alpie_learn_prototype_GGUF_NEW",
|
|
27
27
|
"api_key": "not-needed",
|
|
28
28
|
"hf_token": None,
|
|
29
|
-
"max_turns":
|
|
29
|
+
"max_turns": 20,
|
|
30
30
|
"temperature": 0.1,
|
|
31
31
|
"max_tokens": 8192,
|
|
32
32
|
"enable_thinking": False, # Reasoning OFF by default
|
|
@@ -88,7 +88,7 @@ class Config:
|
|
|
88
88
|
model_repo: str = "169Pi/Alpie_learn_prototype_GGUF_NEW" # HuggingFace repo for offline GGUF
|
|
89
89
|
api_key: str = "not-needed"
|
|
90
90
|
hf_token: Optional[str] = None
|
|
91
|
-
max_turns: int =
|
|
91
|
+
max_turns: int = 20
|
|
92
92
|
temperature: float = 0.1
|
|
93
93
|
max_tokens: int = 8192
|
|
94
94
|
enable_thinking: bool = False
|
|
@@ -111,12 +111,13 @@ def load_config() -> Config:
|
|
|
111
111
|
# v1 → v2: n_ctx was 16384, upgrade to 32768
|
|
112
112
|
if saved_data.get("n_ctx") == 16384:
|
|
113
113
|
data["n_ctx"] = 32768
|
|
114
|
-
#
|
|
114
|
+
# v5 → v6: smart flow, max_turns 20
|
|
115
115
|
data["base_url"] = "http://20.245.200.125:8000/v1"
|
|
116
116
|
data["model"] = "alpie_9b"
|
|
117
117
|
data["enable_thinking"] = False
|
|
118
118
|
data["temperature"] = 0.1
|
|
119
119
|
data["max_tokens"] = 8192
|
|
120
|
+
data["max_turns"] = 20
|
|
120
121
|
data["config_version"] = CONFIG_VERSION
|
|
121
122
|
needs_save = True
|
|
122
123
|
except Exception:
|
|
@@ -11,9 +11,16 @@ and Google Colab via cell/line magic commands and rich interactive displays:
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
import sys
|
|
14
|
+
import warnings
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Optional
|
|
16
17
|
|
|
18
|
+
# Suppress noisy Jupyter / Python 3.12+ deprecation warnings (e.g. datetime.utcnow in jupyter_client)
|
|
19
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
20
|
+
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
21
|
+
warnings.filterwarnings("ignore", module="jupyter_client.*")
|
|
22
|
+
warnings.filterwarnings("ignore", module="ipykernel.*")
|
|
23
|
+
|
|
17
24
|
from .config import load_config
|
|
18
25
|
from .agent import run_agent
|
|
19
26
|
|
|
@@ -42,6 +49,15 @@ def _display_html(html_str: str):
|
|
|
42
49
|
print(html_str)
|
|
43
50
|
|
|
44
51
|
|
|
52
|
+
def _display_markdown(md_str: str):
|
|
53
|
+
"""Render rich Markdown in Jupyter or Google Colab."""
|
|
54
|
+
try:
|
|
55
|
+
from IPython.display import Markdown, display
|
|
56
|
+
display(Markdown(md_str))
|
|
57
|
+
except Exception:
|
|
58
|
+
print(md_str)
|
|
59
|
+
|
|
60
|
+
|
|
45
61
|
def alpie_magic(line: str, cell: Optional[str] = None):
|
|
46
62
|
"""
|
|
47
63
|
%alpie <task> (line magic)
|
|
@@ -1,40 +1,31 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Agent orchestrator for AlpieCode.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
Yields a stream of transport-agnostic AgentEvent objects.
|
|
4
|
+
Owns the turn loop, backend resolution, caching, and event stream.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
|
-
import
|
|
9
|
-
from dataclasses import dataclass
|
|
10
|
-
from
|
|
11
|
-
from typing import Any, Dict, Iterator, Optional
|
|
7
|
+
import copy
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any, Dict, Iterator, List, Optional
|
|
12
10
|
|
|
13
|
-
from .backends.base import
|
|
11
|
+
from .backends.base import InferenceBackend, ChatResponse
|
|
14
12
|
from .backends.local_backend import LocalBackend
|
|
15
13
|
from .backends.openai_backend import OpenAIBackend
|
|
16
14
|
from .cache import get_cache
|
|
17
15
|
from .config import Config, is_server_reachable
|
|
18
|
-
from .context import ContextManager
|
|
19
|
-
from .executor import ToolExecutor, ToolResult
|
|
20
16
|
from .memory import extract_and_save_memories
|
|
21
|
-
from .prompt import PromptBuilder,
|
|
17
|
+
from .prompt import PromptBuilder, classify_task, COMPLEXITY_CONFIG
|
|
22
18
|
from .session import Session, SessionManager
|
|
23
19
|
|
|
24
20
|
|
|
25
21
|
@dataclass
|
|
26
22
|
class AgentEvent:
|
|
27
|
-
"""Structured event yielded by the orchestrator."""
|
|
28
23
|
type: str
|
|
29
24
|
data: Dict[str, Any]
|
|
30
25
|
|
|
31
26
|
|
|
32
27
|
def resolve_backend(cfg: Config, timeout: float = 2.0) -> InferenceBackend:
|
|
33
|
-
"""Resolve online vs offline backend based on server reachability.
|
|
34
|
-
|
|
35
|
-
Uses a generous timeout at startup (2s default) to avoid false negatives
|
|
36
|
-
when the remote API is slow to respond (e.g. Azure VM cold start).
|
|
37
|
-
"""
|
|
28
|
+
"""Resolve online vs offline backend based on server reachability."""
|
|
38
29
|
if is_server_reachable(cfg.base_url, timeout=timeout):
|
|
39
30
|
return OpenAIBackend(cfg)
|
|
40
31
|
return LocalBackend(cfg)
|
|
@@ -60,12 +51,25 @@ class AgentOrchestrator:
|
|
|
60
51
|
video_path: Optional[str] = None,
|
|
61
52
|
url: Optional[str] = None,
|
|
62
53
|
github_repo: Optional[str] = None,
|
|
54
|
+
complexity: Optional[str] = None,
|
|
63
55
|
) -> Iterator[AgentEvent]:
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
"""Run full agent task loop. Yields AgentEvents."""
|
|
57
|
+
|
|
58
|
+
# ── Auto-classify complexity if not provided ──
|
|
59
|
+
if complexity is None:
|
|
60
|
+
complexity = classify_task(task)
|
|
61
|
+
|
|
62
|
+
comp_cfg = COMPLEXITY_CONFIG.get(complexity, COMPLEXITY_CONFIG["low"])
|
|
63
|
+
|
|
64
|
+
# ── Determine effective max_turns and max_tokens ──
|
|
65
|
+
effective_max_turns = min(cfg.max_turns, comp_cfg["max_turns"])
|
|
66
|
+
effective_max_tokens = comp_cfg["max_tokens"]
|
|
67
|
+
|
|
68
|
+
# User override: if they set --max-turns explicitly, respect it
|
|
69
|
+
if cfg.max_turns != 20: # 20 is new default, so non-default = explicit
|
|
70
|
+
effective_max_turns = cfg.max_turns
|
|
71
|
+
|
|
72
|
+
# ── Response cache check ──
|
|
69
73
|
is_cacheable = not any([image_path, video_path, url, github_repo])
|
|
70
74
|
if is_cacheable:
|
|
71
75
|
cache = get_cache()
|
|
@@ -77,6 +81,7 @@ class AgentOrchestrator:
|
|
|
77
81
|
"backend": "cache",
|
|
78
82
|
"is_offline": False,
|
|
79
83
|
"tool_count": 0,
|
|
84
|
+
"complexity": complexity,
|
|
80
85
|
})
|
|
81
86
|
yield AgentEvent("cache_hit", {
|
|
82
87
|
"message": "Returning cached response (instant)",
|
|
@@ -87,19 +92,18 @@ class AgentOrchestrator:
|
|
|
87
92
|
yield AgentEvent("done", {"summary": cached["response"]})
|
|
88
93
|
return
|
|
89
94
|
|
|
90
|
-
# Dynamic backend re-check
|
|
91
|
-
# API is now reachable, switch to OnlineBackend automatically.
|
|
92
|
-
# This handles the case where the server started offline but the
|
|
93
|
-
# remote API came online later (e.g. VM cold start, network hiccup).
|
|
95
|
+
# ── Dynamic backend re-check ──
|
|
94
96
|
if isinstance(self.backend, LocalBackend) and is_server_reachable(cfg.base_url, timeout=1.5):
|
|
95
97
|
self.backend = OpenAIBackend(cfg)
|
|
96
98
|
|
|
97
99
|
is_offline = not self.backend.is_available or isinstance(self.backend, LocalBackend)
|
|
98
100
|
session.is_offline = is_offline
|
|
99
101
|
|
|
100
|
-
# Configure tools & system prompt
|
|
101
|
-
active_tools = self.prompt_builder.get_tools(is_offline=is_offline)
|
|
102
|
-
system_prompt = self.prompt_builder.build_system_prompt(
|
|
102
|
+
# ── Configure tools & system prompt based on complexity ──
|
|
103
|
+
active_tools = self.prompt_builder.get_tools(is_offline=is_offline, complexity=complexity)
|
|
104
|
+
system_prompt = self.prompt_builder.build_system_prompt(
|
|
105
|
+
session.workdir, is_offline=is_offline, complexity=complexity
|
|
106
|
+
)
|
|
103
107
|
session.context.set_system_prompt(system_prompt)
|
|
104
108
|
|
|
105
109
|
user_content = self.prompt_builder.build_user_content(
|
|
@@ -118,50 +122,65 @@ class AgentOrchestrator:
|
|
|
118
122
|
"backend": self.backend.name,
|
|
119
123
|
"is_offline": is_offline,
|
|
120
124
|
"tool_count": len(active_tools),
|
|
125
|
+
"complexity": complexity,
|
|
121
126
|
})
|
|
122
127
|
|
|
123
|
-
# Adaptive thinking
|
|
128
|
+
# ── Adaptive thinking ──
|
|
124
129
|
enable_thinking = cfg.enable_thinking
|
|
125
|
-
if enable_thinking and
|
|
130
|
+
if enable_thinking and complexity in ("qa", "low"):
|
|
126
131
|
enable_thinking = False
|
|
127
132
|
yield AgentEvent("adaptive_mode", {"message": "Simple task detected, skipping deep reasoning."})
|
|
128
133
|
|
|
129
|
-
|
|
134
|
+
# ── Turn loop ──
|
|
135
|
+
wrap_up_injected = False
|
|
136
|
+
|
|
137
|
+
for turn in range(effective_max_turns):
|
|
130
138
|
if session.cancelled:
|
|
131
139
|
yield AgentEvent("cancelled", {"turn": turn + 1})
|
|
132
140
|
break
|
|
133
141
|
|
|
134
|
-
# Context compaction
|
|
142
|
+
# Context compaction
|
|
135
143
|
if session.context.check_and_compact():
|
|
136
144
|
yield AgentEvent("compaction", {"turn": turn + 1})
|
|
137
145
|
|
|
146
|
+
# ── Wrap-up injection at 80% of turns ──
|
|
147
|
+
if not wrap_up_injected and turn >= int(effective_max_turns * 0.8):
|
|
148
|
+
wrap_up_injected = True
|
|
149
|
+
remaining = effective_max_turns - turn
|
|
150
|
+
session.context.add_user_message(
|
|
151
|
+
f"[SYSTEM] You have {remaining} turns remaining. "
|
|
152
|
+
"Finish your current work now. If code is written and tested, "
|
|
153
|
+
"output DONE: <summary>. If code has errors, make one final fix attempt."
|
|
154
|
+
)
|
|
155
|
+
yield AgentEvent("wrap_up", {"turn": turn + 1, "remaining": remaining})
|
|
156
|
+
|
|
138
157
|
yield AgentEvent("turn_start", {"turn": turn + 1})
|
|
139
158
|
|
|
140
159
|
try:
|
|
141
160
|
if enable_thinking:
|
|
142
|
-
max_tokens = 4096 if is_offline else max(
|
|
161
|
+
max_tokens = 4096 if is_offline else max(effective_max_tokens, 16384)
|
|
143
162
|
else:
|
|
144
|
-
max_tokens = 2048 if is_offline else
|
|
163
|
+
max_tokens = 2048 if is_offline else effective_max_tokens
|
|
145
164
|
|
|
146
165
|
resp = self.backend.chat_completion(
|
|
147
166
|
messages=session.context.messages,
|
|
148
|
-
tools=active_tools,
|
|
167
|
+
tools=active_tools if active_tools else None,
|
|
149
168
|
temperature=cfg.temperature,
|
|
150
169
|
max_tokens=max_tokens,
|
|
151
170
|
enable_thinking=enable_thinking,
|
|
152
171
|
)
|
|
153
172
|
except Exception as e:
|
|
154
|
-
# Online error
|
|
173
|
+
# Online error -> fallback to local
|
|
155
174
|
if not is_offline and isinstance(self.backend, OpenAIBackend):
|
|
156
175
|
yield AgentEvent("fallback", {"error": str(e), "message": "Falling back to local engine"})
|
|
157
176
|
self.backend = LocalBackend(cfg)
|
|
158
177
|
session.is_offline = True
|
|
159
178
|
is_offline = True
|
|
160
|
-
active_tools = self.prompt_builder.get_tools(is_offline=True)
|
|
179
|
+
active_tools = self.prompt_builder.get_tools(is_offline=True, complexity=complexity)
|
|
161
180
|
try:
|
|
162
181
|
resp = self.backend.chat_completion(
|
|
163
182
|
messages=session.context.messages,
|
|
164
|
-
tools=active_tools,
|
|
183
|
+
tools=active_tools if active_tools else None,
|
|
165
184
|
temperature=cfg.temperature,
|
|
166
185
|
max_tokens=2048,
|
|
167
186
|
enable_thinking=enable_thinking,
|
|
@@ -178,7 +197,38 @@ class AgentOrchestrator:
|
|
|
178
197
|
|
|
179
198
|
session.context.add_assistant_response(resp)
|
|
180
199
|
|
|
181
|
-
#
|
|
200
|
+
# ── DONE detection in assistant content ──
|
|
201
|
+
if resp.content and "DONE:" in resp.content.upper():
|
|
202
|
+
# Model said DONE — finish even if there are tool calls
|
|
203
|
+
tool_calls = session.executor.extract_tool_calls(resp)
|
|
204
|
+
if tool_calls:
|
|
205
|
+
# Execute final tool calls before finishing
|
|
206
|
+
results = session.executor.execute_tool_calls(tool_calls)
|
|
207
|
+
for res in results:
|
|
208
|
+
yield AgentEvent("tool_result", {
|
|
209
|
+
"turn": turn + 1,
|
|
210
|
+
"id": res.tool_call_id,
|
|
211
|
+
"name": res.name,
|
|
212
|
+
"content": res.content,
|
|
213
|
+
"duration_ms": res.duration_ms,
|
|
214
|
+
})
|
|
215
|
+
session.context.add_tool_result(res.tool_call_id, res.content)
|
|
216
|
+
|
|
217
|
+
yield AgentEvent("message", {"content": resp.content})
|
|
218
|
+
extract_and_save_memories(session.workdir, session.context.messages)
|
|
219
|
+
|
|
220
|
+
# Cache if single-turn
|
|
221
|
+
if is_cacheable and turn == 0:
|
|
222
|
+
try:
|
|
223
|
+
cache = get_cache()
|
|
224
|
+
cache.put(task, resp.content, reasoning=resp.reasoning)
|
|
225
|
+
except Exception:
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
yield AgentEvent("done", {"summary": resp.content})
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
# Extract and execute tool calls
|
|
182
232
|
tool_calls = session.executor.extract_tool_calls(resp)
|
|
183
233
|
|
|
184
234
|
if tool_calls:
|
|
@@ -204,9 +254,8 @@ class AgentOrchestrator:
|
|
|
204
254
|
|
|
205
255
|
continue
|
|
206
256
|
|
|
207
|
-
#
|
|
257
|
+
# Text-only response = done
|
|
208
258
|
if resp.content:
|
|
209
|
-
# Cache this response — it completed in a single turn without tools
|
|
210
259
|
if is_cacheable and turn == 0:
|
|
211
260
|
try:
|
|
212
261
|
cache = get_cache()
|
|
@@ -219,10 +268,10 @@ class AgentOrchestrator:
|
|
|
219
268
|
yield AgentEvent("done", {"summary": resp.content})
|
|
220
269
|
return
|
|
221
270
|
|
|
222
|
-
#
|
|
271
|
+
# Empty response
|
|
223
272
|
extract_and_save_memories(session.workdir, session.context.messages)
|
|
224
273
|
yield AgentEvent("done", {"summary": "Task completed."})
|
|
225
274
|
return
|
|
226
275
|
|
|
227
|
-
yield AgentEvent("max_turns_reached", {"max_turns":
|
|
276
|
+
yield AgentEvent("max_turns_reached", {"max_turns": effective_max_turns})
|
|
228
277
|
extract_and_save_memories(session.workdir, session.context.messages)
|