alpiecode 0.6.0__tar.gz → 0.7.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-0.6.0 → alpiecode-0.7.0}/PKG-INFO +2 -2
- {alpiecode-0.6.0 → alpiecode-0.7.0}/pyproject.toml +2 -2
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/alpiecode.egg-info/PKG-INFO +2 -2
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/alpiecode.egg-info/requires.txt +1 -1
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/agent.py +73 -2
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/config.py +19 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/local_model.py +45 -14
- {alpiecode-0.6.0 → alpiecode-0.7.0}/README.md +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/setup.cfg +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/alpiecode.egg-info/SOURCES.txt +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/__init__.py +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/cli.py +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/compaction.py +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/github.py +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/guardian.py +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/media.py +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/memory.py +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/tools.py +0 -0
- {alpiecode-0.6.0 → alpiecode-0.7.0}/src/codeagent/updater.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpiecode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Requires-Dist: huggingface_hub>=0.20.0
|
|
7
7
|
Requires-Dist: rich>=13.0
|
|
8
8
|
Requires-Dist: click>=8.0
|
|
9
9
|
Requires-Dist: openai>=1.0.0
|
|
10
|
-
Requires-Dist: httpx>=0.24.0
|
|
10
|
+
Requires-Dist: httpx[http2]>=0.24.0
|
|
11
11
|
Requires-Dist: duckduckgo-search>=7.0.0
|
|
12
12
|
Requires-Dist: beautifulsoup4>=4.12.0
|
|
13
13
|
Requires-Dist: yt-dlp>=2024.0.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "alpiecode"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM"
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
dependencies = [
|
|
@@ -12,7 +12,7 @@ dependencies = [
|
|
|
12
12
|
"rich>=13.0",
|
|
13
13
|
"click>=8.0",
|
|
14
14
|
"openai>=1.0.0",
|
|
15
|
-
"httpx>=0.24.0",
|
|
15
|
+
"httpx[http2]>=0.24.0",
|
|
16
16
|
"duckduckgo-search>=7.0.0",
|
|
17
17
|
"beautifulsoup4>=4.12.0",
|
|
18
18
|
"yt-dlp>=2024.0.0",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpiecode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Requires-Dist: huggingface_hub>=0.20.0
|
|
7
7
|
Requires-Dist: rich>=13.0
|
|
8
8
|
Requires-Dist: click>=8.0
|
|
9
9
|
Requires-Dist: openai>=1.0.0
|
|
10
|
-
Requires-Dist: httpx>=0.24.0
|
|
10
|
+
Requires-Dist: httpx[http2]>=0.24.0
|
|
11
11
|
Requires-Dist: duckduckgo-search>=7.0.0
|
|
12
12
|
Requires-Dist: beautifulsoup4>=4.12.0
|
|
13
13
|
Requires-Dist: yt-dlp>=2024.0.0
|
|
@@ -13,6 +13,7 @@ import json
|
|
|
13
13
|
import re
|
|
14
14
|
import subprocess
|
|
15
15
|
import sys
|
|
16
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
|
|
18
19
|
import httpx
|
|
@@ -23,6 +24,26 @@ from .tools import TOOLS, make_dispatch
|
|
|
23
24
|
from .compaction import needs_compaction, compact_messages
|
|
24
25
|
from .memory import format_memories_for_prompt, extract_and_save_memories
|
|
25
26
|
|
|
27
|
+
# ── Tool classification for parallel dispatch ─────────────────────────
|
|
28
|
+
# READ_ONLY tools can execute concurrently; MUTATING tools must be sequential
|
|
29
|
+
READ_ONLY_TOOLS = frozenset({"read_file", "list_files", "file_search", "fetch_url", "web_search"})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _is_simple_task(task: str) -> bool:
|
|
33
|
+
"""Detect simple tasks that don't benefit from deep reasoning traces."""
|
|
34
|
+
task_lower = task.lower().strip()
|
|
35
|
+
# Short prompts (< 80 chars) are almost always simple
|
|
36
|
+
if len(task_lower) < 80:
|
|
37
|
+
return True
|
|
38
|
+
# Keyword patterns indicating simple edits
|
|
39
|
+
simple_patterns = [
|
|
40
|
+
"fix typo", "add comment", "rename", "format", "add docstring",
|
|
41
|
+
"remove unused", "add import", "update version", "change color",
|
|
42
|
+
"fix indent", "add logging", "hello world", "fibonacci",
|
|
43
|
+
"print ", "add a test", "calculator",
|
|
44
|
+
]
|
|
45
|
+
return any(pat in task_lower for pat in simple_patterns)
|
|
46
|
+
|
|
26
47
|
SYSTEM_PROMPT = """\
|
|
27
48
|
You are AlpieCode, an autonomous software-engineering agent built by 169Pi. You operate \
|
|
28
49
|
autonomously to solve the user's requirements end to end, bringing the judgement \
|
|
@@ -482,10 +503,11 @@ def run_agent(task: str, workdir: Path, cfg: Config, verbose: bool = True,
|
|
|
482
503
|
|
|
483
504
|
if server_online:
|
|
484
505
|
# ── ONLINE MODE: Server reachable ─────────────────────────────
|
|
506
|
+
from .config import get_shared_http_client
|
|
485
507
|
client = OpenAI(
|
|
486
508
|
base_url=cfg.base_url,
|
|
487
509
|
api_key=cfg.api_key or "not-needed",
|
|
488
|
-
|
|
510
|
+
http_client=get_shared_http_client(),
|
|
489
511
|
)
|
|
490
512
|
local_model = None
|
|
491
513
|
else:
|
|
@@ -559,6 +581,14 @@ def run_agent(task: str, workdir: Path, cfg: Config, verbose: bool = True,
|
|
|
559
581
|
compile_fail_counts = {} # Track compilation failures per file
|
|
560
582
|
tool_call_history = [] # Track repeated tool calls to prevent infinite loops
|
|
561
583
|
|
|
584
|
+
# ── Adaptive Thinking Router ──────────────────────────────────────
|
|
585
|
+
# Auto-disable thinking for simple tasks to save 10-20s of reasoning
|
|
586
|
+
if cfg.enable_thinking and _is_simple_task(task):
|
|
587
|
+
cfg = Config(**{f.name: getattr(cfg, f.name) for f in cfg.__dataclass_fields__.values()})
|
|
588
|
+
cfg.enable_thinking = False
|
|
589
|
+
if verbose and HAS_RICH:
|
|
590
|
+
console.print("⚡ [dim]Adaptive mode: simple task detected, skipping deep reasoning for speed[/dim]")
|
|
591
|
+
|
|
562
592
|
for turn in range(cfg.max_turns):
|
|
563
593
|
# Context compaction check — use actual n_ctx for offline (32k), full for online (262k)
|
|
564
594
|
ctx_limit = cfg.n_ctx if not server_online else 262_144
|
|
@@ -665,6 +695,46 @@ def run_agent(task: str, workdir: Path, cfg: Config, verbose: bool = True,
|
|
|
665
695
|
})
|
|
666
696
|
|
|
667
697
|
if raw_tool_calls:
|
|
698
|
+
# ── Parallel Multi-Tool Dispatch Engine ───────────────────
|
|
699
|
+
# Classify calls: execute contiguous READ_ONLY tools concurrently
|
|
700
|
+
# Mutating tools (write_file, edit_file, bash) remain sequential
|
|
701
|
+
all_read_only = all(tc["name"] in READ_ONLY_TOOLS for tc in raw_tool_calls)
|
|
702
|
+
use_parallel = all_read_only and len(raw_tool_calls) > 1
|
|
703
|
+
|
|
704
|
+
if use_parallel:
|
|
705
|
+
# Execute all read-only tools concurrently
|
|
706
|
+
if verbose and HAS_RICH:
|
|
707
|
+
console.print(f" ⚡ [dim]Parallel dispatch: {len(raw_tool_calls)} read-only tools[/dim]")
|
|
708
|
+
results_map = {}
|
|
709
|
+
with ThreadPoolExecutor(max_workers=min(8, len(raw_tool_calls))) as pool:
|
|
710
|
+
future_to_tc = {}
|
|
711
|
+
for tc in raw_tool_calls:
|
|
712
|
+
fn_name = tc["name"]
|
|
713
|
+
args = tc["arguments"]
|
|
714
|
+
if verbose:
|
|
715
|
+
_print_tool_call(turn, fn_name, args)
|
|
716
|
+
future = pool.submit(dispatch[fn_name], args)
|
|
717
|
+
future_to_tc[future] = tc
|
|
718
|
+
for future in as_completed(future_to_tc):
|
|
719
|
+
tc = future_to_tc[future]
|
|
720
|
+
try:
|
|
721
|
+
results_map[tc["id"]] = future.result()
|
|
722
|
+
except Exception as e:
|
|
723
|
+
results_map[tc["id"]] = f"error: {e}"
|
|
724
|
+
# Append results in original order
|
|
725
|
+
for tc in raw_tool_calls:
|
|
726
|
+
result = str(results_map[tc["id"]])
|
|
727
|
+
if verbose:
|
|
728
|
+
_print_tool_result(result)
|
|
729
|
+
messages.append({
|
|
730
|
+
"role": "tool",
|
|
731
|
+
"tool_call_id": tc["id"],
|
|
732
|
+
"content": result,
|
|
733
|
+
})
|
|
734
|
+
_checkpoint(workdir, f"checkpoint: turn {turn + 1}")
|
|
735
|
+
continue
|
|
736
|
+
|
|
737
|
+
# Sequential execution for mutating tools (or single tool calls)
|
|
668
738
|
for tc in raw_tool_calls:
|
|
669
739
|
fn_name = tc["name"]
|
|
670
740
|
args = tc["arguments"]
|
|
@@ -775,10 +845,11 @@ def run_chat(workdir: Path, cfg: Config, verbose: bool = True) -> None:
|
|
|
775
845
|
server_online = is_server_reachable(cfg.base_url)
|
|
776
846
|
|
|
777
847
|
if server_online:
|
|
848
|
+
from .config import get_shared_http_client
|
|
778
849
|
client = OpenAI(
|
|
779
850
|
base_url=cfg.base_url,
|
|
780
851
|
api_key=cfg.api_key or "not-needed",
|
|
781
|
-
|
|
852
|
+
http_client=get_shared_http_client(),
|
|
782
853
|
)
|
|
783
854
|
local_model = None
|
|
784
855
|
else:
|
|
@@ -62,6 +62,25 @@ def is_internet_available(timeout: float = 1.0) -> bool:
|
|
|
62
62
|
return False
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
# ── Shared HTTP/2 connection pool ─────────────────────────────────────
|
|
66
|
+
_SHARED_HTTP_CLIENT = None
|
|
67
|
+
|
|
68
|
+
def get_shared_http_client():
|
|
69
|
+
"""
|
|
70
|
+
Get or create a shared HTTP/2 client with TCP keep-alive and connection pooling.
|
|
71
|
+
Eliminates 150-300ms connection overhead per API call by reusing sockets.
|
|
72
|
+
"""
|
|
73
|
+
global _SHARED_HTTP_CLIENT
|
|
74
|
+
if _SHARED_HTTP_CLIENT is None:
|
|
75
|
+
import httpx
|
|
76
|
+
_SHARED_HTTP_CLIENT = httpx.Client(
|
|
77
|
+
http2=True,
|
|
78
|
+
timeout=httpx.Timeout(30.0, connect=3.0),
|
|
79
|
+
limits=httpx.Limits(max_keepalive_connections=20, max_connections=50),
|
|
80
|
+
)
|
|
81
|
+
return _SHARED_HTTP_CLIENT
|
|
82
|
+
|
|
83
|
+
|
|
65
84
|
@dataclass
|
|
66
85
|
class Config:
|
|
67
86
|
base_url: Optional[str] = None
|
|
@@ -296,14 +296,32 @@ class LocalModel:
|
|
|
296
296
|
Llama = _ensure_llama_cpp()
|
|
297
297
|
|
|
298
298
|
accel = "GPU" if self.n_gpu_layers != 0 else "CPU"
|
|
299
|
+
|
|
300
|
+
# ── Smart P-Core Topology Auto-Tuning ─────────────────────────
|
|
301
|
+
# On hybrid CPUs (Intel 12th+), over-subscribing E-cores causes
|
|
302
|
+
# cache thrashing. Use physical core count to avoid this.
|
|
303
|
+
n_threads = max(1, (os.cpu_count() or 4) - 1)
|
|
304
|
+
try:
|
|
305
|
+
import subprocess
|
|
306
|
+
# Linux: count physical cores only (excludes hyperthreads)
|
|
307
|
+
res = subprocess.run(
|
|
308
|
+
["nproc", "--all"],
|
|
309
|
+
capture_output=True, text=True, timeout=2
|
|
310
|
+
)
|
|
311
|
+
if res.returncode == 0:
|
|
312
|
+
logical = int(res.stdout.strip())
|
|
313
|
+
# Heuristic: physical cores ≈ logical / 2 on HT systems
|
|
314
|
+
# Use 75% of logical cores as a safe threading target
|
|
315
|
+
n_threads = max(1, int(logical * 0.75))
|
|
316
|
+
except Exception:
|
|
317
|
+
pass
|
|
318
|
+
|
|
299
319
|
print(f"🧠 Loading local GGUF model: {model_path.name}")
|
|
300
|
-
print(f" Context: {self.n_ctx} tokens | Mode: {accel} | Threads: {
|
|
320
|
+
print(f" Context: {self.n_ctx} tokens | Mode: {accel} | Threads: {n_threads}")
|
|
301
321
|
if accel == "CPU":
|
|
302
322
|
print(f" ⏳ CPU loading ~4GB model — this takes 30-90 seconds on first run...")
|
|
303
323
|
else:
|
|
304
|
-
print(f" ⚡ GPU-accelerated loading...")
|
|
305
|
-
|
|
306
|
-
n_threads = max(1, (os.cpu_count() or 4) - 1)
|
|
324
|
+
print(f" ⚡ GPU-accelerated loading (Q8 KV cache enabled)...")
|
|
307
325
|
|
|
308
326
|
# Suppress noisy ggml_vulkan/ggml_cuda stderr messages from C library
|
|
309
327
|
# These confuse users ("ggml_vulkan: Found 1 Vulkan devices: Intel UHD...")
|
|
@@ -318,17 +336,30 @@ class LocalModel:
|
|
|
318
336
|
except Exception:
|
|
319
337
|
_stderr_fd = None # Fallback: don't suppress if dup2 fails
|
|
320
338
|
|
|
339
|
+
# ── Build Llama kwargs with Q8_0 KV Cache for GPU ─────────────
|
|
340
|
+
llama_kwargs = dict(
|
|
341
|
+
model_path=str(model_path),
|
|
342
|
+
n_ctx=self.n_ctx,
|
|
343
|
+
n_batch=2048 if self.n_gpu_layers == 0 else 4096,
|
|
344
|
+
n_threads=n_threads,
|
|
345
|
+
n_gpu_layers=self.n_gpu_layers,
|
|
346
|
+
use_mmap=True,
|
|
347
|
+
flash_attn=(self.n_gpu_layers != 0), # Flash Attention only for GPU
|
|
348
|
+
verbose=False,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Quantized KV Cache (Q8_0) — halves VRAM usage, ~0% quality loss
|
|
352
|
+
# Only enable for GPU mode where VRAM savings matter
|
|
353
|
+
if self.n_gpu_layers != 0:
|
|
354
|
+
try:
|
|
355
|
+
llama_kwargs["type_k"] = 8 # GGML_TYPE_Q8_0
|
|
356
|
+
llama_kwargs["type_v"] = 8 # GGML_TYPE_Q8_0
|
|
357
|
+
llama_kwargs["offload_kqv"] = True # Keep K/V tensors in VRAM
|
|
358
|
+
except Exception:
|
|
359
|
+
pass # Older llama-cpp-python versions may not support these
|
|
360
|
+
|
|
321
361
|
try:
|
|
322
|
-
self._llm = Llama(
|
|
323
|
-
model_path=str(model_path),
|
|
324
|
-
n_ctx=self.n_ctx,
|
|
325
|
-
n_batch=2048 if self.n_gpu_layers == 0 else 4096,
|
|
326
|
-
n_threads=n_threads,
|
|
327
|
-
n_gpu_layers=self.n_gpu_layers,
|
|
328
|
-
use_mmap=True,
|
|
329
|
-
flash_attn=(self.n_gpu_layers != 0), # Flash Attention only for GPU
|
|
330
|
-
verbose=False,
|
|
331
|
-
)
|
|
362
|
+
self._llm = Llama(**llama_kwargs)
|
|
332
363
|
finally:
|
|
333
364
|
# Restore stderr
|
|
334
365
|
if _stderr_fd is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|