perf(ai): CPU-tuned local inference + qwen2.5-coder sandbox path

Tier A/B/C wins for the CPU-only Ollama box (no GPU → optimize TTFT and
tokens/sec, not VRAM):

- Separate qwen2.5-coder provider for the sandbox `!task` path; chat keeps
  the general model. Auto-selected when chat is Ollama and a coder build is
  present, override with --code-model.
- OllamaProvider num_ctx default 8192→4096 (8192 was a GPU-mindset default
  that inflates prefill/TTFT on CPU); expose num_thread; add --num-ctx,
  --num-thread, --num-predict. token_budget default 3000→2000 to fit.
- OllamaProvider.stream() generator over Ollama's stream=True chat endpoint
  (provider half of token streaming; agent/Rust rendering is a follow-up).
- Few-shot request→shell exemplars in SANDBOX_SYSTEM to anchor the small
  model's fenced-command output.
- Matryoshka embedding truncation: OllamaEmbedder truncate_dim=256 (--embed-dim)
  for faster pure-Python cosine and less RAM; query+stored share the dim.
- docs/ai-perf-plan.md records all 8 items with status and the server-side
  env (OLLAMA_NUM_PARALLEL=1, keep_alive) that must be set where ollama serve runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-06-02 22:37:59 -07:00
parent e5e1ad8dee
commit 26c651e9ac
4 changed files with 180 additions and 12 deletions
+20 -4
View File
@@ -37,7 +37,19 @@ SANDBOX_SYSTEM = (
"block — no prose, no comments, no explanation. Prefer non-interactive "
"commands. Create files with heredocs (cat > path <<'EOF' … EOF). Keep it to "
"a handful of commands. Never include destructive commands unless the request "
"explicitly demands them."
"explicitly demands them.\n\n"
"Examples:\n"
"Request: create a hello.py that prints hello world and run it\n"
"```sh\n"
"cat > hello.py <<'EOF'\n"
"print(\"hello world\")\n"
"EOF\n"
"python3 hello.py\n"
"```\n\n"
"Request: show disk usage of the current directory, largest first\n"
"```sh\n"
"du -sh ./* | sort -rh\n"
"```"
)
# Heuristic guard for obviously dangerous commands — not exhaustive; the owner's
@@ -67,12 +79,16 @@ class AgentBridge(Client):
def __init__(self, server: str, port: int, name: str, provider: Provider,
password: str | None = None, insecure: bool = False, no_tls: bool = False,
system_prompt: str | None = None, context_window: int = 12,
token_budget: int = 3000, embedder=None, rag_top_k: int = 4,
rag_min_score: float = 0.35):
token_budget: int = 2000, embedder=None, rag_top_k: int = 4,
rag_min_score: float = 0.35, code_provider: Provider | None = None):
super().__init__(server, port, username=name, password=password,
insecure=insecure, no_tls=no_tls)
self.name = name
self.provider = provider
# Optional code-specialized provider (e.g. qwen2.5-coder) used only for
# the sandbox `!task` path; chat keeps the general `provider`. Falls back
# to the chat provider when not supplied.
self.code_provider = code_provider or provider
self.system_prompt = (system_prompt or DEFAULT_SYSTEM).format(name=name)
self.context_window = context_window
# Soft cap (approx tokens) on how much transcript we feed the model per
@@ -330,7 +346,7 @@ class AgentBridge(Client):
try:
context = await self._model_messages(task)
plan = await asyncio.to_thread(
self.provider.complete,
self.code_provider.complete,
SANDBOX_SYSTEM.format(name=self.name),
context + [Msg("user", f"{asker} wants this done in the shell: {task}")],
)