alpiecode 0.6.0__tar.gz → 0.7.1__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.
Files changed (22) hide show
  1. {alpiecode-0.6.0 → alpiecode-0.7.1}/PKG-INFO +2 -2
  2. {alpiecode-0.6.0 → alpiecode-0.7.1}/pyproject.toml +2 -2
  3. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/alpiecode.egg-info/PKG-INFO +2 -2
  4. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/alpiecode.egg-info/requires.txt +1 -1
  5. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/agent.py +73 -2
  6. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/config.py +19 -0
  7. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/local_model.py +65 -27
  8. {alpiecode-0.6.0 → alpiecode-0.7.1}/README.md +0 -0
  9. {alpiecode-0.6.0 → alpiecode-0.7.1}/setup.cfg +0 -0
  10. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/alpiecode.egg-info/SOURCES.txt +0 -0
  11. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  12. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/alpiecode.egg-info/entry_points.txt +0 -0
  13. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/alpiecode.egg-info/top_level.txt +0 -0
  14. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/__init__.py +0 -0
  15. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/cli.py +0 -0
  16. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/compaction.py +0 -0
  17. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/github.py +0 -0
  18. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/guardian.py +0 -0
  19. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/media.py +0 -0
  20. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/memory.py +0 -0
  21. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/tools.py +0 -0
  22. {alpiecode-0.6.0 → alpiecode-0.7.1}/src/codeagent/updater.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 0.6.0
3
+ Version: 0.7.1
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.6.0"
7
+ version = "0.7.1"
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.6.0
3
+ Version: 0.7.1
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
@@ -2,7 +2,7 @@ huggingface_hub>=0.20.0
2
2
  rich>=13.0
3
3
  click>=8.0
4
4
  openai>=1.0.0
5
- httpx>=0.24.0
5
+ httpx[http2]>=0.24.0
6
6
  duckduckgo-search>=7.0.0
7
7
  beautifulsoup4>=4.12.0
8
8
  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
- timeout=httpx.Timeout(30.0, connect=3.0),
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
- timeout=httpx.Timeout(30.0, connect=3.0),
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(120.0, connect=5.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
@@ -242,30 +242,37 @@ def _ensure_llama_cpp():
242
242
  else:
243
243
  wheel_url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34-vulkan/llama_cpp_python-0.3.34-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
244
244
 
245
+ install_targets = [wheel_url, "llama-cpp-python"]
245
246
  installed = False
246
247
  last_error = ""
247
- for cmd_base in [["uv", "pip", "install", wheel_url], [sys.executable, "-m", "pip", "install", wheel_url]]:
248
- try:
249
- res = subprocess.run(cmd_base, capture_output=True, text=True, timeout=120)
250
- if res.returncode == 0:
251
- installed = True
252
- break
253
- else:
254
- last_error = res.stderr.strip() or res.stdout.strip()
255
- except Exception as e:
256
- last_error = str(e)
257
- continue
248
+ for target in install_targets:
249
+ for cmd_base in [
250
+ ["uv", "pip", "install", target],
251
+ [sys.executable, "-m", "pip", "install", target],
252
+ ]:
253
+ try:
254
+ res = subprocess.run(cmd_base, capture_output=True, text=True, timeout=180)
255
+ if res.returncode == 0:
256
+ installed = True
257
+ break
258
+ else:
259
+ last_error = res.stderr.strip() or res.stdout.strip()
260
+ except Exception as e:
261
+ last_error = str(e)
262
+ continue
263
+ if installed:
264
+ break
258
265
 
259
266
  try:
260
267
  from llama_cpp import Llama
261
268
  print("✅ Pre-compiled local GGUF engine installed successfully!")
262
269
  return Llama
263
270
  except ImportError:
264
- err_detail = f"\n Last error: {last_error[:200]}" if last_error else ""
271
+ err_detail = f"\n Last error: {last_error[:300]}" if last_error else ""
265
272
  raise RuntimeError(
266
273
  "\n╭────────────────────────────────────────────────────────────╮\n"
267
274
  "│ Failed to auto-install local GGUF engine. │\n"
268
- "│ Internet connection required for first-time setup. │\n"
275
+ "│ Please check your internet connection for first setup. │\n"
269
276
  "╰────────────────────────────────────────────────────────────╯"
270
277
  f"{err_detail}"
271
278
  )
@@ -296,14 +303,32 @@ class LocalModel:
296
303
  Llama = _ensure_llama_cpp()
297
304
 
298
305
  accel = "GPU" if self.n_gpu_layers != 0 else "CPU"
306
+
307
+ # ── Smart P-Core Topology Auto-Tuning ─────────────────────────
308
+ # On hybrid CPUs (Intel 12th+), over-subscribing E-cores causes
309
+ # cache thrashing. Use physical core count to avoid this.
310
+ n_threads = max(1, (os.cpu_count() or 4) - 1)
311
+ try:
312
+ import subprocess
313
+ # Linux: count physical cores only (excludes hyperthreads)
314
+ res = subprocess.run(
315
+ ["nproc", "--all"],
316
+ capture_output=True, text=True, timeout=2
317
+ )
318
+ if res.returncode == 0:
319
+ logical = int(res.stdout.strip())
320
+ # Heuristic: physical cores ≈ logical / 2 on HT systems
321
+ # Use 75% of logical cores as a safe threading target
322
+ n_threads = max(1, int(logical * 0.75))
323
+ except Exception:
324
+ pass
325
+
299
326
  print(f"🧠 Loading local GGUF model: {model_path.name}")
300
- print(f" Context: {self.n_ctx} tokens | Mode: {accel} | Threads: {max(1, (os.cpu_count() or 4) - 1)}")
327
+ print(f" Context: {self.n_ctx} tokens | Mode: {accel} | Threads: {n_threads}")
301
328
  if accel == "CPU":
302
329
  print(f" ⏳ CPU loading ~4GB model — this takes 30-90 seconds on first run...")
303
330
  else:
304
- print(f" ⚡ GPU-accelerated loading...")
305
-
306
- n_threads = max(1, (os.cpu_count() or 4) - 1)
331
+ print(f" ⚡ GPU-accelerated loading (Q8 KV cache enabled)...")
307
332
 
308
333
  # Suppress noisy ggml_vulkan/ggml_cuda stderr messages from C library
309
334
  # These confuse users ("ggml_vulkan: Found 1 Vulkan devices: Intel UHD...")
@@ -318,17 +343,30 @@ class LocalModel:
318
343
  except Exception:
319
344
  _stderr_fd = None # Fallback: don't suppress if dup2 fails
320
345
 
346
+ # ── Build Llama kwargs with Q8_0 KV Cache for GPU ─────────────
347
+ llama_kwargs = dict(
348
+ model_path=str(model_path),
349
+ n_ctx=self.n_ctx,
350
+ n_batch=2048 if self.n_gpu_layers == 0 else 4096,
351
+ n_threads=n_threads,
352
+ n_gpu_layers=self.n_gpu_layers,
353
+ use_mmap=True,
354
+ flash_attn=(self.n_gpu_layers != 0), # Flash Attention only for GPU
355
+ verbose=False,
356
+ )
357
+
358
+ # Quantized KV Cache (Q8_0) — halves VRAM usage, ~0% quality loss
359
+ # Only enable for GPU mode where VRAM savings matter
360
+ if self.n_gpu_layers != 0:
361
+ try:
362
+ llama_kwargs["type_k"] = 8 # GGML_TYPE_Q8_0
363
+ llama_kwargs["type_v"] = 8 # GGML_TYPE_Q8_0
364
+ llama_kwargs["offload_kqv"] = True # Keep K/V tensors in VRAM
365
+ except Exception:
366
+ pass # Older llama-cpp-python versions may not support these
367
+
321
368
  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
- )
369
+ self._llm = Llama(**llama_kwargs)
332
370
  finally:
333
371
  # Restore stderr
334
372
  if _stderr_fd is not None:
File without changes
File without changes