feat: native-bakeoff scaffold — Ollama JSON vs native-token tool-calling
Three-arm harness under scripts/native-bakeoff/: - arm A: /api/chat with JSON tools (current default) - arm B: /api/generate raw:true with canonical HF jinja template rendered directly - arm C: google-deepmind/gemma JAX ToolSampler (env-gated, JAX required) Interim finding from A+B sweep on matt-strix gemma4:26b Q4: Ollama's bidirectional JSON↔native tool-call translator is faithful. The "long" multi-tool task produces identical behavior (7 steps / 6 tools) on both arms. Earlier arm-B parser bug that looked like a divergence was a harness issue: preserving the model's <|channel>thought\n<channel|> prefix as assistant content tripped the jinja template's tool_response-following conditional, appending a spurious <turn|>\n that corrupted the next step's prompt. Fixed by dropping the channel prefix on the assistant message. Arm C left as scaffolded-but-not-run — the JAX/bf16 reference path would answer "does the GGUF runtime diverge from DeepMind's implementation" but requires a separate env with the `gemma` PyPI package. Parked pending SDXL eviction or vast-h100 session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
# Native Bakeoff — Gemma 4 Inference Path Comparison
|
||||||
|
|
||||||
|
Three-arm bakeoff comparing how different inference paths handle the
|
||||||
|
same Gemma 4 tool-calling workload. Isolates Ollama's JSON↔native
|
||||||
|
translator and the runtime itself as variables.
|
||||||
|
|
||||||
|
## The three arms
|
||||||
|
|
||||||
|
| Arm | Path | What varies |
|
||||||
|
|-----|------|-------------|
|
||||||
|
| A. `ollama-json` | `/api/chat` with OpenAI-style `tools:[...]` | Ollama translates JSON → native tokens on input, native tool-call tokens → structured JSON on output. |
|
||||||
|
| B. `ollama-native` | `/api/generate` with `raw:true` + canonical HF jinja template | No JSON translation. Rendered tokens go straight to the model; the harness parses `<\|tool_call>` spans out of the completion. |
|
||||||
|
| C. `jax-native` | `google-deepmind/gemma` reference `ToolSampler` | No Ollama. No llama.cpp. No GGUF quant. Reference Python + JAX + bf16. |
|
||||||
|
|
||||||
|
## Research question
|
||||||
|
|
||||||
|
> Does Ollama's JSON tools path materially diverge from the native/reference path?
|
||||||
|
|
||||||
|
- A vs B divergence ⇒ the Ollama server-side parser is the variable.
|
||||||
|
- B vs C divergence ⇒ llama.cpp runtime / GGUF quantization / Ollama
|
||||||
|
scheduler is the variable.
|
||||||
|
- A ≡ B ≡ C ⇒ Ollama's path is faithful to the reference, current
|
||||||
|
production usage is fine.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
**Arms A and B:** local Ollama with `gemma4:latest` (E4B 8B) or
|
||||||
|
`gemma4:e4b-it-q8_0` pulled. Python 3.10+, `aiohttp`, `jinja2`.
|
||||||
|
|
||||||
|
**Arm C:** separate env with `jax` and `gemma` installed; HF
|
||||||
|
credentials for checkpoint download (~8GB for E4B-it). See
|
||||||
|
`arms/jax_native.py` module docstring.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd scripts/native-bakeoff
|
||||||
|
|
||||||
|
# One arm, one task:
|
||||||
|
python3 harness.py --arm ollama-json --task memory --out runs/A/memory.json
|
||||||
|
python3 harness.py --arm ollama-native --task memory --out runs/B/memory.json
|
||||||
|
python3 harness.py --arm jax-native --task memory --out runs/C/memory.json
|
||||||
|
|
||||||
|
# Full sweep (A + B, 4 tasks each):
|
||||||
|
for arm in ollama-json ollama-native; do
|
||||||
|
for task in movies research memory long; do
|
||||||
|
python3 harness.py --arm "$arm" --task "$task" \
|
||||||
|
--out "runs/${arm}/${task}.json"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Default model is `gemma4:latest` for Ollama arms (the E4B-it variant).
|
||||||
|
Override with `--model gemma4:26b` if you want the MoE bakeoff
|
||||||
|
(expect slower; 26B is 18GB GGUF).
|
||||||
|
|
||||||
|
## Trace schema
|
||||||
|
|
||||||
|
Each run writes a JSON with:
|
||||||
|
- `arm`, `model`, `task`, `task_prompt`
|
||||||
|
- `turns[]` — per-step metrics: `elapsed_s`, `prompt_eval_count`,
|
||||||
|
`eval_count`, `tool_call_count`, `content_len`, etc.
|
||||||
|
- `final` — `halt_reason`, `steps_used`, `tool_calls_total`,
|
||||||
|
`wall_clock_s`, `final_history_chars`
|
||||||
|
|
||||||
|
Halt reasons: `no_tool_calls` (model produced final answer),
|
||||||
|
`step_budget` (hit 20-step limit), `error:*`, `env_missing` (arm C
|
||||||
|
only), `sampler_error:*` (arm C only).
|
||||||
|
|
||||||
|
## Smoke test evidence
|
||||||
|
|
||||||
|
First wiring run on 2026-04-19 against `gemma4:latest` on steel141
|
||||||
|
(local Ollama, CPU):
|
||||||
|
|
||||||
|
| Arm | Task | Steps | Tools | Halt | Wall |
|
||||||
|
|-----|------|-------|-------|------|------|
|
||||||
|
| A (ollama-json) | memory | 2 | 1 | `no_tool_calls` | 10.16s |
|
||||||
|
| B (ollama-native) | memory | 2 | 1 | `no_tool_calls` | 2.39s |
|
||||||
|
|
||||||
|
Identical *behavioral* shape (one tool call, clean final answer)
|
||||||
|
on this simple task. The wall-clock delta is interesting but not
|
||||||
|
conclusive on a single run — could be cache warmth or could be
|
||||||
|
Ollama's parser overhead. A full sweep will separate signal from
|
||||||
|
noise.
|
||||||
|
|
||||||
|
## Known limitations
|
||||||
|
|
||||||
|
- **Arm C system prompt handling.** `gm.text.ToolSampler` doesn't
|
||||||
|
take a pre-populated message history cleanly, so arm C folds a
|
||||||
|
compact version of `FAKE_HISTORY` into the user message. Arms A
|
||||||
|
and B feed history through proper role-tagged turns. Fidelity
|
||||||
|
compromise — if a C vs A/B delta traces here, rebuild
|
||||||
|
`sampler.turns` directly before calling `.chat()`.
|
||||||
|
- **Arm C sampler caveat.** The deepmind-gemma `ToolSampler`
|
||||||
|
docstring notes "Gemma 1, 2 and 3 models were not specifically
|
||||||
|
trained for tool use" and flags the sampler as a proof-of-concept.
|
||||||
|
Gemma 4 *is* tool-trained, so it should do better, but if arm C
|
||||||
|
underperforms A/B the sampler implementation may be the
|
||||||
|
variable, not the model.
|
||||||
|
- **Quantization confounder.** Ollama arms run Q8 (E4B) or Q4 (26B);
|
||||||
|
arm C runs bf16. A non-trivial A vs C delta could be the
|
||||||
|
quantization. Only A ≡ B ≢ C cleanly implicates the inference
|
||||||
|
engine rather than the bits.
|
||||||
|
|
||||||
|
## Related artifacts
|
||||||
|
|
||||||
|
- `scripts/mort-bakeoff/harness.py` — the round-3 bakeoff that
|
||||||
|
established `think:false` kills 26B in multi-turn tool loops.
|
||||||
|
Task definitions are lifted from there.
|
||||||
|
- `docs/reference/bakeoff-2026-04-18.md` — round-3 writeup.
|
||||||
|
- `CORPUS_tool_calling_format.md` — the native Gemma 4 tool-call
|
||||||
|
token syntax this harness implements.
|
||||||
|
- `tooling/huggingface/model-cards/gemma-4-E4B-it-chat_template.jinja`
|
||||||
|
— the canonical template arm B renders.
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
"""Arm C: google-deepmind/gemma JAX ToolSampler (reference path).
|
||||||
|
|
||||||
|
This arm runs against the *canonical* JAX reference implementation. No
|
||||||
|
Ollama, no llama.cpp, no GGUF quantization, no wire protocol — the
|
||||||
|
chat template, token-level sampling, and tool-call parsing all happen
|
||||||
|
inside the Python process using the code Google wrote for Gemma 4.
|
||||||
|
|
||||||
|
**Environment requirement** — this arm cannot run inside the Ollama-only
|
||||||
|
environment used by arms A/B. Setup:
|
||||||
|
|
||||||
|
pip install jax[cuda12] gemma # or jax[cpu] for CPU fallback
|
||||||
|
huggingface-cli login # weights download via HF
|
||||||
|
|
||||||
|
It will download `gm.ckpts.CheckpointPath.GEMMA4_E4B_IT` on first run
|
||||||
|
(~8GB). Run this arm on a host with ≥16GB RAM (CPU) or ≥10GB VRAM (GPU).
|
||||||
|
|
||||||
|
**Known caveat** — the `gm.text.ToolSampler` docstring notes that
|
||||||
|
"Gemma 1, 2 and 3 models were not specifically trained for tool use"
|
||||||
|
and flags the sampler as a proof-of-concept. Gemma 4 *is* tool-trained
|
||||||
|
so it should do better here, but if this arm underperforms A/B it may
|
||||||
|
be the sampler wrapper, not the model. The trace logs the raw sampler
|
||||||
|
turns so that can be diagnosed post-hoc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
# Local imports are guarded so the harness can at least import this
|
||||||
|
# module on a non-JAX host for syntax checking. The actual run() call
|
||||||
|
# will blow up with a clean ImportError if the env isn't set up.
|
||||||
|
try:
|
||||||
|
from gemma import gm # type: ignore
|
||||||
|
_GEMMA_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
gm = None # type: ignore
|
||||||
|
_GEMMA_AVAILABLE = False
|
||||||
|
|
||||||
|
from tasks import SYSTEM_PROMPT, FAKE_HISTORY, TASKS, execute_tool_stub # noqa: F401 (TASKS for parity with A/B)
|
||||||
|
|
||||||
|
|
||||||
|
# -------- Tool wrappers: one gm.tools.Tool subclass per stub --------
|
||||||
|
#
|
||||||
|
# ToolSampler requires DESCRIPTION + EXAMPLE for each tool so the model
|
||||||
|
# sees an in-context example of the calling pattern. The EXAMPLE bodies
|
||||||
|
# are intentionally short — they're primers, not test cases.
|
||||||
|
|
||||||
|
def _build_tools():
|
||||||
|
"""Build the 8 ToolSampler-compatible wrappers. Deferred so that
|
||||||
|
`import gm` only happens when we actually intend to run the arm."""
|
||||||
|
assert gm is not None
|
||||||
|
|
||||||
|
class WebSearch(gm.tools.Tool):
|
||||||
|
DESCRIPTION = "Search the web for current information."
|
||||||
|
EXAMPLE = gm.tools.Example(
|
||||||
|
query="recent Home Assistant release notes",
|
||||||
|
thought="web_search is the right tool for current events / docs.",
|
||||||
|
tool_kwargs={"query": "home assistant latest release"},
|
||||||
|
tool_kwargs_doc={"query": "<search query string>"},
|
||||||
|
result="1. HA 2026.4 released...",
|
||||||
|
answer="Home Assistant 2026.4 is the most recent release.",
|
||||||
|
)
|
||||||
|
def call(self, query: str) -> str:
|
||||||
|
return execute_tool_stub("web_search", {"query": query})
|
||||||
|
|
||||||
|
class SethSearch(gm.tools.Tool):
|
||||||
|
DESCRIPTION = "Search Seth's homelab (repos, wiki, media). Use source='sethflix' for movies/TV."
|
||||||
|
EXAMPLE = gm.tools.Example(
|
||||||
|
query="any cyberpunk movies on sethflix?",
|
||||||
|
thought="Use source=sethflix to search the movie library.",
|
||||||
|
tool_kwargs={"query": "cyberpunk", "source": "sethflix"},
|
||||||
|
tool_kwargs_doc={
|
||||||
|
"query": "<search query>",
|
||||||
|
"source": "<'sethflix' | 'general'>",
|
||||||
|
"limit": "<int, default 10>",
|
||||||
|
},
|
||||||
|
result="Blade Runner 2049, Ghost in the Shell, ...",
|
||||||
|
answer="Yes — Blade Runner 2049, Ghost in the Shell, and a few others.",
|
||||||
|
)
|
||||||
|
def call(self, query: str, source: str = "general", limit: int = 10) -> str:
|
||||||
|
return execute_tool_stub("sethsearch", {"query": query, "source": source, "limit": limit})
|
||||||
|
|
||||||
|
class CheckSethflix(gm.tools.Tool):
|
||||||
|
DESCRIPTION = "Verify which comma-separated titles are in sethflix."
|
||||||
|
EXAMPLE = gm.tools.Example(
|
||||||
|
query="is The Matrix in the library?",
|
||||||
|
thought="check_sethflix verifies library membership.",
|
||||||
|
tool_kwargs={"titles": "The Matrix"},
|
||||||
|
tool_kwargs_doc={"titles": "<comma-separated title list>"},
|
||||||
|
result="- The Matrix: IN LIBRARY",
|
||||||
|
answer="Yes, The Matrix is in the library.",
|
||||||
|
)
|
||||||
|
def call(self, titles: str) -> str:
|
||||||
|
return execute_tool_stub("check_sethflix", {"titles": titles})
|
||||||
|
|
||||||
|
class MemoryRead(gm.tools.Tool):
|
||||||
|
DESCRIPTION = "Look up stored facts about a topic or user."
|
||||||
|
EXAMPLE = gm.tools.Example(
|
||||||
|
query="what do I have about home automation?",
|
||||||
|
thought="memory_read is the right tool.",
|
||||||
|
tool_kwargs={"query": "home automation"},
|
||||||
|
tool_kwargs_doc={"query": "<topic>", "user": "<optional user filter>"},
|
||||||
|
result="- home_automation: Seth uses HA on VM 706...",
|
||||||
|
answer="You have notes about HA on VM 706 with Zigbee2MQTT.",
|
||||||
|
)
|
||||||
|
def call(self, query: str, user: str = "") -> str:
|
||||||
|
return execute_tool_stub("memory_read", {"query": query, "user": user})
|
||||||
|
|
||||||
|
class MemoryWrite(gm.tools.Tool):
|
||||||
|
DESCRIPTION = "Store a durable fact."
|
||||||
|
EXAMPLE = gm.tools.Example(
|
||||||
|
query="remember that Seth prefers dark themes",
|
||||||
|
thought="memory_write stores a key/content pair.",
|
||||||
|
tool_kwargs={"key": "theme_preference", "content": "dark with orange accents"},
|
||||||
|
tool_kwargs_doc={"key": "<short id>", "content": "<fact body>", "user": "<optional>"},
|
||||||
|
result="stored: theme_preference = dark with orange accents",
|
||||||
|
answer="Saved.",
|
||||||
|
)
|
||||||
|
def call(self, key: str, content: str, user: str = "") -> str:
|
||||||
|
return execute_tool_stub("memory_write", {"key": key, "content": content, "user": user})
|
||||||
|
|
||||||
|
class WebFetch(gm.tools.Tool):
|
||||||
|
DESCRIPTION = "Fetch the text contents of a URL."
|
||||||
|
EXAMPLE = gm.tools.Example(
|
||||||
|
query="fetch https://example.com/docs",
|
||||||
|
thought="web_fetch pulls page text.",
|
||||||
|
tool_kwargs={"url": "https://example.com/docs"},
|
||||||
|
tool_kwargs_doc={"url": "<absolute URL>"},
|
||||||
|
result="fetched content: ...",
|
||||||
|
answer="The page discusses X, Y, Z.",
|
||||||
|
)
|
||||||
|
def call(self, url: str) -> str:
|
||||||
|
return execute_tool_stub("web_fetch", {"url": url})
|
||||||
|
|
||||||
|
class ChatSearch(gm.tools.Tool):
|
||||||
|
DESCRIPTION = "Search message history across Matrix rooms."
|
||||||
|
EXAMPLE = gm.tools.Example(
|
||||||
|
query="have we talked about grafana before?",
|
||||||
|
thought="chat_search looks through prior messages.",
|
||||||
|
tool_kwargs={"query": "grafana"},
|
||||||
|
tool_kwargs_doc={"query": "<search query>"},
|
||||||
|
result="[2026-03-14] @seth: grafana dashboard...",
|
||||||
|
answer="Yes — you discussed a grafana dashboard on March 14.",
|
||||||
|
)
|
||||||
|
def call(self, query: str) -> str:
|
||||||
|
return execute_tool_stub("chat_search", {"query": query})
|
||||||
|
|
||||||
|
class GenerateImage(gm.tools.Tool):
|
||||||
|
DESCRIPTION = "Generate an image via SDXL."
|
||||||
|
EXAMPLE = gm.tools.Example(
|
||||||
|
query="make me a sunset image",
|
||||||
|
thought="generate_image dispatches to SDXL.",
|
||||||
|
tool_kwargs={"prompt": "dramatic ocean sunset"},
|
||||||
|
tool_kwargs_doc={"prompt": "<image description>"},
|
||||||
|
result="image generated: /mxc/abc/sunset.png",
|
||||||
|
answer="Done — here's the sunset image.",
|
||||||
|
)
|
||||||
|
def call(self, prompt: str) -> str:
|
||||||
|
return execute_tool_stub("generate_image", {"prompt": prompt})
|
||||||
|
|
||||||
|
return [
|
||||||
|
WebSearch(), SethSearch(), CheckSethflix(),
|
||||||
|
MemoryRead(), MemoryWrite(), WebFetch(),
|
||||||
|
ChatSearch(), GenerateImage(),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def run(
|
||||||
|
*,
|
||||||
|
ollama_url: str, # unused; kept for CLI parity with arms A/B
|
||||||
|
model: str, # unused; arm C loads its own checkpoint
|
||||||
|
task_prompt: str,
|
||||||
|
num_ctx: int, # unused; ToolSampler uses its own seq_len
|
||||||
|
num_predict: int,
|
||||||
|
step_budget: int,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if not _GEMMA_AVAILABLE:
|
||||||
|
return {
|
||||||
|
"arm": "jax-native",
|
||||||
|
"error": "gemma package not importable — run in a JAX+gemma env. See module docstring.",
|
||||||
|
"final": {"halt_reason": "env_missing", "steps_used": 0, "tool_calls_total": 0, "wall_clock_s": 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Let JAX use the whole GPU if present (per colab_tool_use.ipynb hint).
|
||||||
|
os.environ.setdefault("XLA_PYTHON_CLIENT_MEM_FRACTION", "0.95")
|
||||||
|
|
||||||
|
t_load_start = time.time()
|
||||||
|
model_net = gm.nn.Gemma4_E4B()
|
||||||
|
params = gm.ckpts.load_params(gm.ckpts.CheckpointPath.GEMMA4_E4B_IT)
|
||||||
|
tools = _build_tools()
|
||||||
|
sampler = gm.text.ToolSampler(
|
||||||
|
model=model_net,
|
||||||
|
params=params,
|
||||||
|
tools=tools,
|
||||||
|
print_stream=False,
|
||||||
|
)
|
||||||
|
load_elapsed_s = round(time.time() - t_load_start, 2)
|
||||||
|
|
||||||
|
# ToolSampler doesn't natively consume a system prompt + pre-populated
|
||||||
|
# history. We simulate the same mid-session context by prepending a
|
||||||
|
# compact version of FAKE_HISTORY into the user message itself. This is
|
||||||
|
# a fidelity compromise documented in the writeup — the A/B arms feed
|
||||||
|
# history through proper role-tagged turns. If a delta between arms is
|
||||||
|
# traced to this, rebuild the sampler's turn list directly from
|
||||||
|
# `sampler.turns` pre-population.
|
||||||
|
history_compact = "\n".join(
|
||||||
|
f"{m['role'].upper()}: {m['content']}" for m in FAKE_HISTORY[-6:]
|
||||||
|
)
|
||||||
|
user_msg = (
|
||||||
|
f"[prior chat context]\n{history_compact}\n\n"
|
||||||
|
f"[2026-04-18 14:20] @seth:sethpc.xyz: {task_prompt}"
|
||||||
|
)
|
||||||
|
|
||||||
|
trace: dict[str, Any] = {
|
||||||
|
"arm": "jax-native",
|
||||||
|
"checkpoint": "GEMMA4_E4B_IT",
|
||||||
|
"tools_registered": [t.__class__.__name__ for t in tools],
|
||||||
|
"load_elapsed_s": load_elapsed_s,
|
||||||
|
"step_budget_note": "ToolSampler manages its own step loop; step_budget ignored",
|
||||||
|
"started_at": time.time(),
|
||||||
|
"turns": [],
|
||||||
|
"final": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
t0 = time.time()
|
||||||
|
answer = sampler.chat(user_msg)
|
||||||
|
elapsed = round(time.time() - t0, 2)
|
||||||
|
except Exception as e:
|
||||||
|
trace["final"] = {"halt_reason": f"sampler_error: {e}", "steps_used": 0,
|
||||||
|
"tool_calls_total": 0, "wall_clock_s": round(time.time() - trace["started_at"], 2)}
|
||||||
|
return trace
|
||||||
|
|
||||||
|
# Extract per-turn info from sampler.turns — the library exposes the
|
||||||
|
# full trace (thoughts, tool calls, tool results, final answer).
|
||||||
|
sampler_turns = list(getattr(sampler, "turns", []) or [])
|
||||||
|
tool_call_total = 0
|
||||||
|
for i, t in enumerate(sampler_turns):
|
||||||
|
# Different releases of gemma have different turn schemas. We
|
||||||
|
# log defensively — whatever attributes the turn object has end
|
||||||
|
# up in the JSON so we can inspect post-hoc.
|
||||||
|
info: dict[str, Any] = {"step": i + 1, "turn_type": t.__class__.__name__}
|
||||||
|
for attr in ("query", "thought", "tool_name", "tool_kwargs", "tool_result", "answer"):
|
||||||
|
v = getattr(t, attr, None)
|
||||||
|
if v is not None:
|
||||||
|
info[attr] = v if isinstance(v, (str, int, float, bool, list, dict)) else str(v)
|
||||||
|
if info.get("tool_name"):
|
||||||
|
tool_call_total += 1
|
||||||
|
trace["turns"].append(info)
|
||||||
|
|
||||||
|
trace["final"] = {
|
||||||
|
"halt_reason": "answer_returned" if answer else "no_answer",
|
||||||
|
"steps_used": len(sampler_turns),
|
||||||
|
"tool_calls_total": tool_call_total,
|
||||||
|
"wall_clock_s": round(time.time() - trace["started_at"], 2),
|
||||||
|
"model_answer": answer,
|
||||||
|
"sampler_elapsed_s": elapsed,
|
||||||
|
}
|
||||||
|
return trace
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
"""Arm A: Ollama /api/chat with JSON tools.
|
||||||
|
|
||||||
|
This is the baseline — what mort-bot, OpenWebUI, and every other Ollama
|
||||||
|
client does. Ollama's server translates the OpenAI-style JSON tools
|
||||||
|
array into Gemma's native <|tool>declaration:...<tool|> tokens and
|
||||||
|
parses the model's <|tool_call>call:...<tool_call|> output back into
|
||||||
|
structured tool_calls. This arm measures what we already live with.
|
||||||
|
|
||||||
|
Think setting: fixed to `false` per round-3 bakeoff finding (26B silently
|
||||||
|
stops on think:true in multi-turn tool loops). For E4B the finding was
|
||||||
|
less load-bearing but we hold think:false constant across arms so
|
||||||
|
only the inference path varies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from tasks import SYSTEM_PROMPT, TOOLS, FAKE_HISTORY, execute_tool_stub
|
||||||
|
|
||||||
|
|
||||||
|
async def run(
|
||||||
|
*,
|
||||||
|
ollama_url: str,
|
||||||
|
model: str,
|
||||||
|
task_prompt: str,
|
||||||
|
num_ctx: int,
|
||||||
|
num_predict: int,
|
||||||
|
step_budget: int,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
messages = [{"role": "system", "content": SYSTEM_PROMPT}] + list(FAKE_HISTORY)
|
||||||
|
messages.append({"role": "user", "content": f"[2026-04-18 14:20] @seth:sethpc.xyz: {task_prompt}"})
|
||||||
|
|
||||||
|
trace: dict[str, Any] = {
|
||||||
|
"arm": "ollama-json",
|
||||||
|
"model": model,
|
||||||
|
"num_ctx": num_ctx,
|
||||||
|
"num_predict": num_predict,
|
||||||
|
"started_at": time.time(),
|
||||||
|
"turns": [],
|
||||||
|
"final": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_call_total = 0
|
||||||
|
halt: str | None = None
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
for step in range(1, step_budget + 1):
|
||||||
|
t0 = time.time()
|
||||||
|
payload = {
|
||||||
|
"model": model,
|
||||||
|
"messages": messages,
|
||||||
|
"tools": TOOLS,
|
||||||
|
"stream": False,
|
||||||
|
"think": False,
|
||||||
|
"options": {"num_ctx": num_ctx, "num_predict": num_predict,
|
||||||
|
"temperature": 0.7, "top_p": 0.95, "top_k": 64},
|
||||||
|
"keep_alive": "2h",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
async with session.post(
|
||||||
|
f"{ollama_url}/api/chat", json=payload,
|
||||||
|
timeout=aiohttp.ClientTimeout(total=300),
|
||||||
|
) as resp:
|
||||||
|
r = await resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
halt = f"error: {e}"
|
||||||
|
trace["turns"].append({"step": step, "error": str(e)})
|
||||||
|
break
|
||||||
|
|
||||||
|
msg = r.get("message", {}) or {}
|
||||||
|
content = msg.get("content", "") or ""
|
||||||
|
tool_calls = msg.get("tool_calls") or []
|
||||||
|
history_chars = sum(len(m.get("content", "") or "") for m in messages)
|
||||||
|
|
||||||
|
trace["turns"].append({
|
||||||
|
"step": step,
|
||||||
|
"elapsed_s": round(time.time() - t0, 2),
|
||||||
|
"prompt_eval_count": r.get("prompt_eval_count"),
|
||||||
|
"eval_count": r.get("eval_count"),
|
||||||
|
"content_len": len(content),
|
||||||
|
"tool_call_count": len(tool_calls),
|
||||||
|
"history_chars_before_append": history_chars,
|
||||||
|
})
|
||||||
|
messages.append(msg)
|
||||||
|
|
||||||
|
if not tool_calls:
|
||||||
|
halt = "no_tool_calls"
|
||||||
|
break
|
||||||
|
|
||||||
|
tool_call_total += len(tool_calls)
|
||||||
|
for tc in tool_calls:
|
||||||
|
fn = tc.get("function", {})
|
||||||
|
name = fn.get("name")
|
||||||
|
args = fn.get("arguments") or {}
|
||||||
|
if isinstance(args, str):
|
||||||
|
try:
|
||||||
|
args = json.loads(args)
|
||||||
|
except Exception:
|
||||||
|
args = {}
|
||||||
|
result = execute_tool_stub(name, args)
|
||||||
|
messages.append({"role": "tool", "content": result})
|
||||||
|
|
||||||
|
if step == step_budget:
|
||||||
|
halt = "step_budget"
|
||||||
|
break
|
||||||
|
|
||||||
|
trace["final"] = {
|
||||||
|
"halt_reason": halt,
|
||||||
|
"steps_used": len(trace["turns"]),
|
||||||
|
"tool_calls_total": tool_call_total,
|
||||||
|
"wall_clock_s": round(time.time() - trace["started_at"], 2),
|
||||||
|
"final_message_count": len(messages),
|
||||||
|
"final_history_chars": sum(len(m.get("content", "") or "") for m in messages),
|
||||||
|
}
|
||||||
|
return trace
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
"""Arm B: Ollama /api/generate with raw:true and native Gemma 4 tokens.
|
||||||
|
|
||||||
|
Renders the canonical HF jinja chat template directly, sends the
|
||||||
|
resulting string to Ollama's /api/generate with `raw: true` (which
|
||||||
|
bypasses Ollama's own templating / BOS handling), and parses
|
||||||
|
<|tool_call>call:NAME{args}<tool_call|> out of the completion with a
|
||||||
|
regex.
|
||||||
|
|
||||||
|
The point of this arm: isolate Ollama's tool parser. Arm A lets
|
||||||
|
Ollama's server translate OpenAI-shaped JSON tools into native tokens
|
||||||
|
AND translate the model's native <|tool_call> output back into
|
||||||
|
structured `tool_calls`. Arm B keeps everything native end-to-end and
|
||||||
|
only uses Ollama as a thin completion engine. If A and B diverge, the
|
||||||
|
delta lives in Ollama's bidirectional JSON↔native translator.
|
||||||
|
|
||||||
|
Template source: tooling/huggingface/model-cards/gemma-4-E4B-it-chat_template.jinja
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
from tasks import SYSTEM_PROMPT, TOOLS, FAKE_HISTORY, execute_tool_stub
|
||||||
|
|
||||||
|
|
||||||
|
_REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
_TEMPLATE_PATH = _REPO_ROOT / "tooling" / "huggingface" / "model-cards" / "gemma-4-E4B-it-chat_template.jinja"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_template() -> jinja2.Template:
|
||||||
|
env = jinja2.Environment(
|
||||||
|
keep_trailing_newline=True,
|
||||||
|
# Canonical template uses `{%- ... -%}` whitespace control; keep
|
||||||
|
# jinja defaults so it renders exactly as HF's template expects.
|
||||||
|
)
|
||||||
|
return env.from_string(_TEMPLATE_PATH.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
_TOOL_CALL_RE = re.compile(
|
||||||
|
r"<\|tool_call>call:(?P<name>\w+)\{(?P<body>.*?)\}<tool_call\|>",
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_native_args(body: str) -> dict[str, Any]:
|
||||||
|
"""Parse the body of a <|tool_call>call:NAME{...}<tool_call|>.
|
||||||
|
|
||||||
|
Gemma 4 native arg format (from the jinja template's format_argument
|
||||||
|
macro with escape_keys=False):
|
||||||
|
- key:<|"|>stringval<|"|>
|
||||||
|
- key:123
|
||||||
|
- key:true / key:false
|
||||||
|
- key:{nested:...} (for mapping args — not used by our stubs)
|
||||||
|
- key:[<|"|>item<|"|>,...] (for array args — not used by our stubs)
|
||||||
|
|
||||||
|
Our stub tool schemas are flat (string / integer / bool), so a
|
||||||
|
simple top-level comma split is enough. If a future tool needs
|
||||||
|
nested args this needs depth-aware splitting.
|
||||||
|
"""
|
||||||
|
out: dict[str, Any] = {}
|
||||||
|
if not body:
|
||||||
|
return out
|
||||||
|
|
||||||
|
# Top-level comma split, respecting only the `<|"|>...<|"|>` string
|
||||||
|
# delimiter (since our tool args don't nest). This intentionally
|
||||||
|
# doesn't handle {...} or [...] — flag it with a log entry in the
|
||||||
|
# harness if a future tool needs those.
|
||||||
|
parts: list[str] = []
|
||||||
|
buf = ""
|
||||||
|
i = 0
|
||||||
|
str_delim = '<|"|>'
|
||||||
|
in_str = False
|
||||||
|
while i < len(body):
|
||||||
|
if body[i : i + len(str_delim)] == str_delim:
|
||||||
|
in_str = not in_str
|
||||||
|
buf += str_delim
|
||||||
|
i += len(str_delim)
|
||||||
|
continue
|
||||||
|
if body[i] == "," and not in_str:
|
||||||
|
parts.append(buf)
|
||||||
|
buf = ""
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
buf += body[i]
|
||||||
|
i += 1
|
||||||
|
if buf:
|
||||||
|
parts.append(buf)
|
||||||
|
|
||||||
|
for p in parts:
|
||||||
|
if ":" not in p:
|
||||||
|
continue
|
||||||
|
k, _, v = p.partition(":")
|
||||||
|
k = k.strip()
|
||||||
|
v = v.strip()
|
||||||
|
if v.startswith(str_delim) and v.endswith(str_delim):
|
||||||
|
out[k] = v[len(str_delim) : -len(str_delim)]
|
||||||
|
elif v == "true":
|
||||||
|
out[k] = True
|
||||||
|
elif v == "false":
|
||||||
|
out[k] = False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
out[k] = int(v)
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
out[k] = float(v)
|
||||||
|
except ValueError:
|
||||||
|
out[k] = v
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _render(messages: list[dict[str, Any]]) -> str:
|
||||||
|
tmpl = _load_template()
|
||||||
|
return tmpl.render(
|
||||||
|
messages=messages,
|
||||||
|
tools=TOOLS,
|
||||||
|
add_generation_prompt=True,
|
||||||
|
bos_token="<bos>",
|
||||||
|
enable_thinking=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def run(
|
||||||
|
*,
|
||||||
|
ollama_url: str,
|
||||||
|
model: str,
|
||||||
|
task_prompt: str,
|
||||||
|
num_ctx: int,
|
||||||
|
num_predict: int,
|
||||||
|
step_budget: int,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
messages: list[dict[str, Any]] = [{"role": "system", "content": SYSTEM_PROMPT}] + list(FAKE_HISTORY)
|
||||||
|
messages.append({"role": "user", "content": f"[2026-04-18 14:20] @seth:sethpc.xyz: {task_prompt}"})
|
||||||
|
|
||||||
|
trace: dict[str, Any] = {
|
||||||
|
"arm": "ollama-native",
|
||||||
|
"model": model,
|
||||||
|
"num_ctx": num_ctx,
|
||||||
|
"num_predict": num_predict,
|
||||||
|
"started_at": time.time(),
|
||||||
|
"turns": [],
|
||||||
|
"final": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_call_total = 0
|
||||||
|
halt: str | None = None
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
for step in range(1, step_budget + 1):
|
||||||
|
t0 = time.time()
|
||||||
|
prompt = _render(messages)
|
||||||
|
payload = {
|
||||||
|
"model": model,
|
||||||
|
"prompt": prompt,
|
||||||
|
"raw": True,
|
||||||
|
"stream": False,
|
||||||
|
"options": {
|
||||||
|
"num_ctx": num_ctx,
|
||||||
|
"num_predict": num_predict,
|
||||||
|
"temperature": 0.7, "top_p": 0.95, "top_k": 64,
|
||||||
|
# Stop at either end-of-turn (final answer) or end-of-tool-call.
|
||||||
|
# "<tool_call|>" lets the regex match on the full call; we
|
||||||
|
# re-append "<tool_call|>" before parsing to keep the regex
|
||||||
|
# simple. "<turn|>" catches a clean final answer.
|
||||||
|
"stop": ["<turn|>", "<tool_call|>"],
|
||||||
|
},
|
||||||
|
"keep_alive": "2h",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
async with session.post(
|
||||||
|
f"{ollama_url}/api/generate", json=payload,
|
||||||
|
timeout=aiohttp.ClientTimeout(total=300),
|
||||||
|
) as resp:
|
||||||
|
r = await resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
halt = f"error: {e}"
|
||||||
|
trace["turns"].append({"step": step, "error": str(e)})
|
||||||
|
break
|
||||||
|
|
||||||
|
completion = r.get("response", "") or ""
|
||||||
|
stop_reason_native = r.get("done_reason") or r.get("stop_reason") or ""
|
||||||
|
|
||||||
|
# Rebuild the full assistant turn. Ollama's /api/generate
|
||||||
|
# strips the matched stop token from the response, so we
|
||||||
|
# always re-append based on which open token is present.
|
||||||
|
# An unclosed `<|tool_call>` means the model was emitting a
|
||||||
|
# tool call when the stop token fired; otherwise the model
|
||||||
|
# was producing a final text turn.
|
||||||
|
if completion.rstrip().endswith(("<tool_call|>", "<turn|>")):
|
||||||
|
full = completion
|
||||||
|
elif "<|tool_call>" in completion and "<tool_call|>" not in completion:
|
||||||
|
full = completion + "<tool_call|>"
|
||||||
|
else:
|
||||||
|
full = completion + "<turn|>"
|
||||||
|
|
||||||
|
matches = list(_TOOL_CALL_RE.finditer(full))
|
||||||
|
history_chars = sum(len(m.get("content", "") or "") for m in messages)
|
||||||
|
|
||||||
|
trace["turns"].append({
|
||||||
|
"step": step,
|
||||||
|
"elapsed_s": round(time.time() - t0, 2),
|
||||||
|
"prompt_eval_count": r.get("prompt_eval_count"),
|
||||||
|
"eval_count": r.get("eval_count"),
|
||||||
|
"content_len": len(completion),
|
||||||
|
"tool_call_count": len(matches),
|
||||||
|
"stop_reason": stop_reason_native,
|
||||||
|
"history_chars_before_append": history_chars,
|
||||||
|
"raw_completion_head": completion[:240],
|
||||||
|
"raw_completion_tail": completion[-240:] if len(completion) > 240 else "",
|
||||||
|
"prompt_tail": prompt[-400:],
|
||||||
|
"prompt_head": prompt[:200],
|
||||||
|
})
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
# Final answer — take the text minus any trailing <turn|>.
|
||||||
|
content = full.replace("<turn|>", "").strip()
|
||||||
|
messages.append({"role": "assistant", "content": content})
|
||||||
|
halt = "no_tool_calls"
|
||||||
|
break
|
||||||
|
|
||||||
|
# Build an assistant message with tool_calls (OpenAI shape) so the
|
||||||
|
# jinja template re-renders them correctly on the next iteration.
|
||||||
|
tool_calls_msg: list[dict[str, Any]] = []
|
||||||
|
for m in matches:
|
||||||
|
name = m.group("name")
|
||||||
|
args = _parse_native_args(m.group("body"))
|
||||||
|
tool_calls_msg.append({
|
||||||
|
"id": f"call_{step}_{len(tool_calls_msg)}",
|
||||||
|
"function": {"name": name, "arguments": args},
|
||||||
|
})
|
||||||
|
# Content MUST be empty when the message has tool_calls + will
|
||||||
|
# have tool_responses inlined on next render. The jinja
|
||||||
|
# template's post-turn conditional checks message.get('content')
|
||||||
|
# before strip_thinking and any non-empty string (even a bare
|
||||||
|
# <|channel>thought\n<channel|> prefix from the model) causes
|
||||||
|
# a spurious <turn|>\n to be appended after <tool_response|>,
|
||||||
|
# which breaks turn continuation on the following step.
|
||||||
|
messages.append({
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "",
|
||||||
|
"tool_calls": tool_calls_msg,
|
||||||
|
})
|
||||||
|
|
||||||
|
tool_call_total += len(tool_calls_msg)
|
||||||
|
for tc in tool_calls_msg:
|
||||||
|
fn = tc["function"]
|
||||||
|
result = execute_tool_stub(fn["name"], fn["arguments"])
|
||||||
|
messages.append({
|
||||||
|
"role": "tool",
|
||||||
|
"tool_call_id": tc["id"],
|
||||||
|
"name": fn["name"],
|
||||||
|
"content": result,
|
||||||
|
})
|
||||||
|
|
||||||
|
if step == step_budget:
|
||||||
|
halt = "step_budget"
|
||||||
|
break
|
||||||
|
|
||||||
|
trace["final"] = {
|
||||||
|
"halt_reason": halt,
|
||||||
|
"steps_used": len(trace["turns"]),
|
||||||
|
"tool_calls_total": tool_call_total,
|
||||||
|
"wall_clock_s": round(time.time() - trace["started_at"], 2),
|
||||||
|
"final_message_count": len(messages),
|
||||||
|
"final_history_chars": sum(len(m.get("content", "") or "") for m in messages),
|
||||||
|
}
|
||||||
|
return trace
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
"""Native-bakeoff entry point.
|
||||||
|
|
||||||
|
Three arms, one invocation. Compares inference paths for Gemma 4:
|
||||||
|
A. ollama-json — /api/chat with JSON tools (current default)
|
||||||
|
B. ollama-native — /api/generate raw:true with canonical jinja template
|
||||||
|
C. jax-native — google-deepmind/gemma reference ToolSampler
|
||||||
|
|
||||||
|
Research question: does the inference path materially change behavior,
|
||||||
|
or is Ollama's JSON tools path faithful to the reference? If arms A and
|
||||||
|
B diverge, Ollama's parser is the variable. If B and C diverge, that's
|
||||||
|
the llama.cpp runtime / GGUF quantization / Ollama's scheduler.
|
||||||
|
|
||||||
|
Arms A and B run against a local Ollama at http://127.0.0.1:11434
|
||||||
|
by default. Arm C needs its own Python env with JAX + the `gemma`
|
||||||
|
package (see `arms/jax_native.py` module docstring).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 harness.py --arm ollama-json --task movies --out runs/A/movies.json
|
||||||
|
python3 harness.py --arm ollama-native --task movies --out runs/B/movies.json
|
||||||
|
python3 harness.py --arm jax-native --task movies --out runs/C/movies.json
|
||||||
|
|
||||||
|
# Default model targets: E4B. Override with --model:
|
||||||
|
python3 harness.py --arm ollama-json --task movies --model gemma4:26b --out ...
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import importlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Make `tasks` and sibling `arms/` importable regardless of where the
|
||||||
|
# harness is invoked from.
|
||||||
|
_HERE = Path(__file__).resolve().parent
|
||||||
|
sys.path.insert(0, str(_HERE))
|
||||||
|
|
||||||
|
from tasks import TASKS # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
ARMS = {
|
||||||
|
"ollama-json": "arms.ollama_json",
|
||||||
|
"ollama-native": "arms.ollama_native",
|
||||||
|
"jax-native": "arms.jax_native",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_MODELS = {
|
||||||
|
"ollama-json": "gemma4:e4b-it-q8_0",
|
||||||
|
"ollama-native": "gemma4:e4b-it-q8_0",
|
||||||
|
"jax-native": "google-deepmind/gemma:GEMMA4_E4B_IT",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def _main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description="Three-arm native Gemma 4 bakeoff harness.")
|
||||||
|
ap.add_argument("--arm", required=True, choices=list(ARMS))
|
||||||
|
ap.add_argument("--task", required=True, choices=list(TASKS))
|
||||||
|
ap.add_argument("--out", required=True, type=Path)
|
||||||
|
ap.add_argument("--model", default=None, help="override default model for this arm")
|
||||||
|
ap.add_argument("--ollama-url", default=os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434"))
|
||||||
|
ap.add_argument("--num-ctx", type=int, default=8192)
|
||||||
|
ap.add_argument("--num-predict", type=int, default=2048)
|
||||||
|
ap.add_argument("--step-budget", type=int, default=20)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
arm_mod = importlib.import_module(ARMS[args.arm])
|
||||||
|
model = args.model or DEFAULT_MODELS[args.arm]
|
||||||
|
task_prompt = TASKS[args.task]
|
||||||
|
|
||||||
|
trace = await arm_mod.run(
|
||||||
|
ollama_url=args.ollama_url,
|
||||||
|
model=model,
|
||||||
|
task_prompt=task_prompt,
|
||||||
|
num_ctx=args.num_ctx,
|
||||||
|
num_predict=args.num_predict,
|
||||||
|
step_budget=args.step_budget,
|
||||||
|
)
|
||||||
|
trace.setdefault("task", args.task)
|
||||||
|
trace.setdefault("task_prompt", task_prompt)
|
||||||
|
|
||||||
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.out.write_text(json.dumps(trace, indent=2, default=str))
|
||||||
|
|
||||||
|
f = trace.get("final") or {}
|
||||||
|
print(
|
||||||
|
f"arm={args.arm:14s} task={args.task:8s} "
|
||||||
|
f"steps={f.get('steps_used', '?')} tools={f.get('tool_calls_total', '?')} "
|
||||||
|
f"halt={f.get('halt_reason', '?')} wall={f.get('wall_clock_s', '?')}s"
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(asyncio.run(_main()))
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-native",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600600.7731934,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 4.12,
|
||||||
|
"prompt_eval_count": 1389,
|
||||||
|
"eval_count": 27,
|
||||||
|
"content_len": 109,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3009,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:memory_read{query:<|\"|>home_automation<|\"|>,user:<|\"|>seth<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "ry for what I have on home_automation, (2) search sethflix for any home-automation documentaries, (3) web_search for current news about Home Assistant version releases, (4) fetch the top search result for details, (5) check chat_search for prior mentions, (6) summarize all findings and write a new memory entry with the summary. Do each step in order and report back at the end.<turn|>\n<|turn>model\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 1.0,
|
||||||
|
"prompt_eval_count": 1511,
|
||||||
|
"eval_count": 27,
|
||||||
|
"content_len": 116,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3309,
|
||||||
|
"raw_completion_head": "thought\n<channel|><|tool_call>call:sethsearch{query:<|\"|>home automation documentary<|\"|>,source:<|\"|>sethflix<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "e_automation<|\"|>,user:<|\"|>seth<|\"|>}<tool_call|><|tool_response>response:memory_read{value:<|\"|>memories matching 'home_automation':\n- home_automation: Seth uses Home Assistant on VM 706 (pve173) with Zigbee2MQTT and MQTT broker on CT 149. Integrates with LG TV, lights, and Frigate NVR.\n- preferences: dark theme with orange accents (#D35400), Sethflix/Sethian brand.<|\"|>}<tool_response|><turn|>\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"elapsed_s": 0.82,
|
||||||
|
"prompt_eval_count": 1654,
|
||||||
|
"eval_count": 20,
|
||||||
|
"content_len": 98,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3618,
|
||||||
|
"raw_completion_head": "<channel|><|tool_call>call:web_search{query:<|\"|>current Home Assistant version release news<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "ethflix<|\"|>}<tool_call|><|tool_response>response:sethsearch{value:<|\"|>sethflix search 'home automation documentary': The Matrix (1999), The Matrix Reloaded (2003), The Matrix Revolutions (2003), The Matrix Resurrections (2021), Equilibrium (2002), Dark City (1998), Minority Report (2002), Ex Machina (2014), Blade Runner 2049 (2017), Ghost in the Shell (1995).<|\"|>}<tool_response|>thought<turn|>\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"elapsed_s": 0.93,
|
||||||
|
"prompt_eval_count": 1821,
|
||||||
|
"eval_count": 23,
|
||||||
|
"content_len": 83,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 4244,
|
||||||
|
"raw_completion_head": "thought\n<channel|><|tool_call>call:web_fetch{url:<|\"|>https://example.com/one<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "ity discussion with multiple perspectives and useful links to follow up on. https://example.com/two\n3. Example result three \u2014 official documentation or reference material. https://example.com/three\n4. Example result four \u2014 a recent news article with relevant context. https://example.com/four\n5. Example result five \u2014 a tutorial or how-to guide. https://example.com/five<|\"|>}<tool_response|><turn|>\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 5,
|
||||||
|
"elapsed_s": 0.81,
|
||||||
|
"prompt_eval_count": 1941,
|
||||||
|
"eval_count": 18,
|
||||||
|
"content_len": 79,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 4690,
|
||||||
|
"raw_completion_head": "thought\n<channel|><|tool_call>call:chat_search{query:<|\"|>home automation<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "veral paragraphs of extracted text. It covers the topic requested with examples and context. The full text runs to about 2000 characters of real prose in production; here's a reasonable approximation for the bakeoff harness. Key details are preserved \u2014 author, date, main argument \u2014 followed by supporting evidence and a conclusion that ties back to the headline.<|\"|>}<tool_response|>thought<turn|>\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 6,
|
||||||
|
"elapsed_s": 3.97,
|
||||||
|
"prompt_eval_count": 2076,
|
||||||
|
"eval_count": 171,
|
||||||
|
"content_len": 652,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 4935,
|
||||||
|
"raw_completion_head": "\nthought\n<channel|><|tool_call>call:memory_write{content:<|\"|>Summary of Home Automation research: \n1. Memory: Seth uses Home Assistant on VM 706 (pve173) with Zigbee2MQTT/MQTT (CT 149), integrating LG TV, lights, and Frigate.\n2. Sethflix: ",
|
||||||
|
"raw_completion_tail": " Assistant updates and technical implementations.\n4. Chat History: Previous discussions involved setting up Grafana dashboards for infrastructure monitoring (CT 300).<|\"|>,key:<|\"|>home_automation_research_20260418<|\"|>,user:<|\"|>seth<|\"|>}",
|
||||||
|
"prompt_tail": "thought<turn|>\n<|tool_call>call:chat_search{query:<|\"|>home automation<|\"|>}<tool_call|><|tool_response>response:chat_search{value:<|\"|>chat_search results:\n[2026-03-14 22:00] @seth:sethpc.xyz in #general: we should set up a shared grafana dashboard for the proxmox cluster\n[2026-03-20 18:30] @seth:sethpc.xyz in #infra: done, it's on CT 300 at grafana.sethpc.xyz<|\"|>}<tool_response|>thought<turn|>\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 7,
|
||||||
|
"elapsed_s": 5.66,
|
||||||
|
"prompt_eval_count": 2292,
|
||||||
|
"eval_count": 248,
|
||||||
|
"content_len": 975,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 5060,
|
||||||
|
"raw_completion_head": "\t<channel|>Here's the breakdown of my research:\n\n1. **Memory Check**: You're running Home Assistant on **VM 706 (pve173)**, utilizing Zigbee2MQTT and an MQTT broker on **CT 149**, with integrations for your LG TV, lights, and Frigate NVR.\n",
|
||||||
|
"raw_completion_tail": "me automation\" in chat, though you previously discussed setting up the **Grafana dashboard** (CT 300) for cluster monitoring.\n6. **Memory Updated**: I've stored a summary of these findings under the key `home_automation_research_20260418`.",
|
||||||
|
"prompt_tail": "ons.\n4. Chat History: Previous discussions involved setting up Grafana dashboards for infrastructure monitoring (CT 300).<|\"|>,key:<|\"|>home_automation_research_20260418<|\"|>,user:<|\"|>seth<|\"|>}<tool_call|><|tool_response>response:memory_write{value:<|\"|>stored: home_automation_research_20260418 = Summary of Home Automation research: \n1. Memory: Seth uses H...<|\"|>}<tool_response|>thought<turn|>\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 7,
|
||||||
|
"tool_calls_total": 6,
|
||||||
|
"wall_clock_s": 17.31,
|
||||||
|
"final_message_count": 26,
|
||||||
|
"final_history_chars": 6034
|
||||||
|
},
|
||||||
|
"task": "long",
|
||||||
|
"task_prompt": "Research question with multiple steps: (1) check memory for what I have on home_automation, (2) search sethflix for any home-automation documentaries, (3) web_search for current news about Home Assistant version releases, (4) fetch the top search result for details, (5) check chat_search for prior mentions, (6) summarize all findings and write a new memory entry with the summary. Do each step in order and report back at the end."
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-json",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600292.960776,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 0.84,
|
||||||
|
"prompt_eval_count": 1393,
|
||||||
|
"eval_count": 24,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 3009
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 0.96,
|
||||||
|
"prompt_eval_count": 1507,
|
||||||
|
"eval_count": 29,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 3281
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"elapsed_s": 0.81,
|
||||||
|
"prompt_eval_count": 1645,
|
||||||
|
"eval_count": 24,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 3572
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"elapsed_s": 0.88,
|
||||||
|
"prompt_eval_count": 1808,
|
||||||
|
"eval_count": 25,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 4188
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 5,
|
||||||
|
"elapsed_s": 0.87,
|
||||||
|
"prompt_eval_count": 1923,
|
||||||
|
"eval_count": 24,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 4616
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 6,
|
||||||
|
"elapsed_s": 4.01,
|
||||||
|
"prompt_eval_count": 2053,
|
||||||
|
"eval_count": 177,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 4843
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 7,
|
||||||
|
"elapsed_s": 5.56,
|
||||||
|
"prompt_eval_count": 2277,
|
||||||
|
"eval_count": 247,
|
||||||
|
"content_len": 950,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"history_chars_before_append": 4958
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 7,
|
||||||
|
"tool_calls_total": 6,
|
||||||
|
"wall_clock_s": 13.93,
|
||||||
|
"final_message_count": 26,
|
||||||
|
"final_history_chars": 5908
|
||||||
|
},
|
||||||
|
"task": "long",
|
||||||
|
"task_prompt": "Research question with multiple steps: (1) check memory for what I have on home_automation, (2) search sethflix for any home-automation documentaries, (3) web_search for current news about Home Assistant version releases, (4) fetch the top search result for details, (5) check chat_search for prior mentions, (6) summarize all findings and write a new memory entry with the summary. Do each step in order and report back at the end."
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-json",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600290.2718768,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 0.72,
|
||||||
|
"prompt_eval_count": 1310,
|
||||||
|
"eval_count": 23,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 2656
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 1.74,
|
||||||
|
"prompt_eval_count": 1422,
|
||||||
|
"eval_count": 67,
|
||||||
|
"content_len": 209,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"history_chars_before_append": 2928
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 2,
|
||||||
|
"tool_calls_total": 1,
|
||||||
|
"wall_clock_s": 2.46,
|
||||||
|
"final_message_count": 16,
|
||||||
|
"final_history_chars": 3137
|
||||||
|
},
|
||||||
|
"task": "memory",
|
||||||
|
"task_prompt": "What do I have stored about home automation? If anything, summarize it briefly."
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-json",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600280.7781782,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 0.87,
|
||||||
|
"prompt_eval_count": 1322,
|
||||||
|
"eval_count": 30,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 2697
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 0.92,
|
||||||
|
"prompt_eval_count": 1501,
|
||||||
|
"eval_count": 27,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 3306
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"elapsed_s": 1.41,
|
||||||
|
"prompt_eval_count": 1670,
|
||||||
|
"eval_count": 51,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 3914
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"elapsed_s": 2.11,
|
||||||
|
"prompt_eval_count": 1806,
|
||||||
|
"eval_count": 86,
|
||||||
|
"content_len": 311,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"history_chars_before_append": 4188
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 4,
|
||||||
|
"tool_calls_total": 3,
|
||||||
|
"wall_clock_s": 5.31,
|
||||||
|
"final_message_count": 20,
|
||||||
|
"final_history_chars": 4499
|
||||||
|
},
|
||||||
|
"task": "movies",
|
||||||
|
"task_prompt": "Recommend 3 sci-fi movies NOT already in my sethflix library. Check your picks against check_sethflix before finalizing."
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-json",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600286.2860382,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 0.93,
|
||||||
|
"prompt_eval_count": 1315,
|
||||||
|
"eval_count": 33,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 2,
|
||||||
|
"history_chars_before_append": 2677
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 2.81,
|
||||||
|
"prompt_eval_count": 1600,
|
||||||
|
"eval_count": 115,
|
||||||
|
"content_len": 499,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"history_chars_before_append": 3499
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 2,
|
||||||
|
"tool_calls_total": 2,
|
||||||
|
"wall_clock_s": 3.74,
|
||||||
|
"final_message_count": 17,
|
||||||
|
"final_history_chars": 3998
|
||||||
|
},
|
||||||
|
"task": "research",
|
||||||
|
"task_prompt": "Look up what Home Assistant is, then check chat history for any prior mentions of it in this server."
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-native",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600755.785702,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 1.05,
|
||||||
|
"prompt_eval_count": 1389,
|
||||||
|
"eval_count": 27,
|
||||||
|
"content_len": 109,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3009,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:memory_read{query:<|\"|>home_automation<|\"|>,user:<|\"|>seth<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "ry for what I have on home_automation, (2) search sethflix for any home-automation documentaries, (3) web_search for current news about Home Assistant version releases, (4) fetch the top search result for details, (5) check chat_search for prior mentions, (6) summarize all findings and write a new memory entry with the summary. Do each step in order and report back at the end.<turn|>\n<|turn>model\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 1.07,
|
||||||
|
"prompt_eval_count": 1509,
|
||||||
|
"eval_count": 28,
|
||||||
|
"content_len": 126,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3281,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:sethsearch{query:<|\"|>home automation documentary<|\"|>,source:<|\"|>sethflix<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "<|\"|>home_automation<|\"|>,user:<|\"|>seth<|\"|>}<tool_call|><|tool_response>response:memory_read{value:<|\"|>memories matching 'home_automation':\n- home_automation: Seth uses Home Assistant on VM 706 (pve173) with Zigbee2MQTT and MQTT broker on CT 149. Integrates with LG TV, lights, and Frigate NVR.\n- preferences: dark theme with orange accents (#D35400), Sethflix/Sethian brand.<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"elapsed_s": 0.92,
|
||||||
|
"prompt_eval_count": 1649,
|
||||||
|
"eval_count": 23,
|
||||||
|
"content_len": 116,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3572,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:web_search{query:<|\"|>Home Assistant current version release news<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": ">,source:<|\"|>sethflix<|\"|>}<tool_call|><|tool_response>response:sethsearch{value:<|\"|>sethflix search 'home automation documentary': The Matrix (1999), The Matrix Reloaded (2003), The Matrix Revolutions (2003), The Matrix Resurrections (2021), Equilibrium (2002), Dark City (1998), Minority Report (2002), Ex Machina (2014), Blade Runner 2049 (2017), Ghost in the Shell (1995).<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"elapsed_s": 0.97,
|
||||||
|
"prompt_eval_count": 1814,
|
||||||
|
"eval_count": 24,
|
||||||
|
"content_len": 93,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 4188,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:web_fetch{url:<|\"|>https://example.com/one<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "a community discussion with multiple perspectives and useful links to follow up on. https://example.com/two\n3. Example result three \u2014 official documentation or reference material. https://example.com/three\n4. Example result four \u2014 a recent news article with relevant context. https://example.com/four\n5. Example result five \u2014 a tutorial or how-to guide. https://example.com/five<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 5,
|
||||||
|
"elapsed_s": 0.88,
|
||||||
|
"prompt_eval_count": 1931,
|
||||||
|
"eval_count": 19,
|
||||||
|
"content_len": 89,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 4616,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:chat_search{query:<|\"|>home automation<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "le body with several paragraphs of extracted text. It covers the topic requested with examples and context. The full text runs to about 2000 characters of real prose in production; here's a reasonable approximation for the bakeoff harness. Key details are preserved \u2014 author, date, main argument \u2014 followed by supporting evidence and a conclusion that ties back to the headline.<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 6,
|
||||||
|
"elapsed_s": 4.38,
|
||||||
|
"prompt_eval_count": 2063,
|
||||||
|
"eval_count": 188,
|
||||||
|
"content_len": 692,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 4843,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:memory_write{content:<|\"|>Summary of research on home_automation: \n1. Memory: Seth uses Home Assistant on VM 706 (pve173) with Zigbee2MQTT and MQTT broker on CT 149, integrating LG TV, lights, an",
|
||||||
|
"raw_completion_tail": "sults (details from example source).\n4. Chat History: Previous discussions focused on setting up a shared Grafana dashboard for the Proxmox cluster on CT 300.<|\"|>,key:<|\"|>home_automation_research_summary_20260418<|\"|>,user:<|\"|>seth<|\"|>}",
|
||||||
|
"prompt_tail": "eadline.<|\"|>}<tool_response|><|tool_call>call:chat_search{query:<|\"|>home automation<|\"|>}<tool_call|><|tool_response>response:chat_search{value:<|\"|>chat_search results:\n[2026-03-14 22:00] @seth:sethpc.xyz in #general: we should set up a shared grafana dashboard for the proxmox cluster\n[2026-03-20 18:30] @seth:sethpc.xyz in #infra: done, it's on CT 300 at grafana.sethpc.xyz<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 7,
|
||||||
|
"elapsed_s": 5.08,
|
||||||
|
"prompt_eval_count": 2296,
|
||||||
|
"eval_count": 217,
|
||||||
|
"content_len": 873,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 4958,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|>Here is the summary of my research:\n\n1. **Existing Memory**: You are running Home Assistant on VM 706 (pve173), utilizing Zigbee2MQTT and an MQTT broker on CT 149, with integrations for your LG TV, lights, and F",
|
||||||
|
"raw_completion_tail": "tory**: Previous mentions of automation/monitoring were related to setting up a shared Grafana dashboard for the Proxmox cluster on CT 300.\n\nI have saved this summary to your memory under the key `home_automation_research_summary_20260418`.",
|
||||||
|
"prompt_tail": ". Chat History: Previous discussions focused on setting up a shared Grafana dashboard for the Proxmox cluster on CT 300.<|\"|>,key:<|\"|>home_automation_research_summary_20260418<|\"|>,user:<|\"|>seth<|\"|>}<tool_call|><|tool_response>response:memory_write{value:<|\"|>stored: home_automation_research_summary_20260418 = Summary of research on home_automation: \n1. Memory: Seth use...<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 7,
|
||||||
|
"tool_calls_total": 6,
|
||||||
|
"wall_clock_s": 14.34,
|
||||||
|
"final_message_count": 26,
|
||||||
|
"final_history_chars": 5831
|
||||||
|
},
|
||||||
|
"task": "long",
|
||||||
|
"task_prompt": "Research question with multiple steps: (1) check memory for what I have on home_automation, (2) search sethflix for any home-automation documentaries, (3) web_search for current news about Home Assistant version releases, (4) fetch the top search result for details, (5) check chat_search for prior mentions, (6) summarize all findings and write a new memory entry with the summary. Do each step in order and report back at the end."
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-native",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600752.7453067,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 0.89,
|
||||||
|
"prompt_eval_count": 1306,
|
||||||
|
"eval_count": 26,
|
||||||
|
"content_len": 109,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 2656,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:memory_read{query:<|\"|>home automation<|\"|>,user:<|\"|>seth<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "yz: hey mort what's the deal with the proxmox cluster nodes again<turn|>\n<|turn>model\nFour nodes: pve173 (tank ZFS host, PowerEdge R820), pve112 (workhorse), pve241 (caddy + game servers), and pve197 (GPU inference). Corosync on 10.10.10.0/24.<turn|>\n<|turn>user\n[2026-04-18 14:20] @seth:sethpc.xyz: What do I have stored about home automation? If anything, summarize it briefly.<turn|>\n<|turn>model\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 1.81,
|
||||||
|
"prompt_eval_count": 1424,
|
||||||
|
"eval_count": 65,
|
||||||
|
"content_len": 233,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 2928,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|>You've got the standard setup: Home Assistant running on VM 706 (on `pve173`), utilizing Zigbee2MQTT and an MQTT broker on CT 149. It's currently handling your LG TV, lighting, and Frigate NVR integration.",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "<|\"|>home automation<|\"|>,user:<|\"|>seth<|\"|>}<tool_call|><|tool_response>response:memory_read{value:<|\"|>memories matching 'home automation':\n- home_automation: Seth uses Home Assistant on VM 706 (pve173) with Zigbee2MQTT and MQTT broker on CT 149. Integrates with LG TV, lights, and Frigate NVR.\n- preferences: dark theme with orange accents (#D35400), Sethflix/Sethian brand.<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 2,
|
||||||
|
"tool_calls_total": 1,
|
||||||
|
"wall_clock_s": 2.7,
|
||||||
|
"final_message_count": 16,
|
||||||
|
"final_history_chars": 3161
|
||||||
|
},
|
||||||
|
"task": "memory",
|
||||||
|
"task_prompt": "What do I have stored about home automation? If anything, summarize it briefly."
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-native",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600742.5834217,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 1.09,
|
||||||
|
"prompt_eval_count": 1318,
|
||||||
|
"eval_count": 33,
|
||||||
|
"content_len": 109,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 2697,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:web_search{query:<|\"|>highly rated sci-fi movies 2024 2025<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "xmox cluster nodes again<turn|>\n<|turn>model\nFour nodes: pve173 (tank ZFS host, PowerEdge R820), pve112 (workhorse), pve241 (caddy + game servers), and pve197 (GPU inference). Corosync on 10.10.10.0/24.<turn|>\n<|turn>user\n[2026-04-18 14:20] @seth:sethpc.xyz: Recommend 3 sci-fi movies NOT already in my sethflix library. Check your picks against check_sethflix before finalizing.<turn|>\n<|turn>model\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 1.56,
|
||||||
|
"prompt_eval_count": 1503,
|
||||||
|
"eval_count": 53,
|
||||||
|
"content_len": 187,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3306,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:check_sethflix{titles:<|\"|>Arrival,Ex Machina,Blade Runner 2049,Interstellar,Children of Men,Looper,District 9,Sunshine,Primer,Coherence<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "a community discussion with multiple perspectives and useful links to follow up on. https://example.com/two\n3. Example result three \u2014 official documentation or reference material. https://example.com/three\n4. Example result four \u2014 a recent news article with relevant context. https://example.com/four\n5. Example result five \u2014 a tutorial or how-to guide. https://example.com/five<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"elapsed_s": 2.88,
|
||||||
|
"prompt_eval_count": 1649,
|
||||||
|
"eval_count": 120,
|
||||||
|
"content_len": 460,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3587,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|>Since you've already got the heavy hitters like *Ex Machina* and *Blade Runner 2049* in the library, here are three sci-fi picks you're currently missing:\n\n1. **Arrival** \u2014 Essential viewing if you haven't seen i",
|
||||||
|
"raw_completion_tail": "f you haven't seen it; it's high-concept linguistics and first contact.\n2. **Interstellar** \u2014 A massive space epic that's a must-watch for the visuals alone.\n3. **Children of Men** \u2014 Gritty, single-take masterpiece about a dystopian future.",
|
||||||
|
"prompt_tail": " 9,Sunshine,Primer,Coherence<|\"|>}<tool_call|><|tool_response>response:check_sethflix{value:<|\"|>- Arrival: NOT IN LIBRARY\n- Ex Machina: IN LIBRARY\n- Blade Runner 2049: IN LIBRARY\n- Interstellar: NOT IN LIBRARY\n- Children of Men: NOT IN LIBRARY\n- Looper: NOT IN LIBRARY\n- District 9: NOT IN LIBRARY\n- Sunshine: NOT IN LIBRARY\n- Primer: NOT IN LIBRARY\n- Coherence: NOT IN LIBRARY<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 3,
|
||||||
|
"tool_calls_total": 2,
|
||||||
|
"wall_clock_s": 5.54,
|
||||||
|
"final_message_count": 18,
|
||||||
|
"final_history_chars": 4047
|
||||||
|
},
|
||||||
|
"task": "movies",
|
||||||
|
"task_prompt": "Recommend 3 sci-fi movies NOT already in my sethflix library. Check your picks against check_sethflix before finalizing."
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-native",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600748.3832083,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 0.8,
|
||||||
|
"prompt_eval_count": 1311,
|
||||||
|
"eval_count": 21,
|
||||||
|
"content_len": 95,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 2677,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:web_search{query:<|\"|>what is Home Assistant<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "he deal with the proxmox cluster nodes again<turn|>\n<|turn>model\nFour nodes: pve173 (tank ZFS host, PowerEdge R820), pve112 (workhorse), pve241 (caddy + game servers), and pve197 (GPU inference). Corosync on 10.10.10.0/24.<turn|>\n<|turn>user\n[2026-04-18 14:20] @seth:sethpc.xyz: Look up what Home Assistant is, then check chat history for any prior mentions of it in this server.<turn|>\n<|turn>model\n",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 0.84,
|
||||||
|
"prompt_eval_count": 1472,
|
||||||
|
"eval_count": 19,
|
||||||
|
"content_len": 88,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3272,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:chat_search{query:<|\"|>Home Assistant<|\"|>}",
|
||||||
|
"raw_completion_tail": "",
|
||||||
|
"prompt_tail": "a community discussion with multiple perspectives and useful links to follow up on. https://example.com/two\n3. Example result three \u2014 official documentation or reference material. https://example.com/three\n4. Example result four \u2014 a recent news article with relevant context. https://example.com/four\n5. Example result five \u2014 a tutorial or how-to guide. https://example.com/five<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"elapsed_s": 2.43,
|
||||||
|
"prompt_eval_count": 1604,
|
||||||
|
"eval_count": 97,
|
||||||
|
"content_len": 502,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 3499,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|>Home Assistant is an open-source home automation platform that acts as a central hub for controlling smart devices (lights, sensors, thermostats, etc.) from different manufacturers, allowing you to create complex",
|
||||||
|
"raw_completion_tail": "oards.\n\nAs for the chat history, I couldn't find any prior mentions of \"Home Assistant\" in this server. The only recent infrastructure-related discussions I found were regarding setting up a shared Grafana dashboard for the Proxmox cluster.",
|
||||||
|
"prompt_tail": ".com/five<|\"|>}<tool_response|><|tool_call>call:chat_search{query:<|\"|>Home Assistant<|\"|>}<tool_call|><|tool_response>response:chat_search{value:<|\"|>chat_search results:\n[2026-03-14 22:00] @seth:sethpc.xyz in #general: we should set up a shared grafana dashboard for the proxmox cluster\n[2026-03-20 18:30] @seth:sethpc.xyz in #infra: done, it's on CT 300 at grafana.sethpc.xyz<|\"|>}<tool_response|>",
|
||||||
|
"prompt_head": "<bos><|turn>system\nYou are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.\n\nWhen a tool can answer the question, "
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 3,
|
||||||
|
"tool_calls_total": 2,
|
||||||
|
"wall_clock_s": 4.07,
|
||||||
|
"final_message_count": 18,
|
||||||
|
"final_history_chars": 4001
|
||||||
|
},
|
||||||
|
"task": "research",
|
||||||
|
"task_prompt": "Look up what Home Assistant is, then check chat history for any prior mentions of it in this server."
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-json",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600243.836896,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 3.52,
|
||||||
|
"prompt_eval_count": 1310,
|
||||||
|
"eval_count": 16,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 2656
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 0.89,
|
||||||
|
"prompt_eval_count": 1436,
|
||||||
|
"eval_count": 27,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 2883
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"elapsed_s": 2.74,
|
||||||
|
"prompt_eval_count": 1552,
|
||||||
|
"eval_count": 115,
|
||||||
|
"content_len": 374,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"history_chars_before_append": 3155
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 3,
|
||||||
|
"tool_calls_total": 2,
|
||||||
|
"wall_clock_s": 7.15,
|
||||||
|
"final_message_count": 18,
|
||||||
|
"final_history_chars": 3529
|
||||||
|
},
|
||||||
|
"task": "memory",
|
||||||
|
"task_prompt": "What do I have stored about home automation? If anything, summarize it briefly."
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-native",
|
||||||
|
"model": "gemma4:26b",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776600258.6906579,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 0.81,
|
||||||
|
"prompt_eval_count": 1306,
|
||||||
|
"eval_count": 26,
|
||||||
|
"content_len": 109,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 2656,
|
||||||
|
"raw_completion_head": "<|channel>thought\n<channel|><|tool_call>call:memory_read{query:<|\"|>home automation<|\"|>,user:<|\"|>seth<|\"|>}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 2.69,
|
||||||
|
"prompt_eval_count": 1426,
|
||||||
|
"eval_count": 109,
|
||||||
|
"content_len": 356,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 2956,
|
||||||
|
"raw_completion_head": "thought\n<channel|>You've got a fairly solid Home Assistant setup running. Here's the gist:\n\n* **Core:** Running on **VM 706** (hosted on `pve173`).\n* **Connectivity:** Uses **Zigbee2MQTT** and an **MQTT broker** (running on **CT 149**)."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 2,
|
||||||
|
"tool_calls_total": 1,
|
||||||
|
"wall_clock_s": 3.49,
|
||||||
|
"final_message_count": 16,
|
||||||
|
"final_history_chars": 3312
|
||||||
|
},
|
||||||
|
"task": "memory",
|
||||||
|
"task_prompt": "What do I have stored about home automation? If anything, summarize it briefly."
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-json",
|
||||||
|
"model": "gemma4:latest",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776572739.0110834,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 8.67,
|
||||||
|
"prompt_eval_count": 1306,
|
||||||
|
"eval_count": 23,
|
||||||
|
"content_len": 0,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"history_chars_before_append": 2656
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 1.48,
|
||||||
|
"prompt_eval_count": 1421,
|
||||||
|
"eval_count": 133,
|
||||||
|
"content_len": 485,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"history_chars_before_append": 2928
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 2,
|
||||||
|
"tool_calls_total": 1,
|
||||||
|
"wall_clock_s": 10.16,
|
||||||
|
"final_message_count": 16,
|
||||||
|
"final_history_chars": 3413
|
||||||
|
},
|
||||||
|
"task": "memory",
|
||||||
|
"task_prompt": "What do I have stored about home automation? If anything, summarize it briefly."
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"arm": "ollama-native",
|
||||||
|
"model": "gemma4:latest",
|
||||||
|
"num_ctx": 8192,
|
||||||
|
"num_predict": 2048,
|
||||||
|
"started_at": 1776572778.1317718,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"elapsed_s": 0.7,
|
||||||
|
"prompt_eval_count": 1306,
|
||||||
|
"eval_count": 15,
|
||||||
|
"content_len": 61,
|
||||||
|
"tool_call_count": 1,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 2656,
|
||||||
|
"raw_completion_head": "<|tool_call>call:memory_read{query:<|\"|>home automation<|\"|>}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"elapsed_s": 1.69,
|
||||||
|
"prompt_eval_count": 1417,
|
||||||
|
"eval_count": 137,
|
||||||
|
"content_len": 457,
|
||||||
|
"tool_call_count": 0,
|
||||||
|
"stop_reason": "stop",
|
||||||
|
"history_chars_before_append": 2928,
|
||||||
|
"raw_completion_head": "I found a couple of things stored about home automation:\n\n1. **System Setup:** You're running **Home Assistant** on **VM 706** (hosted on `pve173`).\n2. **Integrations:** It uses **Zigbee2MQTT** and an **MQTT broker** located on **CT 149**"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "no_tool_calls",
|
||||||
|
"steps_used": 2,
|
||||||
|
"tool_calls_total": 1,
|
||||||
|
"wall_clock_s": 2.39,
|
||||||
|
"final_message_count": 16,
|
||||||
|
"final_history_chars": 3385
|
||||||
|
},
|
||||||
|
"task": "memory",
|
||||||
|
"task_prompt": "What do I have stored about home automation? If anything, summarize it briefly."
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"arm": "jax-native",
|
||||||
|
"error": "gemma package not importable \u2014 run in a JAX+gemma env. See module docstring.",
|
||||||
|
"final": {
|
||||||
|
"halt_reason": "env_missing",
|
||||||
|
"steps_used": 0,
|
||||||
|
"tool_calls_total": 0,
|
||||||
|
"wall_clock_s": 0
|
||||||
|
},
|
||||||
|
"task": "memory",
|
||||||
|
"task_prompt": "What do I have stored about home automation? If anything, summarize it briefly."
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
"""Shared task definitions for the native-bakeoff.
|
||||||
|
|
||||||
|
Lifted verbatim from scripts/mort-bakeoff/harness.py so all three arms
|
||||||
|
(ollama-json, ollama-native, jax-native) see identical tasks, stubs,
|
||||||
|
system prompt, and fake history. If these ever drift, the comparison
|
||||||
|
becomes meaningless.
|
||||||
|
|
||||||
|
The goal of this harness is to isolate the *inference path* as the
|
||||||
|
only variable:
|
||||||
|
- Arm A: Ollama /api/chat with JSON `tools:[...]` (current default)
|
||||||
|
- Arm B: Ollama /api/generate with raw:true + native Gemma tokens
|
||||||
|
- Arm C: google-deepmind/gemma JAX `gm.text.ToolSampler`
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = """You are Mort, a direct and witty AI assistant on Seth's Matrix server. Powered by Gemma 4. Current time: Saturday, April 18 2026 02:30 PM EDT.
|
||||||
|
|
||||||
|
When a tool can answer the question, invoke it immediately — do not narrate intent or describe what you would do. Chain tools when a single call isn't sufficient: search → fetch → synthesize. If a tool returns an error or empty results, try an alternative tool or query before answering from memory. Base your response on tool results, not your training data — cite what you found.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
- **sethsearch** — search Seth's homelab (repos, wiki, media, feeds). Use `source: "sethflix"` for movies/TV/music.
|
||||||
|
- **check_sethflix** — verify which titles are in sethflix. Pass a comma-separated list.
|
||||||
|
- **web_search** — search the internet for current information
|
||||||
|
- **chat_search** — search message history across all rooms
|
||||||
|
- **memory_read / memory_write** — recall and store durable facts about users and topics
|
||||||
|
- **web_fetch** — fetch and extract text from a URL
|
||||||
|
- **generate_image** — generate an image via SDXL.
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
- Only persist durable facts to memory, not ephemeral chat
|
||||||
|
- You have no memory between sessions. Your context is a sliding window — older messages fall off silently. Do not claim to "remember," promise to "do better," or describe your own architecture.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# OpenAI-style tool schema. Arm A consumes this as-is. Arm B serializes
|
||||||
|
# it into Gemma's native <|tool>declaration:...<tool|> syntax. Arm C
|
||||||
|
# wraps each tool into a gm.tools.Tool subclass.
|
||||||
|
TOOLS = [
|
||||||
|
{"type": "function", "function": {"name": "web_search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}},
|
||||||
|
{"type": "function", "function": {"name": "sethsearch", "description": "Search Seth's homelab or sethflix (use source='sethflix' for movies/TV).", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "source": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["query"]}}},
|
||||||
|
{"type": "function", "function": {"name": "check_sethflix", "description": "Verify which titles are in sethflix.", "parameters": {"type": "object", "properties": {"titles": {"type": "string", "description": "comma-separated"}}, "required": ["titles"]}}},
|
||||||
|
{"type": "function", "function": {"name": "memory_read", "description": "Look up stored facts.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "user": {"type": "string"}}, "required": ["query"]}}},
|
||||||
|
{"type": "function", "function": {"name": "memory_write", "description": "Store a fact.", "parameters": {"type": "object", "properties": {"key": {"type": "string"}, "content": {"type": "string"}, "user": {"type": "string"}}, "required": ["key", "content"]}}},
|
||||||
|
{"type": "function", "function": {"name": "web_fetch", "description": "Fetch URL text.", "parameters": {"type": "object", "properties": {"url": {"type": "string"}}, "required": ["url"]}}},
|
||||||
|
{"type": "function", "function": {"name": "chat_search", "description": "Search message history.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}},
|
||||||
|
{"type": "function", "function": {"name": "generate_image", "description": "Generate an image.", "parameters": {"type": "object", "properties": {"prompt": {"type": "string"}}, "required": ["prompt"]}}},
|
||||||
|
]
|
||||||
|
|
||||||
|
FAKE_HISTORY = [
|
||||||
|
{"role": "user", "content": "[2026-04-18 13:45] @alice:sethpc.xyz: what's the weather looking like for Monday"},
|
||||||
|
{"role": "assistant", "content": "Checking forecasts now — looks like Monday in Seth's area will be mostly cloudy, high of 67°F, 30% chance of rain in the afternoon."},
|
||||||
|
{"role": "user", "content": "[2026-04-18 13:47] @bob:sethpc.xyz: any good movies added to sethflix lately"},
|
||||||
|
{"role": "assistant", "content": "Fresh adds this week include *Dune: Part Two*, *The Zone of Interest*, *Poor Things*, and a 4K remaster of *The Thing* (1982). Anything catch your eye?"},
|
||||||
|
{"role": "user", "content": "[2026-04-18 13:50] @alice:sethpc.xyz: I liked the thing, any horror recs along that line"},
|
||||||
|
{"role": "assistant", "content": "If you liked *The Thing*, you'll probably want *Annihilation*, *The Mist*, *Bone Tomahawk*, or *Event Horizon* — all in the library. Body-horror/dread tone, same energy."},
|
||||||
|
{"role": "user", "content": "[2026-04-18 13:55] @bob:sethpc.xyz: can you remind me what the sethpc monitoring dashboard is called"},
|
||||||
|
{"role": "assistant", "content": "That's Uptime Kuma — runs on CT 147 at `https://uptime.sethpc.xyz`. Covers all the containers and external services."},
|
||||||
|
{"role": "user", "content": "[2026-04-18 14:10] @alice:sethpc.xyz: noted thanks"},
|
||||||
|
{"role": "user", "content": "[2026-04-18 14:15] @bob:sethpc.xyz: hey mort what's the deal with the proxmox cluster nodes again"},
|
||||||
|
{"role": "assistant", "content": "Four nodes: pve173 (tank ZFS host, PowerEdge R820), pve112 (workhorse), pve241 (caddy + game servers), and pve197 (GPU inference). Corosync on 10.10.10.0/24."},
|
||||||
|
]
|
||||||
|
|
||||||
|
TASKS = {
|
||||||
|
"movies": "Recommend 3 sci-fi movies NOT already in my sethflix library. Check your picks against check_sethflix before finalizing.",
|
||||||
|
"research": "Look up what Home Assistant is, then check chat history for any prior mentions of it in this server.",
|
||||||
|
"memory": "What do I have stored about home automation? If anything, summarize it briefly.",
|
||||||
|
"long": ("Research question with multiple steps: (1) check memory for what I have on home_automation, "
|
||||||
|
"(2) search sethflix for any home-automation documentaries, (3) web_search for current news about "
|
||||||
|
"Home Assistant version releases, (4) fetch the top search result for details, (5) check chat_search "
|
||||||
|
"for prior mentions, (6) summarize all findings and write a new memory entry with the summary. "
|
||||||
|
"Do each step in order and report back at the end."),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def execute_tool_stub(name: str, args: dict) -> str:
|
||||||
|
"""Deterministic tool stubs — same as mort-bakeoff/harness.py."""
|
||||||
|
if name == "web_search":
|
||||||
|
q = args.get("query", "")
|
||||||
|
return (f"Search results for '{q}':\n"
|
||||||
|
"1. Example result one — a detailed article that covers the topic at length "
|
||||||
|
"with concrete examples and technical background. https://example.com/one\n"
|
||||||
|
"2. Example result two — a community discussion with multiple perspectives "
|
||||||
|
"and useful links to follow up on. https://example.com/two\n"
|
||||||
|
"3. Example result three — official documentation or reference material. "
|
||||||
|
"https://example.com/three\n"
|
||||||
|
"4. Example result four — a recent news article with relevant context. "
|
||||||
|
"https://example.com/four\n"
|
||||||
|
"5. Example result five — a tutorial or how-to guide. https://example.com/five")
|
||||||
|
|
||||||
|
if name == "sethsearch":
|
||||||
|
src = args.get("source", "general")
|
||||||
|
q = args.get("query", "")
|
||||||
|
if src == "sethflix":
|
||||||
|
return (f"sethflix search '{q}': The Matrix (1999), The Matrix Reloaded (2003), "
|
||||||
|
"The Matrix Revolutions (2003), The Matrix Resurrections (2021), "
|
||||||
|
"Equilibrium (2002), Dark City (1998), Minority Report (2002), "
|
||||||
|
"Ex Machina (2014), Blade Runner 2049 (2017), Ghost in the Shell (1995).")
|
||||||
|
return (f"homelab search '{q}': 3 repos, 5 wiki pages, 2 service docs matched. "
|
||||||
|
"Top hits: services_directory.md, DECISIONS.md, CORPUS_architecture.md.")
|
||||||
|
|
||||||
|
if name == "check_sethflix":
|
||||||
|
titles = args.get("titles", "")
|
||||||
|
items = [t.strip() for t in titles.split(",") if t.strip()]
|
||||||
|
in_lib = {"The Matrix", "Blade Runner 2049", "Ex Machina", "The Thing"}
|
||||||
|
return "\n".join(
|
||||||
|
f"- {t}: IN LIBRARY" if t in in_lib else f"- {t}: NOT IN LIBRARY"
|
||||||
|
for t in items
|
||||||
|
)
|
||||||
|
|
||||||
|
if name == "memory_read":
|
||||||
|
q = args.get("query", "")
|
||||||
|
return (f"memories matching '{q}':\n"
|
||||||
|
"- home_automation: Seth uses Home Assistant on VM 706 (pve173) with "
|
||||||
|
"Zigbee2MQTT and MQTT broker on CT 149. Integrates with LG TV, lights, "
|
||||||
|
"and Frigate NVR.\n"
|
||||||
|
"- preferences: dark theme with orange accents (#D35400), Sethflix/Sethian brand.")
|
||||||
|
|
||||||
|
if name == "memory_write":
|
||||||
|
return f"stored: {args.get('key', '?')} = {args.get('content', '?')[:60]}..."
|
||||||
|
|
||||||
|
if name == "web_fetch":
|
||||||
|
return ("fetched content (trimmed): This is a typical article body with several "
|
||||||
|
"paragraphs of extracted text. It covers the topic requested with examples "
|
||||||
|
"and context. The full text runs to about 2000 characters of real prose in "
|
||||||
|
"production; here's a reasonable approximation for the bakeoff harness. "
|
||||||
|
"Key details are preserved — author, date, main argument — followed by "
|
||||||
|
"supporting evidence and a conclusion that ties back to the headline.")
|
||||||
|
|
||||||
|
if name == "chat_search":
|
||||||
|
return ("chat_search results:\n"
|
||||||
|
"[2026-03-14 22:00] @seth:sethpc.xyz in #general: we should set up a shared "
|
||||||
|
"grafana dashboard for the proxmox cluster\n"
|
||||||
|
"[2026-03-20 18:30] @seth:sethpc.xyz in #infra: done, it's on CT 300 at "
|
||||||
|
"grafana.sethpc.xyz")
|
||||||
|
|
||||||
|
if name == "generate_image":
|
||||||
|
return f"image generated: /mxc/abc123/sunset.png (SDXL, 1024x1024, prompt={args.get('prompt','')[:40]}...)"
|
||||||
|
|
||||||
|
return f"ERROR: unknown tool {name}"
|
||||||
Reference in New Issue
Block a user